diff --git a/ios/RNCAsyncStorage.m b/ios/RNCAsyncStorage.m index d258f554..18556296 100644 --- a/ios/RNCAsyncStorage.m +++ b/ios/RNCAsyncStorage.m @@ -17,6 +17,7 @@ #import static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1"; +static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1"; static NSString *const RCTManifestFileName = @"manifest.json"; static const NSUInteger RCTInlineValueThreshold = 1024; @@ -78,27 +79,38 @@ static void RCTAppendError(NSDictionary *error, NSMutableArray * return nil; } +static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir) { + NSString *storageDirectoryPath; +#if TARGET_OS_TV + storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; +#else + storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; +#endif + storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir]; + return storageDirectoryPath; +} + static NSString *RCTGetStorageDirectory() { static NSString *storageDirectory = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ -#if TARGET_OS_TV - storageDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; -#else - storageDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; -#endif - storageDirectory = [storageDirectory stringByAppendingPathComponent:RCTStorageDirectory]; + storageDirectory = RCTCreateStorageDirectoryPath(RCTStorageDirectory); }); return storageDirectory; } +static NSString *RCTCreateManifestFilePath(NSString *storageDirectory) +{ + return [RCTCreateStorageDirectoryPath(storageDirectory) stringByAppendingPathComponent:RCTManifestFileName]; +} + static NSString *RCTGetManifestFilePath() { static NSString *manifestFilePath = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName]; + manifestFilePath = RCTCreateManifestFilePath(RCTStorageDirectory); }); return manifestFilePath; } @@ -168,6 +180,74 @@ static dispatch_queue_t RCTGetMethodQueue() return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil; } +static NSDate *RCTManifestModificationDate(NSString *manifestFilePath) +{ + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:manifestFilePath error:nil]; + return [attributes fileModificationDate]; +} + +/** + * Creates an NSException used during Storage Directory Migration. + */ +static void RCTStorageDirectoryMigrationLogError(NSString *reason, NSError *error) +{ + RCTLogWarn(@"%@: %@", reason, error ? error.description : @""); +} + +static void RCTStorageDirectoryCleanupOld() +{ + NSError *error; + if (![[NSFileManager defaultManager] removeItemAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) error:&error]) { + RCTStorageDirectoryMigrationLogError(@"Failed to remove old storage directory during migration", error); + } +} + +static void RCTStorageDirectoryMigrate() +{ + NSError *error; + // Migrate data by copying old storage directory to new storage directory location + if (![[NSFileManager defaultManager] copyItemAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) toPath:RCTGetStorageDirectory() error:&error]) { + RCTStorageDirectoryMigrationLogError(@"Failed to copy old storage directory to new storage directory location during migration", error); + } else { + // If copying succeeds, remove old storage directory + RCTStorageDirectoryCleanupOld(); + } +} + +/** + * This check is added to make sure that anyone coming from pre-1.2.2 does not lose cached data. + * Data is migrated from the "RNCAsyncLocalStorage_V1" directory to the "RCTAsyncLocalStorage_V1" directory. + */ +static void RCTStorageDirectoryMigrationCheck() +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + BOOL isDir; + // If the old directory exists, it means we may need to migrate old data to the new directory + if ([[NSFileManager defaultManager] fileExistsAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) isDirectory:&isDir] && isDir) { + // Check if the new storage directory location already exists + if ([[NSFileManager defaultManager] fileExistsAtPath:RCTGetStorageDirectory()]) { + // If new storage location exists, check if the new storage has been modified sooner + if ([RCTManifestModificationDate(RCTGetManifestFilePath()) compare:RCTManifestModificationDate(RCTCreateManifestFilePath(RCTOldStorageDirectory))] == 1) { + // If new location has been modified more recently, simply clean out old data + RCTStorageDirectoryCleanupOld(); + } else { + // If old location has been modified more recently, remove new storage and migrate + if (![[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error]) { + RCTStorageDirectoryMigrationLogError(@"Failed to remove new storage directory during migration", error); + } else { + RCTStorageDirectoryMigrate(); + } + } + } else { + // If new storage location doesn't exist, migrate data + RCTStorageDirectoryMigrate(); + } + } + }); +} + #pragma mark - RNCAsyncStorage @implementation RNCAsyncStorage @@ -179,6 +259,20 @@ @implementation RNCAsyncStorage NSMutableDictionary *_manifest; } ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + RCTStorageDirectoryMigrationCheck(); + return self; +} + RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue