Skip to content
108 changes: 101 additions & 7 deletions ios/RNCAsyncStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import <React/RCTUtils.h>

static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1";
static NSString *const RCTManifestFileName = @"manifest.json";
static const NSUInteger RCTInlineValueThreshold = 1024;

Expand Down Expand Up @@ -78,27 +79,38 @@ static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> *
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;
}
Expand Down Expand Up @@ -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
Expand All @@ -179,6 +259,20 @@ @implementation RNCAsyncStorage
NSMutableDictionary<NSString *, NSString *> *_manifest;
}

+ (BOOL)requiresMainQueueSetup
{
return NO;
}

- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
RCTStorageDirectoryMigrationCheck();
return self;
}

RCT_EXPORT_MODULE()

- (dispatch_queue_t)methodQueue
Expand Down