| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| #import "CDVFile.h" |
| #import "NSArray+Comparisons.h" |
| #import "NSDictionary+Extensions.h" |
| #import "CDVJSON.h" |
| #import "NSData+Base64.h" |
| #import <AssetsLibrary/ALAsset.h> |
| #import <AssetsLibrary/ALAssetRepresentation.h> |
| #import <AssetsLibrary/ALAssetsLibrary.h> |
| #import <MobileCoreServices/MobileCoreServices.h> |
| #import "CDVAvailability.h" |
| #import "sys/xattr.h" |
| |
| extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import)); |
| |
| #ifndef __IPHONE_5_1 |
| NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey"; |
| #endif |
| |
| NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; |
| |
| @implementation CDVFile |
| |
| @synthesize appDocsPath, appLibraryPath, appTempPath, persistentPath, temporaryPath, userHasAllowed; |
| |
| - (id)initWithWebView:(UIWebView*)theWebView |
| { |
| self = (CDVFile*)[super initWithWebView:theWebView]; |
| if (self) { |
| // get the documents directory path |
| NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); |
| self.appDocsPath = [paths objectAtIndex:0]; |
| |
| paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); |
| self.appLibraryPath = [paths objectAtIndex:0]; |
| |
| self.appTempPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() |
| |
| self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]]; |
| self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]]; |
| // NSLog(@"docs: %@ - temp: %@", self.appDocsPath, self.appTempPath); |
| } |
| |
| return self; |
| } |
| |
| - (NSNumber*)checkFreeDiskSpace:(NSString*)appPath |
| { |
| NSFileManager* fMgr = [[NSFileManager alloc] init]; |
| |
| NSError* __autoreleasing pError = nil; |
| |
| NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError]; |
| NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize]; |
| |
| return pNumAvail; |
| } |
| |
| // figure out if the pathFragment represents a persistent of temporary directory and return the full application path. |
| // returns nil if path is not persistent or temporary |
| - (NSString*)getAppPath:(NSString*)pathFragment |
| { |
| NSString* appPath = nil; |
| NSRange rangeP = [pathFragment rangeOfString:self.persistentPath]; |
| NSRange rangeT = [pathFragment rangeOfString:self.temporaryPath]; |
| |
| if ((rangeP.location != NSNotFound) && (rangeT.location != NSNotFound)) { |
| // we found both in the path, return whichever one is first |
| if (rangeP.length < rangeT.length) { |
| appPath = self.appDocsPath; |
| } else { |
| appPath = self.appTempPath; |
| } |
| } else if (rangeP.location != NSNotFound) { |
| appPath = self.appDocsPath; |
| } else if (rangeT.location != NSNotFound) { |
| appPath = self.appTempPath; |
| } |
| return appPath; |
| } |
| |
| /* get the full path to this resource |
| * IN |
| * NSString* pathFragment - full Path from File or Entry object (includes system path info) |
| * OUT |
| * NSString* fullPath - full iOS path to this resource, nil if not found |
| */ |
| |
| /* Was here in order to NOT have to return full path, but W3C synchronous DirectoryEntry.toURI() killed that idea since I can't call into iOS to |
| * resolve full URI. Leaving this code here in case W3C spec changes. |
| -(NSString*) getFullPath: (NSString*)pathFragment |
| { |
| return pathFragment; |
| NSString* fullPath = nil; |
| NSString *appPath = [ self getAppPath: pathFragment]; |
| if (appPath){ |
| |
| // remove last component from appPath |
| NSRange range = [appPath rangeOfString:@"/" options: NSBackwardsSearch]; |
| NSString* newPath = [appPath substringToIndex:range.location]; |
| // add pathFragment to get test Path |
| fullPath = [newPath stringByAppendingPathComponent:pathFragment]; |
| } |
| return fullPath; |
| } */ |
| |
| /* Request the File System info |
| * |
| * IN: |
| * arguments[0] - type (number as string) |
| * TEMPORARY = 0, PERSISTENT = 1; |
| * arguments[1] - size |
| * |
| * OUT: |
| * Dictionary representing FileSystem object |
| * name - the human readable directory name |
| * root = DirectoryEntry object |
| * bool isDirectory |
| * bool isFile |
| * string name |
| * string fullPath |
| * fileSystem = FileSystem object - !! ignored because creates circular reference !! |
| */ |
| |
| - (void)requestFileSystem:(CDVInvokedUrlCommand*)command |
| { |
| NSArray* arguments = command.arguments; |
| |
| // arguments |
| NSString* strType = [arguments objectAtIndex:0]; |
| unsigned long long size = [[arguments objectAtIndex:1] longLongValue]; |
| |
| int type = [strType intValue]; |
| CDVPluginResult* result = nil; |
| |
| if (type > 1) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR]; |
| NSLog(@"iOS only supports TEMPORARY and PERSISTENT file systems"); |
| } else { |
| // NSString* fullPath = [NSString stringWithFormat:@"/%@", (type == 0 ? [self.appTempPath lastPathComponent] : [self.appDocsPath lastPathComponent])]; |
| NSString* fullPath = (type == 0 ? self.appTempPath : self.appDocsPath); |
| // check for avail space for size request |
| NSNumber* pNumAvail = [self checkFreeDiskSpace:fullPath]; |
| // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]); |
| if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; |
| } else { |
| NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; |
| [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary:kW3FilePersistent) forKey:@"name"]; |
| NSDictionary* dirEntry = [self getDirectoryEntry:fullPath isDirectory:YES]; |
| [fileSystem setObject:dirEntry forKey:@"root"]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; |
| } |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* Creates a dictionary representing an Entry Object |
| * |
| * IN: |
| * NSString* fullPath of the entry |
| * FileSystem type |
| * BOOL isDirectory - YES if this is a directory, NO if is a file |
| * OUT: |
| * NSDictionary* |
| Entry object |
| * bool as NSNumber isDirectory |
| * bool as NSNumber isFile |
| * NSString* name - last part of path |
| * NSString* fullPath |
| * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! |
| */ |
| - (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir |
| { |
| NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:4]; |
| NSString* lastPart = [fullPath lastPathComponent]; |
| |
| [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"]; |
| [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"]; |
| // NSURL* fileUrl = [NSURL fileURLWithPath:fullPath]; |
| // [dirEntry setObject: [fileUrl absoluteString] forKey: @"fullPath"]; |
| [dirEntry setObject:fullPath forKey:@"fullPath"]; |
| [dirEntry setObject:lastPart forKey:@"name"]; |
| |
| return dirEntry; |
| } |
| |
| /* |
| * Given a URI determine the File System information associated with it and return an appropriate W3C entry object |
| * IN |
| * NSString* fileURI - currently requires full file URI |
| * OUT |
| * Entry object |
| * bool isDirectory |
| * bool isFile |
| * string name |
| * string fullPath |
| * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! |
| */ |
| - (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* inputUri = [command.arguments objectAtIndex:0]; |
| |
| // don't know if string is encoded or not so unescape |
| NSString* cleanUri = [inputUri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
| // now escape in order to create URL |
| NSString* strUri = [cleanUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
| NSURL* testUri = [NSURL URLWithString:strUri]; |
| CDVPluginResult* result = nil; |
| |
| if (!testUri || ![testUri isFileURL]) { |
| // issue ENCODING_ERR |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; |
| } else { |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| NSString* path = [testUri path]; |
| // NSLog(@"url path: %@", path); |
| BOOL isDir = NO; |
| // see if exists and is file or dir |
| BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir]; |
| if (bExists) { |
| // see if it contains docs path |
| NSRange range = [path rangeOfString:self.appDocsPath]; |
| NSString* foundFullPath = nil; |
| // there's probably an api or easier way to figure out the path type but I can't find it! |
| if ((range.location != NSNotFound) && (range.length == [self.appDocsPath length])) { |
| foundFullPath = self.appDocsPath; |
| } else { |
| // see if it contains the temp path |
| range = [path rangeOfString:self.appTempPath]; |
| if ((range.location != NSNotFound) && (range.length == [self.appTempPath length])) { |
| foundFullPath = self.appTempPath; |
| } |
| } |
| if (foundFullPath == nil) { |
| // error SECURITY_ERR - not one of the two paths types supported |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SECURITY_ERR]; |
| } else { |
| NSDictionary* fileSystem = [self getDirectoryEntry:path isDirectory:isDir]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; |
| } |
| } else { |
| // return NOT_FOUND_ERR |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| } |
| if (result != nil) { |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| } |
| |
| /* Part of DirectoryEntry interface, creates or returns the specified directory |
| * IN: |
| * NSString* fullPath - full path for this directory |
| * NSString* path - directory to be created/returned; may be full path or relative path |
| * NSDictionary* - Flags object |
| * boolean as NSNumber create - |
| * if create is true and directory does not exist, create dir and return directory entry |
| * if create is true and exclusive is true and directory does exist, return error |
| * if create is false and directory does not exist, return error |
| * if create is false and the path represents a file, return error |
| * boolean as NSNumber exclusive - used in conjunction with create |
| * if exclusive is true and create is true - specifies failure if directory already exists |
| * |
| * |
| */ |
| - (void)getDirectory:(CDVInvokedUrlCommand*)command |
| { |
| NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments]; |
| NSMutableDictionary* options = nil; |
| |
| if ([arguments count] >= 3) { |
| options = [arguments objectAtIndex:2 withDefault:nil]; |
| } |
| // add getDir to options and call getFile() |
| if (options != nil) { |
| options = [NSMutableDictionary dictionaryWithDictionary:options]; |
| } else { |
| options = [NSMutableDictionary dictionaryWithCapacity:1]; |
| } |
| [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"]; |
| if ([arguments count] >= 3) { |
| [arguments replaceObjectAtIndex:2 withObject:options]; |
| } else { |
| [arguments addObject:options]; |
| } |
| CDVInvokedUrlCommand* subCommand = |
| [[CDVInvokedUrlCommand alloc] initWithArguments:arguments |
| callbackId:command.callbackId |
| className:command.className |
| methodName:command.methodName]; |
| |
| [self getFile:subCommand]; |
| } |
| |
| /* Part of DirectoryEntry interface, creates or returns the specified file |
| * IN: |
| * NSString* fullPath - full path for this file |
| * NSString* path - file to be created/returned; may be full path or relative path |
| * NSDictionary* - Flags object |
| * boolean as NSNumber create - |
| * if create is true and file does not exist, create file and return File entry |
| * if create is true and exclusive is true and file does exist, return error |
| * if create is false and file does not exist, return error |
| * if create is false and the path represents a directory, return error |
| * boolean as NSNumber exclusive - used in conjunction with create |
| * if exclusive is true and create is true - specifies failure if file already exists |
| * |
| * |
| */ |
| - (void)getFile:(CDVInvokedUrlCommand*)command |
| { |
| // arguments are URL encoded |
| NSString* fullPath = [command.arguments objectAtIndex:0]; |
| NSString* requestedPath = [command.arguments objectAtIndex:1]; |
| NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil]; |
| |
| // return unsupported result for assets-library URLs |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| CDVPluginResult* result = nil; |
| BOOL bDirRequest = NO; |
| BOOL create = NO; |
| BOOL exclusive = NO; |
| int errorCode = 0; // !!! risky - no error code currently defined for 0 |
| |
| if ([options valueForKeyIsNumber:@"create"]) { |
| create = [(NSNumber*)[options valueForKey:@"create"] boolValue]; |
| } |
| if ([options valueForKeyIsNumber:@"exclusive"]) { |
| exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue]; |
| } |
| |
| if ([options valueForKeyIsNumber:@"getDir"]) { |
| // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method |
| bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue]; |
| } |
| // see if the requested path has invalid characters - should we be checking for more than just ":"? |
| if ([requestedPath rangeOfString:@":"].location != NSNotFound) { |
| errorCode = ENCODING_ERR; |
| } else { |
| // was full or relative path provided? |
| NSRange range = [requestedPath rangeOfString:fullPath]; |
| BOOL bIsFullPath = range.location != NSNotFound; |
| |
| NSString* reqFullPath = nil; |
| |
| if (!bIsFullPath) { |
| reqFullPath = [fullPath stringByAppendingPathComponent:requestedPath]; |
| } else { |
| reqFullPath = requestedPath; |
| } |
| |
| // NSLog(@"reqFullPath = %@", reqFullPath); |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir; |
| BOOL bExists = [fileMgr fileExistsAtPath:reqFullPath isDirectory:&bIsDir]; |
| if (bExists && (create == NO) && (bIsDir == !bDirRequest)) { |
| // path exists and is of requested type - return TYPE_MISMATCH_ERR |
| errorCode = TYPE_MISMATCH_ERR; |
| } else if (!bExists && (create == NO)) { |
| // path does not exist and create is false - return NOT_FOUND_ERR |
| errorCode = NOT_FOUND_ERR; |
| } else if (bExists && (create == YES) && (exclusive == YES)) { |
| // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR |
| errorCode = PATH_EXISTS_ERR; |
| } else { |
| // if bExists and create == YES - just return data |
| // if bExists and create == NO - just return data |
| // if !bExists and create == YES - create and return data |
| BOOL bSuccess = YES; |
| NSError __autoreleasing* pError = nil; |
| if (!bExists && (create == YES)) { |
| if (bDirRequest) { |
| // create the dir |
| bSuccess = [fileMgr createDirectoryAtPath:reqFullPath withIntermediateDirectories:NO attributes:nil error:&pError]; |
| } else { |
| // create the empty file |
| bSuccess = [fileMgr createFileAtPath:reqFullPath contents:nil attributes:nil]; |
| } |
| } |
| if (!bSuccess) { |
| errorCode = ABORT_ERR; |
| if (pError) { |
| NSLog(@"error creating directory: %@", [pError localizedDescription]); |
| } |
| } else { |
| // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]); |
| // file existed or was created |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:reqFullPath isDirectory:bDirRequest]]; |
| } |
| } // are all possible conditions met? |
| } |
| |
| if (errorCode > 0) { |
| // create error callback |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; |
| } |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* |
| * Look up the parent Entry containing this Entry. |
| * If this Entry is the root of its filesystem, its parent is itself. |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath |
| * NSMutableDictionary* options |
| * empty |
| */ |
| - (void)getParent:(CDVInvokedUrlCommand*)command |
| { |
| // arguments are URL encoded |
| NSString* fullPath = [command.arguments objectAtIndex:0]; |
| |
| // we don't (yet?) support getting the parent of an asset |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| CDVPluginResult* result = nil; |
| NSString* newPath = nil; |
| |
| if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { |
| // return self |
| newPath = fullPath; |
| } else { |
| // since this call is made from an existing Entry object - the parent should already exist so no additional error checking |
| // remove last component and return Entry |
| NSRange range = [fullPath rangeOfString:@"/" options:NSBackwardsSearch]; |
| newPath = [fullPath substringToIndex:range.location]; |
| } |
| |
| if (newPath) { |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir; |
| BOOL bExists = [fileMgr fileExistsAtPath:newPath isDirectory:&bIsDir]; |
| if (bExists) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newPath isDirectory:bIsDir]]; |
| } |
| } |
| if (!result) { |
| // invalid path or file does not exist |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* |
| * get MetaData of entry |
| * Currently MetaData only includes modificationTime. |
| */ |
| - (void)getMetadata:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| __block CDVPluginResult* result = nil; |
| |
| if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| // In this case, we need to use an asynchronous method to retrieve the file. |
| // Because of this, we can't just assign to `result` and send it at the end of the method. |
| // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. |
| ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { |
| if (asset) { |
| // We have the asset! Retrieve the metadata and send it off. |
| NSDate* date = [asset valueForProperty:ALAssetPropertyDate]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[date timeIntervalSince1970] * 1000]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } else { |
| // We couldn't find the asset. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| }; |
| // TODO(maxw): Consider making this a class variable since it's the same every time. |
| ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { |
| // Retrieving the asset failed for some reason. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| }; |
| |
| ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; |
| [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; |
| return; |
| } |
| |
| NSString* testPath = argPath; // [self getFullPath: argPath]; |
| |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| NSError* __autoreleasing error = nil; |
| |
| NSDictionary* fileAttribs = [fileMgr attributesOfItemAtPath:testPath error:&error]; |
| |
| if (fileAttribs) { |
| NSDate* modDate = [fileAttribs fileModificationDate]; |
| if (modDate) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[modDate timeIntervalSince1970] * 1000]; |
| } |
| } else { |
| // didn't get fileAttribs |
| CDVFileError errorCode = ABORT_ERR; |
| NSLog (@"error getting metadata: %@", [error localizedDescription]); |
| if ([error code] == NSFileNoSuchFileError) { |
| errorCode = NOT_FOUND_ERR; |
| } |
| // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode]; |
| } |
| if (!result) { |
| // invalid path or file does not exist |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* |
| * set MetaData of entry |
| * Currently we only support "com.apple.MobileBackup" (boolean) |
| */ |
| - (void)setMetadata:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* filePath = [command.arguments objectAtIndex:0]; |
| NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:nil]; |
| CDVPluginResult* result = nil; |
| BOOL ok = NO; |
| |
| // setMetadata doesn't make sense for asset library files |
| if (![filePath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| // we only care about this iCloud key for now. |
| // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute) |
| NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup"; |
| id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey]; |
| |
| if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) { |
| if (IsAtLeastiOSVersion(@"5.1")) { |
| NSURL* url = [NSURL fileURLWithPath:filePath]; |
| NSError* __autoreleasing error = nil; |
| |
| ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error]; |
| } else { // below 5.1 (deprecated - only really supported in 5.01) |
| u_int8_t value = [iCloudBackupExtendedAttributeValue intValue]; |
| if (value == 0) { // remove the attribute (allow backup, the default) |
| ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0); |
| } else { // set the attribute (skip backup) |
| ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0); |
| } |
| } |
| } |
| } |
| |
| if (ok) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
| } else { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* removes the directory or file entry |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath |
| * |
| * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir |
| * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file |
| * returns NOT_FOUND_ERR if file or dir is not found |
| */ |
| - (void)remove:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* fullPath = [command.arguments objectAtIndex:0]; |
| CDVPluginResult* result = nil; |
| CDVFileError errorCode = 0; // !! 0 not currently defined |
| |
| // return error for assets-library URLs |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| errorCode = INVALID_MODIFICATION_ERR; |
| } else if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { |
| // error if try to remove top level (documents or tmp) dir |
| errorCode = NO_MODIFICATION_ALLOWED_ERR; |
| } else { |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir = NO; |
| BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; |
| if (!bExists) { |
| errorCode = NOT_FOUND_ERR; |
| } |
| if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fullPath error:nil] count] != 0)) { |
| // dir is not empty |
| errorCode = INVALID_MODIFICATION_ERR; |
| } |
| } |
| if (errorCode > 0) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; |
| } else { |
| // perform actual remove |
| result = [self doRemove:fullPath]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* recursively removes the directory |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath |
| * |
| * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir |
| * returns NOT_FOUND_ERR if file or dir is not found |
| */ |
| - (void)removeRecursively:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* fullPath = [command.arguments objectAtIndex:0]; |
| |
| // return unsupported result for assets-library URLs |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| CDVPluginResult* result = nil; |
| |
| // error if try to remove top level (documents or tmp) dir |
| if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; |
| } else { |
| result = [self doRemove:fullPath]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* remove the file or directory (recursively) |
| * IN: |
| * NSString* fullPath - the full path to the file or directory to be removed |
| * NSString* callbackId |
| * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling |
| */ |
| |
| - (CDVPluginResult*)doRemove:(NSString*)fullPath |
| { |
| CDVPluginResult* result = nil; |
| BOOL bSuccess = NO; |
| NSError* __autoreleasing pError = nil; |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| |
| @try { |
| bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError]; |
| if (bSuccess) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
| } else { |
| // see if we can give a useful error |
| CDVFileError errorCode = ABORT_ERR; |
| NSLog(@"error getting metadata: %@", [pError localizedDescription]); |
| if ([pError code] == NSFileNoSuchFileError) { |
| errorCode = NOT_FOUND_ERR; |
| } else if ([pError code] == NSFileWriteNoPermissionError) { |
| errorCode = NO_MODIFICATION_ALLOWED_ERR; |
| } |
| |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; |
| } |
| } @catch(NSException* e) { // NSInvalidArgumentException if path is . or .. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR]; |
| } |
| |
| return result; |
| } |
| |
| - (void)copyTo:(CDVInvokedUrlCommand*)command |
| { |
| [self doCopyMove:command isCopy:YES]; |
| } |
| |
| - (void)moveTo:(CDVInvokedUrlCommand*)command |
| { |
| [self doCopyMove:command isCopy:NO]; |
| } |
| |
| /** |
| * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name, |
| * or attempted to copy a directory into a directory that it contains directly or indirectly. |
| * |
| * IN: |
| * NSString* srcDir |
| * NSString* destinationDir |
| * OUT: |
| * YES copy/ move is allows |
| * NO move is onto itself |
| */ |
| - (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest |
| { |
| // This weird test is to determine if we are copying or moving a directory into itself. |
| // Copy /Documents/myDir to /Documents/myDir-backup is okay but |
| // Copy /Documents/myDir to /Documents/myDir/backup not okay |
| BOOL copyOK = YES; |
| NSRange range = [dest rangeOfString:src]; |
| |
| if (range.location != NSNotFound) { |
| NSRange testRange = {range.length - 1, ([dest length] - range.length)}; |
| NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange]; |
| if (resultRange.location != NSNotFound) { |
| copyOK = NO; |
| } |
| } |
| return copyOK; |
| } |
| |
| /* Copy/move a file or directory to a new location |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath of entry |
| * 1 - NSString* newName the new name of the entry, defaults to the current name |
| * NSMutableDictionary* options - DirectoryEntry to which to copy the entry |
| * BOOL - bCopy YES if copy, NO if move |
| * |
| */ |
| - (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy |
| { |
| NSArray* arguments = command.arguments; |
| |
| // arguments |
| NSString* srcFullPath = [arguments objectAtIndex:0]; |
| NSString* destRootPath = [arguments objectAtIndex:1]; |
| // optional argument |
| NSString* newName = ([arguments count] > 2) ? [arguments objectAtIndex:2] : [srcFullPath lastPathComponent]; // use last component from appPath if new name not provided |
| |
| __block CDVPluginResult* result = nil; |
| CDVFileError errCode = 0; // !! Currently 0 is not defined, use this to signal error !! |
| |
| /*NSString* destRootPath = nil; |
| NSString* key = @"fullPath"; |
| if([options valueForKeyIsString:key]){ |
| destRootPath = [options objectForKey:@"fullPath"]; |
| }*/ |
| |
| if (!destRootPath) { |
| // no destination provided |
| errCode = NOT_FOUND_ERR; |
| } else if ([newName rangeOfString:@":"].location != NSNotFound) { |
| // invalid chars in new name |
| errCode = ENCODING_ERR; |
| } else { |
| NSString* newFullPath = [destRootPath stringByAppendingPathComponent:newName]; |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| if ([newFullPath isEqualToString:srcFullPath]) { |
| // source and destination can not be the same |
| errCode = INVALID_MODIFICATION_ERR; |
| } else if ([srcFullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| if (bCopy) { |
| // Copying (as opposed to moving) an assets library file is okay. |
| // In this case, we need to use an asynchronous method to retrieve the file. |
| // Because of this, we can't just assign to `result` and send it at the end of the method. |
| // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. |
| ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { |
| if (asset) { |
| // We have the asset! Get the data and try to copy it over. |
| if (![fileMgr fileExistsAtPath:destRootPath]) { |
| // The destination directory doesn't exist. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } else if ([fileMgr fileExistsAtPath:newFullPath]) { |
| // A file already exists at the destination path. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:PATH_EXISTS_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| // We're good to go! Write the file to the new destination. |
| ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; |
| Byte* buffer = (Byte*)malloc ([assetRepresentation size]); |
| NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; |
| NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; |
| [data writeToFile:newFullPath atomically:YES]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newFullPath isDirectory:NO]]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } else { |
| // We couldn't find the asset. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| }; |
| ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { |
| // Retrieving the asset failed for some reason. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| }; |
| |
| ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; |
| [assetsLibrary assetForURL:[NSURL URLWithString:srcFullPath] resultBlock:resultBlock failureBlock:failureBlock]; |
| return; |
| } else { |
| // Moving an assets library file is not doable, since we can't remove it. |
| errCode = INVALID_MODIFICATION_ERR; |
| } |
| } else { |
| BOOL bSrcIsDir = NO; |
| BOOL bDestIsDir = NO; |
| BOOL bNewIsDir = NO; |
| BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir]; |
| BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir]; |
| BOOL bNewExists = [fileMgr fileExistsAtPath:newFullPath isDirectory:&bNewIsDir]; |
| if (!bSrcExists || !bDestExists) { |
| // the source or the destination root does not exist |
| errCode = NOT_FOUND_ERR; |
| } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) { |
| // can't copy/move dir to file |
| errCode = INVALID_MODIFICATION_ERR; |
| } else { // no errors yet |
| NSError* __autoreleasing error = nil; |
| BOOL bSuccess = NO; |
| if (bCopy) { |
| if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath] /*[newFullPath hasPrefix:srcFullPath]*/) { |
| // can't copy dir into self |
| errCode = INVALID_MODIFICATION_ERR; |
| } else if (bNewExists) { |
| // the full destination should NOT already exist if a copy |
| errCode = PATH_EXISTS_ERR; |
| } else { |
| bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFullPath error:&error]; |
| } |
| } else { // move |
| // iOS requires that destination must not exist before calling moveTo |
| // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents |
| // |
| if (!bSrcIsDir && (bNewExists && bNewIsDir)) { |
| // can't move a file to directory |
| errCode = INVALID_MODIFICATION_ERR; |
| } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath]) { // [newFullPath hasPrefix:srcFullPath]){ |
| // can't move a dir into itself |
| errCode = INVALID_MODIFICATION_ERR; |
| } else if (bNewExists) { |
| if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFullPath error:NULL] count] != 0)) { |
| // can't move dir to a dir that is not empty |
| errCode = INVALID_MODIFICATION_ERR; |
| newFullPath = nil; // so we won't try to move |
| } else { |
| // remove destination so can perform the moveItemAtPath |
| bSuccess = [fileMgr removeItemAtPath:newFullPath error:NULL]; |
| if (!bSuccess) { |
| errCode = INVALID_MODIFICATION_ERR; // is this the correct error? |
| newFullPath = nil; |
| } |
| } |
| } else if (bNewIsDir && [newFullPath hasPrefix:srcFullPath]) { |
| // can't move a directory inside itself or to any child at any depth; |
| errCode = INVALID_MODIFICATION_ERR; |
| newFullPath = nil; |
| } |
| |
| if (newFullPath != nil) { |
| bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFullPath error:&error]; |
| } |
| } |
| if (bSuccess) { |
| // should verify it is there and of the correct type??? |
| NSDictionary* newEntry = [self getDirectoryEntry:newFullPath isDirectory:bSrcIsDir]; // should be the same type as source |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry]; |
| } else { |
| errCode = INVALID_MODIFICATION_ERR; // catch all |
| if (error) { |
| if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) { |
| errCode = NOT_READABLE_ERR; |
| } else if ([error code] == NSFileWriteOutOfSpaceError) { |
| errCode = QUOTA_EXCEEDED_ERR; |
| } else if ([error code] == NSFileWriteNoPermissionError) { |
| errCode = NO_MODIFICATION_ALLOWED_ERR; |
| } |
| } |
| } |
| } |
| } |
| } |
| if (errCode > 0) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* return the URI to the entry |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath of entry |
| * 1 - desired mime type of entry - ignored - always returns file:// |
| */ |
| |
| /* Not needed since W3C toURI is synchronous. Leaving code here for now in case W3C spec changes..... |
| - (void) toURI:(CDVInvokedUrlCommand*)command |
| { |
| NSString* callbackId = command.callbackId; |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| PluginResult* result = nil; |
| NSString* jsString = nil; |
| |
| NSString* fullPath = [self getFullPath: argPath]; |
| if (fullPath) { |
| // do we need to make sure the file actually exists? |
| // create file uri |
| NSString* strUri = [fullPath stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; |
| NSURL* fileUrl = [NSURL fileURLWithPath:strUri]; |
| if (fileUrl) { |
| result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: [fileUrl absoluteString]]; |
| jsString = [result toSuccessCallbackString:callbackId]; |
| } // else NOT_FOUND_ERR |
| } |
| if(!jsString) { |
| // was error |
| result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt: NOT_FOUND_ERR cast: @"window.localFileSystem._castError"]; |
| jsString = [result toErrorCallbackString:callbackId]; |
| } |
| |
| [self writeJavascript:jsString]; |
| }*/ |
| - (void)getFileMetadata:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| |
| __block CDVPluginResult* result = nil; |
| |
| NSString* fullPath = argPath; // [self getFullPath: argPath]; |
| |
| if (fullPath) { |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| // In this case, we need to use an asynchronous method to retrieve the file. |
| // Because of this, we can't just assign to `result` and send it at the end of the method. |
| // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. |
| ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { |
| if (asset) { |
| // We have the asset! Populate the dictionary and send it off. |
| NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; |
| ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; |
| [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"]; |
| [fileInfo setObject:argPath forKey:@"fullPath"]; |
| NSString* filename = [assetRepresentation filename]; |
| [fileInfo setObject:filename forKey:@"name"]; |
| [fileInfo setObject:[self getMimeTypeFromPath:filename] forKey:@"type"]; |
| NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate]; |
| NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000]; |
| [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; |
| |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } else { |
| // We couldn't find the asset. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| }; |
| ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { |
| // Retrieving the asset failed for some reason. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| }; |
| |
| ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; |
| [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; |
| return; |
| } else { |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir = NO; |
| // make sure it exists and is not a directory |
| BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; |
| if (!bExists || bIsDir) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } else { |
| // create dictionary of file info |
| NSError* __autoreleasing error = nil; |
| NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error]; |
| NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; |
| [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; |
| [fileInfo setObject:argPath forKey:@"fullPath"]; |
| [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping |
| [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"]; |
| NSDate* modDate = [fileAttrs fileModificationDate]; |
| NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; |
| [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; |
| } |
| } |
| } |
| if (!result) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_INSTANTIATION_EXCEPTION]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| - (void)readEntries:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* fullPath = [command.arguments objectAtIndex:0]; |
| |
| // return unsupported result for assets-library URLs |
| if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| CDVPluginResult* result = nil; |
| |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| NSError* __autoreleasing error = nil; |
| NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fullPath error:&error]; |
| |
| if (contents) { |
| NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1]; |
| if ([contents count] > 0) { |
| // create an Entry (as JSON) for each file/dir |
| for (NSString* name in contents) { |
| // see if is dir or file |
| NSString* entryPath = [fullPath stringByAppendingPathComponent:name]; |
| BOOL bIsDir = NO; |
| [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir]; |
| NSDictionary* entryDict = [self getDirectoryEntry:entryPath isDirectory:bIsDir]; |
| [entries addObject:entryDict]; |
| } |
| } |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries]; |
| } else { |
| // assume not found but could check error for more specific error conditions |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* read and return file data |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath |
| * 1 - NSString* encoding - NOT USED, iOS reads and writes using UTF8! |
| * 2 - NSString* start - OPTIONAL, only provided when not == 0. |
| * 3 - NSString* end - OPTIONAL, only provided when not == length. |
| */ |
| - (void)readAsText:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| NSInteger start = 0; |
| NSInteger end = -1; |
| |
| if ([command.arguments count] >= 3) { |
| start = [[command.arguments objectAtIndex:2] integerValue]; |
| } |
| if ([command.arguments count] >= 4) { |
| end = [[command.arguments objectAtIndex:3] integerValue]; |
| } |
| |
| // NSString* encoding = [command.arguments objectAtIndex:2]; // not currently used |
| CDVPluginResult* result = nil; |
| |
| NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; |
| |
| if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| // can't read assets-library URLs as text |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; |
| } else if (!file) { |
| // invalid path entry |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } else { |
| if (start > 0) { |
| [file seekToFileOffset:start]; |
| } |
| |
| NSData* readData; |
| if (end < 0) { |
| readData = [file readDataToEndOfFile]; |
| } else { |
| readData = [file readDataOfLength:(end - start)]; |
| } |
| |
| [file closeFile]; |
| NSString* pNStrBuff = nil; |
| if (readData) { |
| pNStrBuff = [[NSString alloc] initWithBytes:[readData bytes] length:[readData length] encoding:NSUTF8StringEncoding]; |
| } else { |
| // return empty string if no data |
| pNStrBuff = @""; |
| } |
| |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:pNStrBuff]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* Read content of text file and return as base64 encoded data url. |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* fullPath |
| * |
| * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined. |
| */ |
| |
| - (void)readAsDataURL:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| NSInteger start = 0; |
| NSInteger end = -1; |
| |
| if ([command.arguments count] >= 2) { |
| start = [[command.arguments objectAtIndex:1] integerValue]; |
| } |
| if ([command.arguments count] >= 3) { |
| end = [[command.arguments objectAtIndex:2] integerValue]; |
| } |
| |
| CDVFileError errCode = ABORT_ERR; |
| __block CDVPluginResult* result = nil; |
| |
| if (!argPath) { |
| errCode = SYNTAX_ERR; |
| } else if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| // In this case, we need to use an asynchronous method to retrieve the file. |
| // Because of this, we can't just assign to `result` and send it at the end of the method. |
| // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. |
| ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { |
| if (asset) { |
| // We have the asset! Get the data and send it off. |
| ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; |
| Byte* buffer = (Byte*)malloc ([assetRepresentation size]); |
| NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; |
| NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; |
| NSString* mimeType = [self getMimeTypeFromPath:[assetRepresentation filename]]; |
| NSString* dataString = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:dataString]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } else { |
| // We couldn't find the asset. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| }; |
| ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { |
| // Retrieving the asset failed for some reason. Send the appropriate error. |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| }; |
| |
| ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; |
| [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; |
| return; |
| } else { |
| NSString* mimeType = [self getMimeTypeFromPath:argPath]; |
| if (!mimeType) { |
| // can't return as data URL if can't figure out the mimeType |
| errCode = ENCODING_ERR; |
| } else { |
| NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; |
| if (start > 0) { |
| [file seekToFileOffset:start]; |
| } |
| |
| NSData* readData; |
| if (end < 0) { |
| readData = [file readDataToEndOfFile]; |
| } else { |
| readData = [file readDataOfLength:(end - start)]; |
| } |
| |
| [file closeFile]; |
| if (readData) { |
| NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [readData base64EncodedString]]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; |
| } else { |
| errCode = NOT_FOUND_ERR; |
| } |
| } |
| } |
| if (!result) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; |
| } |
| // NSLog(@"readAsDataURL return: %@", jsString); |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| /* helper function to get the mimeType from the file extension |
| * IN: |
| * NSString* fullPath - filename (may include path) |
| * OUT: |
| * NSString* the mime type as type/subtype. nil if not able to determine |
| */ |
| - (NSString*)getMimeTypeFromPath:(NSString*)fullPath |
| { |
| NSString* mimeType = nil; |
| |
| if (fullPath) { |
| CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL); |
| if (typeId) { |
| mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType); |
| if (!mimeType) { |
| // special case for m4a |
| if ([(__bridge NSString*) typeId rangeOfString:@"m4a-audio"].location != NSNotFound) { |
| mimeType = @"audio/mp4"; |
| } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) { |
| mimeType = @"audio/wav"; |
| } |
| } |
| CFRelease(typeId); |
| } |
| } |
| return mimeType; |
| } |
| |
| - (void)truncate:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| unsigned long long pos = (unsigned long long)[[command.arguments objectAtIndex:1] longLongValue]; |
| |
| // assets-library files can't be truncated |
| if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| NSString* appFile = argPath; // [self getFullPath:argPath]; |
| |
| unsigned long long newPos = [self truncateFile:appFile atPosition:pos]; |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:newPos]; |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| - (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos |
| { |
| unsigned long long newPos = 0UL; |
| |
| NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath]; |
| |
| if (file) { |
| [file truncateFileAtOffset:(unsigned long long)pos]; |
| newPos = [file offsetInFile]; |
| [file synchronizeFile]; |
| [file closeFile]; |
| } |
| return newPos; |
| } |
| |
| /* write |
| * IN: |
| * NSArray* arguments |
| * 0 - NSString* file path to write to |
| * 1 - NSString* data to write |
| * 2 - NSNumber* position to begin writing |
| */ |
| - (void)write:(CDVInvokedUrlCommand*)command |
| { |
| NSString* callbackId = command.callbackId; |
| NSArray* arguments = command.arguments; |
| |
| // arguments |
| NSString* argPath = [arguments objectAtIndex:0]; |
| NSString* argData = [arguments objectAtIndex:1]; |
| unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue]; |
| |
| // text can't be written into assets-library files |
| if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| return; |
| } |
| |
| NSString* fullPath = argPath; // [self getFullPath:argPath]; |
| |
| [self truncateFile:fullPath atPosition:pos]; |
| |
| [self writeToFile:fullPath withData:argData append:YES callback:callbackId]; |
| } |
| |
| - (void)writeToFile:(NSString*)filePath withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId |
| { |
| CDVPluginResult* result = nil; |
| CDVFileError errCode = INVALID_MODIFICATION_ERR; |
| int bytesWritten = 0; |
| NSData* encData = [data dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; |
| |
| if (filePath) { |
| NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend]; |
| if (fileStream) { |
| NSUInteger len = [encData length]; |
| [fileStream open]; |
| |
| bytesWritten = [fileStream write:[encData bytes] maxLength:len]; |
| |
| [fileStream close]; |
| if (bytesWritten > 0) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten]; |
| // } else { |
| // can probably get more detailed error info via [fileStream streamError] |
| // errCode already set to INVALID_MODIFICATION_ERR; |
| // bytesWritten = 0; // may be set to -1 on error |
| } |
| } // else fileStream not created return INVALID_MODIFICATION_ERR |
| } else { |
| // invalid filePath |
| errCode = NOT_FOUND_ERR; |
| } |
| if (!result) { |
| // was an error |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; |
| } |
| [self.commandDelegate sendPluginResult:result callbackId:callbackId]; |
| } |
| |
| - (void)testFileExists:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| |
| // Get the file manager |
| NSFileManager* fMgr = [NSFileManager defaultManager]; |
| NSString* appFile = argPath; // [ self getFullPath: argPath]; |
| |
| BOOL bExists = [fMgr fileExistsAtPath:appFile]; |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1:0)]; |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| - (void)testDirectoryExists:(CDVInvokedUrlCommand*)command |
| { |
| // arguments |
| NSString* argPath = [command.arguments objectAtIndex:0]; |
| |
| // Get the file manager |
| NSFileManager* fMgr = [[NSFileManager alloc] init]; |
| NSString* appFile = argPath; // [self getFullPath: argPath]; |
| BOOL bIsDir = NO; |
| BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir]; |
| |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1:0)]; |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| // Returns number of bytes available via callback |
| - (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command |
| { |
| // no arguments |
| |
| NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath]; |
| |
| NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]]; |
| // NSLog(@"Free space is %@", strFreeSpace ); |
| |
| CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace]; |
| |
| [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; |
| } |
| |
| @end |