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