123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750 |
- /*
- 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 <Cordova/CDV.h>
- #import <MobileCoreServices/MobileCoreServices.h>
- #import <sys/xattr.h>
-
- @implementation CDVLocalFilesystem
- @synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
-
- - (id) initWithName:(NSString *)name root:(NSString *)fsRoot
- {
- if (self) {
- self.name = name;
- self.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]]) {
- 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:[self mimeTypeForFileAtPath: path] forKey:@"type"];
- [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);
- }
-
- // fix errors that base on Alexsander Akers from http://stackoverflow.com/a/5998683/2613194
- - (NSString*) mimeTypeForFileAtPath: (NSString *) path {
- if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
- return nil;
- }
-
- CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
- CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
- CFRelease(UTI);
-
- if (!mimeType) {
- return @"application/octet-stream";
- }
- return (__bridge NSString *)mimeType;
- }
-
- @end
|