Files
MapLibreParticleLab/platform/darwin/src/MGLOfflineStorage.mm
2020-01-13 10:57:23 +02:00

653 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "MGLOfflineStorage_Private.h"
#import "MGLFoundation_Private.h"
#import "MGLAccountManager_Private.h"
#import "MGLGeometry_Private.h"
#import "MGLOfflinePack_Private.h"
#import "MGLOfflineRegion_Private.h"
#import "MGLTilePyramidOfflineRegion.h"
#import "MGLShapeOfflineRegion.h"
#import "NSBundle+MGLAdditions.h"
#import "NSValue+MGLAdditions.h"
#import "NSDate+MGLAdditions.h"
#import "MGLLoggingConfiguration_Private.h"
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "MMEConstants.h"
#import "MGLMapboxEvents.h"
#endif
#include <mbgl/actor/actor.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/storage/file_source_manager.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/storage/resource_transform.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <memory>
static NSString * const MGLOfflineStorageFileName = @"cache.db";
static NSString * const MGLOfflineStorageFileName3_2_0_beta_1 = @"offline.db";
const NSNotificationName MGLOfflinePackProgressChangedNotification = @"MGLOfflinePackProgressChanged";
const NSNotificationName MGLOfflinePackErrorNotification = @"MGLOfflinePackError";
const NSNotificationName MGLOfflinePackMaximumMapboxTilesReachedNotification = @"MGLOfflinePackMaximumMapboxTilesReached";
const MGLOfflinePackUserInfoKey MGLOfflinePackUserInfoKeyState = @"State";
const MGLOfflinePackUserInfoKey MGLOfflinePackUserInfoKeyProgress = @"Progress";
const MGLOfflinePackUserInfoKey MGLOfflinePackUserInfoKeyError = @"Error";
const MGLOfflinePackUserInfoKey MGLOfflinePackUserInfoKeyMaximumCount = @"MaximumCount";
const MGLExceptionName MGLUnsupportedRegionTypeException = @"MGLUnsupportedRegionTypeException";
@interface MGLOfflineStorage ()
@property (nonatomic, strong, readwrite) NSMutableArray<MGLOfflinePack *> *packs;
@property (nonatomic) std::shared_ptr<mbgl::DatabaseFileSource> mbglDatabaseFileSource;
@property (nonatomic) std::shared_ptr<mbgl::OnlineFileSource> mbglOnlineFileSource;
@property (nonatomic) std::shared_ptr<mbgl::FileSource> mbglFileSource;
@property (nonatomic) std::string mbglCachePath;
@property (nonatomic, getter=isPaused) BOOL paused;
@end
@implementation MGLOfflineStorage {
std::unique_ptr<mbgl::Actor<mbgl::ResourceTransform::TransformCallback>> _mbglResourceTransform;
}
+ (instancetype)sharedOfflineStorage {
static dispatch_once_t onceToken;
static MGLOfflineStorage *sharedOfflineStorage;
dispatch_once(&onceToken, ^{
sharedOfflineStorage = [[self alloc] init];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(unpauseFileSource:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(pauseFileSource:) name:UIApplicationDidEnterBackgroundNotification object:nil];
#endif
[sharedOfflineStorage reloadPacks];
});
return sharedOfflineStorage;
}
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- (void)pauseFileSource:(__unused NSNotification *)notification {
if (self.isPaused) {
return;
}
_mbglFileSource->pause();
self.paused = YES;
}
- (void)unpauseFileSource:(__unused NSNotification *)notification {
if (!self.isPaused) {
return;
}
_mbglFileSource->resume();
self.paused = NO;
}
#endif
- (void)setDelegate:(id<MGLOfflineStorageDelegate>)newValue {
MGLLogDebug(@"Setting delegate: %@", newValue);
_delegate = newValue;
if ([self.delegate respondsToSelector:@selector(offlineStorage:URLForResourceOfKind:withURL:)]) {
_mbglResourceTransform = std::make_unique<mbgl::Actor<mbgl::ResourceTransform::TransformCallback>>(*mbgl::Scheduler::GetCurrent(), [offlineStorage = self](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb) {
NSURL* url =
[NSURL URLWithString:[[NSString alloc] initWithBytes:url_.data()
length:url_.length()
encoding:NSUTF8StringEncoding]];
MGLResourceKind kind = MGLResourceKindUnknown;
switch (kind_) {
case mbgl::Resource::Kind::Tile:
kind = MGLResourceKindTile;
break;
case mbgl::Resource::Kind::Glyphs:
kind = MGLResourceKindGlyphs;
break;
case mbgl::Resource::Kind::Style:
kind = MGLResourceKindStyle;
break;
case mbgl::Resource::Kind::Source:
kind = MGLResourceKindSource;
break;
case mbgl::Resource::Kind::SpriteImage:
kind = MGLResourceKindSpriteImage;
break;
case mbgl::Resource::Kind::SpriteJSON:
kind = MGLResourceKindSpriteJSON;
break;
case mbgl::Resource::Kind::Image:
kind = MGLResourceKindImage;
break;
case mbgl::Resource::Kind::Unknown:
kind = MGLResourceKindUnknown;
break;
}
url = [offlineStorage.delegate offlineStorage:offlineStorage
URLForResourceOfKind:kind
withURL:url];
cb(url.absoluteString.UTF8String);
});
_mbglOnlineFileSource->setResourceTransform({[actorRef = _mbglResourceTransform->self()](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb_){
actorRef.invoke(&mbgl::ResourceTransform::TransformCallback::operator(), kind_, url_, std::move(cb_));
}});
} else {
_mbglResourceTransform.reset();
_mbglOnlineFileSource->setResourceTransform({});
}
}
/**
Returns the file URL to the offline cache, with the option to omit the private
subdirectory for legacy (v3.2.0 - v3.2.3) migration purposes.
The cache is located in a directory specific to the application, so that packs
downloaded by other applications don’t count toward this application’s limits.
The cache is located at:
~/Library/Application Support/tld.app.bundle.id/.mapbox/cache.db
The subdirectory-less cache was located at:
~/Library/Application Support/tld.app.bundle.id/cache.db
*/
+ (NSURL *)cacheURLIncludingSubdirectory:(BOOL)useSubdirectory {
NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier];
if (!bundleIdentifier) {
// There’s no main bundle identifier when running in a unit test bundle.
bundleIdentifier = [[NSUUID UUID] UUIDString];
}
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
if (useSubdirectory) {
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"];
}
[[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL
withIntermediateDirectories:YES
attributes:nil
error:nil];
if (useSubdirectory) {
// Avoid backing up the offline cache onto iCloud, because it can be
// redownloaded. Ideally, we’d even put the ambient cache in Caches, so
// it can be reclaimed by the system when disk space runs low. But
// unfortunately it has to live in the same file as offline resources.
[cacheDirectoryURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL];
}
return [cacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName];
}
/**
Returns the absolute path to the location where v3.2.0-beta.1 placed the
offline cache.
*/
+ (NSString *)legacyCachePath {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
// ~/Documents/offline.db
NSArray *legacyPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *legacyCachePath = [legacyPaths.firstObject stringByAppendingPathComponent:MGLOfflineStorageFileName3_2_0_beta_1];
#elif TARGET_OS_MAC
// ~/Library/Caches/tld.app.bundle.id/offline.db
NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier];
NSURL *legacyCacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:nil];
legacyCacheDirectoryURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
NSURL *legacyCacheURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName3_2_0_beta_1];
NSString *legacyCachePath = legacyCacheURL ? legacyCacheURL.path : @"";
#endif
return legacyCachePath;
}
- (instancetype)init {
MGLInitializeRunLoop();
if (self = [super init]) {
NSURL *cacheURL = [[self class] cacheURLIncludingSubdirectory:YES];
NSString *cachePath = cacheURL.path ?: @"";
// Move the offline cache from v3.2.0-beta.1 to a location that can also
// be used for ambient caching.
if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
NSString *legacyCachePath = [[self class] legacyCachePath];
[[NSFileManager defaultManager] moveItemAtPath:legacyCachePath toPath:cachePath error:NULL];
}
// Move the offline file cache from v3.2.x path to a subdirectory that
// can be reliably excluded from backups.
if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
NSURL *subdirectorylessCacheURL = [[self class] cacheURLIncludingSubdirectory:NO];
[[NSFileManager defaultManager] moveItemAtPath:subdirectorylessCacheURL.path toPath:cachePath error:NULL];
}
_mbglCachePath = cachePath.UTF8String;
mbgl::ResourceOptions options;
options.withCachePath(_mbglCachePath)
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String);
_mbglFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::ResourceLoader, options);
_mbglOnlineFileSource = std::static_pointer_cast<mbgl::OnlineFileSource>(mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Network, options));
_mbglDatabaseFileSource = std::static_pointer_cast<mbgl::DatabaseFileSource>(mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Database, options));
// Observe for changes to the API base URL (and find out the current one).
[[MGLAccountManager sharedManager] addObserver:self
forKeyPath:@"apiBaseURL"
options:(NSKeyValueObservingOptionInitial |
NSKeyValueObservingOptionNew)
context:NULL];
// Observe for changes to the global access token (and find out the current one).
[[MGLAccountManager sharedManager] addObserver:self
forKeyPath:@"accessToken"
options:(NSKeyValueObservingOptionInitial |
NSKeyValueObservingOptionNew)
context:NULL];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"apiBaseURL"];
[[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];
for (MGLOfflinePack *pack in self.packs) {
[pack invalidate];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context {
// Synchronize the file source’s access token with the global one in MGLAccountManager.
if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) {
NSString *accessToken = change[NSKeyValueChangeNewKey];
if (![accessToken isKindOfClass:[NSNull class]]) {
_mbglOnlineFileSource->setProperty(mbgl::ACCESS_TOKEN_KEY, accessToken.UTF8String);
}
} else if ([keyPath isEqualToString:@"apiBaseURL"] && object == [MGLAccountManager sharedManager]) {
NSURL *apiBaseURL = change[NSKeyValueChangeNewKey];
if ([apiBaseURL isKindOfClass:[NSNull class]]) {
_mbglFileSource->setProperty(mbgl::API_BASE_URL_KEY, mbgl::util::API_BASE_URL);
} else {
_mbglFileSource->setProperty(mbgl::API_BASE_URL_KEY, apiBaseURL.absoluteString.UTF8String);
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark Offline merge methods
- (void)addContentsOfFile:(NSString *)filePath withCompletionHandler:(MGLBatchedOfflinePackAdditionCompletionHandler)completion {
MGLLogDebug(@"Adding contentsOfFile: %@ completionHandler: %@", filePath, completion);
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
[self addContentsOfURL:fileURL withCompletionHandler:completion];
}
- (void)addContentsOfURL:(NSURL *)fileURL withCompletionHandler:(MGLBatchedOfflinePackAdditionCompletionHandler)completion {
MGLLogDebug(@"Adding contentsOfURL: %@ completionHandler: %@", fileURL, completion);
NSFileManager *fileManager = [NSFileManager defaultManager];
if (!fileURL.isFileURL) {
[NSException raise:NSInvalidArgumentException format:@"%@ must be a valid file path", fileURL.absoluteString];
}
if (![fileManager isWritableFileAtPath:fileURL.path]) {
[NSException raise:NSInvalidArgumentException format:@"The file path: %@ must be writable", fileURL.absoluteString];
}
__weak MGLOfflineStorage *weakSelf = self;
[self _addContentsOfFile:fileURL.path withCompletionHandler:^(NSArray<MGLOfflinePack *> * _Nullable packs, NSError * _Nullable error) {
if (packs) {
NSMutableDictionary *packsByIdentifier = [NSMutableDictionary dictionary];
MGLOfflineStorage *strongSelf = weakSelf;
for (MGLOfflinePack *pack in packs) {
[packsByIdentifier setObject:pack forKey:@(pack.mbglOfflineRegion->getID())];
}
id mutablePacks = [strongSelf mutableArrayValueForKey:@"packs"];
NSMutableIndexSet *replaceIndexSet = [NSMutableIndexSet indexSet];
NSMutableArray *replacePacksArray = [NSMutableArray array];
[strongSelf.packs enumerateObjectsUsingBlock:^(MGLOfflinePack * _Nonnull pack, NSUInteger idx, BOOL * _Nonnull stop) {
MGLOfflinePack *newPack = packsByIdentifier[@(pack.mbglOfflineRegion->getID())];
if (newPack) {
MGLOfflinePack *previousPack = [mutablePacks objectAtIndex:idx];
[previousPack invalidate];
[replaceIndexSet addIndex:idx];
[replacePacksArray addObject:[packsByIdentifier objectForKey:@(newPack.mbglOfflineRegion->getID())]];
[packsByIdentifier removeObjectForKey:@(newPack.mbglOfflineRegion->getID())];
}
}];
if (replaceIndexSet.count > 0) {
[mutablePacks replaceObjectsAtIndexes:replaceIndexSet withObjects:replacePacksArray];
}
[mutablePacks addObjectsFromArray:packsByIdentifier.allValues];
}
if (completion) {
completion(fileURL, packs, error);
}
}];
}
- (void)_addContentsOfFile:(NSString *)filePath withCompletionHandler:(void (^)(NSArray<MGLOfflinePack *> * _Nullable packs, NSError * _Nullable error))completion {
_mbglDatabaseFileSource->mergeOfflineRegions(std::string(static_cast<const char *>([filePath UTF8String])), [&, completion, filePath](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) {
NSError *error;
NSMutableArray *packs;
if (!result) {
NSString *description = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ADD_FILE_CONTENTS_FAILED_DESC", @"Foundation", nil, @"Unable to add offline packs from the file at %@.", @"User-friendly error description"), filePath];
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey: description,
NSLocalizedFailureReasonErrorKey: @(mbgl::util::toString(result.error()).c_str())
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
} else {
auto& regions = result.value();
packs = [NSMutableArray arrayWithCapacity:regions.size()];
for (auto &region : regions) {
MGLOfflinePack *pack = [[MGLOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))];
[packs addObject:pack];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) {
completion(packs, error);
});
}
});
}
#pragma mark Pack management methods
- (void)addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
MGLLogDebug(@"Adding packForRegion: %@ contextLength: %lu completionHandler: %@", region, (unsigned long)context.length, completion);
__weak MGLOfflineStorage *weakSelf = self;
[self _addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
pack.state = MGLOfflinePackStateInactive;
MGLOfflineStorage *strongSelf = weakSelf;
[[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack];
if (completion) {
completion(pack, error);
}
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
NSMutableDictionary *offlineDownloadStartEventAttributes = [NSMutableDictionary dictionaryWithObject:MMEventTypeOfflineDownloadStart forKey:MMEEventKeyEvent];
if ([region conformsToProtocol:@protocol(MGLOfflineRegion_Private)]) {
NSDictionary *regionAttributes = ((id<MGLOfflineRegion_Private>)region).offlineStartEventAttributes;
[offlineDownloadStartEventAttributes addEntriesFromDictionary:regionAttributes];
}
[MGLMapboxEvents pushEvent:MMEventTypeOfflineDownloadStart withAttributes:offlineDownloadStartEventAttributes];
#endif
}];
}
- (void)_addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
if (![region conformsToProtocol:@protocol(MGLOfflineRegion_Private)]) {
[NSException raise:MGLUnsupportedRegionTypeException
format:@"Regions of type %@ are unsupported.", NSStringFromClass([region class])];
return;
}
const mbgl::OfflineRegionDefinition regionDefinition = [(id <MGLOfflineRegion_Private>)region offlineRegionDefinition];
mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()];
_mbglDatabaseFileSource->createOfflineRegion(regionDefinition, metadata, [&, completion](mbgl::expected<mbgl::OfflineRegion, std::exception_ptr> mbglOfflineRegion) {
NSError *error;
if (!mbglOfflineRegion) {
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str());
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{
NSLocalizedDescriptionKey: errorDescription,
} : nil];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
if (completion) {
MGLOfflinePack *pack = mbglOfflineRegion ? [[MGLOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(mbglOfflineRegion.value()))] : nil;
dispatch_async(dispatch_get_main_queue(), [&, completion, error, pack](void) {
completion(pack, error);
});
}
});
}
- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion {
MGLLogDebug(@"Removing pack: %@ completionHandler: %@", pack, completion);
[[self mutableArrayValueForKey:@"packs"] removeObject:pack];
[self _removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
if (completion) {
completion(error);
}
}];
}
- (void)_removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion {
mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion;
[pack invalidate];
if (!mbglOfflineRegion) {
MGLAssert(pack.state == MGLOfflinePackStateInvalid, @"State should be invalid");
completion(nil);
return;
}
_mbglDatabaseFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
});
}
- (void)invalidatePack:(MGLOfflinePack *)pack withCompletionHandler:(void (^)(NSError * _Nullable))completion {
mbgl::OfflineRegion& region = *pack.mbglOfflineRegion;
NSError *error;
if (!pack.mbglOfflineRegion) {
completion(nil);
return;
}
_mbglDatabaseFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) {
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
});
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
}
- (void)reloadPacks {
MGLLogInfo(@"Reloading packs.");
[self getPacksWithCompletionHandler:^(NSArray<MGLOfflinePack *> *packs, __unused NSError * _Nullable error) {
for (MGLOfflinePack *pack in self.packs) {
[pack invalidate];
}
self.packs = [packs mutableCopy];
}];
}
- (void)getPacksWithCompletionHandler:(void (^)(NSArray<MGLOfflinePack *> *packs, NSError * _Nullable error))completion {
_mbglDatabaseFileSource->listOfflineRegions([&, completion](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) {
NSError *error;
NSMutableArray *packs;
if (!result) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeUnknown userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(result.error()).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
} else {
auto& regions = result.value();
packs = [NSMutableArray arrayWithCapacity:regions.size()];
for (auto &region : regions) {
MGLOfflinePack *pack = [[MGLOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))];
[packs addObject:pack];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) {
completion(packs, error);
});
}
});
}
- (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
MGLLogDebug(@"Setting maximumAllowedMapboxTiles: %lu", (unsigned long)maximumCount);
_mbglDatabaseFileSource->setOfflineMapboxTileCountLimit(maximumCount);
}
#pragma mark - Ambient Cache management
- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError * _Nullable))completion {
_mbglDatabaseFileSource->setMaximumAmbientCacheSize(cacheSize, [&, completion](std::exception_ptr exception) {
NSError *error;
if (completion) {
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
dispatch_sync(dispatch_get_main_queue(), ^ {
completion(error);
});
}
});
}
- (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable))completion {
_mbglDatabaseFileSource->invalidateAmbientCache([&, completion](std::exception_ptr exception){
NSError *error;
if (completion) {
if (exception) {
// Convert std::exception_ptr to an NSError.
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
dispatch_async(dispatch_get_main_queue(), ^ {
completion(error);
});
}
});
}
- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
_mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception){
NSError *error;
if (completion) {
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
});
}
- (void)resetDatabaseWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
_mbglDatabaseFileSource->resetDatabase([&, completion](std::exception_ptr exception) {
NSError *error;
if (completion) {
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeUnknown userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[MMEEventsManager sharedManager] reportError:error];
#endif
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}
});
}
#pragma mark -
- (unsigned long long)countOfBytesCompleted {
NSURL *cacheURL = [[self class] cacheURLIncludingSubdirectory:YES];
NSString *cachePath = cacheURL.path;
if (!cachePath) {
return 0;
}
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:cachePath error:NULL];
return attributes.fileSize;
}
- (void)preloadData:(NSData *)data forURL:(NSURL *)url modificationDate:(nullable NSDate *)modified expirationDate:(nullable NSDate *)expires eTag:(nullable NSString *)eTag mustRevalidate:(BOOL)mustRevalidate {
mbgl::Resource resource(mbgl::Resource::Kind::Unknown, url.absoluteString.UTF8String);
mbgl::Response response;
response.data = std::make_shared<std::string>(static_cast<const char*>(data.bytes), data.length);
response.mustRevalidate = mustRevalidate;
if (eTag) {
response.etag = std::string(eTag.UTF8String);
}
if (modified) {
response.modified = mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(MGLDurationFromTimeInterval(modified.timeIntervalSince1970));
}
if (expires) {
response.expires = mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(MGLDurationFromTimeInterval(expires.timeIntervalSince1970));
}
_mbglDatabaseFileSource->put(resource, response);
}
- (void)putResourceWithUrl:(NSURL *)url data:(NSData *)data modified:(nullable NSDate *)modified expires:(nullable NSDate *)expires etag:(nullable NSString *)etag mustRevalidate:(BOOL)mustRevalidate {
[self preloadData:data forURL:url modificationDate:modified expirationDate:expires eTag:etag mustRevalidate:mustRevalidate];
}
@end