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

Commit

Permalink
[ios] Cache Management API (#14978)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmkiley authored Jul 16, 2019
1 parent 0baa54a commit 9d8839c
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 37 deletions.
87 changes: 84 additions & 3 deletions platform/darwin/src/MGLOfflineStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ typedef NS_ENUM(NSUInteger, MGLResourceKind) {

/**
MGLOfflineStorage implements a singleton (shared object) that manages offline
packs. All of this class’s instance methods are asynchronous, reflecting the
fact that offline resources are stored in a database. The shared object
maintains a canonical collection of offline packs in its `packs` property.
packs and ambient caching. All of this class’s instance methods are asynchronous,
reflecting the fact that offline resources are stored in a database. The shared
object maintains a canonical collection of offline packs in its `packs` property.
#### Related examples
See the <a href="https://docs.mapbox.com/ios/maps/examples/offline-pack/">
Expand Down Expand Up @@ -303,6 +303,21 @@ MGL_EXPORT
*/
- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(nullable MGLOfflinePackRemovalCompletionHandler)completion;

/**
Invalidates the specified offline pack. This method checks that the tiles
in the specified offline pack match those from the server. Local tiles that
do not match the latest version on the server are updated.
This is more efficient than deleting the offline pack and downloading it
again. If the data stored locally matches that on the server, new data will
not be downloaded.
@param pack The offline pack to be invalidated.
@param completion The completion handler to call once the pack has been
removed. This handler is executed asynchronously on the main queue.
*/

- (void)invalidatePack:(MGLOfflinePack *)pack withCompletionHandler:(void (^)(NSError * _Nullable))completion;
/**
Forcibly, asynchronously reloads the `packs` property. At some point after this
method is called, the pointer values of the `MGLOfflinePack` objects in the
Expand Down Expand Up @@ -341,6 +356,72 @@ MGL_EXPORT
*/
@property (nonatomic, readonly) unsigned long long countOfBytesCompleted;


#pragma mark - Managing Ambient Cache

/**
Sets the maximum ambient cache size in megabytes. The default maximum cache
size is 50 MB. To disable ambient caching, set the maximum ambient cache size
to `0`. Setting the maximum ambient cache size does not impact the maximum size
of offline packs.
While this method does not limit the space available to offline packs,
data in offline packs count towards this limit. If the maximum ambient
cache size is set to 30 MB and 20 MB of offline packs are downloaded,
there may be only 10 MB reserved for the ambient cache.
This method should be called before the map and map style have been loaded.
This method is potentially expensive, as the database will trim cached data
in order to prevent the ambient cache from being larger than the
specified amount.
@param cacheSize The maximum size in bytes for the ambient cache.
@param completion The completion handler to call once the maximum ambient cache size
has been set. This handler is executed synchronously on the main queue.
*/

- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError *_Nullable error))completion;

/**
Invalidates the ambient cache. This method checks that the tiles in the
ambient cache match those from the server. If the local tiles do not match
those on the server, they are re-downloaded.
This is recommended over clearing the cache or resetting the database
because valid local tiles will not be downloaded again.
Resources shared with offline packs will not be affected by this method.
@param completion The completion handler to call once the ambient cache has
been revalidated. This handler is executed asynchronously on the main queue.
*/

