Skip to content

Commit 4e49db6

Browse files
authored
fix(iOS): Move the storage location to Application Support (#274)
* Move the storage location to Application Support * Remove unnecessary dispatch_once calls * Add sequential migration and optional flag so we can opt into or out of deleting deprecated storage locations
1 parent 5161cab commit 4e49db6

File tree

1 file changed

+60
-40
lines changed

1 file changed

+60
-40
lines changed

ios/RNCAsyncStorage.m

+60-40
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,24 @@ static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> *
7979
return nil;
8080
}
8181

82+
// DO NOT USE
83+
// This is used internally to migrate data from the old file location to the new one.
84+
// Please use `RCTCreateStorageDirectoryPath` instead
85+
static NSString *RCTCreateStorageDirectoryPath_deprecated(NSString *storageDir) {
86+
NSString *storageDirectoryPath;
87+
#if TARGET_OS_TV
88+
storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
89+
#else
90+
storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
91+
#endif
92+
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
93+
return storageDirectoryPath;
94+
}
95+
8296
static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir) {
83-
NSString *storageDirectoryPath;
84-
#if TARGET_OS_TV
85-
storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
86-
#else
87-
storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
88-
#endif
97+
// We should use the "Application Support/[bundleID]" folder for persistent data storage that's hidden from users
98+
NSString *storageDirectoryPath = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject;
99+
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]; // Per Apple's docs, all app content in Application Support must be within a subdirectory of the app's bundle identifier
89100
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
90101
return storageDirectoryPath;
91102
}
@@ -102,7 +113,7 @@ static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> *
102113

103114
static NSString *RCTCreateManifestFilePath(NSString *storageDirectory)
104115
{
105-
return [RCTCreateStorageDirectoryPath(storageDirectory) stringByAppendingPathComponent:RCTManifestFileName];
116+
return [storageDirectory stringByAppendingPathComponent:RCTManifestFileName];
106117
}
107118

108119
static NSString *RCTGetManifestFilePath()
@@ -194,58 +205,61 @@ static void RCTStorageDirectoryMigrationLogError(NSString *reason, NSError *erro
194205
RCTLogWarn(@"%@: %@", reason, error ? error.description : @"");
195206
}
196207

197-
static void RCTStorageDirectoryCleanupOld()
208+
static void RCTStorageDirectoryCleanupOld(NSString *oldDirectoryPath)
198209
{
199210
NSError *error;
200-
if (![[NSFileManager defaultManager] removeItemAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) error:&error]) {
211+
if (![[NSFileManager defaultManager] removeItemAtPath:oldDirectoryPath error:&error]) {
201212
RCTStorageDirectoryMigrationLogError(@"Failed to remove old storage directory during migration", error);
202213
}
203214
}
204215

205-
static void RCTStorageDirectoryMigrate()
216+
static void RCTStorageDirectoryMigrate(NSString *oldDirectoryPath, NSString *newDirectoryPath, BOOL shouldCleanupOldDirectory)
206217
{
207218
NSError *error;
208219
// Migrate data by copying old storage directory to new storage directory location
209-
if (![[NSFileManager defaultManager] copyItemAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) toPath:RCTGetStorageDirectory() error:&error]) {
220+
if (![[NSFileManager defaultManager] copyItemAtPath:oldDirectoryPath toPath:newDirectoryPath error:&error]) {
210221
RCTStorageDirectoryMigrationLogError(@"Failed to copy old storage directory to new storage directory location during migration", error);
211-
} else {
222+
} else if (shouldCleanupOldDirectory) {
212223
// If copying succeeds, remove old storage directory
213-
RCTStorageDirectoryCleanupOld();
224+
RCTStorageDirectoryCleanupOld(oldDirectoryPath);
214225
}
215226
}
216227

