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