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