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