1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#if __has_include(<EXFileSystem/EXFileSystem.h>) 4#import "EXScopedFileSystemModule.h" 5 6// TODO @sjchmiela: Should this be versioned? It is only used in detached scenario. 7NSString * const EXShellManifestResourceName = @"shell-app-manifest"; 8 9@implementation EXScopedFileSystemModule 10 11- (instancetype)initWithExperienceId:(NSString *)experienceId andConstantsBinding:(EXConstantsBinding *)constantsBinding 12{ 13 NSString *escapedExperienceId = [EXScopedFileSystemModule escapedResourceName:experienceId]; 14 15 NSString *mainDocumentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; 16 NSString *exponentDocumentDirectory = [mainDocumentDirectory stringByAppendingPathComponent:@"ExponentExperienceData"]; 17 NSString *experienceDocumentDirectory = [[exponentDocumentDirectory stringByAppendingPathComponent:escapedExperienceId] stringByStandardizingPath]; 18 19 NSString *mainCachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 20 NSString *exponentCachesDirectory = [mainCachesDirectory stringByAppendingPathComponent:@"ExponentExperienceData"]; 21 NSString *experienceCachesDirectory = [[exponentCachesDirectory stringByAppendingPathComponent:escapedExperienceId] stringByStandardizingPath]; 22 23 if (![@"expo" isEqualToString:constantsBinding.appOwnership]) { 24 [self ensureOldFilesAreMigratedFrom:experienceDocumentDirectory to:mainDocumentDirectory]; 25 26 return [super init]; 27 } 28 29 return [super initWithDocumentDirectory:experienceDocumentDirectory 30 cachesDirectory:experienceCachesDirectory 31 bundleDirectory:nil]; 32} 33 34- (NSDictionary *)constantsToExport 35{ 36 NSMutableDictionary *constants = [[NSMutableDictionary alloc] initWithDictionary:[super constantsToExport]]; 37 constants[@"bundledAssets"] = [self bundledAssets] ?: [NSNull null]; 38 return constants; 39} 40 41+ (NSString *)escapedResourceName:(NSString *)name 42{ 43 NSString *charactersToEscape = @"!*'();:@&=+$,/?%#[]"; 44 NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:charactersToEscape] invertedSet]; 45 return [name stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; 46} 47 48- (NSArray<NSString *> *)bundledAssets 49{ 50 static NSArray<NSString *> *bundledAssets = nil; 51 static dispatch_once_t once; 52 dispatch_once(&once, ^{ 53 NSString *manifestBundlePath = [[NSBundle mainBundle] pathForResource:EXShellManifestResourceName ofType:@"json"]; 54 NSData *data = [NSData dataWithContentsOfFile:manifestBundlePath]; 55 if (data.length == 0) { 56 return; 57 } 58 __block NSError *error; 59 id manifest = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; 60 if (error) { 61 UMLogError(@"Error parsing bundled manifest: %@", error); 62 return; 63 } 64 bundledAssets = manifest[@"bundledAssets"]; 65 }); 66 return bundledAssets; 67} 68 69// This method ensures that data are migrated from the old scoped path to the new, unscoped one. 70// It needs to be called in case somebody wants to update their standalone app. 71// This method can be removed when SDK32 is phased out. 72- (void)ensureOldFilesAreMigratedFrom:(NSString *)fromDirectory to:(NSString *)toDirectory 73{ 74 NSFileManager *fileManager = [NSFileManager defaultManager]; 75 if ([fileManager fileExistsAtPath:fromDirectory]) { 76 NSArray<NSString *> *files = [fileManager contentsOfDirectoryAtPath:fromDirectory error:nil]; 77 78 for (NSString *file in files) { 79 [fileManager moveItemAtPath:[fromDirectory stringByAppendingPathComponent:file] 80 toPath:[toDirectory stringByAppendingPathComponent:file] 81 error:nil]; 82 } 83 [fileManager removeItemAtPath:fromDirectory error:nil]; 84 } 85} 86 87@end 88#endif 89