[ios] Webstorage backup to cloud/local-only LocalStorage and WebSQL databases have been manually backed up in cordova since iOS5.1 moved them into a Caches directory. iOS6 adds a UserDefaults setting to automatically persist these databases, but with the added feature of backup up to iCloud.
We wanted to allow disabling iCloud backup on iOS6, as well as marking our manual backups to not be backed up to cloud on iOS5.1. This patch changes the BackupWebStorage plist option from a boolean to a 3-option string: [none, local, cloud]. Toggling between none/local/cloud as well as upgrading ios versions should work correctly. Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/commit/a39921be Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/tree/a39921be Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/diff/a39921be Branch: refs/heads/master Commit: a39921bed239706dc4a0b3a22762d97bc0b2c993 Parents: 78ba908 Author: Michal Mocny <mmo...@gmail.com> Authored: Mon Oct 22 13:44:03 2012 -0400 Committer: Michal Mocny <mmo...@gmail.com> Committed: Mon Oct 22 13:44:03 2012 -0400 ---------------------------------------------------------------------- CordovaLib/Classes/CDVLocalStorage.h | 3 +- CordovaLib/Classes/CDVLocalStorage.m | 89 +++++++++++------- CordovaLib/Classes/CDVViewController.m | 25 +++-- bin/templates/project/__TESTING__/Cordova.plist | 2 +- 4 files changed, 71 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a39921be/CordovaLib/Classes/CDVLocalStorage.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVLocalStorage.h b/CordovaLib/Classes/CDVLocalStorage.h index 251eb17..e5e3112 100644 --- a/CordovaLib/Classes/CDVLocalStorage.h +++ b/CordovaLib/Classes/CDVLocalStorage.h @@ -31,12 +31,11 @@ - (void)backup:(CDVInvokedUrlCommand*)command; - (void)restore:(CDVInvokedUrlCommand*)command; -+ (void)__verifyAndFixDatabaseLocations; ++ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType; // Visible for testing. + (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict bundlePath :(NSString*)bundlePath fileManager :(NSFileManager*)fileManager; -+ (void)__fixLegacyDatabaseLocationIssues; @end @interface CDVBackupInfo : NSObject http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a39921be/CordovaLib/Classes/CDVLocalStorage.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVLocalStorage.m b/CordovaLib/Classes/CDVLocalStorage.m index 7d39052..79e8db7 100644 --- a/CordovaLib/Classes/CDVLocalStorage.m +++ b/CordovaLib/Classes/CDVLocalStorage.m @@ -31,21 +31,18 @@ @synthesize backupInfo, webviewDelegate; -- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings { - self = (CDVLocalStorage*)[super initWithWebView:theWebView]; + self = (CDVLocalStorage*)[super initWithWebView:theWebView settings:classSettings]; if (self) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) name:UIApplicationWillResignActiveNotification object:nil]; - self.backupInfo = [[self class] createBackupInfo]; + self.backupInfo = [[self class] createBackupInfoWithCloudBackup:((NSString*)[classSettings objectForKey:@"backupType"] == @"cloud")]; // over-ride current webview delegate (for restore reasons) self.webviewDelegate = theWebView.delegate; theWebView.delegate = self; - - // verify the and fix the iOS 5.1 database locations once - [[self class] __verifyAndFixDatabaseLocations]; } return self; @@ -54,7 +51,7 @@ #pragma mark - #pragma mark Plugin interface methods -+ (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir:(NSString*)backupDir rename:(BOOL)rename ++ (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir:(NSString*)backupDir targetDirNests:(BOOL)targetDirNests backupDirNests:(BOOL)backupDirNests rename:(BOOL)rename { NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:3]; @@ -64,8 +61,9 @@ // ////////// LOCALSTORAGE - original = [targetDir stringByAppendingPathComponent:@"file__0.localstorage"]; - backup = [backupDir stringByAppendingPathComponent:rename ? @"localstorage.appdata.db":@"file__0.localstorage"]; + original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0.localstorage":@"file__0.localstorage"]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; + backup = [backupDir stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db":@"file__0.localstorage")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -76,8 +74,9 @@ // ////////// WEBSQL MAIN DB - original = [targetDir stringByAppendingPathComponent:@"Databases.db"]; - backup = [backupDir stringByAppendingPathComponent:rename ? @"websqlmain.appdata.db":@"Databases.db"]; + original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/Databases/Databases.db":@"Databases.db"]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/Databases":@"")]; + backup = [backupDir stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db":@"Databases.db")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -88,8 +87,9 @@ // ////////// WEBSQL DATABASES - original = [targetDir stringByAppendingPathComponent:@"file__0"]; - backup = [backupDir stringByAppendingPathComponent:rename ? @"websqldbs.appdata.db":@"file__0"]; + original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/Databases/file__0":@"file__0"]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/Databases":@"")]; + backup = [backupDir stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db":@"file__0")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -101,16 +101,32 @@ return backupInfo; } -+ (NSMutableArray*)createBackupInfo ++ (NSMutableArray*)createBackupInfoWithCloudBackup:(BOOL)cloudBackup { + // create backup info from backup folder to caches folder NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"]; - NSString* backupsFolder = [appLibraryFolder stringByAppendingPathComponent:@"Backups"]; + NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"]; - // create the backups folder + // create the backups folder, if needed [[NSFileManager defaultManager] createDirectoryAtPath:backupsFolder withIntermediateDirectories:YES attributes:nil error:nil]; + [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:backupsFolder] skip:cloudBackup]; + + return [self createBackupInfoWithTargetDir:cacheFolder backupDir:backupsFolder targetDirNests:NO backupDirNests:NO rename:YES]; +} - return [self createBackupInfoWithTargetDir:cacheFolder backupDir:backupsFolder rename:YES]; ++ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL skip:(BOOL)skip +{ + assert(IsAtLeastiOSVersion(@"5.1")); + assert([[NSFileManager defaultManager] fileExistsAtPath:[URL path]]); + + NSError* error = nil; + BOOL success = [URL setResourceValue:[NSNumber numberWithBool:skip] forKey:NSURLIsExcludedFromBackupKey error:&error]; + if (!success) { + NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); + } + return success; } + (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError * __autoreleasing*)error @@ -252,6 +268,12 @@ } } ++ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType +{ + [self __verifyAndFixDatabaseLocations]; + [self __restoreLegacyDatabaseLocationsWithBackupType:backupType]; +} + + (void)__verifyAndFixDatabaseLocations { NSBundle* mainBundle = [NSBundle mainBundle]; @@ -303,31 +325,26 @@ return dirty; } -+ (void)__fixLegacyDatabaseLocationIssues ++ (void)__restoreLegacyDatabaseLocationsWithBackupType:(NSString*)backupType { - if (IsAtLeastiOSVersion(@"6.0")) { - // Ensure that the webview does not backup the databases to iCloud (We set to this true in 2.1, so need to reset it for upgraders). - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitStoreWebDataForBackup"]; + // on iOS 6, if you toggle between cloud/local backup, you must move database locations. Default upgrade from iOS5.1 to iOS6 is like a toggle from local to cloud. + if (!IsAtLeastiOSVersion(@"6.0")) { + return; } - /* - * There are currently two legacy storage locations: - * {Lib}/WebKit/LocalStorage which was used for store database (without backup renaming) in ios <5.1 and briefly for ios6, and - * {Doc}/Backups which was used to store database backups (with backup renaming) with ios5.1+ until Apple started rejecting apps - * for using the {Doc} folder to save non user generated content. - */ NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - // targetDir is where we want our databases to end up - NSString* targetDir = [appLibraryFolder stringByAppendingPathComponent:@"Caches"]; - - // backupDir's are the places where we may find old legacy backups - NSString* backupDir = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"]; - NSString* backupDir2 = [appLibraryFolder stringByAppendingPathComponent:@"WebKit/LocalStorage"]; + NSMutableArray* backupInfo = nil; - NSMutableArray* backupInfo = [self createBackupInfoWithTargetDir:targetDir backupDir:backupDir rename:YES]; - [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:targetDir backupDir:backupDir2 rename:NO]]; + if ([backupType isEqualToString:@"cloud"]) { + // We would like to restore old backups/caches databases to the new destination (nested in lib folder) + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appDocumentsFolder stringByAppendingPathComponent:@"Backups"] targetDirNests:YES backupDirNests:NO rename:YES]]; + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] targetDirNests:YES backupDirNests:NO rename:NO]]; + } else { + assert([backupType isEqualToString:@"local"]); + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] backupDir:appLibraryFolder targetDirNests:NO backupDirNests:YES rename:NO]]; + } NSFileManager* manager = [NSFileManager defaultManager]; @@ -341,6 +358,8 @@ [manager removeItemAtPath:info.backup error:nil]; } } + + [[NSUserDefaults standardUserDefaults] setBool:(backupType == @"cloud") forKey:@"WebKitStoreWebDataForBackup"]; } #pragma mark - http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a39921be/CordovaLib/Classes/CDVViewController.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVViewController.m b/CordovaLib/Classes/CDVViewController.m index 987ecee..c1648d9 100644 --- a/CordovaLib/Classes/CDVViewController.m +++ b/CordovaLib/Classes/CDVViewController.m @@ -175,16 +175,19 @@ // // Fix the iOS 5.1 SECURITY_ERR bug (CB-347), this must be before the webView is instantiated //// - BOOL backupWebStorage = YES; // default value - if ([self.settings objectForKey:@"BackupWebStorage"]) { - backupWebStorage = [(NSNumber*)[settings objectForKey:@"BackupWebStorage"] boolValue]; - } + NSString* backupWebStorageType = @"cloud"; // default value - if (backupWebStorage) { - [CDVLocalStorage __verifyAndFixDatabaseLocations]; + id backupWebStorage = [self.settings objectForKey:@"BackupWebStorage"]; + if ([backupWebStorage isKindOfClass:[NSString class]]) { + backupWebStorageType = backupWebStorage; + } else if ([backupWebStorage isKindOfClass:[NSNumber class]]) { + backupWebStorageType = [(NSNumber*) backupWebStorage boolValue] ? @"cloud" : @"none"; } + NSLog(@"BackupType: %@", backupWebStorageType); - [CDVLocalStorage __fixLegacyDatabaseLocationIssues]; + if (IsAtLeastiOSVersion(@"5.1")) { + [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType]; + } // // Instantiate the WebView /////////////// @@ -211,10 +214,12 @@ } /* - * Fire up CDVLocalStorage on iOS 5.1+ to work-around WebKit storage limitations + * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup. */ - if (IsAtLeastiOSVersion(@"5.1") && backupWebStorage) { - [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])]; + if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorage isEqualToString:@"local"]) || + ([backupWebStorage isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { + [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView settings:[NSDictionary dictionaryWithObjectsAndKeys: + @"backupType", backupWebStorageType, nil]] withClassName:NSStringFromClass([CDVLocalStorage class])]; } /* http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a39921be/bin/templates/project/__TESTING__/Cordova.plist ---------------------------------------------------------------------- diff --git a/bin/templates/project/__TESTING__/Cordova.plist b/bin/templates/project/__TESTING__/Cordova.plist index 54892bc..2c92742 100644 --- a/bin/templates/project/__TESTING__/Cordova.plist +++ b/bin/templates/project/__TESTING__/Cordova.plist @@ -45,7 +45,7 @@ <key>OpenAllWhitelistURLsInWebView</key> <false/> <key>BackupWebStorage</key> - <true/> + <string>gray</string> <key>ExternalHosts</key> <array/> <key>Plugins</key>