+ [uploadQueue addOperation:operation];
+ } else {
+ // Upload directory, confirm first
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Upload directory"];
+ [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert addButtonWithTitle:@"Cancel"];
+ NSInteger choice = [alert runModal];
+ if (choice == NSAlertFirstButtonReturn) {
+ NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
+ precomposedStringWithCanonicalMapping];
+ // Operation: Upload a local directory and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSMutableArray *objectNames = nil;
+ NSMutableArray *contentTypes = nil;
+ NSMutableArray *filePaths = nil;
+ NSMutableArray *hashesArrays = nil;
+ NSMutableArray *directoryObjectRequests = nil;
+ NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
+ containerName:containerName
+ objectName:objectName
+ blockSize:blockSize
+ blockHash:blockHash
+ forDirectory:filePath
+ checkIfExists:YES
+ objectNames:&objectNames
+ contentTypes:&contentTypes
+ filePaths:&filePaths
+ hashesArrays:&hashesArrays
+ directoryObjectRequests:&directoryObjectRequests
+ sharingAccount:destinationNode.sharingAccount];
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
+ message:messagePrefix];
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ if (!operation.isCancelled && objectRequests) {
+ for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+ currentBytes:0];
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ containerName, @"containerName",
+ [objectNames objectAtIndex:i], @"objectName",
+ [contentTypes objectAtIndex:i], @"contentType",
+ [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
+ blockHash, @"blockHash",
+ [filePaths objectAtIndex:i], @"filePath",
+ [hashesArrays objectAtIndex:i], @"hashes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ [NSNumber numberWithUnsignedInteger:10], @"iteration",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil]];
+ if (destinationNode.sharingAccount)
+ [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }
+ }];
+ [uploadQueue addOperation:operation];
+ }
+ }
+ }
+ }
+ return YES;
+}
+
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+ if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+ (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
+ return NO;
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+
+ for (PithosNode *node in nodes) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Move an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if ([node.pithosObject.name hasSuffix:@"/"])
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Move a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if (node.pithosObject.subdir)
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ }
+ }
+ return YES;
+}
+
+- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+ if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+ (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
+ return NO;
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+
+ for (PithosNode *node in nodes) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Copy an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName;
+ if (![destinationNode isEqualTo:node.parent]) {
+ destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if ([node.pithosObject.name hasSuffix:@"/"])
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ } else {
+ destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
+ containerName:containerName
+ objectName:node.pithosObject.name];
+ }
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ copyNetworkQueue, @"networkQueue",
+ @"copy", @"operationType",
+ nil]];
+ [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }];
+ [copyQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Copy a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName;
+ if (![destinationNode isEqualTo:node.parent]) {
+ destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if (node.pithosObject.subdir)
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ } else {
+ destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+ containerName:containerName
+ subdirName:node.pithosObject.name];
+ }
+ if (operation.isCancelled)
+ return;
+ NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ copyNetworkQueue, @"networkQueue",
+ @"copy", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [copyQueue addOperation:operation];
+ }
+ }
+ return YES;
+}
+
+#pragma mark -
+#pragma mark ASIHTTPRequestDelegate
+
+- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
+ NSOperationQueue *callbackQueue;
+ NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+ if ([operationType isEqualToString:@"move"])
+ callbackQueue = moveCallbackQueue;
+ else if ([operationType isEqualToString:@"copy"])
+ callbackQueue = copyCallbackQueue;
+ else if ([operationType isEqualToString:@"delete"])
+ callbackQueue = deleteCallbackQueue;
+ else if ([operationType isEqualToString:@"upload"])
+ callbackQueue = uploadCallbackQueue;
+ else if ([operationType isEqualToString:@"download"])
+ callbackQueue = downloadCallbackQueue;
+ else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ }
+ }
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+}
+
+- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
+ if (request.isCancelled) {
+ // Request has been cancelled
+ // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
+ [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ withObject:request];
+ } else {
+ NSOperationQueue *callbackQueue;
+ NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+ if ([operationType isEqualToString:@"move"])
+ callbackQueue = moveCallbackQueue;
+ else if ([operationType isEqualToString:@"copy"])
+ callbackQueue = copyCallbackQueue;
+ else if ([operationType isEqualToString:@"delete"])
+ callbackQueue = deleteCallbackQueue;
+ else if ([operationType isEqualToString:@"upload"])
+ callbackQueue = uploadCallbackQueue;
+ else if ([operationType isEqualToString:@"download"])
+ callbackQueue = downloadCallbackQueue;
+ else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ }
+ }
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+ }
+}
+
+- (void)requestFailed:(ASIPithosRequest *)request {
+ @autoreleasepool {
+ NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+ DLog(@"Request failed: %@", request.url);
+ if (operation.isCancelled)
+ return;
+ if (request.isCancelled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+ if (retries > 0) {
+ ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+ [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+ [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
+ [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
+ [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
+ else
+ [PithosUtilities httpRequestErrorAlertWithRequest:request];
+ }
+ }
+}
+
+- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Download finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 200) {
+ NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSUInteger totalBytes = activity.totalBytes;
+
+ // XXX change contentLength to objectContentLength if it is fixed in the server
+ if ([objectRequest contentLength] == 0) {
+ // The check above was:
+ // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
+ // I checked for directory content types in order not to create a file in place of a directory,
+ // but this callback method is not called in the case of a directory download.
+ // It maybe the case though, when downloading an old version of an object, is of a directory content type.
+ // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
+ DLog(@"Downloaded 0 bytes");
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:filePath]) {
+ if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Create File Error"];
+ [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ }
+ }
+ }
+
+ NSUInteger currentBytes = [objectRequest objectContentLength];
+ if (currentBytes == 0)
+ currentBytes = totalBytes;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:totalBytes
+ currentBytes:currentBytes];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload directory object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ DLog(@"Directory object created: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload using hashmap finished: %@", objectRequest.url);
+ NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSUInteger totalBytes = activity.totalBytes;
+ NSUInteger currentBytes = activity.currentBytes;
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ DLog(@"Object created: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:totalBytes
+ currentBytes:totalBytes];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else if (objectRequest.responseStatusCode == 409) {
+ NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
+ if (iteration == 0) {
+ DLog(@"Upload iteration limit reached: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Upload Timeout"];
+ [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
+ [objectRequest.userInfo objectForKey:@"objectName"]]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ return;
+ }
+ DLog(@"object is missing hashes: %@", objectRequest.url);
+ NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+ withMissingHashes:[objectRequest hashes]];
+ NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+ if (totalBytes >= [missingBlocks count]*blockSize)
+ currentBytes = totalBytes - [missingBlocks count]*blockSize;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
+ totalBytes:totalBytes
+ currentBytes:currentBytes];
+ });
+ NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:[objectRequest.userInfo objectForKey:@"containerName"]
+ blockSize:blockSize
+ forFile:[objectRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = objectRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
+ [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+ @autoreleasepool {
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload of missing block finished: %@", containerRequest.url);
+ NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+ NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
+ PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else if (containerRequest.responseStatusCode == 202) {
+ NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
+ NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+ missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+ if (missingBlockIndex == NSNotFound) {
+ NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
+ ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:[containerRequest.userInfo objectForKey:@"containerName"]
+ objectName:[containerRequest.userInfo objectForKey:@"objectName"]
+ contentType:[containerRequest.userInfo objectForKey:@"contentType"]
+ blockSize:blockSize
+ blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ checkIfExists:NO
+ hashes:&hashes
+ sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+ newObjectRequest.delegate = self;
+ newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newObjectRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:[containerRequest.userInfo objectForKey:@"containerName"]
+ blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];