Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[ios, osx] Centralized offline pack management
Browse files Browse the repository at this point in the history
MGLOfflineStorage now maintains a centralized, strongly held, KVO-compliant collection of all extant offline packs. MGLOfflineStorage issues notifications for progress updates. Client code can now react to the successful addition of an offline pack either via the completion block or via a KVO insertion.

Fixes #4287.
  • Loading branch information
1ec5 committed Mar 28, 2016
1 parent 71f6773 commit cd3964a
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 82 deletions.
10 changes: 0 additions & 10 deletions platform/darwin/include/MGLOfflinePack.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,6 @@ typedef struct MGLOfflinePackProgress {
*/
- (void)suspend;

/**
Request an asynchronous update to the pack’s `state` and `progress` properties.
The state and progress of an inactive or completed pack are computed lazily. If
you need the state or progress of a pack inside an
`MGLOfflinePackListingCompletionHandler`, set the `delegate` property then call
this method.
*/
- (void)requestProgress;

@end

/**
Expand Down
36 changes: 17 additions & 19 deletions platform/darwin/include/MGLOfflineStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ NS_ASSUME_NONNULL_BEGIN
@class MGLOfflinePack;
@protocol MGLOfflineRegion;

extern NSString * const MGLOfflinePackProgressChangedNotification;
extern NSString * const MGLOfflinePackErrorNotification;
extern NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification;

extern NSString * const MGLOfflinePackErrorUserInfoKey;
extern NSString * const MGLOfflinePackMaximumCountUserInfoKey;

/**
A block to be called once an offline pack has been completely created and
added.
Expand All @@ -28,16 +35,6 @@ typedef void (^MGLOfflinePackAdditionCompletionHandler)(MGLOfflinePack * _Nullab
*/
typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error);

/**
A block to be called with a complete list of offline packs.
@param pack Contains a pointer an array of packs, or `nil` if there was an
error obtaining the packs.
@param error Contains a pointer to an error object (if any) indicating why the
list of packs could not be obtained.
*/
typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error);

/**
MGLOfflineStorage implements a singleton (shared object) that manages offline
packs. All of this class’s instance methods are asynchronous, reflecting the
Expand All @@ -50,7 +47,16 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac
*/
+ (instancetype)sharedOfflineStorage;

- (instancetype)init NS_UNAVAILABLE;
/**
An array of all known offline packs.
This property is set to `nil`, indicating that the receiver does not yet know
the existing packs, for an undefined amount of time starting from the moment
the shared offline storage object is initialized until the packs are fetched
from the database. After that point, this property is always non-nil, but it
may be empty to indicate that no packs are present.
*/
@property (nonatomic, copy, readonly, nullable) NS_ARRAY_OF(MGLOfflinePack *) *packs;

/**
Creates and registers an offline pack that downloads the resources needed to
Expand Down Expand Up @@ -84,14 +90,6 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac
*/
- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(nullable MGLOfflinePackRemovalCompletionHandler)completion;

/**
Asynchronously calls a completion callback with all existing offline packs.
@param completion The completion handler to call with the list of packs. This
handler is executed asynchronously on the main queue.
*/
- (void)getPacksWithCompletionHandler:(MGLOfflinePackListingCompletionHandler)completion;

/**
Sets the maximum number of Mapbox-hosted tiles that may be downloaded and
stored on the current device.
Expand Down
10 changes: 10 additions & 0 deletions platform/darwin/src/MGLOfflinePack_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ NS_ASSUME_NONNULL_BEGIN

- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region;

/**
Request an asynchronous update to the pack’s `state` and `progress` properties.
The state and progress of an inactive or completed pack are computed lazily. If
you need the state or progress of a pack inside an
`MGLOfflinePackListingCompletionHandler`, set the `delegate` property then call
this method.
*/
- (void)requestProgress;

/**
Invalidates the pack and ensures that no future progress update can ever
revalidate it.
Expand Down
110 changes: 98 additions & 12 deletions platform/darwin/src/MGLOfflineStorage.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,30 @@

#include <mbgl/util/string.hpp>

@interface MGLOfflineStorage ()
static NSString * const MGLOfflineStorageFileName = @"cache.db";
static NSString * const MGLOfflineStorageFileName3_2_0_beta_1 = @"offline.db";

@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;
NSString * const MGLOfflinePackProgressChangedNotification = @"MGLOfflinePackProgressChanged";
NSString * const MGLOfflinePackErrorNotification = @"MGLOfflinePackError";
NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification = @"MGLOfflinePackMaximumMapboxTilesReached";

NSString * const MGLOfflinePackErrorUserInfoKey = @"Error";
NSString * const MGLOfflinePackMaximumCountUserInfoKey = @"MaximumCount";

/**
A block to be called with a complete list of offline packs.
@param pack Contains a pointer an array of packs, or `nil` if there was an
error obtaining the packs.
@param error Contains a pointer to an error object (if any) indicating why the
list of packs could not be obtained.
*/
typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error);

