openEventsPlatformApp/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m
plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.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 <sys/xattr.h>
23
24@implementation CDVLocalFilesystem
25@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
26
27- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
28{
29 if (self) {
30 _name = name;
31 _fsRoot = fsRoot;
32 }
33 return self;
34}
35
36/*
37 * IN
38 * NSString localURI
39 * OUT
40 * CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
41 * URI represents a non-existent path, or is unrecognized or otherwise malformed.
42 */
43- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
44{
45 CDVPluginResult* result = nil;
46 NSDictionary* entry = [self makeEntryForLocalURL:url];
47 if (entry) {
48 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
49 } else {
50 // return NOT_FOUND_ERR
51 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
52 }
53 return result;
54}
55- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
56 NSString *path = [self filesystemPathForURL:url];
57 NSFileManager* fileMgr = [[NSFileManager alloc] init];
58 BOOL isDir = NO;
59 // see if exists and is file or dir
60 BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
61 if (bExists) {
62 return [self makeEntryForPath:url.fullPath isDirectory:isDir];
63 } else {
64 return nil;
65 }
66}
67- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
68{
69 NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
70 NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
71 if (isDir && ![fullPath hasSuffix:@"/"]) {
72 fullPath = [fullPath stringByAppendingString:@"/"];
73 }
74 [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
75 [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
76 [dirEntry setObject:fullPath forKey:@"fullPath"];
77 [dirEntry setObject:lastPart forKey:@"name"];
78 [dirEntry setObject:self.name forKey: @"filesystemName"];
79
80 NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
81 if (self.urlTransformer) {
82 nativeURL = self.urlTransformer(nativeURL);
83 }
84
85 dirEntry[@"nativeURL"] = [nativeURL absoluteString];
86
87 return dirEntry;
88}
89
90- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
91{
92 NSRange questionMark = [fullPath rangeOfString:@"?"];
93 if (questionMark.location != NSNotFound) {
94 return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
95 }
96 return fullPath;
97}
98
99- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
100{
101 NSString *path = nil;
102 NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
103 path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
104 if ([path length] > 1 && [path hasSuffix:@"/"]) {
105 path = [path substringToIndex:([path length]-1)];
106 }
107 return path;
108}
109/*
110 * IN
111 * NSString localURI
112 * OUT
113 * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
114 * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
115 * or if the URL is malformed.
116 * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
117 */
118- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
119{
120 return [self filesystemPathForFullPath:url.fullPath];
121}
122
123- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
124{
125 if (fullPath) {
126 NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
127 if ([fullPath hasPrefix:@"/"]) {
128 return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
129 }
130 return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
131 }
132 return nil;
133}
134
135- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
136{
137 return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
138
139}
140
141- (NSString *)normalizePath:(NSString *)rawPath
142{
143 // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
144 BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
145 if (isAbsolutePath) {
146 rawPath = [rawPath substringFromIndex:1];
147 }
148 NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
149 for (int index = 0; index < [components count]; ++index) {
150 if ([[components objectAtIndex:index] isEqualToString:@".."]) {
151 [components removeObjectAtIndex:index];
152 if (index > 0) {
153 [components removeObjectAtIndex:index-1];
154 --index;
155 }
156 }
157 }
158
159 if (isAbsolutePath) {
160 return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
161 } else {
162 return [components componentsJoinedByString:@"/"];
163 }
164
165
166}
167
168- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
169{
170 BOOL bNumber = NO;
171 NSObject* value = dict[key];
172 if (value) {
173 bNumber = [value isKindOfClass:[NSNumber class]];
174 }
175 return bNumber;
176}
177
178- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
179{
180 CDVPluginResult* result = nil;
181 BOOL bDirRequest = NO;
182 BOOL create = NO;
183 BOOL exclusive = NO;
184 int errorCode = 0; // !!! risky - no error code currently defined for 0
185
186 if ([self valueForKeyIsNumber:options key:@"create"]) {
187 create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
188 }
189 if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
190 exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
191 }
192 if ([self valueForKeyIsNumber:options key:@"getDir"]) {
193 // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
194 bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
195 }
196 // see if the requested path has invalid characters - should we be checking for more than just ":"?
197 if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
198 errorCode = ENCODING_ERR;
199 } else {
200 // Build new fullPath for the requested resource.
201 // We concatenate the two paths together, and then scan the resulting string to remove
202 // parent ("..") references. Any parent references at the beginning of the string are
203 // silently removed.
204 NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
205 combinedPath = [self normalizePath:combinedPath];
206 CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
207
208 NSFileManager* fileMgr = [[NSFileManager alloc] init];
209 BOOL bIsDir;
210 BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
211 if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
212 // path exists and is not of requested type - return TYPE_MISMATCH_ERR
213 errorCode = TYPE_MISMATCH_ERR;
214 } else if (!bExists && (create == NO)) {
215 // path does not exist and create is false - return NOT_FOUND_ERR
216 errorCode = NOT_FOUND_ERR;
217 } else if (bExists && (create == YES) && (exclusive == YES)) {
218 // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
219 errorCode = PATH_EXISTS_ERR;
220 } else {
221 // if bExists and create == YES - just return data
222 // if bExists and create == NO - just return data
223 // if !bExists and create == YES - create and return data
224 BOOL bSuccess = YES;
225 NSError __autoreleasing* pError = nil;
226 if (!bExists && (create == YES)) {
227 if (bDirRequest) {
228 // create the dir
229 bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
230 } else {
231 // create the empty file
232 bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
233 }
234 }
235 if (!bSuccess) {
236 errorCode = ABORT_ERR;
237 if (pError) {
238 NSLog(@"error creating directory: %@", [pError localizedDescription]);
239 }
240 } else {
241 // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
242 // file existed or was created
243 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
244 }
245 } // are all possible conditions met?
246 }
247
248 if (errorCode > 0) {
249 // create error callback
250 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
251 }
252 return result;
253
254}
255
256- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
257{
258 CDVPluginResult* result = nil;
259 CDVFilesystemURL *newURI = nil;
260 if ([localURI.fullPath isEqualToString:@""]) {
261 // return self
262 newURI = localURI;
263 } else {
264 newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
265 }
266 NSFileManager* fileMgr = [[NSFileManager alloc] init];
267 BOOL bIsDir;
268 BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
269 if (bExists) {
270 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
271 } else {
272 // invalid path or file does not exist
273 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
274 }
275 return result;
276}
277
278- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
279{
280 BOOL ok = NO;
281
282 NSString* filePath = [self filesystemPathForURL:localURI];
283 // we only care about this iCloud key for now.
284 // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
285 NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
286 id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
287
288 if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
289// todo: fix me
290// if (IsAtLeastiOSVersion(@"5.1")) {
291// NSURL* url = [NSURL fileURLWithPath:filePath];
292// NSError* __autoreleasing error = nil;
293//
294// ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
295// } else { // below 5.1 (deprecated - only really supported in 5.01)
296// u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
297// if (value == 0) { // remove the attribute (allow backup, the default)
298// ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
299// } else { // set the attribute (skip backup)
300// ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
301// }
302// }
303 }
304
305 if (ok) {
306 return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
307 } else {
308 return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
309 }
310}
311
312/* remove the file or directory (recursively)
313 * IN:
314 * NSString* fullPath - the full path to the file or directory to be removed
315 * NSString* callbackId
316 * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
317 */
318
319- (CDVPluginResult*)doRemove:(NSString*)fullPath
320{
321 CDVPluginResult* result = nil;
322 BOOL bSuccess = NO;
323 NSError* __autoreleasing pError = nil;
324 NSFileManager* fileMgr = [[NSFileManager alloc] init];
325
326 @try {
327 bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
328 if (bSuccess) {
329 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
330 } else {
331 // see if we can give a useful error
332 CDVFileError errorCode = ABORT_ERR;
333 NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
334 if ([pError code] == NSFileNoSuchFileError) {
335 errorCode = NOT_FOUND_ERR;
336 } else if ([pError code] == NSFileWriteNoPermissionError) {
337 errorCode = NO_MODIFICATION_ALLOWED_ERR;
338 }
339
340 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
341 }
342 } @catch(NSException* e) { // NSInvalidArgumentException if path is . or ..
343 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
344 }
345
346 return result;
347}
348
349- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
350{
351 NSString *fileSystemPath = [self filesystemPathForURL:localURI];
352
353 NSFileManager* fileMgr = [[NSFileManager alloc] init];
354 BOOL bIsDir = NO;
355 BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
356 if (!bExists) {
357 return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
358 }
359 if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
360 // dir is not empty
361 return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
362 }
363 return [self doRemove:fileSystemPath];
364}
365
366- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
367{
368 NSString *fileSystemPath = [self filesystemPathForURL:localURI];
369 return [self doRemove:fileSystemPath];
370}
371
372/*
373 * IN
374 * NSString localURI
375 * OUT
376 * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
377 * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
378 * or if the URL is malformed.
379 * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
380 */
381- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
382{
383 if ([fsPath hasPrefix:self.fsRoot]) {
384 return [fsPath substringFromIndex:[self.fsRoot length]];
385 }
386 return nil;
387}
388
389
390- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
391{
392 NSFileManager* fileMgr = [[NSFileManager alloc] init];
393 NSError* __autoreleasing error = nil;
394 NSString *fileSystemPath = [self filesystemPathForURL:localURI];
395
396 NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
397
398 if (contents) {
399 NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
400 if ([contents count] > 0) {
401 // create an Entry (as JSON) for each file/dir
402 for (NSString* name in contents) {
403 // see if is dir or file
404 NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
405 BOOL bIsDir = NO;
406 [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
407 NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
408 [entries addObject:entryDict];
409 }
410 }
411 return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
412 } else {
413 // assume not found but could check error for more specific error conditions
414 return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
415 }
416}
417
418- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
419{
420 unsigned long long newPos = 0UL;
421
422 NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
423
424 if (file) {
425 [file truncateFileAtOffset:(unsigned long long)pos];
426 newPos = [file offsetInFile];
427 [file synchronizeFile];
428 [file closeFile];
429 }
430 return newPos;
431}
432
433- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
434{
435 unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
436 return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
437}
438
439- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
440{
441 NSString *filePath = [self filesystemPathForURL:localURL];
442
443 CDVPluginResult* result = nil;
444 CDVFileError errCode = INVALID_MODIFICATION_ERR;
445 int bytesWritten = 0;
446
447 if (filePath) {
448 NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
449 if (fileStream) {
450 NSUInteger len = [encData length];
451 if (len == 0) {
452 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
453 } else {
454 [fileStream open];
455
456 bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
457
458 [fileStream close];
459 if (bytesWritten > 0) {
460 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
461 // } else {
462 // can probably get more detailed error info via [fileStream streamError]
463 // errCode already set to INVALID_MODIFICATION_ERR;
464 // bytesWritten = 0; // may be set to -1 on error
465 }
466 }
467 } // else fileStream not created return INVALID_MODIFICATION_ERR
468 } else {
469 // invalid filePath
470 errCode = NOT_FOUND_ERR;
471 }
472 if (!result) {
473 // was an error
474 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
475 }
476 return result;
477}
478
479/**
480 * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
481 * or attempted to copy a directory into a directory that it contains directly or indirectly.
482 *
483 * IN:
484 * NSString* srcDir
485 * NSString* destinationDir
486 * OUT:
487 * YES copy/ move is allows
488 * NO move is onto itself
489 */
490- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
491{
492 // This weird test is to determine if we are copying or moving a directory into itself.
493 // Copy /Documents/myDir to /Documents/myDir-backup is okay but
494 // Copy /Documents/myDir to /Documents/myDir/backup not okay
495 BOOL copyOK = YES;
496 NSRange range = [dest rangeOfString:src];
497
498 if (range.location != NSNotFound) {
499 NSRange testRange = {range.length - 1, ([dest length] - range.length)};
500 NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
501 if (resultRange.location != NSNotFound) {
502 copyOK = NO;
503 }
504 }
505 return copyOK;
506}
507
508- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
509{
510 NSFileManager *fileMgr = [[NSFileManager alloc] init];
511 NSString *destRootPath = [self filesystemPathForURL:destURL];
512 BOOL bDestIsDir = NO;
513 BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
514
515 NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
516 NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
517
518 BOOL bNewIsDir = NO;
519 BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
520
521 CDVPluginResult *result = nil;
522 int errCode = 0;
523
524 if (!bDestExists) {
525 // the destination root does not exist
526 errCode = NOT_FOUND_ERR;
527 }
528
529 else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
530 /* Same FS, we can shortcut with NSFileManager operations */
531 NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
532
533 BOOL bSrcIsDir = NO;
534 BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
535
536 if (!bSrcExists) {
537 // the source does not exist
538 errCode = NOT_FOUND_ERR;
539 } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
540 // source and destination can not be the same
541 errCode = INVALID_MODIFICATION_ERR;
542 } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
543 // can't copy/move dir to file
544 errCode = INVALID_MODIFICATION_ERR;
545 } else { // no errors yet
546 NSError* __autoreleasing error = nil;
547 BOOL bSuccess = NO;
548 if (bCopy) {
549 if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
550 // can't copy dir into self
551 errCode = INVALID_MODIFICATION_ERR;
552 } else if (bNewExists) {
553 // the full destination should NOT already exist if a copy
554 errCode = PATH_EXISTS_ERR;
555 } else {
556 bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
557 }
558 } else { // move
559 // iOS requires that destination must not exist before calling moveTo
560 // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
561 //
562 if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
563 // can't move a file to directory
564 errCode = INVALID_MODIFICATION_ERR;
565 } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
566 // can't move a dir into itself
567 errCode = INVALID_MODIFICATION_ERR;
568 } else if (bNewExists) {
569 if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
570 // can't move dir to a dir that is not empty
571 errCode = INVALID_MODIFICATION_ERR;
572 newFileSystemPath = nil; // so we won't try to move
573 } else {
574 // remove destination so can perform the moveItemAtPath
575 bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
576 if (!bSuccess) {
577 errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
578 newFileSystemPath = nil;
579 }
580 }
581 } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
582 // can't move a directory inside itself or to any child at any depth;
583 errCode = INVALID_MODIFICATION_ERR;
584 newFileSystemPath = nil;
585 }
586
587 if (newFileSystemPath != nil) {
588 bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
589 }
590 }
591 if (bSuccess) {
592 // should verify it is there and of the correct type???
593 NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
594 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
595 } else {
596 if (error) {
597 if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
598 errCode = NOT_READABLE_ERR;
599 } else if ([error code] == NSFileWriteOutOfSpaceError) {
600 errCode = QUOTA_EXCEEDED_ERR;
601 } else if ([error code] == NSFileWriteNoPermissionError) {
602 errCode = NO_MODIFICATION_ALLOWED_ERR;
603 }
604 }
605 }
606 }
607 } else {
608 // Need to copy the hard way
609 [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
610 CDVPluginResult* result = nil;
611 if (data != nil) {
612 BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
613 if (bSuccess) {
614 // should verify it is there and of the correct type???
615 NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
616 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
617 } else {
618 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
619 }
620 } else {
621 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
622 }
623 callback(result);
624 }];
625 return; // Async IO; return without callback.
626 }
627 if (result == nil) {
628 if (!errCode) {
629 errCode = INVALID_MODIFICATION_ERR; // Catch-all default
630 }
631 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
632 }
633 callback(result);
634}
635
636/* helper function to get the mimeType from the file extension
637 * IN:
638 * NSString* fullPath - filename (may include path)
639 * OUT:
640 * NSString* the mime type as type/subtype. nil if not able to determine
641 */
642+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
643{
644 NSString* mimeType = nil;
645
646 if (fullPath) {
647 CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
648 if (typeId) {
649 mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
650 if (!mimeType) {
651 // special case for m4a
652 if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
653 mimeType = @"audio/mp4";
654 } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
655 mimeType = @"audio/wav";
656 } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
657 mimeType = @"text/css";
658 }
659 }
660 CFRelease(typeId);
661 }
662 }
663 return mimeType;
664}
665
666- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
667{
668 NSString *path = [self filesystemPathForURL:localURL];
669
670 NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
671 if (mimeType == nil) {
672 mimeType = @"*/*";
673 }
674 NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
675 if (start > 0) {
676 [file seekToFileOffset:start];
677 }
678
679 NSData* readData;
680 if (end < 0) {
681 readData = [file readDataToEndOfFile];
682 } else {
683 readData = [file readDataOfLength:(end - start)];
684 }
685 [file closeFile];
686
687 callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
688}
689
690- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
691{
692 NSString *path = [self filesystemPathForURL:localURL];
693 CDVPluginResult *result;
694 NSFileManager* fileMgr = [[NSFileManager alloc] init];
695
696 NSError* __autoreleasing error = nil;
697 NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
698
699 if (fileAttrs) {
700
701 // create dictionary of file info
702 NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
703
704 [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
705 [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
706 [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
707
708 // Ensure that directories (and other non-regular files) report size of 0
709 unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
710 [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
711
712 NSDate* modDate = [fileAttrs fileModificationDate];
713 if (modDate) {
714 [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
715 }
716
717 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
718
719 } else {
720 // didn't get fileAttribs
721 CDVFileError errorCode = ABORT_ERR;
722 NSLog(@"error getting metadata: %@", [error localizedDescription]);
723 if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
724 errorCode = NOT_FOUND_ERR;
725 }
726 // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
727 result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
728 }
729
730 callback(result);
731}
732
733@end