| /* |
| 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 "CDVLocalFilesystem.h" |
| #import <sys/xattr.h> |
| |
| @implementation CDVLocalFilesystem |
| @synthesize name=_name, fsRoot=_fsRoot, urlTransformer; |
| |
| - (id) initWithName:(NSString *)name root:(NSString *)fsRoot |
| { |
| if (self) { |
| _name = name; |
| _fsRoot = fsRoot; |
| } |
| return self; |
| } |
| |
| /* |
| * IN |
| * NSString localURI |
| * OUT |
| * CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the |
| * URI represents a non-existent path, or is unrecognized or otherwise malformed. |
| */ |
| - (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url |
| { |
| CDVPluginResult* result = nil; |
| NSDictionary* entry = [self makeEntryForLocalURL:url]; |
| if (entry) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry]; |
| } else { |
| // return NOT_FOUND_ERR |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| return result; |
| } |
| - (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url { |
| NSString *path = [self filesystemPathForURL:url]; |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL isDir = NO; |
| // see if exists and is file or dir |
| BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir]; |
| if (bExists) { |
| return [self makeEntryForPath:url.fullPath isDirectory:isDir]; |
| } else { |
| return nil; |
| } |
| } |
| - (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir |
| { |
| NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5]; |
| NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent]; |
| if (isDir && ![fullPath hasSuffix:@"/"]) { |
| fullPath = [fullPath stringByAppendingString:@"/"]; |
| } |
| [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"]; |
| [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"]; |
| [dirEntry setObject:fullPath forKey:@"fullPath"]; |
| [dirEntry setObject:lastPart forKey:@"name"]; |
| [dirEntry setObject:self.name forKey: @"filesystemName"]; |
| |
| NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]]; |
| if (self.urlTransformer) { |
| nativeURL = self.urlTransformer(nativeURL); |
| } |
| |
| dirEntry[@"nativeURL"] = [nativeURL absoluteString]; |
| |
| return dirEntry; |
| } |
| |
| - (NSString *)stripQueryParametersFromPath:(NSString *)fullPath |
| { |
| NSRange questionMark = [fullPath rangeOfString:@"?"]; |
| if (questionMark.location != NSNotFound) { |
| return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)]; |
| } |
| return fullPath; |
| } |
| |
| - (NSString *)filesystemPathForFullPath:(NSString *)fullPath |
| { |
| NSString *path = nil; |
| NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath]; |
| path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath]; |
| if ([path length] > 1 && [path hasSuffix:@"/"]) { |
| path = [path substringToIndex:([path length]-1)]; |
| } |
| return path; |
| } |
| /* |
| * IN |
| * NSString localURI |
| * OUT |
| * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible |
| * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized, |
| * or if the URL is malformed. |
| * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected). |
| */ |
| - (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url |
| { |
| return [self filesystemPathForFullPath:url.fullPath]; |
| } |
| |
| - (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath |
| { |
| if (fullPath) { |
| NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
| if ([fullPath hasPrefix:@"/"]) { |
| return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]]; |
| } |
| return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]]; |
| } |
| return nil; |
| } |
| |
| - (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path |
| { |
| return [self URLforFullPath:[self fullPathForFileSystemPath:path]]; |
| |
| } |
| |
| - (NSString *)normalizePath:(NSString *)rawPath |
| { |
| // If this is an absolute path, the first path component will be '/'. Skip it if that's the case |
| BOOL isAbsolutePath = [rawPath hasPrefix:@"/"]; |
| if (isAbsolutePath) { |
| rawPath = [rawPath substringFromIndex:1]; |
| } |
| NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]]; |
| for (int index = 0; index < [components count]; ++index) { |
| if ([[components objectAtIndex:index] isEqualToString:@".."]) { |
| [components removeObjectAtIndex:index]; |
| if (index > 0) { |
| [components removeObjectAtIndex:index-1]; |
| --index; |
| } |
| } |
| } |
| |
| if (isAbsolutePath) { |
| return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]]; |
| } else { |
| return [components componentsJoinedByString:@"/"]; |
| } |
| |
| |
| } |
| |
| - (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key |
| { |
| BOOL bNumber = NO; |
| NSObject* value = dict[key]; |
| if (value) { |
| bNumber = [value isKindOfClass:[NSNumber class]]; |
| } |
| return bNumber; |
| } |
| |
| - (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options |
| { |
| CDVPluginResult* result = nil; |
| BOOL bDirRequest = NO; |
| BOOL create = NO; |
| BOOL exclusive = NO; |
| int errorCode = 0; // !!! risky - no error code currently defined for 0 |
| |
| if ([self valueForKeyIsNumber:options key:@"create"]) { |
| create = [(NSNumber*)[options valueForKey:@"create"] boolValue]; |
| } |
| if ([self valueForKeyIsNumber:options key:@"exclusive"]) { |
| exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue]; |
| } |
| if ([self valueForKeyIsNumber:options key:@"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 { |
| // Build new fullPath for the requested resource. |
| // We concatenate the two paths together, and then scan the resulting string to remove |
| // parent ("..") references. Any parent references at the beginning of the string are |
| // silently removed. |
| NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath]; |
| combinedPath = [self normalizePath:combinedPath]; |
| CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath]; |
| |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir; |
| BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir]; |
| if (bExists && (create == NO) && (bIsDir == !bDirRequest)) { |
| // path exists and is not 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:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError]; |
| } else { |
| // create the empty file |
| bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] 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 makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]]; |
| } |
| } // are all possible conditions met? |
| } |
| |
| if (errorCode > 0) { |
| // create error callback |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; |
| } |
| return result; |
| |
| } |
| |
| - (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI |
| { |
| CDVPluginResult* result = nil; |
| CDVFilesystemURL *newURI = nil; |
| if ([localURI.fullPath isEqualToString:@""]) { |
| // return self |
| newURI = localURI; |
| } else { |
| newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */ |
| } |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir; |
| BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir]; |
| if (bExists) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]]; |
| } else { |
| // invalid path or file does not exist |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| return result; |
| } |
| |
| - (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options |
| { |
| BOOL ok = NO; |
| |
| NSString* filePath = [self filesystemPathForURL:localURI]; |
| // 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]]) { |
| // todo: fix me |
| // 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) { |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
| } else { |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; |
| } |
| } |
| |
| /* 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 removing filesystem entry at %@: %@", fullPath, [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; |
| } |
| |
| - (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI |
| { |
| NSString *fileSystemPath = [self filesystemPathForURL:localURI]; |
| |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| BOOL bIsDir = NO; |
| BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir]; |
| if (!bExists) { |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) { |
| // dir is not empty |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR]; |
| } |
| return [self doRemove:fileSystemPath]; |
| } |
| |
| - (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI |
| { |
| NSString *fileSystemPath = [self filesystemPathForURL:localURI]; |
| return [self doRemove:fileSystemPath]; |
| } |
| |
| /* |
| * IN |
| * NSString localURI |
| * OUT |
| * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible |
| * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized, |
| * or if the URL is malformed. |
| * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected). |
| */ |
| - (NSString *)fullPathForFileSystemPath:(NSString *)fsPath |
| { |
| if ([fsPath hasPrefix:self.fsRoot]) { |
| return [fsPath substringFromIndex:[self.fsRoot length]]; |
| } |
| return nil; |
| } |
| |
| |
| - (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI |
| { |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| NSError* __autoreleasing error = nil; |
| NSString *fileSystemPath = [self filesystemPathForURL:localURI]; |
| |
| NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath 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 = [fileSystemPath stringByAppendingPathComponent:name]; |
| BOOL bIsDir = NO; |
| [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir]; |
| NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir]; |
| [entries addObject:entryDict]; |
| } |
| } |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries]; |
| } else { |
| // assume not found but could check error for more specific error conditions |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; |
| } |
| } |
| |
| - (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; |
| } |
| |
| - (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos |
| { |
| unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos]; |
| return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos]; |
| } |
| |
| - (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend |
| { |
| NSString *filePath = [self filesystemPathForURL:localURL]; |
| |
| CDVPluginResult* result = nil; |
| CDVFileError errCode = INVALID_MODIFICATION_ERR; |
| int bytesWritten = 0; |
| |
| if (filePath) { |
| NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend]; |
| if (fileStream) { |
| NSUInteger len = [encData length]; |
| if (len == 0) { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len]; |
| } else { |
| [fileStream open]; |
| |
| bytesWritten = (int)[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]; |
| } |
| return result; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| - (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback |
| { |
| NSFileManager *fileMgr = [[NSFileManager alloc] init]; |
| NSString *destRootPath = [self filesystemPathForURL:destURL]; |
| BOOL bDestIsDir = NO; |
| BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir]; |
| |
| NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName]; |
| NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath]; |
| |
| BOOL bNewIsDir = NO; |
| BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir]; |
| |
| CDVPluginResult *result = nil; |
| int errCode = 0; |
| |
| if (!bDestExists) { |
| // the destination root does not exist |
| errCode = NOT_FOUND_ERR; |
| } |
| |
| else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) { |
| /* Same FS, we can shortcut with NSFileManager operations */ |
| NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL]; |
| |
| BOOL bSrcIsDir = NO; |
| BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir]; |
| |
| if (!bSrcExists) { |
| // the source does not exist |
| errCode = NOT_FOUND_ERR; |
| } else if ([newFileSystemPath isEqualToString:srcFullPath]) { |
| // source and destination can not be the same |
| errCode = INVALID_MODIFICATION_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:newFileSystemPath]) { |
| // 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:newFileSystemPath 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:newFileSystemPath]) { |
| // can't move a dir into itself |
| errCode = INVALID_MODIFICATION_ERR; |
| } else if (bNewExists) { |
| if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) { |
| // can't move dir to a dir that is not empty |
| errCode = INVALID_MODIFICATION_ERR; |
| newFileSystemPath = nil; // so we won't try to move |
| } else { |
| // remove destination so can perform the moveItemAtPath |
| bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL]; |
| if (!bSuccess) { |
| errCode = INVALID_MODIFICATION_ERR; // is this the correct error? |
| newFileSystemPath = nil; |
| } |
| } |
| } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) { |
| // can't move a directory inside itself or to any child at any depth; |
| errCode = INVALID_MODIFICATION_ERR; |
| newFileSystemPath = nil; |
| } |
| |
| if (newFileSystemPath != nil) { |
| bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error]; |
| } |
| } |
| if (bSuccess) { |
| // should verify it is there and of the correct type??? |
| NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry]; |
| } else { |
| 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; |
| } |
| } |
| } |
| } |
| } else { |
| // Need to copy the hard way |
| [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { |
| CDVPluginResult* result = nil; |
| if (data != nil) { |
| BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES]; |
| if (bSuccess) { |
| // should verify it is there and of the correct type??? |
| NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO]; |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry]; |
| } else { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR]; |
| } |
| } else { |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; |
| } |
| callback(result); |
| }]; |
| return; // Async IO; return without callback. |
| } |
| if (result == nil) { |
| if (!errCode) { |
| errCode = INVALID_MODIFICATION_ERR; // Catch-all default |
| } |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode]; |
| } |
| callback(result); |
| } |
| |
| /* 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"; |
| } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) { |
| mimeType = @"text/css"; |
| } |
| } |
| CFRelease(typeId); |
| } |
| } |
| return mimeType; |
| } |
| |
| - (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback |
| { |
| NSString *path = [self filesystemPathForURL:localURL]; |
| |
| NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path]; |
| if (mimeType == nil) { |
| mimeType = @"*/*"; |
| } |
| NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path]; |
| if (start > 0) { |
| [file seekToFileOffset:start]; |
| } |
| |
| NSData* readData; |
| if (end < 0) { |
| readData = [file readDataToEndOfFile]; |
| } else { |
| readData = [file readDataOfLength:(end - start)]; |
| } |
| [file closeFile]; |
| |
| callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR); |
| } |
| |
| - (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback |
| { |
| NSString *path = [self filesystemPathForURL:localURL]; |
| CDVPluginResult *result; |
| NSFileManager* fileMgr = [[NSFileManager alloc] init]; |
| |
| NSError* __autoreleasing error = nil; |
| NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error]; |
| |
| if (fileAttrs) { |
| |
| // create dictionary of file info |
| NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; |
| |
| [fileInfo setObject:localURL.fullPath forKey:@"fullPath"]; |
| [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping |
| [fileInfo setObject:[path lastPathComponent] forKey:@"name"]; |
| |
| // Ensure that directories (and other non-regular files) report size of 0 |
| unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0); |
| [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"]; |
| |
| NSDate* modDate = [fileAttrs fileModificationDate]; |
| if (modDate) { |
| [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"]; |
| } |
| |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; |
| |
| } else { |
| // didn't get fileAttribs |
| CDVFileError errorCode = ABORT_ERR; |
| NSLog(@"error getting metadata: %@", [error localizedDescription]); |
| if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) { |
| errorCode = NOT_FOUND_ERR; |
| } |
| // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns |
| result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode]; |
| } |
| |
| callback(result); |
| } |
| |
| @end |