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