From 9e53135fca7e1ad53b2bd768c8cd067f66525991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Tue, 5 May 2020 18:02:36 -0700 Subject: [PATCH] [ios, macos] Added Info.plist key to customize offline database path --- platform/darwin/src/MGLOfflineStorage.h | 8 ++ platform/darwin/src/MGLOfflineStorage.mm | 119 ++++++++++-------- platform/ios/CHANGELOG.md | 2 +- platform/ios/docs/guides/Info.plist Keys.md | 10 ++ platform/macos/CHANGELOG.md | 2 +- platform/macos/docs/guides/Info.plist Keys.md | 10 ++ 6 files changed, 99 insertions(+), 52 deletions(-) diff --git a/platform/darwin/src/MGLOfflineStorage.h b/platform/darwin/src/MGLOfflineStorage.h index c1041af7ba..87588324e7 100644 --- a/platform/darwin/src/MGLOfflineStorage.h +++ b/platform/darwin/src/MGLOfflineStorage.h @@ -217,11 +217,19 @@ MGL_EXPORT /** The file path at which offline packs and the ambient cache are stored. + + To customize this path, specify the + [`MGLOfflineStorageDatabasePath`](../infoplist-keys.html#mglofflinestoragedatabasepath) + key in Info.plist. */ @property (nonatomic, readonly, copy) NSString *databasePath; /** The file URL at which offline packs and the ambient cache are stored. + + To customize this path, specify the + [`MGLOfflineStorageDatabasePath`](../infoplist-keys.html#mglofflinestoragedatabasepath) + key in Info.plist. */ @property (nonatomic, readonly, copy) NSURL *databaseURL; diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index 035c30dd3a..56d8d920c2 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -29,6 +29,7 @@ #include +static NSString * const MGLOfflineStorageDatabasePathInfoDictionaryKey = @"MGLOfflineStorageDatabasePath"; static NSString * const MGLOfflineStorageFileName = @"cache.db"; static NSString * const MGLOfflineStorageFileName3_2_0_beta_1 = @"offline.db"; @@ -53,6 +54,7 @@ @interface MGLOfflineStorage () @end @implementation MGLOfflineStorage { + NSURL *_databaseURL; std::unique_ptr> _mbglResourceTransform; } @@ -161,26 +163,8 @@ - (instancetype)init { MGLInitializeRunLoop(); if (self = [super init]) { - _databaseURL = [[self class] cacheURLIncludingSubdirectory:YES]; - NSString *cachePath = self.databasePath; - NSAssert(cachePath, @"Offline pack database URL “%@” is not a valid file URL.", _databaseURL); - - // 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]; - } - mbgl::ResourceOptions options; - options.withCachePath(cachePath.UTF8String) + options.withCachePath(self.databasePath.UTF8String) .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String); _mbglFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::ResourceLoader, options); _mbglOnlineFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Network, options); @@ -238,70 +222,105 @@ - (NSString *)databasePath { return self.databaseURL.path; } +- (NSURL *)databaseURL { + if (!_databaseURL) { + NSString *customPath = [NSBundle.mainBundle objectForInfoDictionaryKey:MGLOfflineStorageDatabasePathInfoDictionaryKey]; + if ([customPath isKindOfClass:[NSString class]]) { + _databaseURL = [NSURL fileURLWithPath:customPath.stringByStandardizingPath isDirectory:NO relativeToURL:NSBundle.mainBundle.resourceURL]; + + NSURL *directoryURL = _databaseURL.URLByDeletingLastPathComponent; + [NSFileManager.defaultManager createDirectoryAtURL:directoryURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } else { + _databaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:YES]; + } + NSString *databasePath = self.databasePath; + NSAssert(databasePath, @"Offline pack database URL “%@” is not a valid file URL.", _databaseURL); + + // Move the offline database from v3.2.0-beta.1 to a location that can + // also be used for ambient caching. + if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { + NSString *legacyDatabasePath = [[self class] legacyDatabasePath]; + [[NSFileManager defaultManager] moveItemAtPath:legacyDatabasePath toPath:databasePath error:NULL]; + } + + // Move the offline database from v3.2.x path to a subdirectory that can + // be reliably excluded from backups. + if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { + NSURL *subdirectorylessDatabaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:NO]; + [[NSFileManager defaultManager] moveItemAtPath:subdirectorylessDatabaseURL.path toPath:databasePath error:NULL]; + } + } + return _databaseURL; +} + /** - 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. + Returns the default file URL to the offline pack database, 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 database 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: + The database is located at: ~/Library/Application Support/tld.app.bundle.id/.mapbox/cache.db - The subdirectory-less cache was located at: + The subdirectory-less database 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]; ++ (NSURL *)defaultDatabaseURLIncludingSubdirectory:(BOOL)useSubdirectory { + NSURL *databaseDirectoryURL = [[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]; + databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; if (useSubdirectory) { - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; + databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:@".mapbox"]; } - [[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL + [[NSFileManager defaultManager] createDirectoryAtURL:databaseDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil]; if (useSubdirectory) { - // Avoid backing up the offline cache onto iCloud, because it can be + // Avoid backing up the database 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]; + [databaseDirectoryURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; } - return [cacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName]; + return [databaseDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName]; } /** Returns the absolute path to the location where v3.2.0-beta.1 placed the - offline cache. + offline pack database. */ -+ (NSString *)legacyCachePath { ++ (NSString *)legacyDatabasePath { #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]; + NSString *legacyDatabasePath = [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 : @""; + NSURL *legacyDatabaseDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + legacyDatabaseDirectoryURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; + NSURL *legacyDatabaseURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName3_2_0_beta_1]; + NSString *legacyDatabasePath = legacyDatabaseURL ? legacyDatabaseURL.path : @""; #endif - return legacyCachePath; + return legacyDatabasePath; } - (void)addContentsOfFile:(NSString *)filePath withCompletionHandler:(MGLBatchedOfflinePackAdditionCompletionHandler)completion { @@ -553,7 +572,7 @@ - (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount { _mbglDatabaseFileSource->setOfflineMapboxTileCountLimit(maximumCount); } -#pragma mark - Ambient Cache management +#pragma mark - Ambient cache management - (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError * _Nullable))completion { _mbglDatabaseFileSource->setMaximumAmbientCacheSize(cacheSize, [&, completion](std::exception_ptr exception) { diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index ec82624a41..0ed033ea4c 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -18,7 +18,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Offline maps * Added the `-[MGLOfflinePack setContext:completionHandler:]` method for replacing the data associated with an offline pack, such as a name. ([#288](https://github.com/mapbox/mapbox-gl-native-ios/pull/288)) -* Added the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties. ([#298](https://github.com/mapbox/mapbox-gl-native-ios/pull/298)) +* Added the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties to obtain the path of the database that contains offline packs and the ambient cache. To customize this path, set the `MGLOfflineStorageDatabasePath` in Info.plist. ([#298](https://github.com/mapbox/mapbox-gl-native-ios/pull/298)) * Fixed an error that occurred if your implementation of the `-[MGLOfflineStorageDelegate offlineStorage:URLForResourceOfKind:]` method returned a local file URL. ([mapbox/mapbox-gl-native#16428](https://github.com/mapbox/mapbox-gl-native/pull/16428)) ### Other changes diff --git a/platform/ios/docs/guides/Info.plist Keys.md b/platform/ios/docs/guides/Info.plist Keys.md index 8da0907c60..4460457646 100644 --- a/platform/ios/docs/guides/Info.plist Keys.md +++ b/platform/ios/docs/guides/Info.plist Keys.md @@ -30,6 +30,16 @@ This key can either be set to a single string or an array of strings, which the To disable client-side rendering of CJK characters in favor of [server-side rendering](customizing-fonts.html#server-side-fonts), set this key to the Boolean value `NO`. +## MGLOfflineStorageDatabasePath + +This key customizes the file path at which `MGLOfflineStorage` keeps the offline map database, which contains any offline packs as well as the ambient cache. Most applications should not need to customize this path; however, you could customize it to implement a migration path between different versions of your application. + +The key is interpreted as either an absolute file path or a file path relative to the main bundle’s resource folder, resolving any tilde or symbolic link. The path must be writable. If a database does not exist at the path you specify, one will be created automatically. + +An offline map database can consume a significant amount of the user’s bandwidth and iCloud storage due to iCloud backups. To exclude the database from backups, set the containing directory’s `NSURLIsExcludedFromBackupKey` resource property to the Boolean value `YES` using the `-[NSURL setResourceValue:forKey:error:]` method. The entire directory will be affected, not just the database file. If the user restores the application from a backup, your application will need to restore any offline packs that had been previously downloaded. + +At runtime, you can obtain the value of this key using the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties. + ## MGLCollisionBehaviorPre4_0 If this key is set to YES (`true`), collision detection is performed only between symbol style layers based on the same source, as in versions 2.0–3.7 of the Mapbox Maps SDK for iOS. In other words, symbols in an `MGLSymbolStyleLayer` based on one source (for example, an `MGLShapeSource`) may overlap with symbols in another layer that is based on a different source (such as the Mapbox Streets source). This is the case regardless of the `MGLSymbolStyleLayer.iconAllowsOverlap`, `MGLSymbolStyleLayer.iconIgnoresPlacement`, `MGLSymbolStyleLayer.textAllowsOverlap`, and `MGLSymbolStyleLayer.textIgnoresPlacement` properties. diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 3e4b50d19d..9ad4e93853 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -16,7 +16,7 @@ ### Offline maps * Added the `-[MGLOfflinePack setContext:completionHandler:]` method for replacing the data associated with an offline pack, such as a name. ([#288](https://github.com/mapbox/mapbox-gl-native-ios/pull/288)) -* Added the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties. ([#298](https://github.com/mapbox/mapbox-gl-native-ios/pull/298)) +* Added the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties to obtain the path of the database that contains offline packs and the ambient cache. To customize this path, set the `MGLOfflineStorageDatabasePath` in Info.plist. ([#298](https://github.com/mapbox/mapbox-gl-native-ios/pull/298)) * Fixed an error that occurred if your implementation of the `-[MGLOfflineStorageDelegate offlineStorage:URLForResourceOfKind:]` method returned a local file URL. ([mapbox/mapbox-gl-native#16428](https://github.com/mapbox/mapbox-gl-native/pull/16428)) ### Other changes diff --git a/platform/macos/docs/guides/Info.plist Keys.md b/platform/macos/docs/guides/Info.plist Keys.md index 0b74e9fd6b..5fcdb463be 100644 --- a/platform/macos/docs/guides/Info.plist Keys.md +++ b/platform/macos/docs/guides/Info.plist Keys.md @@ -26,6 +26,16 @@ This key can either be set to a single string or an array of strings, which the To disable client-side rendering of CJK characters in favor of [server-side rendering](customizing-fonts.html#server-side-fonts), set this key to the Boolean value `NO`. +## MGLOfflineStorageDatabasePath + +This key customizes the file path at which `MGLOfflineStorage` keeps the offline map database, which contains any offline packs as well as the ambient cache. Most applications should not need to customize this path; however, you could customize it to implement a migration path between different versions of your application. + +The key is interpreted as either an absolute file path or a file path relative to the main bundle’s resource folder, resolving any tilde or symbolic link. The path must be writable. If a database does not exist at the path you specify, one will be created automatically. + +An offline map database can consume a significant amount of the user’s bandwidth and iCloud storage due to iCloud backups. To exclude the database from backups, set the containing directory’s `NSURLIsExcludedFromBackupKey` resource property to the Boolean value `YES` using the `-[NSURL setResourceValue:forKey:error:]` method. The entire directory will be affected, not just the database file. If the user restores the application from a backup, your application will need to restore any offline packs that had been previously downloaded. + +At runtime, you can obtain the value of this key using the `MGLOfflineStorage.databasePath` and `MGLOfflineStorage.databaseURL` properties. + ## MGLCollisionBehaviorPre4_0 If this key is set to YES (`true`), collision detection is performed only between symbol style layers based on the same source, as in versions 0.1–0.7 of the Mapbox Maps SDK for iOS. In other words, symbols in an `MGLSymbolStyleLayer` based on one source (for example, an `MGLShapeSource`) may overlap with symbols in another layer that is based on a different source (such as the Mapbox Streets source). This is the case regardless of the `MGLSymbolStyleLayer.iconAllowsOverlap`, `MGLSymbolStyleLayer.iconIgnoresPlacement`, `MGLSymbolStyleLayer.textAllowsOverlap`, and `MGLSymbolStyleLayer.textIgnoresPlacement` properties.