217228
/**
218229
* This check is added to make sure that anyone coming from pre-1.2.2 does not lose cached data.
219-
* Data is migrated from the "RNCAsyncLocalStorage_V1" directory to the "RCTAsyncLocalStorage_V1" directory.
230+
* Check that data is migrated from the old location to the new location
231+
* fromStorageDirectory: the directory where the older data lives
232+
* toStorageDirectory: the directory where the new data should live
233+
* shouldCleanupOldDirectoryAndOverwriteNewDirectory: YES if we should delete the old directory's contents and overwrite the new directory's contents during the migration to the new directory
220234
*/
221-
static void RCTStorageDirectoryMigrationCheck()
235+
static void RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory, NSString *toStorageDirectory, BOOL shouldCleanupOldDirectoryAndOverwriteNewDirectory)
222236
{
223-
static dispatch_once_t onceToken;
224-
dispatch_once(&onceToken, ^{
225-
NSError *error;
226-
BOOL isDir;
227-
// If the old directory exists, it means we may need to migrate old data to the new directory
228-
if ([[NSFileManager defaultManager] fileExistsAtPath:RCTCreateStorageDirectoryPath(RCTOldStorageDirectory) isDirectory:&isDir] && isDir) {
229-
// Check if the new storage directory location already exists
230-
if ([[NSFileManager defaultManager] fileExistsAtPath:RCTGetStorageDirectory()]) {
231-
// If new storage location exists, check if the new storage has been modified sooner
232-
if ([RCTManifestModificationDate(RCTGetManifestFilePath()) compare:RCTManifestModificationDate(RCTCreateManifestFilePath(RCTOldStorageDirectory))] == 1) {
233-
// If new location has been modified more recently, simply clean out old data
234-
RCTStorageDirectoryCleanupOld();
237+
NSError *error;
238+
BOOL isDir;
239+
NSFileManager *fileManager = [NSFileManager defaultManager];
240+
// If the old directory exists, it means we may need to migrate old data to the new directory
241+
if ([fileManager fileExistsAtPath:fromStorageDirectory isDirectory:&isDir] && isDir) {
242+
// Check if the new storage directory location already exists
243+
if ([fileManager fileExistsAtPath:toStorageDirectory]) {
244+
// If new storage location exists, check if the new storage has been modified sooner in which case we may want to cleanup the old location
245+
if ([RCTManifestModificationDate(RCTCreateManifestFilePath(toStorageDirectory)) compare:RCTManifestModificationDate(RCTCreateManifestFilePath(fromStorageDirectory))] == 1) {
246+
// If new location has been modified more recently, simply clean out old data
247+
if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
248+
RCTStorageDirectoryCleanupOld(fromStorageDirectory);
249+
}
250+
} else if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
251+
// If old location has been modified more recently, remove new storage and migrate
252+
if (![fileManager removeItemAtPath:toStorageDirectory error:&error]) {
253+
RCTStorageDirectoryMigrationLogError(@"Failed to remove new storage directory during migration", error);
235254
} else {
236-
// If old location has been modified more recently, remove new storage and migrate
237-
if (![[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error]) {
238-
RCTStorageDirectoryMigrationLogError(@"Failed to remove new storage directory during migration", error);
239-
} else {
240-
RCTStorageDirectoryMigrate();
241-
}
255+
RCTStorageDirectoryMigrate(fromStorageDirectory, toStorageDirectory, shouldCleanupOldDirectoryAndOverwriteNewDirectory);
242256
}
243-
} else {
244-
// If new storage location doesn't exist, migrate data
245-
RCTStorageDirectoryMigrate();
246257
}
258+
} else {
259+
// If new storage location doesn't exist, migrate data
260+
RCTStorageDirectoryMigrate(fromStorageDirectory, toStorageDirectory, shouldCleanupOldDirectoryAndOverwriteNewDirectory);
247261
}
248-
});
262+
}
249263
}
250264

251265
#pragma mark - RNCAsyncStorage
@@ -269,7 +283,13 @@ - (instancetype)init
269283
if (!(self = [super init])) {
270284
return nil;
271285
}
272-
RCTStorageDirectoryMigrationCheck();
286+
287+
// First migrate our deprecated path "Documents/.../RNCAsyncLocalStorage_V1" to "Documents/.../RCTAsyncLocalStorage_V1"
288+
RCTStorageDirectoryMigrationCheck(RCTCreateStorageDirectoryPath_deprecated(RCTOldStorageDirectory), RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory), YES);
289+
290+
// Then migrate what's in "Documents/.../RCTAsyncLocalStorage_V1" to "Application Support/[bundleID]/RCTAsyncLocalStorage_V1"
291+
RCTStorageDirectoryMigrationCheck(RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory), RCTCreateStorageDirectoryPath(RCTStorageDirectory), NO);
292+
273293
return self;
274294
}
275295

@@ -346,7 +366,7 @@ - (NSDictionary *)_ensureSetup
346366

347367
if (!_haveSetup) {
348368
NSDictionary *errorOut = nil;
349-
NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), RCTManifestFileName, &errorOut);
369+
NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()), RCTManifestFileName, &errorOut);
350370
if (!serialized) {
351371
if (errorOut) {
352372
// We cannot simply create a new manifest in case the file does exist but we have no access to it.
@@ -377,7 +397,7 @@ - (NSDictionary *)_writeManifest:(NSMutableArray<NSDictionary *> **)errors
377397
{
378398
NSError *error;
379399
NSString *serialized = RCTJSONStringify(_manifest, &error);
380-
[serialized writeToFile:RCTGetManifestFilePath() atomically:YES encoding:NSUTF8StringEncoding error:&error];
400+
[serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()) atomically:YES encoding:NSUTF8StringEncoding error:&error];
381401
NSDictionary *errorOut;
382402
if (error) {
383403
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);

0 commit comments

Comments
 (0)