+ }
+ }];
+ [downloadQueue addOperation:operation];
+ } else if ([node class] == [PithosObjectNode class]) {
+ // Operation: Download an object node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ version:version
+ toDirectory:dirPath
+ withNewFileName:newFileName
+ checkIfExists:checkIfExists
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:node.pithosObject.bytes
+ currentBytes:0];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ downloadNetworkQueue, @"networkQueue",
+ @"download", @"operationType",
+ nil]];
+ [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }];
+ [downloadQueue addOperation:operation];
+ }
+}
+
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
+ if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+ return NO;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+ if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
+ containerName:containerName];
+ [PithosUtilities startAndWaitForRequest:containerRequest];
+ if ([containerRequest error]) {
+ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
+ return NO;
+ } else if (containerRequest.responseStatusCode != 204) {
+ [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+ return NO;
+ }
+ destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
+ destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
+ }
+ NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
+ NSString *blockHash = destinationNode.pithosContainer.blockHash;
+
+ for (NSString *filePath in filenames) {
+ BOOL isDirectory;
+ if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+ if (!isDirectory) {
+ // Upload file
+ NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
+ precomposedStringWithCanonicalMapping];
+ // Operation: Upload a local file
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSError *error = nil;
+ NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
+ if (contentType == nil)
+ contentType = @"application/octet-stream";
+ #if DEBUG_PITHOS
+ if (error)
+ DLog(@"contentType detection error: %@", error);
+ #endif
+ NSArray *hashes = nil;
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:containerName
+ objectName:objectName
+ contentType:contentType
+ blockSize:blockSize
+ blockHash:blockHash
+ forFile:filePath
+ checkIfExists:YES
+ hashes:&hashes
+ sharingAccount:destinationNode.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
+ 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",
+ objectName, @"objectName",
+ contentType, @"contentType",
+ [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
+ blockHash, @"blockHash",
+ filePath, @"filePath",
+ hashes, @"hashes",
+ [NSArray arrayWithObject:destinationNode], @"refreshNodes",
+ [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"];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }];
+ [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;