arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-file/src/osx/CDVFile.m
plugins/cordova-plugin-file/src/osx/CDVFile.mRaw
   1/*
   2 Licensed to the Apache Software Foundation (ASF) under one
   3 or more contributor license agreements.  See the NOTICE file
   4 distributed with this work for additional information
   5 regarding copyright ownership.  The ASF licenses this file
   6 to you under the Apache License, Version 2.0 (the
   7 "License"); you may not use this file except in compliance
   8 with the License.  You may obtain a copy of the License at
   9
  10 http://www.apache.org/licenses/LICENSE-2.0
  11
  12 Unless required by applicable law or agreed to in writing,
  13 software distributed under the License is distributed on an
  14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15 KIND, either express or implied.  See the License for the
  16 specific language governing permissions and limitations
  17 under the License.
  18 */
  19
  20#import "CDVFile.h"
  21#import "CDVLocalFilesystem.h"
  22#import <objc/message.h>
  23
  24static NSString* toBase64(NSData* data) {
  25    SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
  26    SEL s2 = NSSelectorFromString(@"base64EncodedString");
  27    SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
  28
  29    if ([data respondsToSelector:s1]) {
  30        NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
  31        return func(data, s1);
  32    } else if ([data respondsToSelector:s2]) {
  33        NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
  34        return func(data, s2);
  35    } else if ([data respondsToSelector:s3]) {
  36        NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
  37        return func(data, s3, 0);
  38    } else {
  39        return nil;
  40    }
  41}
  42
  43CDVFile *filePlugin = nil;
  44
  45extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
  46
  47#ifndef __IPHONE_5_1
  48    NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey";
  49#endif
  50
  51NSString* const kCDVFilesystemURLPrefix = @"cdvfile";
  52
  53@implementation CDVFilesystemURL
  54@synthesize url=_url;
  55@synthesize fileSystemName=_fileSystemName;
  56@synthesize fullPath=_fullPath;
  57
  58- (id) initWithString:(NSString *)strURL
  59{
  60    if ( self = [super init] ) {
  61        NSURL *decodedURL = [NSURL URLWithString:strURL];
  62        return [self initWithURL:decodedURL];
  63    }
  64    return nil;
  65}
  66
  67-(id) initWithURL:(NSURL *)URL
  68{
  69    if ( self = [super init] ) {
  70        _url = URL;
  71        _fileSystemName = [self filesystemNameForLocalURI:URL];
  72        _fullPath = [self fullPathForLocalURI:URL];
  73    }
  74    return self;
  75}
  76
  77/*
  78 * IN
  79 *  NSString localURI
  80 * OUT
  81 *  NSString FileSystem Name for this URI, or nil if it is not recognized.
  82 */
  83- (NSString *)filesystemNameForLocalURI:(NSURL *)uri
  84{
  85    if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
  86        NSArray *pathComponents = [uri pathComponents];
  87        if (pathComponents != nil && pathComponents.count > 1) {
  88            return [pathComponents objectAtIndex:1];
  89        }
  90    }
  91    return nil;
  92}
  93
  94/*
  95 * IN
  96 *  NSString localURI
  97 * OUT
  98 *  NSString fullPath component suitable for an Entry object.
  99 * The incoming URI should be properly escaped. The returned fullPath is unescaped.
 100 */
 101- (NSString *)fullPathForLocalURI:(NSURL *)uri
 102{
 103    if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
 104        NSString *path = [uri path];
 105        if ([uri query]) {
 106            path = [NSString stringWithFormat:@"%@?%@", path, [uri query]];
 107        }
 108        NSRange slashRange = [path rangeOfString:@"/" options:0 range:NSMakeRange(1, path.length-1)];
 109        if (slashRange.location == NSNotFound) {
 110            return @"";
 111        }
 112        return [path substringFromIndex:slashRange.location];
 113    }
 114    return nil;
 115}
 116
 117+ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL
 118{
 119    return [[CDVFilesystemURL alloc] initWithString:strURL];
 120}
 121
 122+ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL
 123{
 124    return [[CDVFilesystemURL alloc] initWithURL:URL];
 125}
 126
 127- (NSString *)absoluteURL
 128{
 129    return [NSString stringWithFormat:@"cdvfile://localhost/%@%@", self.fileSystemName, self.fullPath];
 130}
 131
 132@end
 133
 134@implementation CDVFilesystemURLProtocol
 135
 136+ (BOOL)canInitWithRequest:(NSURLRequest*)request
 137{
 138    NSURL* url = [request URL];
 139    return [[url scheme] isEqualToString:kCDVFilesystemURLPrefix];
 140}
 141
 142+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
 143{
 144    return request;
 145}
 146
 147+ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB
 148{
 149    return [[[requestA URL] resourceSpecifier] isEqualToString:[[requestB URL] resourceSpecifier]];
 150}
 151
 152- (void)startLoading
 153{
 154    CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithURL:[[self request] URL]];
 155    NSObject<CDVFileSystem> *fs = [filePlugin filesystemForURL:url];
 156    [fs readFileAtURL:url start:0 end:-1 callback:^void(NSData *data, NSString *mimetype, CDVFileError error) {
 157        NSMutableDictionary* responseHeaders = [[NSMutableDictionary alloc] init];
 158        responseHeaders[@"Cache-Control"] = @"no-cache";
 159
 160        if (!error) {
 161            responseHeaders[@"Content-Type"] = mimetype;
 162            NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:200 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
 163            [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
 164            [[self client] URLProtocol:self didLoadData:data];
 165            [[self client] URLProtocolDidFinishLoading:self];
 166        } else {
 167            NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:404 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
 168            [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
 169            [[self client] URLProtocolDidFinishLoading:self];
 170        }
 171    }];
 172}
 173
 174- (void)stopLoading
 175{}
 176
 177- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
 178                  willCacheResponse:(NSCachedURLResponse*)cachedResponse {
 179    return nil;
 180}
 181
 182@end
 183
 184
 185@implementation CDVFile
 186
 187@synthesize appDocsPath, appDataPath, appSupportPath, appTempPath, appCachePath, userHasAllowed, fileSystems=fileSystems_;
 188
 189- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs {
 190    __weak CDVFile* weakSelf = self;
 191    SEL sel = NSSelectorFromString(@"urlTransformer");
 192    // for backwards compatibility - we check if this property is there
 193    // we create a wrapper block because the urlTransformer property
 194    // on the commandDelegate might be set dynamically at a future time
 195    // (and not dependent on plugin loading order)
 196    if ([self.commandDelegate respondsToSelector:sel]) {
 197        fs.urlTransformer = ^NSURL*(NSURL* urlToTransform) {
 198            // grab the block from the commandDelegate
 199            NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(weakSelf.commandDelegate, sel);
 200            // if block is not null, we call it
 201            if (urlTransformer) {
 202                return urlTransformer(urlToTransform);
 203            } else { // else we return the same url
 204                return urlToTransform;
 205            }
 206        };
 207    }
 208    [fileSystems_ addObject:fs];
 209}
 210
 211- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName
 212{
 213    if (self.fileSystems != nil) {
 214        for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
 215            if ([fs.name isEqualToString:fsName]) {
 216                return fs;
 217            }
 218        }
 219    }
 220    return nil;
 221
 222}
 223
 224- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL {
 225    if (localURL.fileSystemName == nil) return nil;
 226    @try {
 227        return [self fileSystemByName:localURL.fileSystemName];
 228    }
 229    @catch (NSException *e) {
 230        return nil;
 231    }
 232}
 233
 234- (NSArray *)getExtraFileSystemsPreference:(NSWindowController *)vc
 235{
 236    NSString *filesystemsStr = nil;
 237    if([self.viewController isKindOfClass:[CDVViewController class]]) {
 238        CDVViewController *vc = self.viewController;
 239        NSDictionary *settings = [vc settings];
 240        filesystemsStr = [settings[CDV_PREF_EXTRA_FILESYSTEM] lowercaseString];
 241    }
 242    if (!filesystemsStr) {
 243        filesystemsStr = CDV_FILESYSTEMS_DEFAULT;
 244    }
 245    return [filesystemsStr componentsSeparatedByString:@","];
 246}
 247
 248- (void)makeNonSyncable:(NSString*)path {
 249    [[NSFileManager defaultManager] createDirectoryAtPath:path
 250              withIntermediateDirectories:YES
 251                               attributes:nil
 252                                    error:nil];
 253    NSURL* url = [NSURL fileURLWithPath:path];
 254    [url setResourceValue: [NSNumber numberWithBool: YES]
 255                   forKey: NSURLIsExcludedFromBackupKey error:nil];
 256
 257}
 258
 259- (void)registerExtraFileSystems:(NSArray *)filesystems fromAvailableSet:(NSDictionary *)availableFileSystems
 260{
 261    NSMutableSet *installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7];
 262
 263    /* Register filesystems in order */
 264    for (NSString *fsName in filesystems) {
 265        if (![installedFilesystems containsObject:fsName]) {
 266            NSString *fsRoot = availableFileSystems[fsName];
 267            if (fsRoot) {
 268                [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName root:fsRoot]];
 269                [installedFilesystems addObject:fsName];
 270            } else {
 271                NSLog(@"Unrecognized extra filesystem identifier: %@", fsName);
 272            }
 273        }
 274    }
 275}
 276
 277- (NSDictionary *)getAvailableFileSystems
 278{
 279    return @{
 280        @"documents": self.appDocsPath,
 281        @"cache": self.appCachePath,
 282        @"bundle": [[NSBundle mainBundle] bundlePath],
 283        @"root": @"/"
 284    };
 285}
 286
 287- (void)pluginInitialize
 288{   filePlugin = self;
 289    [NSURLProtocol registerClass:[CDVFilesystemURLProtocol class]];
 290
 291    fileSystems_ = [[NSMutableArray alloc] initWithCapacity:3];
 292
 293    // Get the Temporary directory path
 294    self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath];   // remove trailing slash from NSTemporaryDirectory()
 295    [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"temporary" root:self.appTempPath]];
 296
 297    // ~/Library/Application Support/<bundle-id>
 298    self.appSupportPath = [self getSupportDirectoryFor:NSApplicationSupportDirectory pathComponents:nil];
 299
 300    // ~/Library/Application Support/<bundle-id>/files
 301    self.appDataPath = [self getSupportDirectoryFor:NSApplicationSupportDirectory pathComponents:@[@"files"]];
 302    [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.appDataPath]];
 303
 304    // ~/Documents/
 305    self.appDocsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
 306
 307    // ~/Library/Caches/<bundle-id>/files
 308    self.appCachePath = [self getSupportDirectoryFor:NSCachesDirectory pathComponents:nil];
 309
 310    [self registerExtraFileSystems:[self getExtraFileSystemsPreference:self.viewController]
 311                  fromAvailableSet:[self getAvailableFileSystems]];
 312}
 313
 314- (NSString*) getSupportDirectoryFor: (NSSearchPathDirectory) directory pathComponents: (NSArray*) components  {
 315    NSError* error;
 316    NSFileManager* fm = [NSFileManager defaultManager];
 317    NSURL* supportDir = [fm URLForDirectory:directory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
 318    if (supportDir == nil) {
 319        NSLog(@"unable to get support directory: %@", error);
 320        return nil;
 321    }
 322
 323    NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
 324    NSURL *dirPath = [supportDir URLByAppendingPathComponent:bundleID];
 325    for (NSString* pathComponent in components) {
 326        dirPath = [dirPath URLByAppendingPathComponent:pathComponent];
 327    }
 328
 329    if (![fm fileExistsAtPath:dirPath.path]) {
 330        if (![fm createDirectoryAtURL:dirPath withIntermediateDirectories:YES attributes:nil error:&error]) {
 331            NSLog(@"unable to create support directory: %@", error);
 332            return nil;
 333        }
 334    }
 335    return dirPath.path;
 336}
 337
 338
 339- (CDVFilesystemURL *)fileSystemURLforArg:(NSString *)urlArg
 340{
 341    CDVFilesystemURL* ret = nil;
 342    if ([urlArg hasPrefix:@"file://"]) {
 343        /* This looks like a file url. Get the path, and see if any handlers recognize it. */
 344        NSURL *fileURL = [NSURL URLWithString:urlArg];
 345        NSURL *resolvedFileURL = [fileURL URLByResolvingSymlinksInPath];
 346        NSString *path = [resolvedFileURL path];
 347        ret = [self fileSystemURLforLocalPath:path];
 348    } else {
 349        ret = [CDVFilesystemURL fileSystemURLWithString:urlArg];
 350    }
 351    return ret;
 352}
 353
 354- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath
 355{
 356    CDVFilesystemURL *localURL = nil;
 357    NSUInteger shortestFullPath = 0;
 358
 359    // Try all installed filesystems, in order. Return the most match url.
 360    for (id object in self.fileSystems) {
 361        if ([object respondsToSelector:@selector(URLforFilesystemPath:)]) {
 362            CDVFilesystemURL *url = [object URLforFilesystemPath:localPath];
 363            if (url){
 364                // A shorter fullPath would imply that the filesystem is a better match for the local path
 365                if (!localURL || ([[url fullPath] length] < shortestFullPath)) {
 366                    localURL = url;
 367                    shortestFullPath = [[url fullPath] length];
 368                }
 369            }
 370        }
 371    }
 372    return localURL;
 373}
 374
 375- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath
 376{
 377    NSFileManager* fMgr = [[NSFileManager alloc] init];
 378
 379    NSError* __autoreleasing pError = nil;
 380
 381    NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError];
 382    NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize];
 383
 384    return pNumAvail;
 385}
 386
 387/* Request the File System info
 388 *
 389 * IN:
 390 * arguments[0] - type (number as string)
 391 *	TEMPORARY = 0, PERSISTENT = 1;
 392 * arguments[1] - size
 393 *
 394 * OUT:
 395 *	Dictionary representing FileSystem object
 396 *		name - the human readable directory name
 397 *		root = DirectoryEntry object
 398 *			bool isDirectory
 399 *			bool isFile
 400 *			string name
 401 *			string fullPath
 402 *			fileSystem = FileSystem object - !! ignored because creates circular reference !!
 403 */
 404
 405- (void)requestFileSystem:(CDVInvokedUrlCommand*)command
 406{
 407    // arguments
 408    NSString* strType = [command argumentAtIndex:0];
 409    unsigned long long size = [[command argumentAtIndex:1] longLongValue];
 410
 411    int type = [strType intValue];
 412    CDVPluginResult* result = nil;
 413
 414    if (type > self.fileSystems.count) {
 415        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
 416        NSLog(@"No filesystem of type requested");
 417    } else {
 418        NSString* fullPath = @"/";
 419        // check for avail space for size request
 420        NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appSupportPath];
 421        // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]);
 422        if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) {
 423            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR];
 424        } else {
 425            NSObject<CDVFileSystem> *rootFs = [self.fileSystems objectAtIndex:type];
 426            if (rootFs == nil) {
 427                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
 428                NSLog(@"No filesystem of type requested");
 429            } else {
 430                NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2];
 431                [fileSystem setObject:rootFs.name forKey:@"name"];
 432                NSDictionary* dirEntry = [self makeEntryForPath:fullPath fileSystemName:rootFs.name isDirectory:YES];
 433                [fileSystem setObject:dirEntry forKey:@"root"];
 434                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
 435            }
 436        }
 437    }
 438    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 439}
 440
 441
 442- (void)requestAllFileSystems:(CDVInvokedUrlCommand*)command
 443{
 444    NSMutableArray* ret = [[NSMutableArray alloc] init];
 445    for (NSObject<CDVFileSystem>* root in fileSystems_) {
 446        [ret addObject:[self makeEntryForPath:@"/" fileSystemName:root.name isDirectory:YES]];
 447    }
 448    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:ret];
 449    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 450}
 451
 452- (void)requestAllPaths:(CDVInvokedUrlCommand*)command
 453{
 454    NSDictionary* ret = @{
 455        @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] resourcePath]] absoluteString],
 456        @"applicationStorageDirectory": [[NSURL fileURLWithPath:self.appSupportPath] absoluteString],
 457        @"dataDirectory": [[NSURL fileURLWithPath:self.appDataPath] absoluteString],
 458        @"documentsDirectory": [[NSURL fileURLWithPath:self.appDocsPath] absoluteString],
 459        @"cacheDirectory": [[NSURL fileURLWithPath:self.appCachePath] absoluteString],
 460        @"tempDirectory":[[NSURL fileURLWithPath:self.appTempPath] absoluteString],
 461        @"rootDirectory": @"file:///",
 462    };
 463
 464    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret];
 465    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 466}
 467
 468/* Creates and returns a dictionary representing an Entry Object
 469 *
 470 * IN:
 471 * NSString* fullPath of the entry
 472 * int fsType - FileSystem type
 473 * BOOL isDirectory - YES if this is a directory, NO if is a file
 474 * OUT:
 475 * NSDictionary* Entry object
 476 *		bool as NSNumber isDirectory
 477 *		bool as NSNumber isFile
 478 *		NSString*  name - last part of path
 479 *		NSString* fullPath
 480 *		NSString* filesystemName - FileSystem name -- actual filesystem will be created on the JS side if necessary, to avoid
 481 *         creating circular reference (FileSystem contains DirectoryEntry which contains FileSystem.....!!)
 482 */
 483- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir
 484{
 485    NSObject<CDVFileSystem> *fs = [self fileSystemByName:fsName];
 486    return [fs makeEntryForPath:fullPath isDirectory:isDir];
 487}
 488
 489- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)localURL
 490{
 491    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURL];
 492    return [fs makeEntryForLocalURL:localURL];
 493}
 494
 495- (NSDictionary *)makeEntryForURL:(NSURL *)URL
 496{
 497    CDVFilesystemURL* fsURL = [self fileSystemURLforArg:[URL absoluteString]];
 498    return [self makeEntryForLocalURL:fsURL];
 499}
 500
 501/*
 502 * Given a URI determine the File System information associated with it and return an appropriate W3C entry object
 503 * IN
 504 *	NSString* localURI: Should be an escaped local filesystem URI
 505 * OUT
 506 *	Entry object
 507 *		bool isDirectory
 508 *		bool isFile
 509 *		string name
 510 *		string fullPath
 511 *		fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!!
 512 */
 513- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command
 514{
 515    // arguments
 516    NSString* localURIstr = [command argumentAtIndex:0];
 517    CDVPluginResult* result;
 518
 519    localURIstr = [self encodePath:localURIstr]; //encode path before resolving
 520    CDVFilesystemURL* inputURI = [self fileSystemURLforArg:localURIstr];
 521
 522    if (inputURI == nil || inputURI.fileSystemName == nil) {
 523        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
 524    } else {
 525        NSObject<CDVFileSystem> *fs = [self filesystemForURL:inputURI];
 526        if (fs == nil) {
 527            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
 528        } else {
 529            result = [fs entryForLocalURI:inputURI];
 530        }
 531    }
 532    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 533}
 534
 535//encode path with percent escapes
 536-(NSString *)encodePath:(NSString *)path
 537{
 538    NSString *decodedPath = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //decode incase it's already encoded to avoid encoding twice
 539    return [decodedPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
 540}
 541
 542
 543/* Part of DirectoryEntry interface,  creates or returns the specified directory
 544 * IN:
 545 *	NSString* localURI - local filesystem URI for this directory
 546 *	NSString* path - directory to be created/returned; may be full path or relative path
 547 *	NSDictionary* - Flags object
 548 *		boolean as NSNumber create -
 549 *			if create is true and directory does not exist, create dir and return directory entry
 550 *			if create is true and exclusive is true and directory does exist, return error
 551 *			if create is false and directory does not exist, return error
 552 *			if create is false and the path represents a file, return error
 553 *		boolean as NSNumber exclusive - used in conjunction with create
 554 *			if exclusive is true and create is true - specifies failure if directory already exists
 555 *
 556 *
 557 */
 558- (void)getDirectory:(CDVInvokedUrlCommand*)command
 559{
 560    NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments];
 561    NSMutableDictionary* options = nil;
 562
 563    if ([arguments count] >= 3) {
 564        options = [command argumentAtIndex:2 withDefault:nil];
 565    }
 566    // add getDir to options and call getFile()
 567    if (options != nil) {
 568        options = [NSMutableDictionary dictionaryWithDictionary:options];
 569    } else {
 570        options = [NSMutableDictionary dictionaryWithCapacity:1];
 571    }
 572    [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"];
 573    if ([arguments count] >= 3) {
 574        [arguments replaceObjectAtIndex:2 withObject:options];
 575    } else {
 576        [arguments addObject:options];
 577    }
 578    CDVInvokedUrlCommand* subCommand =
 579        [[CDVInvokedUrlCommand alloc] initWithArguments:arguments
 580                                             callbackId:command.callbackId
 581                                              className:command.className
 582                                             methodName:command.methodName];
 583
 584    [self getFile:subCommand];
 585}
 586
 587/* Part of DirectoryEntry interface,  creates or returns the specified file
 588 * IN:
 589 *	NSString* baseURI - local filesytem URI for the base directory to search
 590 *	NSString* requestedPath - file to be created/returned; may be absolute path or relative path
 591 *	NSDictionary* options - Flags object
 592 *		boolean as NSNumber create -
 593 *			if create is true and file does not exist, create file and return File entry
 594 *			if create is true and exclusive is true and file does exist, return error
 595 *			if create is false and file does not exist, return error
 596 *			if create is false and the path represents a directory, return error
 597 *		boolean as NSNumber exclusive - used in conjunction with create
 598 *			if exclusive is true and create is true - specifies failure if file already exists
 599 */
 600- (void)getFile:(CDVInvokedUrlCommand*)command
 601{
 602    NSString* baseURIstr = [command argumentAtIndex:0];
 603    CDVFilesystemURL* baseURI = [self fileSystemURLforArg:baseURIstr];
 604    NSString* requestedPath = [command argumentAtIndex:1];
 605    NSDictionary* options = [command argumentAtIndex:2 withDefault:nil];
 606
 607    NSObject<CDVFileSystem> *fs = [self filesystemForURL:baseURI];
 608    CDVPluginResult* result = [fs getFileForURL:baseURI requestedPath:requestedPath options:options];
 609
 610
 611    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 612}
 613
 614/*
 615 * Look up the parent Entry containing this Entry.
 616 * If this Entry is the root of its filesystem, its parent is itself.
 617 * IN:
 618 * NSArray* arguments
 619 *	0 - NSString* localURI
 620 * NSMutableDictionary* options
 621 *	empty
 622 */
 623- (void)getParent:(CDVInvokedUrlCommand*)command
 624{
 625    // arguments are URL encoded
 626    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 627
 628    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 629    CDVPluginResult* result = [fs getParentForURL:localURI];
 630
 631    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 632}
 633
 634/*
 635 * set MetaData of entry
 636 * Currently we only support "com.apple.MobileBackup" (boolean)
 637 */
 638- (void)setMetadata:(CDVInvokedUrlCommand*)command
 639{
 640    // arguments
 641    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 642    NSDictionary* options = [command argumentAtIndex:1 withDefault:nil];
 643    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 644    CDVPluginResult* result = [fs setMetadataForURL:localURI withObject:options];
 645
 646    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 647}
 648
 649/* removes the directory or file entry
 650 * IN:
 651 * NSArray* arguments
 652 *	0 - NSString* localURI
 653 *
 654 * returns NO_MODIFICATION_ALLOWED_ERR  if is top level directory or no permission to delete dir
 655 * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file
 656 * returns NOT_FOUND_ERR if file or dir is not found
 657*/
 658- (void)remove:(CDVInvokedUrlCommand*)command
 659{
 660    // arguments
 661    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 662    CDVPluginResult* result = nil;
 663
 664    if ([localURI.fullPath isEqualToString:@""]) {
 665        // error if try to remove top level (documents or tmp) dir
 666        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
 667    } else {
 668        NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 669        result = [fs removeFileAtURL:localURI];
 670    }
 671    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 672}
 673
 674/* recursively removes the directory
 675 * IN:
 676 * NSArray* arguments
 677 *	0 - NSString* localURI
 678 *
 679 * returns NO_MODIFICATION_ALLOWED_ERR  if is top level directory or no permission to delete dir
 680 * returns NOT_FOUND_ERR if file or dir is not found
 681 */
 682- (void)removeRecursively:(CDVInvokedUrlCommand*)command
 683{
 684    // arguments
 685    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 686    CDVPluginResult* result = nil;
 687
 688    if ([localURI.fullPath isEqualToString:@""]) {
 689        // error if try to remove top level (documents or tmp) dir
 690        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
 691    } else {
 692        NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 693        result = [fs recursiveRemoveFileAtURL:localURI];
 694    }
 695    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 696}
 697
 698- (void)copyTo:(CDVInvokedUrlCommand*)command
 699{
 700    [self doCopyMove:command isCopy:YES];
 701}
 702
 703- (void)moveTo:(CDVInvokedUrlCommand*)command
 704{
 705    [self doCopyMove:command isCopy:NO];
 706}
 707
 708/* Copy/move a file or directory to a new location
 709 * IN:
 710 * NSArray* arguments
 711 *	0 - NSString* URL of entry to copy
 712 *  1 - NSString* URL of the directory into which to copy/move the entry
 713 *  2 - Optionally, the new name of the entry, defaults to the current name
 714 *	BOOL - bCopy YES if copy, NO if move
 715 */
 716- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy
 717{
 718    NSArray* arguments = command.arguments;
 719
 720    // arguments
 721    NSString* srcURLstr = [command argumentAtIndex:0];
 722    NSString* destURLstr = [command argumentAtIndex:1];
 723
 724    CDVPluginResult *result;
 725
 726    if (!srcURLstr || !destURLstr) {
 727        // either no source or no destination provided
 728        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
 729        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 730        return;
 731    }
 732
 733    CDVFilesystemURL* srcURL = [self fileSystemURLforArg:srcURLstr];
 734    CDVFilesystemURL* destURL = [self fileSystemURLforArg:destURLstr];
 735
 736    NSObject<CDVFileSystem> *srcFs = [self filesystemForURL:srcURL];
 737    NSObject<CDVFileSystem> *destFs = [self filesystemForURL:destURL];
 738
 739    // optional argument; use last component from srcFullPath if new name not provided
 740    NSString* newName = ([arguments count] > 2) ? [command argumentAtIndex:2] : [srcURL.url lastPathComponent];
 741    if ([newName rangeOfString:@":"].location != NSNotFound) {
 742        // invalid chars in new name
 743        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
 744        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 745        return;
 746    }
 747
 748    [destFs copyFileToURL:destURL withName:newName fromFileSystem:srcFs atURL:srcURL copy:bCopy callback:^(CDVPluginResult* result) {
 749        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 750    }];
 751
 752}
 753
 754- (void)getFileMetadata:(CDVInvokedUrlCommand*)command
 755{
 756    // arguments
 757    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 758    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 759
 760    [fs getFileMetadataForURL:localURI callback:^(CDVPluginResult* result) {
 761        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 762    }];
 763}
 764
 765- (void)readEntries:(CDVInvokedUrlCommand*)command
 766{
 767    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 768    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 769    CDVPluginResult *result = [fs readEntriesAtURL:localURI];
 770
 771    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 772}
 773
 774/* read and return file data
 775 * IN:
 776 * NSArray* arguments
 777 *	0 - NSString* fullPath
 778 *	1 - NSString* encoding
 779 *	2 - NSString* start
 780 *	3 - NSString* end
 781 */
 782- (void)readAsText:(CDVInvokedUrlCommand*)command
 783{
 784    // arguments
 785    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 786    NSString* encoding = [command argumentAtIndex:1];
 787    NSInteger start = [[command argumentAtIndex:2] integerValue];
 788    NSInteger end = [[command argumentAtIndex:3] integerValue];
 789
 790    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 791
 792    if (fs == nil) {
 793        CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
 794        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 795        return;
 796    }
 797
 798    // TODO: implement
 799    if ([@"UTF-8" caseInsensitiveCompare : encoding] != NSOrderedSame) {
 800        NSLog(@"Only UTF-8 encodings are currently supported by readAsText");
 801        CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
 802        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 803        return;
 804    }
 805
 806    [self.commandDelegate runInBackground:^ {
 807        [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
 808            CDVPluginResult* result = nil;
 809            if (data != nil) {
 810                NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO];
 811                // Check that UTF8 conversion did not fail.
 812                if (str != nil) {
 813                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str];
 814                    result.associatedObject = data;
 815                } else {
 816                    errorCode = ENCODING_ERR;
 817                }
 818            }
 819            if (result == nil) {
 820                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
 821            }
 822
 823            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 824        }];
 825    }];
 826}
 827
 828/* Read content of text file and return as base64 encoded data url.
 829 * IN:
 830 * NSArray* arguments
 831 *	0 - NSString* fullPath
 832 *	1 - NSString* start
 833 *	2 - NSString* end
 834 *
 835 * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined.
 836 */
 837
 838- (void)readAsDataURL:(CDVInvokedUrlCommand*)command
 839{
 840    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 841    NSInteger start = [[command argumentAtIndex:1] integerValue];
 842    NSInteger end = [[command argumentAtIndex:2] integerValue];
 843
 844    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 845
 846    [self.commandDelegate runInBackground:^ {
 847        [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
 848            CDVPluginResult* result = nil;
 849            if (data != nil) {
 850                NSString* b64Str = toBase64(data);
 851                NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, b64Str];
 852                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output];
 853            } else {
 854                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
 855            }
 856
 857            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 858        }];
 859    }];
 860}
 861
 862/* Read content of text file and return as an arraybuffer
 863 * IN:
 864 * NSArray* arguments
 865 *	0 - NSString* fullPath
 866 *	1 - NSString* start
 867 *	2 - NSString* end
 868 */
 869
 870- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command
 871{
 872    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 873    NSInteger start = [[command argumentAtIndex:1] integerValue];
 874    NSInteger end = [[command argumentAtIndex:2] integerValue];
 875
 876    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 877
 878    [self.commandDelegate runInBackground:^ {
 879        [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
 880            CDVPluginResult* result = nil;
 881            if (data != nil) {
 882                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data];
 883            } else {
 884                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
 885            }
 886
 887            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 888        }];
 889    }];
 890}
 891
 892- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command
 893{
 894    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 895    NSInteger start = [[command argumentAtIndex:1] integerValue];
 896    NSInteger end = [[command argumentAtIndex:2] integerValue];
 897
 898    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 899
 900    [self.commandDelegate runInBackground:^ {
 901        [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
 902            CDVPluginResult* result = nil;
 903            if (data != nil) {
 904                NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO];
 905                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
 906                result.associatedObject = data;
 907            } else {
 908                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
 909            }
 910
 911            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 912        }];
 913    }];
 914}
 915
 916
 917- (void)truncate:(CDVInvokedUrlCommand*)command
 918{
 919    // arguments
 920    CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 921    unsigned long long pos = (unsigned long long)[[command argumentAtIndex:1] longLongValue];
 922
 923    NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 924    CDVPluginResult *result = [fs truncateFileAtURL:localURI atPosition:pos];
 925
 926    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 927}
 928
 929/* write
 930 * IN:
 931 * NSArray* arguments
 932 *  0 - NSString* localURI of file to write to
 933 *  1 - NSString* or NSData* data to write
 934 *  2 - NSNumber* position to begin writing
 935 */
 936- (void)write:(CDVInvokedUrlCommand*)command
 937{
 938    [self.commandDelegate runInBackground:^ {
 939        NSString* callbackId = command.callbackId;
 940
 941        // arguments
 942        CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
 943        id argData = [command argumentAtIndex:1];
 944        unsigned long long pos = (unsigned long long)[[command argumentAtIndex:2] longLongValue];
 945
 946        NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
 947
 948
 949        [fs truncateFileAtURL:localURI atPosition:pos];
 950        CDVPluginResult *result;
 951        if ([argData isKindOfClass:[NSString class]]) {
 952            NSData *encData = [argData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
 953            result = [fs writeToFileAtURL:localURI withData:encData append:YES];
 954        } else if ([argData isKindOfClass:[NSData class]]) {
 955            result = [fs writeToFileAtURL:localURI withData:argData append:YES];
 956        } else {
 957            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
 958        }
 959        [self.commandDelegate sendPluginResult:result callbackId:callbackId];
 960    }];
 961}
 962
 963#pragma mark Methods for converting between URLs and paths
 964
 965- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURL
 966{
 967    for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
 968        if ([fs.name isEqualToString:localURL.fileSystemName]) {
 969            if ([fs respondsToSelector:@selector(filesystemPathForURL:)]) {
 970                return [fs filesystemPathForURL:localURL];
 971            }
 972        }
 973    }
 974    return nil;
 975}
 976
 977#pragma mark Undocumented Filesystem API
 978
 979- (void)testFileExists:(CDVInvokedUrlCommand*)command
 980{
 981    // arguments
 982    NSString* argPath = [command argumentAtIndex:0];
 983
 984    // Get the file manager
 985    NSFileManager* fMgr = [NSFileManager defaultManager];
 986    NSString* appFile = argPath; // [ self getFullPath: argPath];
 987
 988    BOOL bExists = [fMgr fileExistsAtPath:appFile];
 989    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)];
 990
 991    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
 992}
 993
 994- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command
 995{
 996    // arguments
 997    NSString* argPath = [command argumentAtIndex:0];
 998
 999    // Get the file manager
1000    NSFileManager* fMgr = [[NSFileManager alloc] init];
1001    NSString* appFile = argPath; // [self getFullPath: argPath];
1002    BOOL bIsDir = NO;
1003    BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir];
1004
1005    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)];
1006
1007    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
1008}
1009
1010// Returns number of bytes available via callback
1011- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command
1012{
1013    // no arguments
1014
1015    NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath];
1016
1017    NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]];
1018    // NSLog(@"Free space is %@", strFreeSpace );
1019
1020    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace];
1021
1022    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
1023}
1024
1025#pragma mark Compatibility with older File API
1026
1027- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
1028{
1029    return [CDVLocalFilesystem getMimeTypeFromPath:fullPath];
1030}
1031
1032- (NSDictionary *)getDirectoryEntry:(NSString *)localPath isDirectory:(BOOL)bDirRequest
1033{
1034    CDVFilesystemURL *localURL = [self fileSystemURLforLocalPath:localPath];
1035    return [self makeEntryForPath:localURL.fullPath fileSystemName:localURL.fileSystemName isDirectory:bDirRequest];
1036}
1037
1038#pragma mark Internal methods for testing
1039// Internal methods for testing: Get the on-disk location of a local filesystem url.
1040// [Currently used for testing file-transfer]
1041
1042- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command
1043{
1044    CDVFilesystemURL* localURL = [self fileSystemURLforArg:command.arguments[0]];
1045
1046    NSString* fsPath = [self filesystemPathForURL:localURL];
1047    CDVPluginResult* result;
1048    if (fsPath) {
1049        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:fsPath];
1050    } else {
1051        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot resolve URL to a file"];
1052    }
1053    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
1054}
1055
1056@end