public
Last active — forked from steipete/iOSDocumentMigrator.m

Helps migrating documents between iOS <= 5.0 and >= 5.0.1 to comply with Apple's iCloud guidelines. Follow @steipete on Twitter for updates.

  • Download Gist
iOSDocumentMigrator.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
#include <sys/xattr.h>
 
/// Set a flag that the files shouldn't be backuped to iCloud.
+ (void)addSkipBackupAttributeToFile:(NSString *)filePath {
u_int8_t b = 1;
setxattr([filePath fileSystemRepresentation], "com.apple.MobileBackup", &b, 1, 0, 0);
}
 
/// Returns the legacy storage path, used when the com.apple.MobileBackup file attribute is not available.
+ (NSString *)legacyStoragePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
 
/// Returns YES if system supports com.apple.MobileBackup file attribute, marks files/folders as not iCloud-backupable.
+ (BOOL)isBackupXAttributeAvailable {
static BOOL isModern;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// since there is no kCFCoreFoundationVersionNumber_iPhoneOS_5_0_1, we have to do it the ugly way
NSString *version = [[UIDevice currentDevice] systemVersion];
isModern = ![version isEqualToString:@"5.0.0"] && [version intValue] >= 5;
//isModern = NO; // To test migration, enable this to "fake" an old system.
//NSLog(@"Modern OS detected, com.apple.MobileBackup is allowed. Using Documents folder."); // log optionally
});
return isModern;
}
 
/// Storage Path is Documents for iOS >= 5.0.1, and Caches for iOS <= 5.0.
/// This must be done to fully comply with the iCloud storage guidelines.
/// Don't forget to set the xattr on iOS >= 5.0.1!
/// http://developer.apple.com/library/ios/#qa/qa1719/_index.html
/// https://developer.apple.com/icloud/documentation/data-storage/
///
/// The result is cached for faster future access. Can be invoked from any thread.
#define kPSLegacyStoragePathUsed @"PSLegacyStoragePathUsed"
+ (NSString *)storagePath {
static NSString *storagePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([self isBackupXAttributeAvailable]) {
storagePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}else {
storagePath = [self legacyStoragePath];
// mark that we use the legazy storage.
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPSLegacyStoragePathUsed];
// No need for manual synchronize, we assume that our app is running long enough for the automatic sweep.
}
});
return storagePath;
}
 
/// Invoke this in the AppDelegate - moves your documents around.
/// Note: we only support *upgrading* - not OS downgrades.
/// Can be invoked from any thread.
/// Returns YES if a migration was done.
+ (BOOL)checkAndIfNeededMigrateStoragePathBlocking:(BOOL)blocking completionBlock:(void(^)(void))completionBlock {
__block BOOL migrationNeeded = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
BOOL wasUsingLegacyPath = [[NSUserDefaults standardUserDefaults] boolForKey:kPSLegacyStoragePathUsed];
if (wasUsingLegacyPath && [self isBackupXAttributeAvailable]) {
void (^moveBlock)(void) = ^{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *legacyPath = [self legacyStoragePath];
NSString *modernPath = [self storagePath];
NSError *error = nil;
NSArray *directoryContents = [fileManager contentsOfDirectoryAtPath:legacyPath error:&error];
if (!directoryContents) {
NSLog(@"Error while getting contents of directory: %@.", [error localizedDescription]);
}
for (NSString *file in directoryContents) {
NSString *targetPath = [modernPath stringByAppendingPathComponent:file];
if(![fileManager moveItemAtPath:[legacyPath stringByAppendingPathComponent:file]
toPath:targetPath error:&error]) {
NSLog(@"Error while moving %@ from path %@ to %@.", file, legacyPath, modernPath);
// just continue with next file - can't do much about this.
}else {
// apply the new attribute to the file/folder (no need to put it on every file, a parent folder will do)
[self addSkipBackupAttributeToFile:targetPath];
}
}
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPSLegacyStoragePathUsed];
};
if(blocking) {
moveBlock();
if (completionBlock) completionBlock();
}else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
moveBlock();
if (completionBlock) completionBlock();
});
}
migrationNeeded = YES;
}
});
return migrationNeeded;
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.