diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index a59fb699439..dd15920eab2 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -40,58 +40,92 @@ + (instancetype)sharedOfflineStorage { return sharedOfflineStorage; } +/** + 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 mainBundle].bundleIdentifier; + if (!bundleIdentifier) { + // There’s no main bundle identifier when running in a unit test bundle. + bundleIdentifier = [NSBundle bundleForClass:self].bundleIdentifier; + } + 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 mainBundle].bundleIdentifier; + 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 { if (self = [super init]) { - // Place the cache in a location specific to the application, so that - // packs downloaded by other applications don’t count toward this - // application’s limits. - // ~/Library/Application Support/tld.app.bundle.id/cache.db - NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:YES - error:nil]; - NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; - if (!bundleIdentifier) { - // There’s no main bundle identifier when running in a unit test bundle. - bundleIdentifier = [NSBundle bundleForClass:[self class]].bundleIdentifier; - } - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - [[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName]; - NSString *cachePath = cacheURL ? cacheURL.path : @""; - + 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 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 - 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 if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { + NSString *legacyCachePath = [[self class] legacyCachePath]; [[NSFileManager defaultManager] moveItemAtPath:legacyCachePath toPath:cachePath error:NULL]; } - - _mbglFileSource = new mbgl::DefaultFileSource(cachePath.UTF8String, [NSBundle mainBundle].resourceURL.path.UTF8String); - // 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. - [cacheURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey 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]; + } + + _mbglFileSource = new mbgl::DefaultFileSource(cachePath.UTF8String, [NSBundle mainBundle].resourceURL.path.UTF8String); // Observe for changes to the global access token (and find out the current one). [[MGLAccountManager sharedManager] addObserver:self diff --git a/platform/darwin/test/MGLOfflineStorageTests.m b/platform/darwin/test/MGLOfflineStorageTests.m index 415039c527b..e2346c5f61b 100644 --- a/platform/darwin/test/MGLOfflineStorageTests.m +++ b/platform/darwin/test/MGLOfflineStorageTests.m @@ -106,17 +106,18 @@ - (void)testBackupExclusion { // Unit tests don't use the main bundle; use com.mapbox.ios.sdk instead. NSString *bundleIdentifier = [NSBundle bundleForClass:[MGLMapView class]].bundleIdentifier; cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Cache directory should exist."); + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Cache subdirectory should exist."); NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], @"Cache database should exist."); NSError *error = nil; NSNumber *exclusionFlag = nil; - [cacheURL getResourceValue:&exclusionFlag - forKey:NSURLIsExcludedFromBackupKey - error:&error]; - XCTAssertTrue(exclusionFlag && [exclusionFlag boolValue], @"Backup exclusion flag should be set for cache database."); + [cacheDirectoryURL getResourceValue:&exclusionFlag + forKey:NSURLIsExcludedFromBackupKey + error:&error]; + XCTAssertTrue(exclusionFlag && [exclusionFlag boolValue], @"Backup exclusion flag should be set for the directory containing the cache database."); XCTAssertNil(error, @"No errors should be returned when checking backup exclusion flag."); } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 99d092dee96..3723fbd67b9 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -39,7 +39,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CON ### Offline maps - `MGLOfflinePackProgress` now indicates how many tiles have been downloaded and how much space they take up. ([#4874](https://github.com/mapbox/mapbox-gl-native/pull/4874)) -- Fixed an issue (speculatively) where the tile cache could be included in iCloud backups. ([#5124](https://github.com/mapbox/mapbox-gl-native/pull/5124)) +- Fixed an issue where the tile cache could be included in iCloud backups on the first launch. ([#5124](https://github.com/mapbox/mapbox-gl-native/pull/5124), [#5601](https://github.com/mapbox/mapbox-gl-native/pull/5601)) - Suppressed “Unable to make space for entry” console spew. ([#4708](https://github.com/mapbox/mapbox-gl-native/pull/4708)) - Deprecated `-[MGLMapView emptyMemoryCache]`. ([#4725](https://github.com/mapbox/mapbox-gl-native/pull/4725))