diff options
| author | PliablePixels <pliablepixels@gmail.com> | 2015-06-27 09:52:06 -0400 |
|---|---|---|
| committer | PliablePixels <pliablepixels@gmail.com> | 2015-06-27 09:52:06 -0400 |
| commit | 319d4cb6670729708c19ad50b0146d1bcb7b4719 (patch) | |
| tree | c61e9723a1fd217b1816c987bba66e470e73bf02 /plugins/cordova-plugin-file/src/ios | |
| parent | fdc42fae48db0fef5fbdc9ef51a27d219aea3a72 (diff) | |
Added ability to log key events to file and email (useful for release debugging)
Diffstat (limited to 'plugins/cordova-plugin-file/src/ios')
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h | 30 | ||||
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m | 253 | ||||
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVFile.h | 157 | ||||
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVFile.m | 1092 | ||||
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h | 32 | ||||
| -rw-r--r-- | plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m | 734 |
6 files changed, 2298 insertions, 0 deletions
diff --git a/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h b/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h new file mode 100644 index 00000000..e09e2250 --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h @@ -0,0 +1,30 @@ +/* + 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" + +extern NSString* const kCDVAssetsLibraryPrefix; +extern NSString* const kCDVAssetsLibraryScheme; + +@interface CDVAssetLibraryFilesystem : NSObject<CDVFileSystem> { +} + +- (id) initWithName:(NSString *)name; + +@end diff --git a/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m b/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m new file mode 100644 index 00000000..0b95fac3 --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m @@ -0,0 +1,253 @@ +/* + 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 "CDVAssetLibraryFilesystem.h" +#import <Cordova/CDV.h> +#import <AssetsLibrary/ALAsset.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <AssetsLibrary/ALAssetsLibrary.h> +#import <MobileCoreServices/MobileCoreServices.h> + +NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; +NSString* const kCDVAssetsLibraryScheme = @"assets-library"; + +@implementation CDVAssetLibraryFilesystem +@synthesize name=_name, urlTransformer; + + +/* + The CDVAssetLibraryFilesystem works with resources which are identified + by iOS as + asset-library://<path> + and represents them internally as URLs of the form + cdvfile://localhost/assets-library/<path> + */ + +- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url +{ + if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) { + NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]]; + return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]]; + } + return url.url; +} + +- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url +{ + NSDictionary* entry = [self makeEntryForLocalURL:url]; + return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry]; +} + +- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url { + return [self makeEntryForPath:url.fullPath isDirectory:NO]; +} + +- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir +{ + NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5]; + NSString* lastPart = [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 URLWithString:[NSString stringWithFormat:@"assets-library:/%@",fullPath]]; + if (self.urlTransformer) { + nativeURL = self.urlTransformer(nativeURL); + } + dirEntry[@"nativeURL"] = [nativeURL absoluteString]; + + return dirEntry; +} + +/* 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; +} + +- (id)initWithName:(NSString *)name +{ + if (self) { + _name = name; + } + return self; +} + +- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options +{ + // return unsupported result for assets-library URLs + return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."]; +} + +- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI +{ + // we don't (yet?) support getting the parent of an asset + return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; +} + +- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options +{ + // setMetadata doesn't make sense for asset library files + return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; +} + +- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI +{ + // return error for assets-library URLs + return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR]; +} + +- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI +{ + // return error for assets-library URLs + return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."]; +} + +- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI +{ + // return unsupported result for assets-library URLs + return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."]; +} + +- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos +{ + // assets-library files can't be truncated + return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; +} + +- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend +{ + // text can't be written into assets-library files + return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; +} + +- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback +{ + // Copying to an assets library file is not doable, since we can't write it. + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR]; + callback(result); +} + +- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url +{ + NSString *path = nil; + if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) { + path = [url.url path]; + } else { + path = url.fullPath; + } + if ([path hasSuffix:@"/"]) { + path = [path substringToIndex:([path length]-1)]; + } + return path; +} + +- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback +{ + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + NSUInteger size = (end > start) ? (end - start) : [assetRepresentation size]; + Byte* buffer = (Byte*)malloc(size); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:start length:size error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + + callback(data, MIMEType, NO_ERROR); + } else { + callback(nil, nil, NOT_FOUND_ERR); + } + }; + + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + NSLog(@"Error: %@", error); + callback(nil, nil, SECURITY_ERR); + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock]; +} + +- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback +{ + // 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:localURL.fullPath forKey:@"fullPath"]; + NSString* filename = [assetRepresentation filename]; + [fileInfo setObject:filename forKey:@"name"]; + [fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"]; + NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + + callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]); + } else { + // We couldn't find the asset. Send the appropriate error. + callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]); + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]); + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock]; + return; +} +@end diff --git a/plugins/cordova-plugin-file/src/ios/CDVFile.h b/plugins/cordova-plugin-file/src/ios/CDVFile.h new file mode 100644 index 00000000..33630c03 --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVFile.h @@ -0,0 +1,157 @@ +/* + 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 <Foundation/Foundation.h> +#import <Cordova/CDVPlugin.h> + +NSString* const kCDVAssetsLibraryPrefix; +NSString* const kCDVFilesystemURLPrefix; + +enum CDVFileError { + NO_ERROR = 0, + NOT_FOUND_ERR = 1, + SECURITY_ERR = 2, + ABORT_ERR = 3, + NOT_READABLE_ERR = 4, + ENCODING_ERR = 5, + NO_MODIFICATION_ALLOWED_ERR = 6, + INVALID_STATE_ERR = 7, + SYNTAX_ERR = 8, + INVALID_MODIFICATION_ERR = 9, + QUOTA_EXCEEDED_ERR = 10, + TYPE_MISMATCH_ERR = 11, + PATH_EXISTS_ERR = 12 +}; +typedef int CDVFileError; + +@interface CDVFilesystemURL : NSObject { + NSURL *_url; + NSString *_fileSystemName; + NSString *_fullPath; +} + +- (id) initWithString:(NSString*)strURL; +- (id) initWithURL:(NSURL*)URL; ++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL; ++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL; + +- (NSString *)absoluteURL; + +@property (atomic) NSURL *url; +@property (atomic) NSString *fileSystemName; +@property (atomic) NSString *fullPath; + +@end + +@interface CDVFilesystemURLProtocol : NSURLProtocol +@end + +@protocol CDVFileSystem +- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url; +- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options; +- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI; +- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options; +- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI; +- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI; +- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI; +- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos; +- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend; +- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback; +- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback; +- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback; + +- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url; +- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir; + +@property (nonatomic,strong) NSString *name; +@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*); + +@optional +- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI; +- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path; + +@end + +@interface CDVFile : CDVPlugin { + NSString* rootDocsPath; + NSString* appDocsPath; + NSString* appLibraryPath; + NSString* appTempPath; + + NSMutableArray* fileSystems_; + BOOL userHasAllowed; +} + +- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath; +- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir; +- (NSDictionary *)makeEntryForURL:(NSURL *)URL; +- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath; + +- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL; + +/* Native Registration API */ +- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs; +- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName; + +/* Exec API */ +- (void)requestFileSystem:(CDVInvokedUrlCommand*)command; +- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command; +- (void)getDirectory:(CDVInvokedUrlCommand*)command; +- (void)getFile:(CDVInvokedUrlCommand*)command; +- (void)getParent:(CDVInvokedUrlCommand*)command; +- (void)removeRecursively:(CDVInvokedUrlCommand*)command; +- (void)remove:(CDVInvokedUrlCommand*)command; +- (void)copyTo:(CDVInvokedUrlCommand*)command; +- (void)moveTo:(CDVInvokedUrlCommand*)command; +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command; +- (void)readEntries:(CDVInvokedUrlCommand*)command; +- (void)readAsText:(CDVInvokedUrlCommand*)command; +- (void)readAsDataURL:(CDVInvokedUrlCommand*)command; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command; +- (void)write:(CDVInvokedUrlCommand*)command; +- (void)testFileExists:(CDVInvokedUrlCommand*)command; +- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command; +- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command; +- (void)truncate:(CDVInvokedUrlCommand*)command; +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy; + +/* Compatibilty with older File API */ +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath; +- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest; + +/* Conversion between filesystem paths and URLs */ +- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL; + +/* Internal methods for testing */ +- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command; + +@property (nonatomic, strong) NSString* rootDocsPath; +@property (nonatomic, strong) NSString* appDocsPath; +@property (nonatomic, strong) NSString* appLibraryPath; +@property (nonatomic, strong) NSString* appTempPath; +@property (nonatomic, strong) NSString* persistentPath; +@property (nonatomic, strong) NSString* temporaryPath; +@property (nonatomic, strong) NSMutableArray* fileSystems; + +@property BOOL userHasAllowed; + +@end + +#define kW3FileTemporary @"temporary" +#define kW3FilePersistent @"persistent" diff --git a/plugins/cordova-plugin-file/src/ios/CDVFile.m b/plugins/cordova-plugin-file/src/ios/CDVFile.m new file mode 100644 index 00000000..eec8978e --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVFile.m @@ -0,0 +1,1092 @@ +/* + 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 <Cordova/CDV.h> +#import "CDVFile.h" +#import "CDVLocalFilesystem.h" +#import "CDVAssetLibraryFilesystem.h" +#import <objc/message.h> + +CDVFile *filePlugin = nil; + +extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import)); + +#ifndef __IPHONE_5_1 + NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey"; +#endif + +NSString* const kCDVFilesystemURLPrefix = @"cdvfile"; + +@implementation CDVFilesystemURL +@synthesize url=_url; +@synthesize fileSystemName=_fileSystemName; +@synthesize fullPath=_fullPath; + +- (id) initWithString:(NSString *)strURL +{ + if ( self = [super init] ) { + NSURL *decodedURL = [NSURL URLWithString:strURL]; + return [self initWithURL:decodedURL]; + } + return nil; +} + +-(id) initWithURL:(NSURL *)URL +{ + if ( self = [super init] ) { + _url = URL; + _fileSystemName = [self filesystemNameForLocalURI:URL]; + _fullPath = [self fullPathForLocalURI:URL]; + } + return self; +} + +/* + * IN + * NSString localURI + * OUT + * NSString FileSystem Name for this URI, or nil if it is not recognized. + */ +- (NSString *)filesystemNameForLocalURI:(NSURL *)uri +{ + if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) { + NSArray *pathComponents = [uri pathComponents]; + if (pathComponents != nil && pathComponents.count > 1) { + return [pathComponents objectAtIndex:1]; + } + } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) { + return @"assets-library"; + } + return nil; +} + +/* + * IN + * NSString localURI + * OUT + * NSString fullPath component suitable for an Entry object. + * The incoming URI should be properly escaped. The returned fullPath is unescaped. + */ +- (NSString *)fullPathForLocalURI:(NSURL *)uri +{ + if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) { + NSString *path = [uri path]; + if ([uri query]) { + path = [NSString stringWithFormat:@"%@?%@", path, [uri query]]; + } + NSRange slashRange = [path rangeOfString:@"/" options:0 range:NSMakeRange(1, path.length-1)]; + if (slashRange.location == NSNotFound) { + return @""; + } + return [path substringFromIndex:slashRange.location]; + } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) { + return [[uri absoluteString] substringFromIndex:[kCDVAssetsLibraryScheme length]+2]; + } + return nil; +} + ++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL +{ + return [[CDVFilesystemURL alloc] initWithString:strURL]; +} + ++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL +{ + return [[CDVFilesystemURL alloc] initWithURL:URL]; +} + +- (NSString *)absoluteURL +{ + return [NSString stringWithFormat:@"cdvfile://localhost/%@%@", self.fileSystemName, self.fullPath]; +} + +@end + +@implementation CDVFilesystemURLProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest*)request +{ + NSURL* url = [request URL]; + return [[url scheme] isEqualToString:kCDVFilesystemURLPrefix]; +} + ++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request +{ + return request; +} + ++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB +{ + return [[[requestA URL] resourceSpecifier] isEqualToString:[[requestB URL] resourceSpecifier]]; +} + +- (void)startLoading +{ + CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithURL:[[self request] URL]]; + NSObject<CDVFileSystem> *fs = [filePlugin filesystemForURL:url]; + [fs readFileAtURL:url start:0 end:-1 callback:^void(NSData *data, NSString *mimetype, CDVFileError error) { + NSMutableDictionary* responseHeaders = [[NSMutableDictionary alloc] init]; + responseHeaders[@"Cache-Control"] = @"no-cache"; + + if (!error) { + responseHeaders[@"Content-Type"] = mimetype; + NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:200 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders]; + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [[self client] URLProtocol:self didLoadData:data]; + [[self client] URLProtocolDidFinishLoading:self]; + } else { + NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:404 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders]; + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [[self client] URLProtocolDidFinishLoading:self]; + } + }]; +} + +- (void)stopLoading +{} + +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection + willCacheResponse:(NSCachedURLResponse*)cachedResponse { + return nil; +} + +@end + + +@implementation CDVFile + +@synthesize rootDocsPath, appDocsPath, appLibraryPath, appTempPath, userHasAllowed, fileSystems=fileSystems_; + +- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs { + __weak CDVFile* weakSelf = self; + SEL sel = NSSelectorFromString(@"urlTransformer"); + // for backwards compatibility - we check if this property is there + // we create a wrapper block because the urlTransformer property + // on the commandDelegate might be set dynamically at a future time + // (and not dependent on plugin loading order) + if ([self.commandDelegate respondsToSelector:sel]) { + fs.urlTransformer = ^NSURL*(NSURL* urlToTransform) { + // grab the block from the commandDelegate + NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(weakSelf.commandDelegate, sel); + // if block is not null, we call it + if (urlTransformer) { + return urlTransformer(urlToTransform); + } else { // else we return the same url + return urlToTransform; + } + }; + } + [fileSystems_ addObject:fs]; +} + +- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName +{ + if (self.fileSystems != nil) { + for (NSObject<CDVFileSystem> *fs in self.fileSystems) { + if ([fs.name isEqualToString:fsName]) { + return fs; + } + } + } + return nil; + +} + +- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL { + if (localURL.fileSystemName == nil) return nil; + @try { + return [self fileSystemByName:localURL.fileSystemName]; + } + @catch (NSException *e) { + return nil; + } +} + +- (NSArray *)getExtraFileSystemsPreference:(UIViewController *)vc +{ + NSString *filesystemsStr = nil; + if([self.viewController isKindOfClass:[CDVViewController class]]) { + CDVViewController *vc = (CDVViewController *)self.viewController; + NSDictionary *settings = [vc settings]; + filesystemsStr = [settings[@"iosextrafilesystems"] lowercaseString]; + } + if (!filesystemsStr) { + filesystemsStr = @"library,library-nosync,documents,documents-nosync,cache,bundle,root"; + } + return [filesystemsStr componentsSeparatedByString:@","]; +} + +- (void)makeNonSyncable:(NSString*)path { + [[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:nil]; + NSURL* url = [NSURL fileURLWithPath:path]; + [url setResourceValue: [NSNumber numberWithBool: YES] + forKey: NSURLIsExcludedFromBackupKey error:nil]; + +} + +- (void)registerExtraFileSystems:(NSArray *)filesystems fromAvailableSet:(NSDictionary *)availableFileSystems +{ + NSMutableSet *installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7]; + + /* Build non-syncable directories as necessary */ + for (NSString *nonSyncFS in @[@"library-nosync", @"documents-nosync"]) { + if ([filesystems containsObject:nonSyncFS]) { + [self makeNonSyncable:availableFileSystems[nonSyncFS]]; + } + } + + /* Register filesystems in order */ + for (NSString *fsName in filesystems) { + if (![installedFilesystems containsObject:fsName]) { + NSString *fsRoot = availableFileSystems[fsName]; + if (fsRoot) { + [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName root:fsRoot]]; + [installedFilesystems addObject:fsName]; + } else { + NSLog(@"Unrecognized extra filesystem identifier: %@", fsName); + } + } + } +} + +- (NSDictionary *)getAvailableFileSystems +{ + NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + return @{ + @"library": libPath, + @"library-nosync": [libPath stringByAppendingPathComponent:@"NoCloud"], + @"documents": docPath, + @"documents-nosync": [docPath stringByAppendingPathComponent:@"NoCloud"], + @"cache": [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0], + @"bundle": [[NSBundle mainBundle] bundlePath], + @"root": @"/" + }; +} + +- (void)pluginInitialize +{ + filePlugin = self; + [NSURLProtocol registerClass:[CDVFilesystemURLProtocol class]]; + + fileSystems_ = [[NSMutableArray alloc] initWithCapacity:3]; + + // Get the Library directory path + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + self.appLibraryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"files"]; + + // Get the Temporary directory path + self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() + + // Get the Documents directory path + paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + self.rootDocsPath = [paths objectAtIndex:0]; + self.appDocsPath = [self.rootDocsPath stringByAppendingPathComponent:@"files"]; + + + NSString *location = nil; + if([self.viewController isKindOfClass:[CDVViewController class]]) { + CDVViewController *vc = (CDVViewController *)self.viewController; + NSMutableDictionary *settings = vc.settings; + location = [[settings objectForKey:@"iospersistentfilelocation"] lowercaseString]; + } + if (location == nil) { + // Compatibilty by default (if the config preference is not set, or + // if we're not embedded in a CDVViewController somehow.) + location = @"compatibility"; + } + + NSError *error; + if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appTempPath + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"temporary" root:self.appTempPath]]; + } else { + NSLog(@"Unable to create temporary directory: %@", error); + } + if ([location isEqualToString:@"library"]) { + if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appLibraryPath + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.appLibraryPath]]; + } else { + NSLog(@"Unable to create library directory: %@", error); + } + } else if ([location isEqualToString:@"compatibility"]) { + /* + * Fall-back to compatibility mode -- this is the logic implemented in + * earlier versions of this plugin, and should be maintained here so + * that apps which were originally deployed with older versions of the + * plugin can continue to provide access to files stored under those + * versions. + */ + [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.rootDocsPath]]; + } else { + NSAssert(false, + @"File plugin configuration error: Please set iosPersistentFileLocation in config.xml to one of \"library\" (for new applications) or \"compatibility\" (for compatibility with previous versions)"); + } + [self registerFilesystem:[[CDVAssetLibraryFilesystem alloc] initWithName:@"assets-library"]]; + + [self registerExtraFileSystems:[self getExtraFileSystemsPreference:self.viewController] + fromAvailableSet:[self getAvailableFileSystems]]; + +} + +- (CDVFilesystemURL *)fileSystemURLforArg:(NSString *)urlArg +{ + CDVFilesystemURL* ret = nil; + if ([urlArg hasPrefix:@"file://"]) { + /* This looks like a file url. Get the path, and see if any handlers recognize it. */ + NSURL *fileURL = [NSURL URLWithString:urlArg]; + NSURL *resolvedFileURL = [fileURL URLByResolvingSymlinksInPath]; + NSString *path = [resolvedFileURL path]; + ret = [self fileSystemURLforLocalPath:path]; + } else { + ret = [CDVFilesystemURL fileSystemURLWithString:urlArg]; + } + return ret; +} + +- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath +{ + CDVFilesystemURL *localURL = nil; + NSUInteger shortestFullPath = 0; + + // Try all installed filesystems, in order. Return the most match url. + for (id object in self.fileSystems) { + if ([object respondsToSelector:@selector(URLforFilesystemPath:)]) { + CDVFilesystemURL *url = [object URLforFilesystemPath:localPath]; + if (url){ + // A shorter fullPath would imply that the filesystem is a better match for the local path + if (!localURL || ([[url fullPath] length] < shortestFullPath)) { + localURL = url; + shortestFullPath = [[url fullPath] length]; + } + } + } + } + return localURL; +} + +- (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; +} + +/* 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 +{ + // arguments + NSString* strType = [command argumentAtIndex:0]; + unsigned long long size = [[command argumentAtIndex:1] longLongValue]; + + int type = [strType intValue]; + CDVPluginResult* result = nil; + + if (type > self.fileSystems.count) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR]; + NSLog(@"No filesystem of type requested"); + } else { + NSString* fullPath = @"/"; + // check for avail space for size request + NSNumber* pNumAvail = [self checkFreeDiskSpace:self.rootDocsPath]; + // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]); + if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; + } else { + NSObject<CDVFileSystem> *rootFs = [self.fileSystems objectAtIndex:type]; + if (rootFs == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR]; + NSLog(@"No filesystem of type requested"); + } else { + NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; + [fileSystem setObject:rootFs.name forKey:@"name"]; + NSDictionary* dirEntry = [self makeEntryForPath:fullPath fileSystemName:rootFs.name isDirectory:YES]; + [fileSystem setObject:dirEntry forKey:@"root"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } + } + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + + +- (void)requestAllFileSystems:(CDVInvokedUrlCommand*)command +{ + NSMutableArray* ret = [[NSMutableArray alloc] init]; + for (NSObject<CDVFileSystem>* root in fileSystems_) { + [ret addObject:[self makeEntryForPath:@"/" fileSystemName:root.name isDirectory:YES]]; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:ret]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)requestAllPaths:(CDVInvokedUrlCommand*)command +{ + NSString* libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0]; + NSString* libPathSync = [libPath stringByAppendingPathComponent:@"Cloud"]; + NSString* libPathNoSync = [libPath stringByAppendingPathComponent:@"NoCloud"]; + NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + NSString* storagePath = [libPath stringByDeletingLastPathComponent]; + NSString* cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + + // Create the directories if necessary. + [[NSFileManager defaultManager] createDirectoryAtPath:libPathSync withIntermediateDirectories:YES attributes:nil error:nil]; + [[NSFileManager defaultManager] createDirectoryAtPath:libPathNoSync withIntermediateDirectories:YES attributes:nil error:nil]; + // Mark NoSync as non-iCloud. + [[NSURL fileURLWithPath:libPathNoSync] setResourceValue: [NSNumber numberWithBool: YES] + forKey: NSURLIsExcludedFromBackupKey error:nil]; + + NSDictionary* ret = @{ + @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]] absoluteString], + @"applicationStorageDirectory": [[NSURL fileURLWithPath:storagePath] absoluteString], + @"dataDirectory": [[NSURL fileURLWithPath:libPathNoSync] absoluteString], + @"syncedDataDirectory": [[NSURL fileURLWithPath:libPathSync] absoluteString], + @"documentsDirectory": [[NSURL fileURLWithPath:docPath] absoluteString], + @"cacheDirectory": [[NSURL fileURLWithPath:cachePath] absoluteString], + @"tempDirectory": [[NSURL fileURLWithPath:NSTemporaryDirectory()] absoluteString] + }; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* Creates and returns a dictionary representing an Entry Object + * + * IN: + * NSString* fullPath of the entry + * int fsType - 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 + * NSString* filesystemName - FileSystem name -- actual filesystem will be created on the JS side if necessary, to avoid + * creating circular reference (FileSystem contains DirectoryEntry which contains FileSystem.....!!) + */ +- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir +{ + NSObject<CDVFileSystem> *fs = [self fileSystemByName:fsName]; + return [fs makeEntryForPath:fullPath isDirectory:isDir]; +} + +- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)localURL +{ + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURL]; + return [fs makeEntryForLocalURL:localURL]; +} + +- (NSDictionary *)makeEntryForURL:(NSURL *)URL +{ + CDVFilesystemURL* fsURL = [self fileSystemURLforArg:[URL absoluteString]]; + return [self makeEntryForLocalURL:fsURL]; +} + +/* + * Given a URI determine the File System information associated with it and return an appropriate W3C entry object + * IN + * NSString* localURI: Should be an escaped local filesystem 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* localURIstr = [command argumentAtIndex:0]; + CDVPluginResult* result; + + localURIstr = [self encodePath:localURIstr]; //encode path before resolving + CDVFilesystemURL* inputURI = [self fileSystemURLforArg:localURIstr]; + + if (inputURI == nil || inputURI.fileSystemName == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; + } else { + NSObject<CDVFileSystem> *fs = [self filesystemForURL:inputURI]; + if (fs == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; + } else { + result = [fs entryForLocalURI:inputURI]; + } + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +//encode path with percent escapes +-(NSString *)encodePath:(NSString *)path +{ + NSString *decodedPath = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //decode incase it's already encoded to avoid encoding twice + return [decodedPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +} + + +/* Part of DirectoryEntry interface, creates or returns the specified directory + * IN: + * NSString* localURI - local filesystem URI 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 = [command argumentAtIndex: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* baseURI - local filesytem URI for the base directory to search + * NSString* requestedPath - file to be created/returned; may be absolute path or relative path + * NSDictionary* options - 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 +{ + NSString* baseURIstr = [command argumentAtIndex:0]; + CDVFilesystemURL* baseURI = [self fileSystemURLforArg:baseURIstr]; + NSString* requestedPath = [command argumentAtIndex:1]; + NSDictionary* options = [command argumentAtIndex:2 withDefault:nil]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:baseURI]; + CDVPluginResult* result = [fs getFileForURL:baseURI requestedPath:requestedPath options:options]; + + + [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* localURI + * NSMutableDictionary* options + * empty + */ +- (void)getParent:(CDVInvokedUrlCommand*)command +{ + // arguments are URL encoded + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + CDVPluginResult* result = [fs getParentForURL:localURI]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * set MetaData of entry + * Currently we only support "com.apple.MobileBackup" (boolean) + */ +- (void)setMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSDictionary* options = [command argumentAtIndex:1 withDefault:nil]; + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + CDVPluginResult* result = [fs setMetadataForURL:localURI withObject:options]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* removes the directory or file entry + * IN: + * NSArray* arguments + * 0 - NSString* localURI + * + * 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 + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + CDVPluginResult* result = nil; + + if ([localURI.fullPath isEqualToString:@""]) { + // error if try to remove top level (documents or tmp) dir + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + } else { + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + result = [fs removeFileAtURL:localURI]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* recursively removes the directory + * IN: + * NSArray* arguments + * 0 - NSString* localURI + * + * 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 + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + CDVPluginResult* result = nil; + + if ([localURI.fullPath isEqualToString:@""]) { + // error if try to remove top level (documents or tmp) dir + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + } else { + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + result = [fs recursiveRemoveFileAtURL:localURI]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)copyTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:YES]; +} + +- (void)moveTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:NO]; +} + +/* Copy/move a file or directory to a new location + * IN: + * NSArray* arguments + * 0 - NSString* URL of entry to copy + * 1 - NSString* URL of the directory into which to copy/move the entry + * 2 - Optionally, the new name of the entry, defaults to the current name + * BOOL - bCopy YES if copy, NO if move + */ +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy +{ + NSArray* arguments = command.arguments; + + // arguments + NSString* srcURLstr = [command argumentAtIndex:0]; + NSString* destURLstr = [command argumentAtIndex:1]; + + CDVPluginResult *result; + + if (!srcURLstr || !destURLstr) { + // either no source or no destination provided + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + CDVFilesystemURL* srcURL = [self fileSystemURLforArg:srcURLstr]; + CDVFilesystemURL* destURL = [self fileSystemURLforArg:destURLstr]; + + NSObject<CDVFileSystem> *srcFs = [self filesystemForURL:srcURL]; + NSObject<CDVFileSystem> *destFs = [self filesystemForURL:destURL]; + + // optional argument; use last component from srcFullPath if new name not provided + NSString* newName = ([arguments count] > 2) ? [command argumentAtIndex:2] : [srcURL.url lastPathComponent]; + if ([newName rangeOfString:@":"].location != NSNotFound) { + // invalid chars in new name + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + [destFs copyFileToURL:destURL withName:newName fromFileSystem:srcFs atURL:srcURL copy:bCopy callback:^(CDVPluginResult* result) { + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + +} + +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + [fs getFileMetadataForURL:localURI callback:^(CDVPluginResult* result) { + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)readEntries:(CDVInvokedUrlCommand*)command +{ + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + CDVPluginResult *result = [fs readEntriesAtURL:localURI]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* read and return file data + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* encoding + * 2 - NSString* start + * 3 - NSString* end + */ +- (void)readAsText:(CDVInvokedUrlCommand*)command +{ + // arguments + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSString* encoding = [command argumentAtIndex:1]; + NSInteger start = [[command argumentAtIndex:2] integerValue]; + NSInteger end = [[command argumentAtIndex:3] integerValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + if (fs == nil) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + // TODO: implement + if ([@"UTF-8" caseInsensitiveCompare : encoding] != NSOrderedSame) { + NSLog(@"Only UTF-8 encodings are currently supported by readAsText"); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + [self.commandDelegate runInBackground:^ { + [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO]; + // Check that UTF8 conversion did not fail. + if (str != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str]; + result.associatedObject = data; + } else { + errorCode = ENCODING_ERR; + } + } + if (result == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [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 + * 1 - NSString* start + * 2 - NSString* end + * + * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined. + */ + +- (void)readAsDataURL:(CDVInvokedUrlCommand*)command +{ + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + [self.commandDelegate runInBackground:^ { + [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + SEL selector = NSSelectorFromString(@"cdv_base64EncodedString"); + if (![data respondsToSelector:selector]) { + selector = NSSelectorFromString(@"base64EncodedString"); + } + id (*func)(id, SEL) = (void *)[data methodForSelector:selector]; + NSString* b64Str = func(data, selector); + NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, b64Str]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + }]; +} + +/* Read content of text file and return as an arraybuffer + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end + */ + +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command +{ + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + [self.commandDelegate runInBackground:^ { + [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + }]; +} + +- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command +{ + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + [self.commandDelegate runInBackground:^ { + [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload]; + result.associatedObject = data; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + }]; +} + + +- (void)truncate:(CDVInvokedUrlCommand*)command +{ + // arguments + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + unsigned long long pos = (unsigned long long)[[command argumentAtIndex:1] longLongValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + CDVPluginResult *result = [fs truncateFileAtURL:localURI atPosition:pos]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* write + * IN: + * NSArray* arguments + * 0 - NSString* localURI of file to write to + * 1 - NSString* or NSData* data to write + * 2 - NSNumber* position to begin writing + */ +- (void)write:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^ { + NSString* callbackId = command.callbackId; + + // arguments + CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]]; + id argData = [command argumentAtIndex:1]; + unsigned long long pos = (unsigned long long)[[command argumentAtIndex:2] longLongValue]; + + NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI]; + + + [fs truncateFileAtURL:localURI atPosition:pos]; + CDVPluginResult *result; + if ([argData isKindOfClass:[NSString class]]) { + NSData *encData = [argData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + result = [fs writeToFileAtURL:localURI withData:encData append:YES]; + } else if ([argData isKindOfClass:[NSData class]]) { + result = [fs writeToFileAtURL:localURI withData:argData append:YES]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"]; + } + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + }]; +} + +#pragma mark Methods for converting between URLs and paths + +- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURL +{ + for (NSObject<CDVFileSystem> *fs in self.fileSystems) { + if ([fs.name isEqualToString:localURL.fileSystemName]) { + if ([fs respondsToSelector:@selector(filesystemPathForURL:)]) { + return [fs filesystemPathForURL:localURL]; + } + } + } + return nil; +} + +#pragma mark Undocumented Filesystem API + +- (void)testFileExists:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command argumentAtIndex: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 argumentAtIndex: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]; +} + +#pragma mark Compatibility with older File API + +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath +{ + return [CDVLocalFilesystem getMimeTypeFromPath:fullPath]; +} + +- (NSDictionary *)getDirectoryEntry:(NSString *)localPath isDirectory:(BOOL)bDirRequest +{ + CDVFilesystemURL *localURL = [self fileSystemURLforLocalPath:localPath]; + return [self makeEntryForPath:localURL.fullPath fileSystemName:localURL.fileSystemName isDirectory:bDirRequest]; +} + +#pragma mark Internal methods for testing +// Internal methods for testing: Get the on-disk location of a local filesystem url. +// [Currently used for testing file-transfer] + +- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command +{ + CDVFilesystemURL* localURL = [self fileSystemURLforArg:command.arguments[0]]; + + NSString* fsPath = [self filesystemPathForURL:localURL]; + CDVPluginResult* result; + if (fsPath) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:fsPath]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot resolve URL to a file"]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +@end diff --git a/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h b/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h new file mode 100644 index 00000000..a0186c85 --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h @@ -0,0 +1,32 @@ +/* + 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" + +@interface CDVLocalFilesystem : NSObject<CDVFileSystem> { + NSString *_name; + NSString *_fsRoot; +} + +- (id) initWithName:(NSString *)name root:(NSString *)fsRoot; ++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath; + +@property (nonatomic,strong) NSString *fsRoot; + +@end diff --git a/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m b/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m new file mode 100644 index 00000000..72bc421e --- /dev/null +++ b/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m @@ -0,0 +1,734 @@ +/* + 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) { + _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]]) { + 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 |
