aboutsummaryrefslogtreecommitdiffstats
path: root/iPhone/CordovaLib/Classes/CDVFileTransfer.m
diff options
context:
space:
mode:
Diffstat (limited to 'iPhone/CordovaLib/Classes/CDVFileTransfer.m')
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFileTransfer.m278
1 files changed, 195 insertions, 83 deletions
diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.m b/iPhone/CordovaLib/Classes/CDVFileTransfer.m
index 4ccdce6..0f6b174 100755
--- a/iPhone/CordovaLib/Classes/CDVFileTransfer.m
+++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.m
@@ -52,8 +52,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
while (totalBytesWritten < bytesToWrite) {
CFIndex result = CFWriteStreamWrite(stream,
- bytes + totalBytesWritten,
- bytesToWrite - totalBytesWritten);
+ bytes + totalBytesWritten,
+ bytesToWrite - totalBytesWritten);
if (result < 0) {
CFStreamError error = CFWriteStreamGetError(stream);
NSLog(@"WriteStreamError domain: %ld error: %ld", error.domain, error.error);
@@ -126,17 +126,19 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode]
// however, params is a JavaScript object and during marshalling is put into the options dict,
// thus debug and chunkedMode are the 6th and 7th arguments
- NSArray* arguments = command.arguments;
- NSString* target = (NSString*)[arguments objectAtIndex:0];
- NSString* server = (NSString*)[arguments objectAtIndex:1];
- NSString* fileKey = [arguments objectAtIndex:2 withDefault:@"file"];
- NSString* fileName = [arguments objectAtIndex:3 withDefault:@"no-filename"];
- NSString* mimeType = [arguments objectAtIndex:4 withDefault:nil];
- NSDictionary* options = [arguments objectAtIndex:5 withDefault:nil];
+ NSString* target = [command argumentAtIndex:0];
+ NSString* server = [command argumentAtIndex:1];
+ NSString* fileKey = [command argumentAtIndex:2 withDefault:@"file"];
+ NSString* fileName = [command argumentAtIndex:3 withDefault:@"no-filename"];
+ NSString* mimeType = [command argumentAtIndex:4 withDefault:nil];
+ NSDictionary* options = [command argumentAtIndex:5 withDefault:nil];
// BOOL trustAllHosts = [[arguments objectAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs
- BOOL chunkedMode = [[arguments objectAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue];
- NSDictionary* headers = [arguments objectAtIndex:8 withDefault:nil];
-
+ BOOL chunkedMode = [[command argumentAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue];
+ NSDictionary* headers = [command argumentAtIndex:8 withDefault:nil];
+ // Allow alternative http method, default to POST. JS side checks
+ // for allowed methods, currently PUT or POST (forces POST for
+ // unrecognised values)
+ NSString* httpMethod = [command argumentAtIndex:10 withDefault:@"POST"];
CDVPluginResult* result = nil;
CDVFileTransferError errorCode = 0;
@@ -158,7 +160,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
- [req setHTTPMethod:@"POST"];
+
+ [req setHTTPMethod:httpMethod];
// Magic value to set a cookie
if ([options objectForKey:kOptionsKeyCookie]) {
@@ -190,7 +193,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
[postBodyBeforeFile appendData:formBoundaryData];
[postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
[postBodyBeforeFile appendData:[val dataUsingEncoding:NSUTF8StringEncoding]];
- [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding : NSUTF8StringEncoding]];
}
[postBodyBeforeFile appendData:formBoundaryData];
@@ -212,23 +215,29 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
CFStreamCreateBoundPair(NULL, &readStream, &writeStream, kStreamBufferSize);
[req setHTTPBodyStream:CFBridgingRelease(readStream)];
+ self.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+ [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID];
+ self.backgroundTaskID = UIBackgroundTaskInvalid;
+ NSLog(@"Background task to upload media finished.");
+ }];
+
[self.commandDelegate runInBackground:^{
- if (CFWriteStreamOpen (writeStream)) {
- NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile};
- int numChunks = sizeof (chunks) / sizeof (chunks[0]);
-
- for (int i = 0; i < numChunks; ++i) {
- CFIndex result = WriteDataToStream (chunks[i], writeStream);
- if (result <= 0) {
- break;
- }
+ if (CFWriteStreamOpen(writeStream)) {
+ NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile};
+ int numChunks = sizeof(chunks) / sizeof(chunks[0]);
+
+ for (int i = 0; i < numChunks; ++i) {
+ CFIndex result = WriteDataToStream(chunks[i], writeStream);
+ if (result <= 0) {
+ break;
}
- } else {
- NSLog (@"FileTransfer: Failed to open writeStream");
}
- CFWriteStreamClose (writeStream);
- CFRelease (writeStream);
- }];
+ } else {
+ NSLog(@"FileTransfer: Failed to open writeStream");
+ }
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ }];
} else {
[postBodyBeforeFile appendData:fileData];
[postBodyBeforeFile appendData:postBodyAfterFile];
@@ -265,11 +274,11 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// return unsupported result for assets-library URLs
if ([target hasPrefix:kCDVAssetsLibraryPrefix]) {
// Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
- ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) {
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
if (asset) {
// We have the asset! Get the data and send it off.
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
- Byte* buffer = (Byte*)malloc ([assetRepresentation size]);
+ Byte* buffer = (Byte*)malloc([assetRepresentation size]);
NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil];
NSData* fileData = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
[self uploadData:fileData command:command];
@@ -279,7 +288,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
};
- ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) {
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
// Retrieving the asset failed for some reason. Send the appropriate error.
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
@@ -291,12 +300,18 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
} else {
// Extract the path part out of a file: URL.
NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path];
+ if (filePath == nil) {
+ // We couldn't find the asset. Send the appropriate error.
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
// Memory map the file so that it can be read efficiently even if it is large.
NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err];
if (err != nil) {
- NSLog (@"Error opening file %@: %@", target, err);
+ NSLog(@"Error opening file %@: %@", target, err);
}
[self uploadData:fileData command:command];
}
@@ -333,13 +348,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
CDVFileTransferDelegate* delegate = [activeTransfers objectForKey:objectId];
if (delegate != nil) {
- [delegate.connection cancel];
- [activeTransfers removeObjectForKey:objectId];
-
- // delete uncomplete file
- NSFileManager* fileMgr = [NSFileManager defaultManager];
- [fileMgr removeItemAtPath:delegate.target error:nil];
-
+ [delegate cancelTransfer:delegate.connection];
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]];
[self.commandDelegate sendPluginResult:result callbackId:delegate.callbackId];
}
@@ -352,6 +361,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSString* filePath = [command.arguments objectAtIndex:1];
BOOL trustAllHosts = [[command.arguments objectAtIndex:2 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs
NSString* objectId = [command.arguments objectAtIndex:3];
+ NSDictionary* headers = [command.arguments objectAtIndex:4 withDefault:nil];
// return unsupported result for assets-library URLs
if ([filePath hasPrefix:kCDVAssetsLibraryPrefix]) {
@@ -388,7 +398,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
- [self applyRequestHeaders:nil toRequest:req];
+ [self applyRequestHeaders:headers toRequest:req];
CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init];
delegate.command = self;
@@ -413,8 +423,12 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:3];
[result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
- [result setObject:source forKey:@"source"];
- [result setObject:target forKey:@"target"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
NSLog(@"FileTransferError %@", result);
return result;
@@ -429,10 +443,16 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:5];
[result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
- [result setObject:source forKey:@"source"];
- [result setObject:target forKey:@"target"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
[result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"];
- [result setObject:body forKey:@"body"];
+ if (body != nil) {
+ [result setObject:body forKey:@"body"];
+ }
NSLog(@"FileTransferError %@", result);
return result;
@@ -449,19 +469,56 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
@end
+@interface CDVFileTransferEntityLengthRequest : NSObject {
+ NSURLConnection* _connection;
+ CDVFileTransferDelegate* __weak _originalDelegate;
+}
+
+- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate;
+
+@end;
+
+@implementation CDVFileTransferEntityLengthRequest;
+
+- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate
+{
+ if (self) {
+ DLog(@"Requesting entity length for GZIPped content...");
+
+ NSMutableURLRequest* req = [originalRequest mutableCopy];
+ [req setHTTPMethod:@"HEAD"];
+ [req setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"];
+
+ _originalDelegate = originalDelegate;
+ _connection = [NSURLConnection connectionWithRequest:req delegate:self];
+ }
+ return self;
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
+{
+ DLog(@"HEAD request returned; content-length is %lld", [response expectedContentLength]);
+ [_originalDelegate updateBytesExpected:[response expectedContentLength]];
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
+{}
+
+- (void)connectionDidFinishLoading:(NSURLConnection*)connection
+{}
+
+@end
+
@implementation CDVFileTransferDelegate
-@synthesize callbackId, connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId;
+@synthesize callbackId, connection = _connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId, targetFileHandle;
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSString* uploadResponse = nil;
NSString* downloadResponse = nil;
- BOOL downloadWriteOK = NO;
NSMutableDictionary* uploadResult;
CDVPluginResult* result = nil;
- NSError* __autoreleasing error = nil;
- NSString* parentPath;
BOOL bDirRequest = NO;
CDVFile* file;
@@ -484,40 +541,15 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
}
if (self.direction == CDV_TRANSFER_DOWNLOAD) {
- DLog(@"Write file %@", target);
- // error=[[NSError alloc]init];
+ if (self.targetFileHandle) {
+ [self.targetFileHandle closeFile];
+ self.targetFileHandle = nil;
+ DLog(@"File Transfer Download success");
- if ((self.responseCode >= 200) && (self.responseCode < 300)) {
- @try {
- parentPath = [self.target stringByDeletingLastPathComponent];
-
- // check if the path exists => create directories if needed
- if (![[NSFileManager defaultManager] fileExistsAtPath:parentPath]) {
- [[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:nil];
- }
-
- downloadWriteOK = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error];
-
- if (downloadWriteOK == NO) {
- // send our results back
- downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
- result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]];
- } else {
- DLog(@"File Transfer Download success");
-
- file = [[CDVFile alloc] init];
-
- result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]];
- }
- }
- @catch(id exception) {
- // jump back to main thread
- downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
- result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]];
- }
+ file = [[CDVFile alloc] init];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]];
} else {
downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
-
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]];
}
}
@@ -526,11 +558,41 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// remove connection for activeTransfers
[command.activeTransfers removeObjectForKey:objectId];
+
+ // remove background id task in case our upload was done in the background
+ [[UIApplication sharedApplication] endBackgroundTask:self.command.backgroundTaskID];
+ self.command.backgroundTaskID = UIBackgroundTaskInvalid;
+}
+
+- (void)removeTargetFile
+{
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+
+ [fileMgr removeItemAtPath:self.target error:nil];
+}
+
+- (void)cancelTransfer:(NSURLConnection*)connection
+{
+ [connection cancel];
+ [self.command.activeTransfers removeObjectForKey:self.objectId];
+ [self removeTargetFile];
+}
+
+- (void)cancelTransferWithError:(NSURLConnection*)connection errorMessage:(NSString*)errorMessage
+{
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[self.command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:self.source AndTarget:self.target AndHttpStatus:self.responseCode AndBody:errorMessage]];
+
+ NSLog(@"File Transfer Error: %@", errorMessage);
+ [self cancelTransfer:connection];
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
+ NSError* __autoreleasing error = nil;
+
self.mimeType = [response MIMEType];
+ self.targetFileHandle = nil;
// required for iOS 4.3, for some reason; response is
// a plain NSURLResponse, not the HTTP subclass
@@ -539,6 +601,11 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
self.responseCode = [httpResponse statusCode];
self.bytesExpected = [response expectedContentLength];
+ if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode == 200) && (self.bytesExpected == NSURLResponseUnknownLength)) {
+ // Kick off HEAD request to server to get real length
+ // bytesExpected will be updated when that response is returned
+ self.entityLengthRequest = [[CDVFileTransferEntityLengthRequest alloc] initWithOriginalRequest:connection.currentRequest andDelegate:self];
+ }
} else if ([response.URL isFileURL]) {
NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[response.URL path] error:nil];
self.responseCode = 200;
@@ -547,6 +614,31 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
self.responseCode = 200;
self.bytesExpected = NSURLResponseUnknownLength;
}
+ if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode >= 200) && (self.responseCode < 300)) {
+ // Download response is okay; begin streaming output to file
+ NSString* parentPath = [self.target stringByDeletingLastPathComponent];
+
+ // create parent directories if needed
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) {
+ if (error) {
+ [self cancelTransferWithError:connection errorMessage:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]]];
+ } else {
+ [self cancelTransferWithError:connection errorMessage:@"Could not create path to save downloaded file"];
+ }
+ return;
+ }
+ // create target file
+ if ([[NSFileManager defaultManager] createFileAtPath:self.target contents:nil attributes:nil] == NO) {
+ [self cancelTransferWithError:connection errorMessage:@"Could not create target file"];
+ return;
+ }
+ // open target file for writing
+ self.targetFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.target];
+ if (self.targetFileHandle == nil) {
+ [self cancelTransferWithError:connection errorMessage:@"Could not open target file for writing"];
+ }
+ DLog(@"Streaming to file %@", target);
+ }
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
@@ -556,18 +648,37 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSLog(@"File Transfer Error: %@", [error localizedDescription]);
- // remove connection for activeTransfers
- [command.activeTransfers removeObjectForKey:objectId];
+ [self cancelTransfer:connection];
[self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
self.bytesTransfered += data.length;
- [self.responseData appendData:data];
+ if (self.targetFileHandle) {
+ [self.targetFileHandle writeData:data];
+ } else {
+ [self.responseData appendData:data];
+ }
+ [self updateProgress];
+}
+- (void)updateBytesExpected:(NSInteger)newBytesExpected
+{
+ DLog(@"Updating bytesExpected to %d", newBytesExpected);
+ self.bytesExpected = newBytesExpected;
+ [self updateProgress];
+}
+
+- (void)updateProgress
+{
if (self.direction == CDV_TRANSFER_DOWNLOAD) {
BOOL lengthComputable = (self.bytesExpected != NSURLResponseUnknownLength);
+ // If the response is GZipped, and we have an outstanding HEAD request to get
+ // the length, then hold off on sending progress events.
+ if (!lengthComputable && (self.entityLengthRequest != nil)) {
+ return;
+ }
NSMutableDictionary* downloadProgress = [NSMutableDictionary dictionaryWithCapacity:3];
[downloadProgress setObject:[NSNumber numberWithBool:lengthComputable] forKey:@"lengthComputable"];
[downloadProgress setObject:[NSNumber numberWithInt:self.bytesTransfered] forKey:@"loaded"];
@@ -611,6 +722,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
{
if ((self = [super init])) {
self.responseData = [NSMutableData data];
+ self.targetFileHandle = nil;
}
return self;
}