1*51e273e0STomasz Sapeta
2*51e273e0STomasz Sapeta#import <ExpoFileSystem/EXFileSystemAssetLibraryHandler.h>
3*51e273e0STomasz Sapeta#import <ExpoFileSystem/NSData+EXFileSystem.h>
4*51e273e0STomasz Sapeta
5*51e273e0STomasz Sapeta#import <Photos/Photos.h>
6*51e273e0STomasz Sapeta
7*51e273e0STomasz Sapeta@implementation EXFileSystemAssetLibraryHandler
8*51e273e0STomasz Sapeta
9*51e273e0STomasz Sapeta+ (void)getInfoForFile:(NSURL *)fileUri
10*51e273e0STomasz Sapeta           withOptions:(NSDictionary *)options
11*51e273e0STomasz Sapeta              resolver:(EXPromiseResolveBlock)resolve
12*51e273e0STomasz Sapeta              rejecter:(EXPromiseRejectBlock)reject
13*51e273e0STomasz Sapeta{
14*51e273e0STomasz Sapeta  NSError *error;
15*51e273e0STomasz Sapeta  PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:fileUri error:&error];
16*51e273e0STomasz Sapeta  if (error) {
17*51e273e0STomasz Sapeta    reject(@"E_UNSUPPORTED_ARG", error.description, error);
18*51e273e0STomasz Sapeta    return;
19*51e273e0STomasz Sapeta  }
20*51e273e0STomasz Sapeta  if (fetchResult.count > 0) {
21*51e273e0STomasz Sapeta    PHAsset *asset = fetchResult[0];
22*51e273e0STomasz Sapeta    NSMutableDictionary *result = [NSMutableDictionary dictionary];
23*51e273e0STomasz Sapeta    result[@"exists"] = @(YES);
24*51e273e0STomasz Sapeta    result[@"isDirectory"] = @(NO);
25*51e273e0STomasz Sapeta    result[@"uri"] = fileUri;
26*51e273e0STomasz Sapeta    result[@"modificationTime"] = @(asset.modificationDate.timeIntervalSince1970);
27*51e273e0STomasz Sapeta    if (options[@"md5"] || options[@"size"]) {
28*51e273e0STomasz Sapeta      [[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
29*51e273e0STomasz Sapeta        result[@"size"] = @(imageData.length);
30*51e273e0STomasz Sapeta        if (options[@"md5"]) {
31*51e273e0STomasz Sapeta          result[@"md5"] = [imageData md5String];
32*51e273e0STomasz Sapeta        }
33*51e273e0STomasz Sapeta        resolve(result);
34*51e273e0STomasz Sapeta      }];
35*51e273e0STomasz Sapeta    } else {
36*51e273e0STomasz Sapeta      resolve(result);
37*51e273e0STomasz Sapeta    }
38*51e273e0STomasz Sapeta  } else {
39*51e273e0STomasz Sapeta    resolve(@{@"exists": @(NO), @"isDirectory": @(NO)});
40*51e273e0STomasz Sapeta  }
41*51e273e0STomasz Sapeta}
42*51e273e0STomasz Sapeta
43*51e273e0STomasz Sapeta+ (void)copyFrom:(NSURL *)from
44*51e273e0STomasz Sapeta              to:(NSURL *)to
45*51e273e0STomasz Sapeta        resolver:(EXPromiseResolveBlock)resolve
46*51e273e0STomasz Sapeta        rejecter:(EXPromiseRejectBlock)reject
47*51e273e0STomasz Sapeta{
48*51e273e0STomasz Sapeta  NSString *toPath = [to.path stringByStandardizingPath];
49*51e273e0STomasz Sapeta
50*51e273e0STomasz Sapeta  // NOTE: The destination-delete and the copy should happen atomically, but we hope for the best for now
51*51e273e0STomasz Sapeta  NSError *error;
52*51e273e0STomasz Sapeta  if ([[NSFileManager defaultManager] fileExistsAtPath:toPath]) {
53*51e273e0STomasz Sapeta    if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
54*51e273e0STomasz Sapeta      reject(@"E_FILE_NOT_COPIED",
55*51e273e0STomasz Sapeta             [NSString stringWithFormat:@"File '%@' could not be copied to '%@' because a file already exists at "
56*51e273e0STomasz Sapeta              "the destination and could not be deleted.", from, to],
57*51e273e0STomasz Sapeta             error);
58*51e273e0STomasz Sapeta      return;
59*51e273e0STomasz Sapeta    }
60*51e273e0STomasz Sapeta  }
61*51e273e0STomasz Sapeta
62*51e273e0STomasz Sapeta  PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:from error:&error];
63*51e273e0STomasz Sapeta  if (error) {
64*51e273e0STomasz Sapeta    reject(@"E_UNSUPPORTED_ARG", error.description, error);
65*51e273e0STomasz Sapeta    return;
66*51e273e0STomasz Sapeta  }
67*51e273e0STomasz Sapeta
68*51e273e0STomasz Sapeta  if (fetchResult.count > 0) {
69*51e273e0STomasz Sapeta    PHAsset *asset = fetchResult[0];
70*51e273e0STomasz Sapeta    if (asset.mediaType == PHAssetMediaTypeVideo) {
71*51e273e0STomasz Sapeta      [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
72*51e273e0STomasz Sapeta        if (![asset isKindOfClass:[AVURLAsset class]]) {
73*51e273e0STomasz Sapeta          reject(@"ERR_INCORRECT_ASSET_TYPE",
74*51e273e0STomasz Sapeta                 [NSString stringWithFormat:@"File '%@' has incorrect asset type.", from],
75*51e273e0STomasz Sapeta                 nil);
76*51e273e0STomasz Sapeta          return;
77*51e273e0STomasz Sapeta        }
78*51e273e0STomasz Sapeta
79*51e273e0STomasz Sapeta        AVURLAsset* urlAsset = (AVURLAsset*)asset;
80*51e273e0STomasz Sapeta        NSNumber *size;
81*51e273e0STomasz Sapeta        [urlAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
82*51e273e0STomasz Sapeta        NSData *data = [NSData dataWithContentsOfURL:urlAsset.URL];
83*51e273e0STomasz Sapeta
84*51e273e0STomasz Sapeta        [EXFileSystemAssetLibraryHandler copyData:data toPath:toPath resolver:resolve rejecter:reject];
85*51e273e0STomasz Sapeta      }];
86*51e273e0STomasz Sapeta    } else {
87*51e273e0STomasz Sapeta      [[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
88*51e273e0STomasz Sapeta        [EXFileSystemAssetLibraryHandler copyData:imageData toPath:toPath resolver:resolve rejecter:reject];
89*51e273e0STomasz Sapeta      }];
90*51e273e0STomasz Sapeta    }
91*51e273e0STomasz Sapeta  } else {
92*51e273e0STomasz Sapeta    reject(@"E_FILE_NOT_COPIED",
93*51e273e0STomasz Sapeta           [NSString stringWithFormat:@"File '%@' could not be found.", from],
94*51e273e0STomasz Sapeta           error);
95*51e273e0STomasz Sapeta  }
96*51e273e0STomasz Sapeta}
97*51e273e0STomasz Sapeta
98*51e273e0STomasz Sapeta// adapted from RCTImageLoader.m
99*51e273e0STomasz Sapeta+ (PHFetchResult<PHAsset *> *)fetchResultForUri:(NSURL *)url error:(NSError **)error
100*51e273e0STomasz Sapeta{
101*51e273e0STomasz Sapeta  if ([url.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame) {
102*51e273e0STomasz Sapeta    // Fetch assets using PHAsset localIdentifier (recommended)
103*51e273e0STomasz Sapeta    NSString *const localIdentifier = [url.absoluteString substringFromIndex:@"ph://".length];
104*51e273e0STomasz Sapeta    return [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
105*51e273e0STomasz Sapeta  } else if ([url.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
106*51e273e0STomasz Sapeta#if TARGET_OS_MACCATALYST
107*51e273e0STomasz Sapeta    static BOOL hasWarned = NO;
108*51e273e0STomasz Sapeta    if (!hasWarned) {
109*51e273e0STomasz Sapeta      NSLog(@"assets-library:// URLs have been deprecated and cannot be accessed in macOS Catalyst. Returning nil (future warnings will be suppressed).");
110*51e273e0STomasz Sapeta      hasWarned = YES;
111*51e273e0STomasz Sapeta    }
112*51e273e0STomasz Sapeta    return nil;
113*51e273e0STomasz Sapeta#else
114*51e273e0STomasz Sapeta    // This is the older, deprecated way of fetching assets from assets-library
115*51e273e0STomasz Sapeta    // using the "assets-library://" protocol
116*51e273e0STomasz Sapeta    return [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
117*51e273e0STomasz Sapeta#endif
118*51e273e0STomasz Sapeta  }
119*51e273e0STomasz Sapeta
120*51e273e0STomasz Sapeta  NSString *description = [NSString stringWithFormat:@"Invalid URL provided, expected scheme to be either 'ph' or 'assets-library', was '%@'.", url.scheme];
121*51e273e0STomasz Sapeta  *error = [[NSError alloc] initWithDomain:NSURLErrorDomain
122*51e273e0STomasz Sapeta                                      code:NSURLErrorUnsupportedURL
123*51e273e0STomasz Sapeta                                  userInfo:@{NSLocalizedDescriptionKey: description}];
124*51e273e0STomasz Sapeta  return nil;
125*51e273e0STomasz Sapeta}
126*51e273e0STomasz Sapeta
127*51e273e0STomasz Sapeta
128*51e273e0STomasz Sapeta+ (void)copyData:(NSData *)data
129*51e273e0STomasz Sapeta          toPath:(NSString *)path
130*51e273e0STomasz Sapeta        resolver:(EXPromiseResolveBlock)resolve
131*51e273e0STomasz Sapeta        rejecter:(EXPromiseRejectBlock)reject
132*51e273e0STomasz Sapeta{
133*51e273e0STomasz Sapeta  if ([data writeToFile:path atomically:YES]) {
134*51e273e0STomasz Sapeta    resolve(nil);
135*51e273e0STomasz Sapeta  } else {
136*51e273e0STomasz Sapeta    reject(@"E_FILE_NOT_COPIED",
137*51e273e0STomasz Sapeta           [NSString stringWithFormat:@"File could not be copied to '%@'.", path],
138*51e273e0STomasz Sapeta           nil);
139*51e273e0STomasz Sapeta  }
140*51e273e0STomasz Sapeta}
141*51e273e0STomasz Sapeta
142*51e273e0STomasz Sapeta@end
143