@interface MGLOfflineStorage () <MGLOfflinePackDelegate>

- (instancetype)initWithFileName:(NSString *)fileName NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy, readwrite) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *packs;
@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;

@end

Expand All @@ -22,14 +41,20 @@ + (instancetype)sharedOfflineStorage {
static dispatch_once_t onceToken;
static MGLOfflineStorage *sharedOfflineStorage;
dispatch_once(&onceToken, ^{
sharedOfflineStorage = [[self alloc] initWithFileName:@"cache.db"];
sharedOfflineStorage = [[self alloc] init];
[sharedOfflineStorage getPacksWithCompletionHandler:^(NS_ARRAY_OF(MGLOfflinePack *) *packs, __unused NSError *error) {
sharedOfflineStorage.packs = [packs mutableCopy];

for (MGLOfflinePack *pack in packs) {
pack.delegate = sharedOfflineStorage;
[pack requestProgress];
}
}];
});
return sharedOfflineStorage;
}

// This method can’t be called -init, because that selector has been marked
// unavailable in MGLOfflineStorage.h.
- (instancetype)initWithFileName:(NSString *)fileName {
- (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
Expand All @@ -46,16 +71,15 @@ - (instancetype)initWithFileName:(NSString *)fileName {
withIntermediateDirectories:YES
attributes:nil
error:nil];
NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:fileName];
NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName];
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];
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
Expand All @@ -69,7 +93,7 @@ - (instancetype)initWithFileName:(NSString *)fileName {
withIntermediateDirectories:YES
attributes:nil
error:nil];
NSURL *legacyCacheURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:legacyCacheFileName];
NSURL *legacyCacheURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageLegacyFileName];
NSString *legacyCachePath = legacyCacheURL ? legacyCacheURL.path : @"";
#endif
if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
Expand All @@ -95,17 +119,52 @@ - (void)dealloc {
_mbglFileSource = nullptr;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(__unused void *)context {
#pragma mark KVO methods

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(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]]) {
self.mbglFileSource->setAccessToken(accessToken.UTF8String);
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)insertPacks:(NSArray *)packs atIndexes:(NSIndexSet *)indices {
[(NSMutableArray *)self.packs insertObjects:packs atIndexes:indices];
}

- (void)addPacksObject:(MGLOfflinePack *)pack {
[(NSMutableArray *)self.packs addObject:pack];
}

- (void)removePacksAtIndexes:(NSIndexSet *)indices {
[(NSMutableArray *)self.packs removeObjectsAtIndexes:indices];
}

- (void)removePacksObject:(MGLOfflinePack *)pack {
[(NSMutableArray *)self.packs removeObject:pack];
}

#pragma mark Pack management methods

- (void)addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
__weak MGLOfflineStorage *weakSelf = self;
[self _addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
MGLOfflineStorage *strongSelf = weakSelf;
[strongSelf addPacksObject:pack];
pack.delegate = strongSelf;
[pack requestProgress];
if (completion) {
completion(pack, error);
}
}];
}

- (void)_addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
if (![region conformsToProtocol:@protocol(MGLOfflineRegion_Private)]) {
[NSException raise:@"Unsupported region type" format:
@"Regions of type %@ are unsupported.", NSStringFromClass([region class])];
Expand Down Expand Up @@ -133,6 +192,15 @@ - (void)addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)con
}

- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion {
[self removePacksObject: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];
self.mbglFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
Expand Down Expand Up @@ -178,4 +246,22 @@ - (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
_mbglFileSource->setOfflineMapboxTileCountLimit(maximumCount);
}

#pragma mark MGLOfflinePackDelegate methods

- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(__unused MGLOfflinePackProgress)progress {
[[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackProgressChangedNotification object:pack];
}

- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error {
[[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackErrorNotification object:pack userInfo:@{
MGLOfflinePackErrorUserInfoKey: error,
}];
}

- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
[[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackMaximumMapboxTilesReachedNotification object:pack userInfo:@{
MGLOfflinePackMaximumCountUserInfoKey: @(maximumCount),
}];
}

@end
Loading

0 comments on commit cd3964a

Please sign in to comment.