From b493bdc649b4e32cbedb3fd5cb9e3e4157dfdb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 17 Mar 2016 16:37:37 -0700 Subject: [ios, osx] Unified offline and ambient caches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is now only one instance of mbgl::OfflineFileSource, created when the shared MGLOfflineStorage object is initialized. Also create and use the shared MGLOfflineStorage object when initializing an MGLMapView object. The unified cache file is located in a subdirectory of Application Support, where the SDK has control over the file’s lifetime. The subdirectory is already named after the host application’s bundle identifier, ensuring that each Mapbox-powered application has an independent tile limit. If there’s an ambient cache in a subdirectory of Caches, delete it. If there’s an offline cache in a subdirectory of Documents on iOS or Caches on OS X, move it to the unified cache location in a subdirectory of Application Support. Fixes the iOS/OS X side of #4338. --- CHANGELOG.md | 3 +- platform/darwin/src/MGLOfflineStorage.mm | 41 ++++++++++++++++++---- platform/ios/src/MGLMapView.mm | 42 +++++----------------- platform/osx/src/MGLMapView.mm | 60 ++++++++++---------------------- 4 files changed, 64 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 618f4d2615..9cd8ae1e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,9 +57,10 @@ Known issues: - Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802)) - Resolved in 2.2.0 -## iOS master +## iOS 3.2.0 - Offline packs can now be downloaded to allow users to view specific regions of the map offline. A new MGLOfflineStorage class provides APIs for managing MGLOfflinePacks. ([#4221](https://github.com/mapbox/mapbox-gl-native/pull/4221)) +- Tiles and other resources are cached in the same file that holds offline resources. The combined cache file is located in a subdirectory of the user’s Application Support directory, which means iOS will not delete the file when disk space runs low. ([#4377](https://github.com/mapbox/mapbox-gl-native/pull/4377)) - The user dot no longer disappears after panning the map across the antimeridian at low zoom levels. ([#4275](https://github.com/mapbox/mapbox-gl-native/pull/4275)) - The map no longer recoils when panning quickly at low zoom levels. ([#4214](https://github.com/mapbox/mapbox-gl-native/pull/4214)) - An icon laid out along a line no longer appears if it would extend past the end of the line. Some one-way arrows no longer point the wrong way. ([#3839](https://github.com/mapbox/mapbox-gl-native/pull/3839)) diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index 3eac28ca78..1f925b3ee2 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -22,18 +22,20 @@ static dispatch_once_t onceToken; static MGLOfflineStorage *sharedOfflineStorage; dispatch_once(&onceToken, ^{ - sharedOfflineStorage = [[self alloc] initWithFileName:@"offline.db"]; + sharedOfflineStorage = [[self alloc] initWithFileName:@"cache.db"]; }); return sharedOfflineStorage; } +// This method can’t be called -init, because that selector has been marked +// unavailable in MGLOfflineStorage.h. - (instancetype)initWithFileName:(NSString *)fileName { if (self = [super init]) { -#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *cachePath = [paths.firstObject stringByAppendingPathComponent:fileName]; -#elif TARGET_OS_MAC - NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + // 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 @@ -46,7 +48,34 @@ error:nil]; NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:fileName]; NSString *cachePath = cacheURL ? cacheURL.path : @""; + + // Move the offline cache from v3.2.0-beta.1 to a location that can also + // be used for ambient caching. + NSString *legacyCacheFileName = @"offline.db"; +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + // ~/Documents/offline.db + NSArray *legacyPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *legacyCachePath = [legacyPaths.firstObject stringByAppendingPathComponent:legacyCacheFileName]; +#elif TARGET_OS_MAC + // ~/Library/Caches/tld.app.bundle.id/offline.db + NSURL *legacyCacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:YES + error:nil]; + legacyCacheDirectoryURL = [legacyCacheDirectoryURL URLByAppendingPathComponent: + [NSBundle mainBundle].bundleIdentifier]; + [[NSFileManager defaultManager] createDirectoryAtURL:legacyCacheDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + NSURL *legacyCacheURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:legacyCacheFileName]; + NSString *legacyCachePath = legacyCacheURL ? legacyCacheURL.path : @""; #endif + if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { + [[NSFileManager defaultManager] moveItemAtPath:legacyCachePath 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). diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 21d15ed1dd..2def0c54f0 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -30,6 +30,7 @@ #import "Mapbox.h" #import "../../darwin/src/MGLGeometry_Private.h" #import "../../darwin/src/MGLMultiPoint_Private.h" +#import "../../darwin/src/MGLOfflineStorage_Private.h" #import "NSBundle+MGLAdditions.h" #import "NSString+MGLAdditions.h" @@ -37,7 +38,6 @@ #import "NSException+MGLAdditions.h" #import "MGLUserLocationAnnotationView.h" #import "MGLUserLocation_Private.h" -#import "MGLAccountManager_Private.h" #import "MGLAnnotationImage_Private.h" #import "MGLMapboxEvents.h" #import "MGLCompactCalloutView.h" @@ -187,7 +187,6 @@ public: { mbgl::Map *_mbglMap; MBGLView *_mbglView; - mbgl::DefaultFileSource *_mbglFileSource; BOOL _opaque; @@ -303,18 +302,15 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) // setup mbgl view const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; _mbglView = new MBGLView(self, scaleFactor); - - // setup mbgl cache & file source - NSString *fileCachePath = @""; + + // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - if ([paths count] != 0) { - NSString *libraryDirectory = [paths objectAtIndex:0]; - fileCachePath = [libraryDirectory stringByAppendingPathComponent:@"cache.db"]; - } - _mbglFileSource = new mbgl::DefaultFileSource([fileCachePath UTF8String], [[[[NSBundle mainBundle] resourceURL] path] UTF8String]); + NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"]; + [[NSFileManager defaultManager] removeItemAtPath:fileCachePath error:NULL]; // setup mbgl map - _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None); + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; + _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None); // start paused if in IB if (_isTargetingInterfaceBuilder || background) { @@ -322,13 +318,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) _mbglMap->pause(); } - // 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]; - // Notify map object when network reachability status changes. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) @@ -506,7 +495,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"]; [_attributionButton removeObserver:self forKeyPath:@"hidden"]; [self validateDisplayLink]; @@ -517,12 +505,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) _mbglMap = nullptr; } - if (_mbglFileSource) - { - delete _mbglFileSource; - _mbglFileSource = nullptr; - } - if (_mbglView) { delete _mbglView; @@ -1606,15 +1588,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(__unused void *)context { - // Synchronize mbgl::Map’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]]) { - _mbglFileSource->setAccessToken((std::string)[accessToken UTF8String]); - } - } - else if ([keyPath isEqualToString:@"hidden"] && object == _attributionButton) + if ([keyPath isEqualToString:@"hidden"] && object == _attributionButton) { NSNumber *hiddenNumber = change[NSKeyValueChangeNewKey]; BOOL attributionButtonWasHidden = [hiddenNumber boolValue]; diff --git a/platform/osx/src/MGLMapView.mm b/platform/osx/src/MGLMapView.mm index 07c080663f..e09a6c2672 100644 --- a/platform/osx/src/MGLMapView.mm +++ b/platform/osx/src/MGLMapView.mm @@ -4,10 +4,11 @@ #import "MGLOpenGLLayer.h" #import "MGLStyle.h" -#import "../../darwin/src/MGLAccountManager_Private.h" #import "../../darwin/src/MGLGeometry_Private.h" #import "../../darwin/src/MGLMultiPoint_Private.h" +#import "../../darwin/src/MGLOfflineStorage_Private.h" +#import "MGLAccountManager.h" #import "MGLMapCamera.h" #import "MGLPolygon.h" #import "MGLPolyline.h" @@ -151,7 +152,6 @@ public: /// Cross-platform map view controller. mbgl::Map *_mbglMap; MGLMapViewImpl *_mbglView; - mbgl::DefaultFileSource *_mbglFileSource; NSPanGestureRecognizer *_panGestureRecognizer; NSMagnificationGestureRecognizer *_magnificationGestureRecognizer; @@ -232,36 +232,25 @@ public: // Set up cross-platform controllers and resources. _mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor); - // Place the cache in a location that can be shared among all the - // applications that embed the Mapbox OS X SDK. - NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:YES - error:nil]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent: - [[NSBundle mgl_frameworkBundle] bundleIdentifier]]; - [[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; - NSString *cachePath = cacheURL ? cacheURL.path : @""; - _mbglFileSource = new mbgl::DefaultFileSource(cachePath.UTF8String, [[[[NSBundle mainBundle] resourceURL] path] UTF8String]); - - _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None); + // Delete the pre-offline ambient cache at + // ~/Library/Caches/com.mapbox.sdk.ios/cache.db. + NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent: + [NSBundle mgl_frameworkBundle].bundleIdentifier]; + NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"]; + [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL]; + + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; + _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None); // Install the OpenGL layer. Interface Builder’s synchronous drawing means // we can’t display a map, so don’t even bother to have a map layer. self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer]; - // 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]; - // Notify map object when network reachability status changes. MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection]; reachability.reachableBlock = ^(MGLReachability *) { @@ -443,7 +432,6 @@ public: } - (void)dealloc { - [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"]; [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; @@ -455,25 +443,15 @@ public: delete _mbglMap; _mbglMap = nullptr; } - if (_mbglFileSource) { - delete _mbglFileSource; - _mbglFileSource = nullptr; - } if (_mbglView) { delete _mbglView; _mbglView = nullptr; } } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(__unused void *)context { - // Synchronize mbgl::Map’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]]) { - _mbglFileSource->setAccessToken((std::string)accessToken.UTF8String); - } - } else if ([keyPath isEqualToString:@"contentLayoutRect"] || - [keyPath isEqualToString:@"titlebarAppearsTransparent"]) { +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(__unused NSDictionary *)change context:(__unused void *)context { + if ([keyPath isEqualToString:@"contentLayoutRect"] || + [keyPath isEqualToString:@"titlebarAppearsTransparent"]) { [self adjustContentInsets]; } } -- cgit v1.2.1