216 lines
8.0 KiB
Objective-C
216 lines
8.0 KiB
Objective-C
#import "RNDocumentPicker.h"
|
|
|
|
#import <MobileCoreServices/MobileCoreServices.h>
|
|
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
|
|
static NSString *const E_DOCUMENT_PICKER_CANCELED = @"DOCUMENT_PICKER_CANCELED";
|
|
static NSString *const E_INVALID_DATA_RETURNED = @"INVALID_DATA_RETURNED";
|
|
|
|
static NSString *const OPTION_TYPE = @"type";
|
|
static NSString *const OPTION_MULIPLE = @"multiple";
|
|
|
|
static NSString *const FIELD_URI = @"uri";
|
|
static NSString *const FIELD_FILE_COPY_URI = @"fileCopyUri";
|
|
static NSString *const FIELD_COPY_ERR = @"copyError";
|
|
static NSString *const FIELD_NAME = @"name";
|
|
static NSString *const FIELD_TYPE = @"type";
|
|
static NSString *const FIELD_SIZE = @"size";
|
|
|
|
@interface RNDocumentPicker () <UIDocumentPickerDelegate>
|
|
@end
|
|
|
|
@implementation RNDocumentPicker {
|
|
NSMutableArray *composeResolvers;
|
|
NSMutableArray *composeRejecters;
|
|
NSString* copyDestination;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
composeResolvers = [[NSMutableArray alloc] init];
|
|
composeRejecters = [[NSMutableArray alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (BOOL)requiresMainQueueSetup {
|
|
return NO;
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return dispatch_get_main_queue();
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
RCT_EXPORT_METHOD(pick:(NSDictionary *)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
NSArray *allowedUTIs = [RCTConvert NSArray:options[OPTION_TYPE]];
|
|
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:(NSArray *)allowedUTIs inMode:UIDocumentPickerModeImport];
|
|
|
|
[composeResolvers addObject:resolve];
|
|
[composeRejecters addObject:reject];
|
|
copyDestination = options[@"copyTo"] ? options[@"copyTo"] : nil;
|
|
|
|
|
|
documentPicker.delegate = self;
|
|
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
|
|
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
|
|
if (@available(iOS 11, *)) {
|
|
documentPicker.allowsMultipleSelection = [RCTConvert BOOL:options[OPTION_MULIPLE]];
|
|
}
|
|
#endif
|
|
|
|
UIViewController *rootViewController = RCTPresentedViewController();
|
|
|
|
[rootViewController presentViewController:documentPicker animated:YES completion:nil];
|
|
}
|
|
|
|
- (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
|
|
{
|
|
__block NSMutableDictionary* result = [NSMutableDictionary dictionary];
|
|
|
|
[url startAccessingSecurityScopedResource];
|
|
|
|
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
|
|
NSError *fileError;
|
|
|
|
[coordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingResolvesSymbolicLink error:&fileError byAccessor:^(NSURL *newURL) {
|
|
if (!fileError) {
|
|
[result setValue:newURL.absoluteString forKey:FIELD_URI];
|
|
NSError *copyError;
|
|
NSURL* maybeFileCopyPath = copyDestination ? [RNDocumentPicker copyToUniqueDestinationFrom:newURL usingDestinationPreset:copyDestination error:copyError] : newURL;
|
|
[result setValue: maybeFileCopyPath.absoluteString forKey:FIELD_FILE_COPY_URI];
|
|
if (copyError) {
|
|
[result setValue:copyError.description forKey:FIELD_COPY_ERR];
|
|
}
|
|
|
|
[result setValue:[newURL lastPathComponent] forKey:FIELD_NAME];
|
|
|
|
NSError *attributesError = nil;
|
|
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:newURL.path error:&attributesError];
|
|
if(!attributesError) {
|
|
[result setValue:[fileAttributes objectForKey:NSFileSize] forKey:FIELD_SIZE];
|
|
} else {
|
|
NSLog(@"%@", attributesError);
|
|
}
|
|
|
|
if ( newURL.pathExtension != nil ) {
|
|
CFStringRef extension = (__bridge CFStringRef)[newURL pathExtension];
|
|
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
|
|
CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
|
|
CFRelease(uti);
|
|
|
|
NSString *mimeTypeString = (__bridge_transfer NSString *)mimeType;
|
|
[result setValue:mimeTypeString forKey:FIELD_TYPE];
|
|
}
|
|
}
|
|
}];
|
|
|
|
[url stopAccessingSecurityScopedResource];
|
|
|
|
if (fileError) {
|
|
*error = fileError;
|
|
return nil;
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
+ (NSURL*)getDirectoryForFileCopy:(NSString*) copyToDirectory {
|
|
if ([@"cachesDirectory" isEqualToString:copyToDirectory]) {
|
|
return [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject;
|
|
} else if ([@"documentDirectory" isEqualToString:copyToDirectory]) {
|
|
return [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
|
|
}
|
|
// this should not happen as the value is checked in JS, but we fall back to NSTemporaryDirectory()
|
|
return [NSURL fileURLWithPath: NSTemporaryDirectory() isDirectory: YES];
|
|
}
|
|
|
|
+ (NSURL *)copyToUniqueDestinationFrom:(NSURL *) url usingDestinationPreset: (NSString*) copyToDirectory error:(NSError *)error
|
|
{
|
|
NSURL* destinationRootDir = [self getDirectoryForFileCopy:copyToDirectory];
|
|
// we don't want to rename the file so we put it into a unique location
|
|
NSString *uniqueSubDirName = [[NSUUID UUID] UUIDString];
|
|
NSURL *destinationDir = [destinationRootDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@/", uniqueSubDirName]];
|
|
NSURL *destinationUrl = [destinationDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@", url.lastPathComponent]];
|
|
|
|
[NSFileManager.defaultManager createDirectoryAtURL:destinationDir withIntermediateDirectories:YES attributes:nil error:&error];
|
|
if (error) {
|
|
return url;
|
|
}
|
|
[NSFileManager.defaultManager copyItemAtURL:url toURL:destinationUrl error:&error];
|
|
if (error) {
|
|
return url;
|
|
} else {
|
|
return destinationUrl;
|
|
}
|
|
}
|
|
|
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
|
|
{
|
|
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
|
|
RCTPromiseResolveBlock resolve = [composeResolvers lastObject];
|
|
RCTPromiseRejectBlock reject = [composeRejecters lastObject];
|
|
[composeResolvers removeLastObject];
|
|
[composeRejecters removeLastObject];
|
|
|
|
NSError *error;
|
|
NSMutableDictionary* result = [self getMetadataForUrl:url error:&error];
|
|
if (result) {
|
|
NSArray *results = @[result];
|
|
resolve(results);
|
|
} else {
|
|
reject(E_INVALID_DATA_RETURNED, error.localizedDescription, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
|
|
{
|
|
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
|
|
RCTPromiseResolveBlock resolve = [composeResolvers lastObject];
|
|
RCTPromiseRejectBlock reject = [composeRejecters lastObject];
|
|
[composeResolvers removeLastObject];
|
|
[composeRejecters removeLastObject];
|
|
|
|
NSMutableArray *results = [NSMutableArray array];
|
|
for (id url in urls) {
|
|
NSError *error;
|
|
NSMutableDictionary* result = [self getMetadataForUrl:url error:&error];
|
|
if (result) {
|
|
[results addObject:result];
|
|
} else {
|
|
reject(E_INVALID_DATA_RETURNED, error.localizedDescription, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
resolve(results);
|
|
}
|
|
}
|
|
|
|
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
|
|
{
|
|
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
|
|
RCTPromiseRejectBlock reject = [composeRejecters lastObject];
|
|
[composeResolvers removeLastObject];
|
|
[composeRejecters removeLastObject];
|
|
|
|
reject(E_DOCUMENT_PICKER_CANCELED, @"User canceled document picker", nil);
|
|
}
|
|
}
|
|
|
|
@end
|