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