- (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion;

/**
Clears the ambient cache by deleting resources. This method does not
affect resources shared with offline regions.
@param completion The completion handler to call once resources from
the ambient cache have been cleared. This handler is executed
asynchronously on the main queue.
*/

- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion;

/**
Deletes the existing database, which includes both the ambient cache and offline packs,
then reinitializes it.
You typically do not need to call this method.
@param completion The completion handler to call once the pack has database has
been reset. This handler is executed asynchronously on the main queue.
*/

- (void)resetDatabaseWithCompletionHandler:(void (^)(NSError *_Nullable error))completion;

/*
Inserts the provided resource into the ambient cache.
Expand Down
106 changes: 97 additions & 9 deletions platform/darwin/src/MGLOfflineStorage.mm
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ @interface MGLOfflineStorage ()
@property (nonatomic) std::shared_ptr<mbgl::DefaultFileSource> mbglFileSource;
@property (nonatomic) std::string mbglCachePath;
@property (nonatomic, getter=isPaused) BOOL paused;

@end

@implementation MGLOfflineStorage {
Expand Down Expand Up @@ -341,10 +340,11 @@ - (void)_addContentsOfFile:(NSString *)filePath withCompletionHandler:(void (^)(
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:-1 userInfo:@{
NSLocalizedDescriptionKey: description,
NSLocalizedFailureReasonErrorKey: @(mbgl::util::toString(result.error()).c_str())
}];
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey: description,
NSLocalizedFailureReasonErrorKey: @(mbgl::util::toString(result.error()).c_str())
}];
} else {
auto& regions = result.value();
packs = [NSMutableArray arrayWithCapacity:regions.size()];
Expand Down Expand Up @@ -401,7 +401,7 @@ - (void)_addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)co
NSError *error;
if (!mbglOfflineRegion) {
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str());
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:errorDescription ? @{
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{
NSLocalizedDescriptionKey: errorDescription,
} : nil];
}
Expand Down Expand Up @@ -431,11 +431,10 @@ - (void)_removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePack
completion(nil);
return;
}

_mbglFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
Expand All @@ -445,6 +444,29 @@ - (void)_removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePack
});
}
});

}

- (void)invalidatePack:(MGLOfflinePack *)pack withCompletionHandler:(void (^)(NSError * _Nullable))completion {
mbgl::OfflineRegion& region = *pack.mbglOfflineRegion;
NSError *error;
if (!pack.mbglOfflineRegion) {
completion(nil);
return;
}

_mbglFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) {
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeModifyingOfflineStorageFailed userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
});
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
}

- (void)reloadPacks {
Expand All @@ -462,7 +484,7 @@ - (void)getPacksWithCompletionHandler:(void (^)(NSArray<MGLOfflinePack *> *packs
NSError *error;
NSMutableArray *packs;
if (!result) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeUnknown userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(result.error()).c_str()),
}];
} else {
Expand All @@ -486,6 +508,72 @@ - (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
_mbglFileSource->setOfflineMapboxTileCountLimit(maximumCount);
}

#pragma mark - Ambient Cache management

- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError * _Nullable))completion {
_mbglFileSource->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()),
}];
}
dispatch_sync(dispatch_get_main_queue(), ^ {
completion(error);
});
}
});
}

- (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable))completion {
_mbglFileSource->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()),
}];
}
dispatch_async(dispatch_get_main_queue(), ^ {
completion(error);
});
}
});
}

- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
_mbglFileSource->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()),
}];
}
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
});
}

- (void)resetDatabaseWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
_mbglFileSource->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()),
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}
});
}
#pragma mark -

- (unsigned long long)countOfBytesCompleted {
Expand Down
4 changes: 3 additions & 1 deletion platform/darwin/src/MGLTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ typedef NS_ENUM(NSInteger, MGLErrorCode) {
/** Source is in use and cannot be removed */
MGLErrorCodeSourceIsInUseCannotRemove = 7,
/** Source is in use and cannot be removed */
MGLErrorCodeSourceIdentifierMismatch = 8
MGLErrorCodeSourceIdentifierMismatch = 8,
/** An error occurred while modifying the offline storage database */
MGLErrorCodeModifyingOfflineStorageFailed = 9
};

/** Options for enabling debugging features in an `MGLMapView` instance. */
Expand Down
62 changes: 62 additions & 0 deletions platform/darwin/test/MGLOfflineStorageTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,68 @@ - (void)testAddPackForGeometry {
pack = nil;
}

- (void)testInvalidatePack {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect offline pack to be invalidated without an error."];
MGLCoordinateBounds bounds = {
{ .latitude = 48.8660, .longitude = 2.3306 },
{ .latitude = 48.8603, .longitude = 2.3213 },
};

NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:10 toZoomLevel:11];

NSString *nameKey = @"Name";
NSString *name = @"Paris square";

NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey: name}];
[[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
XCTAssertNotNil(pack);
[[MGLOfflineStorage sharedOfflineStorage] invalidatePack:pack withCompletionHandler:^(NSError * _Nullable) {
XCTAssertNotNil(pack);
XCTAssertNil(error);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}

- (void)testSetMaximumAmbientCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect maximum cache size to be raised without an error."];
[[MGLOfflineStorage sharedOfflineStorage] setMaximumAmbientCacheSize:0 withCompletionHandler:^(NSError * _Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];

[self waitForExpectationsWithTimeout:10 handler:nil];
}

- (void)testInvalidateAmbientCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be invalidated without an error."];
[[MGLOfflineStorage sharedOfflineStorage] invalidateAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}

- (void)testClearCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be cleared without an error."];
[[MGLOfflineStorage sharedOfflineStorage] clearAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}

- (void)testResetDatabase {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."];
[[MGLOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}

- (void)testBackupExclusion {
NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
Expand Down
13 changes: 13 additions & 0 deletions platform/ios/app/MBXOfflinePacksTableViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ - (IBAction)addCurrentRegion:(id)sender {
[self presentViewController:alertController animated:YES completion:nil];
}

- (IBAction)invalidatePacks:(id)sender {
for (MGLOfflinePack *pack in [MGLOfflineStorage sharedOfflineStorage].packs) {

CFTimeInterval start = CACurrentMediaTime();
[[MGLOfflineStorage sharedOfflineStorage] invalidatePack:pack withCompletionHandler:^(NSError * _Nullable error) {
CFTimeInterval end = CACurrentMediaTime();
CFTimeInterval difference = end - start;
NSLog(@"invalidatePack Started: %f Ended: %f Total Time: %f", start, end, difference);
}];
}
}


#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
Expand Down
Loading

0 comments on commit 9d8839c

Please sign in to comment.