X-Git-Url: https://code.grnet.gr/git/pithos-macos/blobdiff_plain/919cb04348601696bdbf7cf7d2248fc77adb0d4c..e8e3d71e3b001f4783889dcab46bf356f488b279:/pithos-macos/PithosBrowserController.m diff --git a/pithos-macos/PithosBrowserController.m b/pithos-macos/PithosBrowserController.m index b976f87..e8f0743 100644 --- a/pithos-macos/PithosBrowserController.m +++ b/pithos-macos/PithosBrowserController.m @@ -56,6 +56,8 @@ #import "PithosUtilities.h" #import "UsingSizeTransformer.h" +#define REFRESH_TIMER_INTERVAL 5 + @interface PithosBrowserCell : FileSystemBrowserCell {} @end @@ -102,16 +104,14 @@ @interface PithosBrowserController (Private) - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode; - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode; -- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode; +- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode; @end @implementation PithosBrowserController @synthesize pithos; -@synthesize accountNode; -@synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu; +@synthesize pithosAccountManager, accountNode; @synthesize draggedNodes, draggedParentNode; @synthesize clipboardNodes, clipboardParentNode, clipboardCopy; -@synthesize activityTextField, activityProgressIndicator; #pragma mark - #pragma Object Lifecycle @@ -120,39 +120,12 @@ return [super initWithWindowNibName:@"PithosBrowserController"]; } -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - dispatch_release(moveQueue); - dispatch_release(copyQueue); - dispatch_release(deleteQueue); - dispatch_release(uploadQueue); - dispatch_release(downloadQueue); - [moveNetworkQueue cancelAllOperations]; - [moveNetworkQueue release]; - [copyNetworkQueue cancelAllOperations]; - [copyNetworkQueue release]; - [deleteNetworkQueue cancelAllOperations]; - [deleteNetworkQueue release]; - [uploadNetworkQueue cancelAllOperations]; - [uploadNetworkQueue release]; - [downloadNetworkQueue cancelAllOperations]; - [downloadNetworkQueue release]; - [refreshTimer invalidate]; - [refreshTimer release]; - [clipboardParentNode release]; - [clipboardNodes release]; - [draggedParentNode release]; - [draggedNodes release]; - [sharedPreviewController release]; - [othersSharedNode release]; - [mySharedNode release]; - [sharedNode release]; - [containersNodeChildren release]; - [containersNode release]; - [accountNode release]; - [rootNode release]; - [pithos release]; - [super dealloc]; +- (void)windowDidLoad { + [super windowDidLoad]; + if (browser && !browserInitialized) { + browserInitialized = YES; + [self initBrowser]; + } } - (void)initBrowser { @@ -169,145 +142,275 @@ [browser setAllowsMultipleSelection:YES]; [browser setAllowsEmptySelection:YES]; [browser setAllowsTypeSelect:YES]; + [browser setDoubleAction:@selector(browserDoubleAction:)]; moveNetworkQueue = [[ASINetworkQueue alloc] init]; moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO; - [moveNetworkQueue go]; +// moveNetworkQueue.maxConcurrentOperationCount = 1; copyNetworkQueue = [[ASINetworkQueue alloc] init]; copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO; - [copyNetworkQueue go]; +// copyNetworkQueue.maxConcurrentOperationCount = 1; deleteNetworkQueue = [[ASINetworkQueue alloc] init]; deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO; - [deleteNetworkQueue go]; +// deleteNetworkQueue.maxConcurrentOperationCount = 1; uploadNetworkQueue = [[ASINetworkQueue alloc] init]; uploadNetworkQueue.showAccurateProgress = YES; uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO; - [uploadNetworkQueue go]; +// uploadNetworkQueue.maxConcurrentOperationCount = 1; downloadNetworkQueue = [[ASINetworkQueue alloc] init]; downloadNetworkQueue.showAccurateProgress = YES; downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO; - [downloadNetworkQueue go]; +// downloadNetworkQueue.maxConcurrentOperationCount = 1; + + moveQueue = [[NSOperationQueue alloc] init]; + [moveQueue setSuspended:YES]; + moveQueue.name = @"gr.grnet.pithos.MoveQueue"; +// moveQueue.maxConcurrentOperationCount = 1; + copyQueue = [[NSOperationQueue alloc] init]; + [copyQueue setSuspended:YES]; + copyQueue.name = @"gr.grnet.pithos.CopyQueue"; +// copyQueue.maxConcurrentOperationCount = 1; + deleteQueue = [[NSOperationQueue alloc] init]; + [deleteQueue setSuspended:YES]; + deleteQueue.name = @"gr.grnet.pithos.DeleteQueue"; +// deleteQueue.maxConcurrentOperationCount = 1; + uploadQueue = [[NSOperationQueue alloc] init]; + [uploadQueue setSuspended:YES]; + uploadQueue.name = @"gr.grnet.pithos.UploadQueue"; +// uploadQueue.maxConcurrentOperationCount = 1; + downloadQueue = [[NSOperationQueue alloc] init]; + [downloadQueue setSuspended:YES]; + downloadQueue.name = @"gr.grnet.pithos.DownloadQueue"; +// downloadQueue.maxConcurrentOperationCount = 1; - moveQueue = dispatch_queue_create("gr.grnet.pithos.MoveQueue", NULL); - copyQueue = dispatch_queue_create("gr.grnet.pithos.CopyQueue", NULL); - deleteQueue = dispatch_queue_create("gr.grnet.pithos.DeleteQueue", NULL); - uploadQueue = dispatch_queue_create("gr.grnet.pithos.UploadQueue", NULL); - downloadQueue = dispatch_queue_create("gr.grnet.pithos.DownloadQueue", NULL); + moveCallbackQueue = [[NSOperationQueue alloc] init]; + [moveCallbackQueue setSuspended:YES]; + moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue"; +// moveCallbackQueue.maxConcurrentOperationCount = 1; + copyCallbackQueue = [[NSOperationQueue alloc] init]; + [copyCallbackQueue setSuspended:YES]; + copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue"; +// copyCallbackQueue.maxConcurrentOperationCount = 1; + deleteCallbackQueue = [[NSOperationQueue alloc] init]; + [deleteCallbackQueue setSuspended:YES]; + deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue"; +// deleteCallbackQueue.maxConcurrentOperationCount = 1; + uploadCallbackQueue = [[NSOperationQueue alloc] init]; + [uploadCallbackQueue setSuspended:YES]; + uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue"; +// uploadCallbackQueue.maxConcurrentOperationCount = 1; + downloadCallbackQueue = [[NSOperationQueue alloc] init]; + [downloadCallbackQueue setSuspended:YES]; + downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue"; +// downloadCallbackQueue.maxConcurrentOperationCount = 1; [activityProgressIndicator setUsesThreadedAnimation:YES]; [activityProgressIndicator setMinValue:0.0]; [activityProgressIndicator setMaxValue:1.0]; activityFacility = [PithosActivityFacility defaultPithosActivityFacility]; - self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease]; + self.accountNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos]; containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil]; containersNodeChildren = [[NSMutableArray alloc] init]; sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil]; - mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos]; - mySharedNode.displayName = @"my shared"; + mySharedNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos]; + mySharedNode.displayName = @"shared by me"; mySharedNode.shared = YES; mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]; - othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos]; - othersSharedNode.displayName = @"others shared"; + othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos]; + othersSharedNode.displayName = @"shared with me"; othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]; - [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]]; + [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]]; // Register for updates - // PithosContainerNode updates browser nodes - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(pithosNodeChildrenUpdated:) - name:@"PithosContainerNodeChildrenUpdated" - object:nil]; - // PithosSubdirNode updates browser nodes - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(pithosNodeChildrenUpdated:) - name:@"PithosSubdirNodeChildrenUpdated" - object:nil]; // PithosAccountNode accountNode updates outlineView container nodes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosAccountNodeChildrenUpdated:) - name:@"PithosAccountNodeChildrenUpdated" + name:@"PithosNodeChildrenUpdated" object:accountNode]; - // PithosAccountNode other than accountNode updates nodes + // PithosNode updates browser nodes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosNodeChildrenUpdated:) - name:@"PithosAccountNodeChildrenUpdated" + name:@"PithosNodeChildrenUpdated" object:nil]; - // PithosSharingAccountsNode othersSharedNode updates browser nodes - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(pithosNodeChildrenUpdated:) - name:@"PithosSharingAccountsNodeChildrenUpdated" - object:othersSharedNode]; // Request for browser refresh [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosBrowserRefreshNeeded:) - name:@"PithosBrowserRefreshNeeeded" + name:@"PithosBrowserRefreshNeeded" object:nil]; } - (void)resetBrowser { + @synchronized(self) { + if (!browserActive) + return; + } + [refreshTimer invalidate]; - [refreshTimer release]; - [moveNetworkQueue cancelAllOperations]; - [copyNetworkQueue cancelAllOperations]; - [deleteNetworkQueue cancelAllOperations]; - [uploadNetworkQueue cancelAllOperations]; - [downloadNetworkQueue cancelAllOperations]; + [moveNetworkQueue reset]; + [copyNetworkQueue reset]; + [deleteNetworkQueue reset]; + [uploadNetworkQueue reset]; + [downloadNetworkQueue reset]; + + [moveQueue cancelAllOperations]; + [moveQueue setSuspended:YES]; + [copyQueue cancelAllOperations]; + [copyQueue setSuspended:YES]; + [deleteQueue cancelAllOperations]; + [deleteQueue setSuspended:YES]; + [uploadQueue cancelAllOperations]; + [uploadQueue setSuspended:YES]; + [downloadQueue cancelAllOperations]; + [downloadQueue setSuspended:YES]; + + [moveCallbackQueue cancelAllOperations]; + [moveCallbackQueue setSuspended:YES]; + [copyCallbackQueue cancelAllOperations]; + [copyCallbackQueue setSuspended:YES]; + [deleteCallbackQueue cancelAllOperations]; + [deleteCallbackQueue setSuspended:YES]; + [uploadCallbackQueue cancelAllOperations]; + [uploadCallbackQueue setSuspended:YES]; + [downloadCallbackQueue cancelAllOperations]; + [downloadCallbackQueue setSuspended:YES]; + + [accountNode pithosNodeWillBeRemoved]; + [mySharedNode pithosNodeWillBeRemoved]; + [othersSharedNode pithosNodeWillBeRemoved]; rootNode = nil; [browser loadColumnZero]; [containersNodeChildren removeAllObjects]; [outlineView reloadData]; - // Expand the folder outline view + // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; - [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; + [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; activityFacility.delegate = nil; [activityProgressIndicator setDoubleValue:1.0]; [activityProgressIndicator stopAnimation:self]; + + @synchronized(self) { + browserActive = NO; + } } - (void)startBrowser { + @synchronized(self) { + if (browserActive) + return; + } + + // In the improbable case of leftover operations + [moveNetworkQueue reset]; + [copyNetworkQueue reset]; + [deleteNetworkQueue reset]; + [uploadNetworkQueue reset]; + [downloadNetworkQueue reset]; + [moveQueue cancelAllOperations]; + [copyQueue cancelAllOperations]; + [deleteQueue cancelAllOperations]; + [uploadQueue cancelAllOperations]; + [downloadQueue cancelAllOperations]; + [moveCallbackQueue cancelAllOperations]; + [copyCallbackQueue cancelAllOperations]; + [deleteCallbackQueue cancelAllOperations]; + [uploadCallbackQueue cancelAllOperations]; + [downloadCallbackQueue cancelAllOperations]; + + [moveNetworkQueue go]; + [copyNetworkQueue go]; + [deleteNetworkQueue go]; + [uploadNetworkQueue go]; + [downloadNetworkQueue go]; + [moveQueue setSuspended:NO]; + [copyQueue setSuspended:NO]; + [deleteQueue setSuspended:NO]; + [uploadQueue setSuspended:NO]; + [downloadQueue setSuspended:NO]; + [moveCallbackQueue setSuspended:NO]; + [copyCallbackQueue setSuspended:NO]; + [deleteCallbackQueue setSuspended:NO]; + [uploadCallbackQueue setSuspended:NO]; + [downloadCallbackQueue setSuspended:NO]; + accountNode.pithos = pithos; - [accountNode refresh]; + accountNode.pithosAccountManager = pithosAccountManager; + [accountNode forceRefresh]; mySharedNode.pithos = pithos; - [mySharedNode refresh]; + mySharedNode.pithosAccountManager = pithosAccountManager; + [mySharedNode forceRefresh]; othersSharedNode.pithos = pithos; - [othersSharedNode refresh]; - - [activityFacility reset]; + othersSharedNode.pithosAccountManager = pithosAccountManager; + [othersSharedNode forceRefresh]; + +// [activityFacility reset]; activityFacility.delegate = self; - - refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(forceRefresh:) userInfo:self repeats:YES] retain]; + + refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL + target:self + selector:@selector(forceRefresh:) + userInfo:self + repeats:YES]; + @synchronized(self) { + browserActive = YES; + } } -- (void)setPithos:(ASIPithos *)aPithos { - if (aPithos && [aPithos isNotEqualTo:pithos]) { - [self resetBrowser]; - [pithos release]; - pithos = [aPithos retain]; - [self startBrowser]; - } +- (BOOL)operationsPending { + return ([moveNetworkQueue operationCount] || + [copyNetworkQueue operationCount] || + [deleteNetworkQueue operationCount] || + [uploadNetworkQueue operationCount] || + [downloadNetworkQueue operationCount] || + [moveQueue operationCount] || + [copyQueue operationCount] || + [deleteQueue operationCount] || + [uploadQueue operationCount] || + [downloadQueue operationCount] || + [moveCallbackQueue operationCount] || + [copyCallbackQueue operationCount] || + [deleteCallbackQueue operationCount] || + [uploadCallbackQueue operationCount] || + [downloadCallbackQueue operationCount]); } -- (void)windowDidLoad { - [super windowDidLoad]; - if (browser && !browserInitialized) { - browserInitialized = YES; - [self initBrowser]; +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self resetBrowser]; +} + +- (void)setPithos:(ASIPithos *)aPithos { + if (aPithos) { + if (![aPithos.authUser isEqualToString:pithos.authUser] || + ![aPithos.authToken isEqualToString:pithos.authToken] || + ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] || + ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) { + [self resetBrowser]; + pithos = aPithos; + [self startBrowser]; + } else { + [self startBrowser]; + } } } + #pragma mark - #pragma mark Observers - (void)pithosNodeChildrenUpdated:(NSNotification *)notification { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO]; + return; + } PithosNode *node = (PithosNode *)[notification object]; - if (node == accountNode) + if ((node == accountNode) || ![node.pithos isEqualTo:pithos]) return; - NSLog(@"pithosNodeChildrenUpdated:%@", node.url); + DLog(@"pithosNodeChildrenUpdated:%@", node.url); NSInteger lastColumn = [browser lastColumn]; for (NSInteger column = lastColumn; column >= 0; column--) { if ([[browser parentForItemsInColumn:column] isEqualTo:node]) { @@ -318,6 +421,10 @@ } - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO]; + return; + } BOOL containerPithosFound = NO; BOOL containerTrashFound = NO; NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet]; @@ -347,10 +454,7 @@ // Create pithos node ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos containerName:@"pithos"]; - [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; - while (![containerRequest isFinished]) { - usleep(1); - } + [PithosUtilities startAndWaitForRequest:containerRequest]; if ([containerRequest error]) { [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest]; } else { @@ -361,10 +465,7 @@ // Create trash node ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos containerName:@"trash"]; - [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; - while (![containerRequest isFinished]) { - usleep(1); - } + [PithosUtilities startAndWaitForRequest:containerRequest]; if ([containerRequest error]) { [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest]; } else { @@ -380,7 +481,7 @@ // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; - if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) { + if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) { rootNode = [containersNodeChildren objectAtIndex:0]; [browser loadColumnZero]; } @@ -397,6 +498,12 @@ #pragma mark Actions - (IBAction)forceRefresh:(id)sender { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO]; + return; + } + if (editingItem) + return; if (sender) [accountNode forceRefresh]; for (NSInteger column = [browser lastColumn]; column >= 0; column--) { @@ -408,6 +515,12 @@ } - (IBAction)refresh:(id)sender { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO]; + return; + } + if (editingItem) + return; if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { [self forceRefresh:sender]; } else { @@ -474,10 +587,12 @@ if (node.shared || node.sharingAccount || ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) return NO; + editingItem = YES; return YES; } - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item { + editingItem = NO; PithosNode *node = (PithosNode *)item; NSString *newName = (NSString *)object; NSUInteger newNameLength = [newName length]; @@ -489,101 +604,42 @@ } if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; - if ([newName hasSuffix:@"/"]) - destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; - NSError *error = nil; - BOOL isDirectory; - if ([PithosUtilities objectExistsAtPithos:pithos - containerName:node.pithosContainer.name - objectName:destinationObjectName - error:&error - isDirectory:&isDirectory - sharingAccount:nil]) { - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [alert setMessageText:@"Name Taken"]; - [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; - [alert addButtonWithTitle:@"OK"]; - [alert runModal]; - [pool drain]; - return; - } else if (error) { - [pool drain]; - return; - } - ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:node.pithosContainer.name - destinationObjectName:destinationObjectName - checkIfExists:NO]; - if (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 arrayWithObject:node.parent], @"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", - @"moveQueue", @"queueType", - nil]]; - [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - } - [pool drain]; - }); - } else if ([node class] == [PithosSubdirNode class]) { - if (firstSlashRange.length == 1) - return; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; - NSError *error = nil; - BOOL isDirectory; - if ([PithosUtilities objectExistsAtPithos:pithos - containerName:node.pithosContainer.name - objectName:destinationObjectName - error:&error - isDirectory:&isDirectory - sharingAccount:nil]) { - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [alert setMessageText:@"Name Taken"]; - [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; - [alert addButtonWithTitle:@"OK"]; - [alert runModal]; - [pool drain]; - return; - } else if (error) { - [pool drain]; - return; - } - if (node.pithosObject.subdir) - destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; - NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:node.pithosContainer.name - destinationObjectName:destinationObjectName - checkIfExists:NO]; - if (objectRequests) { - for (ASIPithosObjectRequest *objectRequest in objectRequests) { + // Operation: Rename (move) an object or subdir/ node + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; + if ([newName hasSuffix:@"/"]) + destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; + NSError *error = nil; + BOOL isDirectory; + if ([PithosUtilities objectExistsAtPithos:pithos + containerName:node.pithosContainer.name + objectName:destinationObjectName + error:&error + isDirectory:&isDirectory + sharingAccount:nil]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Name Taken"]; + [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + }); + return; + } else if (error) { + return; + } + if (operation.isCancelled) + return; + ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name + destinationContainerName:node.pithosContainer.name + destinationObjectName:destinationObjectName + checkIfExists:NO]; + if (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -607,13 +663,91 @@ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", moveNetworkQueue, @"networkQueue", - @"moveQueue", @"queueType", + @"move", @"operationType", nil]]; [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } } - [pool drain]; - }); + }]; + [moveQueue addOperation:operation]; + } else if ([node class] == [PithosSubdirNode class]) { + if (firstSlashRange.length == 1) + return; + // Operation: Rename (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 = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; + NSError *error = nil; + BOOL isDirectory; + if ([PithosUtilities objectExistsAtPithos:pithos + containerName:node.pithosContainer.name + objectName:destinationObjectName + error:&error + isDirectory:&isDirectory + sharingAccount:nil]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Name Taken"]; + [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + }); + return; + } else if (error) { + return; + } + if (operation.isCancelled) + return; + if (node.pithosObject.subdir) + destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; + NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name + destinationContainerName:node.pithosContainer.name + destinationObjectName:destinationObjectName + checkIfExists:NO]; + 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 arrayWithObject:node.parent], @"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]; } } @@ -654,108 +788,20 @@ - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]]; - for (PithosNode *node in draggedNodes) { + for (PithosNode *node in draggedNodes) { + [names addObject:node.displayName]; // If the node is a subdir ask if the whole tree should be downloaded if ([node class] == [PithosSubdirNode class]) { - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Download directory"]; [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; - if (choice == NSAlertFirstButtonReturn) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - toDirectory:[dropDestination path] - checkIfExists:YES - sharingAccount:node.sharingAccount]; - if (objectRequests) { - for (__block ASIPithosObjectRequest *objectRequest in objectRequests) { - [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]]; - 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:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] - 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", - @"downloadQueue", @"queueType", - 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]]; - } - } - [pool drain]; - }); - } + if (choice == NSAlertFirstButtonReturn) + [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES]; } else { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - toDirectory:[dropDestination path] - checkIfExists:YES - sharingAccount:node.sharingAccount]; - if (objectRequest) { - [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]]; - 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", - @"downloadQueue", @"queueType", - 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]]; - [pool drain]; - } - }); + [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES]; } } return names; @@ -841,223 +887,384 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { dropOperation:(NSBrowserDropOperation)dropOperation { if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; - NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames); + DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames); if ((column != -1) && (filenames != nil)) { PithosNode *node; if (row != -1) node = [browser itemAtRow:row inColumn:column]; else node = [browser parentForItemsInColumn:column]; - NSLog(@"drag in node: %@", node.url); + DLog(@"drag in node: %@", node.url); return [self uploadFiles:filenames toNode:node]; } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { - NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes); + DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes); if ((column != -1) && (draggedNodes != nil)) { PithosNode *node; if (row != -1) node = [browser itemAtRow:row inColumn:column]; else node = [browser parentForItemsInColumn:column]; - NSLog(@"drag local node: %@", node.url); + DLog(@"drag local node: %@", node.url); if ([info draggingSourceOperationMask] & NSDragOperationMove) return [self moveNodes:draggedNodes toNode:node]; else if ([info draggingSourceOperationMask] & NSDragOperationCopy) - return [self copyNodes:draggedNodes toNode:node]; + return [self cpyNodes:draggedNodes toNode:node]; } } return NO; } #pragma mark - -#pragma mark Drag and Drop methods +#pragma mark NSBrowser Actions -- (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 prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; - while (![containerRequest isFinished]) { - usleep(1); - } - if ([containerRequest error]) { - [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest]; - return NO; - } else if (containerRequest.responseStatusCode != 204) { - [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest]; - return NO; +- (void)browserDoubleAction:(id)sender { + NSInteger column = [browser clickedColumn]; + NSInteger row = [browser clickedRow]; + if ((column == -1) || (row == -1)) + return; + NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row]; + NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; + NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]]; + if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) { + for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) { + [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]]; } - 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]]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSError *error = nil; - NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error]; - if (contentType == nil) - contentType = @"application/octet-stream"; - if (error) - NSLog(@"contentType detection error: %@", error); - NSArray *hashes = nil; - 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 (objectRequest) { + } else { + [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]]; + } + NSMenuItem *menuItem = [[NSMenuItem alloc] init]; + menuItem.representedObject = menuNodes; + [self menuDownload:menuItem]; +} + +#pragma mark - +#pragma mark Drag and Drop methods + +- (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName + version:(NSString *)version checkIfExists:(BOOL)checkIfExists { + if ([node class] == [PithosSubdirNode class]) { + // XXX newFilename and version are ignored in the case of a subdir node for now + // Operation: Download a subdir node and its descendants + // The resulting ASIPithosObjectRequests are chained through dependencies + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name + toDirectory:dirPath + checkIfExists:checkIfExists + sharingAccount:node.sharingAccount]; + if (!operation.isCancelled && objectRequests) { + ASIPithosObjectRequest *previousObjectRequest = nil; + for (__block ASIPithosObjectRequest *objectRequest in objectRequests) { + if (operation.isCancelled) + return; 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 + NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload message:[messagePrefix stringByAppendingString:@" (0%)"] - totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] + totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] currentBytes:0]; + dispatch_async(dispatch_get_main_queue(), ^{ + [activityFacility updateActivity:activity withMessage:activity.message]; + }); [(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(downloadObjectFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", - uploadNetworkQueue, @"networkQueue", - @"uploadQueue", @"queueType", + downloadNetworkQueue, @"networkQueue", + @"download", @"operationType", nil]]; - if (destinationNode.sharingAccount) - [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"]; - [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; + [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)]; + }]; + if (previousObjectRequest) + [objectRequest addDependency:previousObjectRequest]; + previousObjectRequest = objectRequest; + [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; } - [pool drain]; - }); - } else { - // Upload directory, confirm first - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [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]]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - 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]; - for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) { + } + } + }]; + [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:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory - message:messagePrefix]; + 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:@" (finished)"], @"finishedActivityMessage", + [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", - NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", uploadNetworkQueue, @"networkQueue", - @"uploadQueue", @"queueType", + @"upload", @"operationType", nil]]; + if (destinationNode.sharingAccount) + [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"]; [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; } - if (objectRequests) { - for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) { - ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i]; + } + }]; + [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:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload - message:[messagePrefix stringByAppendingString:@" (0%)"] - totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] - currentBytes:0]; + NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory + message:messagePrefix]; [(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", + [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", - NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", uploadNetworkQueue, @"networkQueue", - @"uploadQueue", @"queueType", + @"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]]; } + 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]]; + } + } } - [pool drain]; - }); + }]; + [uploadQueue addOperation:operation]; } } } @@ -1079,60 +1286,21 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { for (PithosNode *node in nodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - 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 (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", - @"moveQueue", @"queueType", - nil]]; - [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - } - [pool drain]; - }); - } else if ([node class] == [PithosSubdirNode class]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - 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 (objectRequests) { - for (ASIPithosObjectRequest *objectRequest in objectRequests) { + // 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:); @@ -1146,7 +1314,6 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { [(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", @@ -1156,19 +1323,74 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", moveNetworkQueue, @"networkQueue", - @"moveQueue", @"queueType", + @"move", @"operationType", nil]]; [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } } - [pool drain]; - }); + }]; + [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)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode { +- (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; @@ -1182,76 +1404,31 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { for (PithosNode *node in nodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - 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]; - } - ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:containerName - destinationObjectName:destinationObjectName - checkIfExists:YES - sharingAccount:node.sharingAccount]; - if (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", - @"copyQueue", @"queueType", - nil]]; - [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - } - [pool drain]; - }); - } else if ([node class] == [PithosSubdirNode class]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - 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]; - } - NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:containerName - destinationObjectName:destinationObjectName - checkIfExists:YES - sharingAccount:node.sharingAccount]; - if (objectRequests) { - for (ASIPithosObjectRequest *objectRequest in objectRequests) { + // 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:); @@ -1274,13 +1451,77 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", copyNetworkQueue, @"networkQueue", - @"copyQueue", @"queueType", + @"copy", @"operationType", nil]]; [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } } - [pool drain]; - }); + }]; + [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; @@ -1290,268 +1531,271 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { #pragma mark ASIHTTPRequestDelegate - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request { - dispatch_queue_t queue; - if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"moveQueue"]) - queue = moveQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"copyQueue"]) - queue = copyQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"deleteQueue"]) - queue = deleteQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"uploadQueue"]) - queue = uploadQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"downloadQueue"]) - queue = downloadQueue; - else - return; - dispatch_async(queue, ^{ - [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request]; - }); -} - -- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request { - dispatch_queue_t queue; - if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"moveQueue"]) - queue = moveQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"copyQueue"]) - queue = copyQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"deleteQueue"]) - queue = deleteQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"uploadQueue"]) - queue = uploadQueue; - else if ([[request.userInfo objectForKey:@"queueType"] isEqualToString:@"downloadQueue"]) - queue = downloadQueue; - else - return; - dispatch_async(queue, ^{ - [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request]; - }); -} - -- (void)requestFailed:(ASIPithosRequest *)request { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Request failed: %@", request.url); - if ([request isCancelled]) { + 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"]]; }); - [pool drain]; return; } - NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue]; - if (retries > 0) { - ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease]; - [(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]]]; + // 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 { - dispatch_async(dispatch_get_main_queue(), ^{ - [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] - withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]]; + 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]; - }); + } } - [pool drain]; } - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Download finished: %@", objectRequest.url); - 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) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) { - NSLog(@"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] autorelease]; - [alert setMessageText:@"Create File Error"]; - [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]]; - [alert addButtonWithTitle:@"OK"]; - [alert runModal]; - }); + @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]; } - - 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]; } - [pool drain]; } - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Upload directory object finished: %@", objectRequest.url); - if (objectRequest.responseStatusCode == 201) { - NSLog(@"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]; + @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]; } - 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]; } - [pool drain]; } - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"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 (objectRequest.responseStatusCode == 201) { - NSLog(@"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) { - NSLog(@"Upload iteration limit reached: %@", objectRequest.url); + @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:@"failedActivityMessage"]]; - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [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]; + 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]; }); - [pool drain]; - return; - } - NSLog(@"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]; - } - [pool drain]; -} - -- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"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 (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 { + } 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:[containerRequest.userInfo objectForKey:@"containerName"] - blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] - forFile:[containerRequest.userInfo objectForKey:@"filePath"] + containerName:[objectRequest.userInfo objectForKey:@"containerName"] + blockSize:blockSize + forFile:[objectRequest.userInfo objectForKey:@"filePath"] missingBlockIndex:missingBlockIndex - sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]]; + sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]]; newContainerRequest.delegate = self; newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); - newContainerRequest.userInfo = containerRequest.userInfo; + 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)] @@ -1559,87 +1803,157 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { 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]; } - } else { - [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"]; - [self requestFailed:containerRequest]; } - [pool drain]; } -- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Move object finished: %@", objectRequest.url); - if (objectRequest.responseStatusCode == 201) { - 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]; +- (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)]; + }]; + [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]]; + } + } else { + [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"]; + [self requestFailed:containerRequest]; } - for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) { - [node refresh]; + } +} + +- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest { + @autoreleasepool { + NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; + DLog(@"Move object finished: %@", objectRequest.url); + if (operation.isCancelled) { + [self requestFailed:objectRequest]; + } else if (objectRequest.responseStatusCode == 201) { + 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]; } - 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]; } - [pool drain]; } - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Copy object finished: %@", objectRequest.url); - if (objectRequest.responseStatusCode == 201) { - 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]; + @autoreleasepool { + NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; + DLog(@"Copy object finished: %@", objectRequest.url); + if (operation.isCancelled) { + [self requestFailed:objectRequest]; + } else if (objectRequest.responseStatusCode == 201) { + 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]; } - 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]; } - [pool drain]; } - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSLog(@"Delete object finished: %@", objectRequest.url); - if (objectRequest.responseStatusCode == 204) { - 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]; + @autoreleasepool { + NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; + DLog(@"Delete object finished: %@", objectRequest.url); + if (operation.isCancelled) { + [self requestFailed:objectRequest]; + } else if (objectRequest.responseStatusCode == 204) { + 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]; } - 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]; } - [pool drain]; } #pragma mark - @@ -1647,9 +1961,9 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) - return 120; + return 140; else - return ([horizontalSplitView bounds].size.height - 108); + return ([horizontalSplitView bounds].size.height - 142); } - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { @@ -1659,23 +1973,20 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { return ([horizontalSplitView bounds].size.height - 108); } -- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex { - if (splitView == verticalSplitView) { - if (proposedPosition < 120) - return 120; - else if (proposedPosition > 220) - return 220; - else - return proposedPosition; - } else { - return ([horizontalSplitView bounds].size.height - 108); +- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { + if (((splitView == verticalSplitView) && (view == leftView)) || + ((splitView == horizontalSplitView) && (view == leftBottomView))) { + return NO; } + return YES; } #pragma mark - #pragma mark NSOutlineViewDataSource - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + if (!browserInitialized) + return 0; if (item == nil) return 2; if (item == containersNode) @@ -1686,6 +1997,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { + if (!browserInitialized) + return nil; if (item == nil) return (!index ? containersNode : sharedNode); if (item == sharedNode) @@ -1719,7 +2032,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { result = NSDragOperationCopy; } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { - if ([info draggingSourceOperationMask] & NSDragOperationMove) { + if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && + ([info draggingSourceOperationMask] & NSDragOperationMove)) { // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove if (![dropNode isEqualTo:draggedParentNode]) result = NSDragOperationMove; @@ -1734,21 +2048,22 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id)info item:(id)item childIndex:(NSInteger)index { if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; - NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames); + DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames); if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) { PithosNode *node = (PithosNode *)item; - NSLog(@"drag in node: %@", node.url); + DLog(@"drag in node: %@", node.url); return [self uploadFiles:filenames toNode:node]; } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { - NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes); + DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes); if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) { PithosNode *node = (PithosNode *)item; - NSLog(@"drag local node: %@", node.url); - if ([info draggingSourceOperationMask] & NSDragOperationMove) + DLog(@"drag local node: %@", node.url); + if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && + ([info draggingSourceOperationMask] & NSDragOperationMove)) return [self moveNodes:draggedNodes toNode:node]; else if ([info draggingSourceOperationMask] & NSDragOperationCopy) - return [self copyNodes:draggedNodes toNode:node]; + return [self cpyNodes:draggedNodes toNode:node]; } } return NO; @@ -1792,15 +2107,26 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSInteger column = [browser clickedColumn]; NSInteger row = [browser clickedRow]; if ((column == -1) || (row == -1)) { - // General context menu - NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; - if ([menuNodesIndexPaths count] == 0) { - menuNode = [browser parentForItemsInColumn:0]; - } else if (([menuNodesIndexPaths count] != 1) || - ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) { - menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)]; + if (column == -1) { + // General context menu + NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; + if ([menuNodesIndexPaths count] == 0) { + menuNode = [browser parentForItemsInColumn:0]; + } else if (([menuNodesIndexPaths count] != 1) || + ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) { + menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)]; + } else { + menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; + } } else { - menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; + menuNode = [browser parentForItemsInColumn:column]; + if ([menuNode class] == [PithosObjectNode class]) { + // Node context menu + menuNodes = [NSMutableArray arrayWithObject:menuNode]; + nodeContextMenu = YES; + } + // else + // General context menu } } else { // Node context menu @@ -1831,283 +2157,422 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { ([menuNode class] == [PithosSharingAccountsNode class]) || ([menuNode class] == [PithosEmptyNode class])) return; - BOOL shared = menuNode.shared; - BOOL sharingAccount = (menuNode.sharingAccount != nil); // New Folder - if (!shared && !sharingAccount) { - menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease]; + if (!menuNode.shared && !menuNode.sharingAccount) { + menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""]; [menuItem setRepresentedObject:menuNode]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; } + // Refresh + menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""]; + [menu addItem:menuItem]; + [menu addItem:[NSMenuItem separatorItem]]; // Get Info - menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease]; + menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") + action:@selector(menuGetInfo:) + keyEquivalent:@""]; [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]]; [menu addItem:menuItem]; // Paste - if (!shared && !sharingAccount) { - if (clipboardNodes) { - NSUInteger clipboardNodesCount = [clipboardNodes count]; - if (clipboardNodesCount == 0) { - self.clipboardNodes = nil; - } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) { - if (clipboardNodesCount == 1) - menuItemTitle = [NSString stringWithString:@"Paste Item"]; - else - menuItemTitle = [NSString stringWithString:@"Paste Items"]; - [menu addItem:[NSMenuItem separatorItem]]; - menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease]; - [menuItem setRepresentedObject:menuNode]; - [menu addItem:menuItem]; - } + if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount && + (([menuNode class] == [PithosContainerNode class]) || + (([menuNode class] == [PithosSubdirNode class]) && + (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) { + NSUInteger clipboardNodesCount = [clipboardNodes count]; + if (clipboardNodesCount == 0) { + self.clipboardNodes = nil; + } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) { + if (clipboardNodesCount == 1) + menuItemTitle = @"Paste Item"; + else + menuItemTitle = @"Paste Items"; + [menu addItem:[NSMenuItem separatorItem]]; + menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""]; + [menuItem setRepresentedObject:menuNode]; + [menu addItem:menuItem]; } } } else { // Node context menu NSUInteger menuNodesCount = [menuNodes count]; - PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0]; - BOOL shared = firstMenuNode.shared; - BOOL sharingAccount = (firstMenuNode.sharingAccount != nil); + PithosNode *firstMenuNode = [menuNodes objectAtIndex:0]; + // Download + if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) { + menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""]; + [menuItem setRepresentedObject:menuNodes]; + [menu addItem:menuItem]; + [menu addItem:[NSMenuItem separatorItem]]; + } // Move to Trash (pithos container only) // Delete - if (!shared && !sharingAccount) { - if ([rootNode class] == [PithosContainerNode class]) { - if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) { - menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease]; - [menuItem setRepresentedObject:menuNodes]; - [menu addItem:menuItem]; - } - menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease]; + if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) { + if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) { + menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash" + action:@selector(menuMoveToTrash:) + keyEquivalent:@""]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; - [menu addItem:[NSMenuItem separatorItem]]; } + menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""]; + [menuItem setRepresentedObject:menuNodes]; + [menu addItem:menuItem]; + [menu addItem:[NSMenuItem separatorItem]]; } + // Refresh + menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""]; + [menu addItem:menuItem]; // Get Info - if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) { - menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease]; + if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) { + [menu addItem:[NSMenuItem separatorItem]]; + menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") + action:@selector(menuGetInfo:) + keyEquivalent:@""]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; - if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class])) + if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class])) [menu addItem:[NSMenuItem separatorItem]]; } // Cut - if (!shared && !sharingAccount) { + if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) { if (menuNodesCount == 1) menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName]; else menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount]; - menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease]; + menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } // Copy - if ((!shared && !sharingAccount) || + if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) { if (menuNodesCount == 1) menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName]; else menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount]; - menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease]; + menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } // Paste - if (!shared && !sharingAccount) { - if (menuNodesCount == 1) { - PithosNode *menuNode = [menuNodes objectAtIndex:0]; - if (([menuNode class] == [PithosSubdirNode class]) && - (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) { - if (clipboardNodes) { - NSUInteger clipboardNodesCount = [clipboardNodes count]; - if (clipboardNodesCount == 0) { - self.clipboardNodes = nil; - } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) { - if (clipboardNodesCount == 1) - menuItemTitle = [NSString stringWithString:@"Paste Item"]; - else - menuItemTitle = [NSString stringWithString:@"Paste Items"]; - menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease]; - [menuItem setRepresentedObject:menuNode]; - [menu addItem:menuItem]; - } - } - } + if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) && + ([firstMenuNode class] == [PithosSubdirNode class]) && + (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) { + NSUInteger clipboardNodesCount = [clipboardNodes count]; + if (clipboardNodesCount == 0) { + self.clipboardNodes = nil; + } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) { + if (clipboardNodesCount == 1) + menuItemTitle = @"Paste Item"; + else + menuItemTitle = @"Paste Items"; + menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""]; + [menuItem setRepresentedObject:firstMenuNode]; + [menu addItem:menuItem]; } } } } #pragma mark - +#pragma mark NSMenuValidation + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) { + NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; + if ([menuNodesIndexPaths count] == 0) + return NO; + + PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; + if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) || + ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) && + (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) || + ((menuItem.action == @selector(delete:)) && + (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) || + ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"])))) + return NO; + + NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]]; + for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) { + [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]]; + } + menuItem.representedObject = menuNodes; + } else if (menuItem.action == @selector(paste:)) { + if (!clipboardNodes || ![clipboardNodes count]) + return NO; + + NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; + PithosNode *menuNode; + if ([menuNodesIndexPaths count] == 0) + menuNode = [browser parentForItemsInColumn:0]; + else if (([menuNodesIndexPaths count] != 1) || + ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) + menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)]; + else + menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; + + if (menuNode.shared || menuNode.sharingAccount || + (([menuNode class] != [PithosContainerNode class]) && + (([menuNode class] != [PithosSubdirNode class]) || + (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) || + (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode])) + return NO; + + menuItem.representedObject = menuNode; + } + return YES; +} + +- (void)cut:(NSMenuItem *)sender { + [self menuCut:sender]; +} + +- (void)copy:(NSMenuItem *)sender { + [self menuCopy:sender]; +} + +- (void)paste:(NSMenuItem *)sender { + [self menuPaste:sender]; +} + +- (void)delete:(NSMenuItem *)sender { + if (sender.tag == 0) + [self menuMoveToTrash:sender]; + else + [self menuDelete:sender]; +} + +#pragma mark - #pragma mark Menu Actions - (void)menuNewFolder:(NSMenuItem *)sender { PithosNode *node = (PithosNode *)[sender representedObject]; if ([node class] == [PithosContainerNode class]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos - containerName:node.pithosContainer.name - subdirName:@"untitled folder"]; - NSString *fileName = [safeObjectName lastPathComponent]; - ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:safeObjectName - eTag:nil - contentType:@"application/directory" - contentEncoding:nil - contentDisposition:nil - manifest:nil - sharing:nil - isPublic:ASIPithosObjectRequestPublicIgnore - metadata:nil - data:[NSData data]]; - objectRequest.delegate = self; - objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); - objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); - NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory - message:messagePrefix]; - objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: - fileName, @"fileName", - [NSArray arrayWithObject:node], @"refreshNodes", - [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(uploadDirectoryObjectFinished:)), @"didFinishSelector", - NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", - uploadNetworkQueue, @"networkQueue", - @"uploadQueue", @"queueType", - nil]; - [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - [pool drain]; - }); + // Operation: Create (upload) a new root application/directory object + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos + containerName:node.pithosContainer.name + subdirName:@"untitled folder"]; + NSString *fileName = [safeObjectName lastPathComponent]; + if (operation.isCancelled) + return; + ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos + containerName:node.pithosContainer.name + objectName:safeObjectName + eTag:nil + contentType:@"application/directory" + contentEncoding:nil + contentDisposition:nil + manifest:nil + sharing:nil + isPublic:ASIPithosObjectRequestPublicIgnore + metadata:nil + data:[NSData data]]; + objectRequest.delegate = self; + objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory + message:messagePrefix]; + objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + fileName, @"fileName", + [NSArray arrayWithObject:node], @"refreshNodes", + [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(uploadDirectoryObjectFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", + uploadNetworkQueue, @"networkQueue", + @"upload", @"operationType", + nil]; + [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + } + }]; + [uploadQueue addOperation:operation]; } else if (([node class] == [PithosSubdirNode class]) && (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos - containerName:node.pithosContainer.name - subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]]; - NSString *fileName = [safeObjectName lastPathComponent]; - ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:safeObjectName - eTag:nil - contentType:@"application/directory" - contentEncoding:nil - contentDisposition:nil - manifest:nil - sharing:nil - isPublic:ASIPithosObjectRequestPublicIgnore - metadata:nil - data:[NSData data]]; - objectRequest.delegate = self; - objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); - objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); - NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory - message:messagePrefix]; - objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: - fileName, @"fileName", - [NSArray arrayWithObject:node], @"refreshNodes", - [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(uploadDirectoryObjectFinished:)), @"didFinishSelector", - NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", - uploadNetworkQueue, @"networkQueue", - @"uploadQueue", @"queueType", - nil]; - [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - [pool drain]; - }); - } -} - -- (void)menuGetInfo:(NSMenuItem *)sender { - for (PithosNode *node in ((NSArray *)[sender representedObject])) { - [node showPithosNodeInfo:sender]; - } -} - -- (void)menuDelete:(NSMenuItem *)sender { - for (PithosNode *node in ((NSArray *)[sender representedObject])) { - if (([node class] == [PithosObjectNode class]) || - (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *fileName = [node.pithosObject.name lastPathComponent]; - if ([node.pithosObject.name hasSuffix:@"/"]) - fileName = [fileName stringByAppendingString:@"/"]; - ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name]; + // Operation: Create (upload) a new aplication/directory object + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos + containerName:node.pithosContainer.name + subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]]; + NSString *fileName = [safeObjectName lastPathComponent]; + if (operation.isCancelled) + return; + ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos + containerName:node.pithosContainer.name + objectName:safeObjectName + eTag:nil + contentType:@"application/directory" + contentEncoding:nil + contentDisposition:nil + manifest:nil + sharing:nil + isPublic:ASIPithosObjectRequestPublicIgnore + metadata:nil + data:[NSData data]]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); - NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete + NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory message:messagePrefix]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", - [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", + [NSArray arrayWithObject:node], @"refreshNodes", + [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(deleteObjectFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", - deleteNetworkQueue, @"networkQueue", - @"deleteQueue", @"queueType", + uploadNetworkQueue, @"networkQueue", + @"upload", @"operationType", nil]; - [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - [pool drain]; - }); + [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + } + }]; + [uploadQueue addOperation:operation]; + } +} + +- (void)menuGetInfo:(NSMenuItem *)sender { + for (PithosNode *node in ((NSArray *)[sender representedObject])) { + [node showPithosNodeInfo:sender]; + } +} + +- (void)menuDownload:(NSMenuItem *)sender { + NSArray *nodes = (NSArray *)[sender representedObject]; + PithosNode *firstNode = [nodes objectAtIndex:0]; + if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) { + NSSavePanel *save = [NSSavePanel savePanel]; + save.nameFieldStringValue = firstNode.displayName; + NSInteger result = [save runModal]; + if (result == NSOKButton) { + NSString *destinationPath = save.URL.path; + NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent]; + NSString *newFileName = [destinationPath lastPathComponent]; + if ([destinationPath hasSuffix:@"/"]) + newFileName = [newFileName stringByAppendingString:@"/"]; + if ([firstNode.displayName isEqualToString:newFileName]) + newFileName = nil; + [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO]; + } + } else { + NSOpenPanel *open = [NSOpenPanel openPanel]; + open.canChooseFiles = NO; + open.canChooseDirectories = YES; + open.canCreateDirectories = YES; + NSInteger result = [open runModal]; + if (result == NSOKButton) { + NSString *directoryPath = open.URL.path; + for (PithosNode *node in nodes) { + [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES]; + } + } + } +} + +- (void)menuDelete:(NSMenuItem *)sender { + for (PithosNode *node in ((NSArray *)[sender representedObject])) { + if (([node class] == [PithosObjectNode class]) || + (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { + // Operation: Delete an object or subdir/ node + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *fileName = [node.pithosObject.name lastPathComponent]; + if ([node.pithosObject.name hasSuffix:@"/"]) + fileName = [fileName stringByAppendingString:@"/"]; + ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name]; + if (operation.isCancelled) + return; + objectRequest.delegate = self; + objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete + message:messagePrefix]; + objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + fileName, @"fileName", + [NSArray arrayWithObject:node.parent], @"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(deleteObjectFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", + deleteNetworkQueue, @"networkQueue", + @"delete", @"operationType", + nil]; + [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + } + }]; + [deleteQueue addOperation:operation]; } else if ([node class] == [PithosSubdirNode class]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name]; - if (objectRequests) { - for (ASIPithosObjectRequest *objectRequest in objectRequests) { - objectRequest.delegate = self; - objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); - objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); - NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; - PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete - message:messagePrefix]; - [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary: - [NSDictionary dictionaryWithObjectsAndKeys: - [NSArray arrayWithObject:node.parent], @"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(deleteObjectFinished:)), @"didFinishSelector", - NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", - deleteNetworkQueue, @"networkQueue", - @"deleteQueue", @"queueType", - nil]]; - [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + // Operation: Delete a subdir node and its descendants + // The resulting ASIPithosObjectRequests are chained through dependencies + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name]; + 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:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; + PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete + message:messagePrefix]; + [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObject:node.parent], @"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(deleteObjectFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", + deleteNetworkQueue, @"networkQueue", + @"delete", @"operationType", + nil]]; + if (previousObjectRequest) + [objectRequest addDependency:previousObjectRequest]; + previousObjectRequest = objectRequest; + [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + } } } - [pool drain]; - }); + }]; + [deleteQueue addOperation:operation]; } } } @@ -2118,63 +2583,22 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos - containerName:@"trash" - objectName:node.pithosObject.name]; - if (safeObjectName) { - ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:@"trash" - destinationObjectName:safeObjectName - checkIfExists:NO]; - if (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 arrayWithObject:node.parent], @"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", - @"moveQueue", @"queueType", - nil]]; - [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; - } - } - [pool drain]; - }); - } else if ([node class] == [PithosSubdirNode class]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos - containerName:@"trash" - subdirName:node.pithosObject.name]; - if (safeObjectName) { - NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos - containerName:node.pithosContainer.name - objectName:node.pithosObject.name - destinationContainerName:@"trash" - destinationObjectName:safeObjectName - checkIfExists:NO]; - if (objectRequests) { - for (ASIPithosObjectRequest *objectRequest in objectRequests) { + // Operation: Move to trash an object or subdir/ node + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos + containerName:@"trash" + objectName:node.pithosObject.name]; + if (!operation.isCancelled && safeObjectName) { + ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name + destinationContainerName:@"trash" + destinationObjectName:safeObjectName + checkIfExists:NO]; + if (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -2197,14 +2621,70 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", moveNetworkQueue, @"networkQueue", - @"moveQueue", @"queueType", + @"move", @"operationType", nil]]; [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } } } - [pool drain]; - }); + }]; + [moveQueue addOperation:operation]; + } else if ([node class] == [PithosSubdirNode class]) { + // Operation: Move to trash a subdir node and its descendants + // The resulting ASIPithosObjectRequests are chained through dependencies + __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { + if (operation.isCancelled) + return; + NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos + containerName:@"trash" + subdirName:node.pithosObject.name]; + if (!operation.isCancelled && safeObjectName) { + NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos + containerName:node.pithosContainer.name + objectName:node.pithosObject.name + destinationContainerName:@"trash" + destinationObjectName:safeObjectName + checkIfExists:NO]; + 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 arrayWithObject:node.parent], @"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]]; + if (previousObjectRequest) + [objectRequest addDependency:previousObjectRequest]; + previousObjectRequest = objectRequest; + [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; + } + } + } + } + }]; + [moveQueue addOperation:operation]; } } } @@ -2226,12 +2706,12 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { return; PithosNode *dropNode = (PithosNode *)[sender representedObject]; NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes]; - if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) { + if (clipboardCopy) { + [self cpyNodes:localClipboardNodes toNode:dropNode]; + } else if (![dropNode isEqualTo:clipboardParentNode]) { self.clipboardNodes = nil; self.clipboardParentNode = nil; [self moveNodes:localClipboardNodes toNode:dropNode]; - } else { - [self copyNodes:localClipboardNodes toNode:dropNode]; } } @@ -2256,7 +2736,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { } if (!message) - message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount]; + message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount]; [activityTextField setStringValue:message]; }