// // PithosBrowserController.m // pithos-macos // // Copyright 2011-2012 GRNET S.A. All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed // or implied, of GRNET S.A. #import "PithosBrowserController.h" #import "PithosNode.h" #import "PithosAccountNode.h" #import "PithosContainerNode.h" #import "PithosSubdirNode.h" #import "PithosObjectNode.h" #import "PithosSharingAccountsNode.h" #import "PithosEmptyNode.h" #import "ImageAndTextCell.h" #import "FileSystemBrowserCell.h" #import "ASINetworkQueue.h" #import "ASIPithosRequest.h" #import "ASIPithos.h" #import "ASIPithosContainerRequest.h" #import "ASIPithosObjectRequest.h" #import "ASIPithosAccount.h" #import "ASIPithosContainer.h" #import "ASIPithosObject.h" #import "PithosUtilities.h" #import "UsingSizeTransformer.h" @interface PithosBrowserCell : FileSystemBrowserCell {} @end @implementation PithosBrowserCell - (id)init { if ((self = [super init])) { [self setLineBreakMode:NSLineBreakByTruncatingMiddle]; [self setEditable:YES]; } return self; } - (void)setObjectValue:(id)object { if ([object isKindOfClass:[PithosNode class]]) { PithosNode *node = (PithosNode *)object; [self setStringValue:node.displayName]; [self setImage:node.icon]; } else { [super setObjectValue:object]; } } @end @interface PithosOutlineViewCell : ImageAndTextCell {} @end @implementation PithosOutlineViewCell - (void)setObjectValue:(id)object { if ([object isKindOfClass:[PithosNode class]]) { PithosNode *node = (PithosNode *)object; [self setStringValue:node.displayName]; [self setImage:node.icon]; [self setEditable:NO]; } else { [super setObjectValue:object]; } } @end @interface PithosBrowserController (Private) - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode; - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode; - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode; @end @implementation PithosBrowserController @synthesize pithos; @synthesize accountNode; @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu; @synthesize draggedNodes, draggedParentNode; @synthesize clipboardNodes, clipboardParentNode, clipboardCopy; @synthesize activityTextField, activityProgressIndicator; #pragma mark - #pragma Object Lifecycle - (id)init { return [super initWithWindowNibName:@"PithosBrowserController"]; } - (void)windowDidLoad { [super windowDidLoad]; if (browser && !browserInitialized) { browserInitialized = YES; [self initBrowser]; } } - (void)initBrowser { [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]]; [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES]; [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]]; [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES]; [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; [browser setCellClass:[PithosBrowserCell class]]; [browser setAllowsBranchSelection:YES]; [browser setAllowsMultipleSelection:YES]; [browser setAllowsEmptySelection:YES]; [browser setAllowsTypeSelect:YES]; moveNetworkQueue = [[ASINetworkQueue alloc] init]; moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO; // moveNetworkQueue.maxConcurrentOperationCount = 1; copyNetworkQueue = [[ASINetworkQueue alloc] init]; copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO; // copyNetworkQueue.maxConcurrentOperationCount = 1; deleteNetworkQueue = [[ASINetworkQueue alloc] init]; deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO; // deleteNetworkQueue.maxConcurrentOperationCount = 1; uploadNetworkQueue = [[ASINetworkQueue alloc] init]; uploadNetworkQueue.showAccurateProgress = YES; uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO; // uploadNetworkQueue.maxConcurrentOperationCount = 1; downloadNetworkQueue = [[ASINetworkQueue alloc] init]; downloadNetworkQueue.showAccurateProgress = YES; downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO; // 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; 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]; 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.shared = YES; mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]; othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos]; othersSharedNode.displayName = @"others shared"; othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]; [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]]; // 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" object:accountNode]; // PithosAccountNode other than accountNode updates nodes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosNodeChildrenUpdated:) name:@"PithosAccountNodeChildrenUpdated" 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" object:nil]; } - (void)resetBrowser { @synchronized(self) { if (!browserActive) return; } [refreshTimer invalidate]; [refreshTimer release]; [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]; rootNode = nil; [browser loadColumnZero]; [containersNodeChildren removeAllObjects]; [outlineView reloadData]; // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; [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 forceRefresh]; mySharedNode.pithos = pithos; [mySharedNode forceRefresh]; othersSharedNode.pithos = pithos; [othersSharedNode forceRefresh]; // [activityFacility reset]; activityFacility.delegate = self; refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(forceRefresh:) userInfo:self repeats:YES] retain]; @synchronized(self) { browserActive = YES; } } - (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)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self resetBrowser]; [moveQueue release]; [copyQueue release]; [deleteQueue release]; [uploadQueue release]; [downloadQueue release]; [moveNetworkQueue release]; [copyNetworkQueue release]; [deleteNetworkQueue release]; [uploadNetworkQueue release]; [downloadNetworkQueue 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)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 release]; pithos = [aPithos retain]; [self startBrowser]; } else { [self startBrowser]; } } } #pragma mark - #pragma mark Observers - (void)pithosNodeChildrenUpdated:(NSNotification *)notification { PithosNode *node = (PithosNode *)[notification object]; if (node == accountNode) return; NSLog(@"pithosNodeChildrenUpdated:%@", node.url); NSInteger lastColumn = [browser lastColumn]; for (NSInteger column = lastColumn; column >= 0; column--) { if ([[browser parentForItemsInColumn:column] isEqualTo:node]) { [browser reloadColumn:column]; return; } } } - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification { BOOL containerPithosFound = NO; BOOL containerTrashFound = NO; NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet]; for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) { if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]]) [removedContainersNodeChildren addIndex:i]; } [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren]; for (PithosContainerNode *containerNode in accountNode.children) { if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) { if (![containersNodeChildren containsObject:containerNode]) [containersNodeChildren insertObject:containerNode atIndex:0]; containerPithosFound = YES; } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) { NSUInteger insertIndex = 1; if (!containerPithosFound) insertIndex = 0; if (![containersNodeChildren containsObject:containerNode]) [containersNodeChildren insertObject:containerNode atIndex:insertIndex]; containerTrashFound = YES; } else if (![containersNodeChildren containsObject:containerNode]) { [containersNodeChildren addObject:containerNode]; } } BOOL refreshAccountNode = NO; if (!containerPithosFound) { // Create pithos node ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos containerName:@"pithos"]; ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES]; if ([containerRequest error]) { dispatch_async(dispatch_get_main_queue(), ^{ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest]; }); } else { refreshAccountNode = YES; } } if (!containerTrashFound) { // Create trash node ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos containerName:@"trash"]; ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES]; if ([containerRequest error]) { dispatch_async(dispatch_get_main_queue(), ^{ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest]; }); } else { refreshAccountNode = YES; } } if (refreshAccountNode) [accountNode refresh]; [outlineView reloadData]; // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) { rootNode = [containersNodeChildren objectAtIndex:0]; [browser loadColumnZero]; } if (notification) [self refresh:nil]; } - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification { [self refresh:nil]; } #pragma mark - #pragma mark Actions - (IBAction)forceRefresh:(id)sender { if (sender) [accountNode forceRefresh]; for (NSInteger column = [browser lastColumn]; column >= 0; column--) { PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column]; node.forcedRefresh = YES; [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren]; } [browser validateVisibleColumns]; } - (IBAction)refresh:(id)sender { if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { [self forceRefresh:sender]; } else { if (sender) [accountNode refresh]; for (NSInteger column = [browser lastColumn]; column >= 0; column--) { [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren]; } [browser validateVisibleColumns]; } } #pragma mark - #pragma mark NSBrowserDelegate - (id)rootItemForBrowser:(NSBrowser *)browser { return rootNode; } - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item { PithosNode *node = (PithosNode *)item; return node.children.count; } - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item { PithosNode *node = (PithosNode *)item; return [node.children objectAtIndex:index]; } - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item { PithosNode *node = (PithosNode *)item; return node.isLeafItem; } - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item { PithosNode *node = (PithosNode *)item; return node; } - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item { if (sharedPreviewController == nil) sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]]; return sharedPreviewController; } //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth { // if (!forUserResize) { // id item = [browser parentForItemsInColumn:columnIndex]; // if ([self browser:browser isLeafItem:item]) { // suggestedWidth = 200; // } // } // return suggestedWidth; //} - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column { return NO; } #pragma mark Editing - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item { PithosNode *node = (PithosNode *)item; if (node.shared || node.sharingAccount || ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) return NO; return YES; } - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item { PithosNode *node = (PithosNode *)item; NSString *newName = (NSString *)object; NSUInteger newNameLength = [newName length]; NSRange firstSlashRange = [newName rangeOfString:@"/"]; if ((newNameLength == 0) || ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || ([newName isEqualToString:node.displayName])) { return; } if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { // Operation: Rename (move) an object or subdir/ node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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] 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 (operation.isCancelled) { [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 (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", [objectRequest.userInfo objectForKey:@"sourceContainerName"], [objectRequest.userInfo objectForKey:@"sourceObjectName"], [objectRequest.userInfo objectForKey:@"destinationContainerName"], [objectRequest.userInfo objectForKey:@"destinationObjectName"]]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove message:messagePrefix]; [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: [NSArray 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]]; [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:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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] 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 (operation.isCancelled) { [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 (!operation.isCancelled && objectRequests) { ASIPithosObjectRequest *previousObjectRequest = nil; for (ASIPithosObjectRequest *objectRequest in objectRequests) { if (operation.isCancelled) { [pool drain]; 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]]; } } [pool drain]; }]; [moveQueue addOperation:operation]; } } #pragma mark Drag and Drop source - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column withEvent:(NSEvent *)event { NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; __block BOOL result = YES; [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]]; if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) { result = NO; *stop = YES; } }]; return result; } - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column toPasteboard:(NSPasteboard *)pasteboard { NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]]; [propertyList addObject:[node.pithosObject.name pathExtension]]; [nodes addObject:node]; }]; [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self]; [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType]; self.draggedNodes = nodes; self.draggedParentNode = [browser parentForItemsInColumn:column]; return YES; } - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]]; for (PithosNode *node in draggedNodes) { // 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]; [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) { // Operation: Download a subdir node and its descendants // The resulting ASIPithosObjectRequests are chained through dependencies __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos containerName:node.pithosContainer.name objectName:node.pithosObject.name toDirectory:[dropDestination path] checkIfExists:YES sharingAccount:node.sharingAccount]; if (!operation.isCancelled && objectRequests) { ASIPithosObjectRequest *previousObjectRequest = nil; for (__block ASIPithosObjectRequest *objectRequest in objectRequests) { if (operation.isCancelled) { [pool drain]; return; } [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", @"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)]; }]; if (previousObjectRequest) [objectRequest addDependency:previousObjectRequest]; previousObjectRequest = objectRequest; [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; } } [pool drain]; }]; [downloadQueue addOperation:operation]; } } else { // Operation: Download an object node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos containerName:node.pithosContainer.name objectName:node.pithosObject.name toDirectory:[dropDestination path] checkIfExists:YES sharingAccount:node.sharingAccount]; if (!operation.isCancelled && 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", @"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]]; [pool drain]; } }]; [downloadQueue addOperation:operation]; } } return names; } #pragma mark Drag and Drop destination - (NSDragOperation)browser:aBrowser validateDrop:(id)info proposedRow:(NSInteger *)row column:(NSInteger *)column dropOperation:(NSBrowserDropOperation *)dropOperation { NSDragOperation result = NSDragOperationNone; if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { // For a drop above, the drop is redirected to the parent item if (*dropOperation == NSBrowserDropAbove) *row = -1; // Only allow dropping in folders if (*column != -1) { PithosNode *dropNode; if (*row != -1) { // Check if the node is not a folder and if so redirect to the parent item dropNode = [browser itemAtRow:*row inColumn:*column]; if ([dropNode class] == [PithosObjectNode class]) *row = -1; } if (*row == -1) dropNode = [browser parentForItemsInColumn:*column]; if (!dropNode.shared && (!dropNode.sharingAccount || ([dropNode class] == [PithosSubdirNode class]) || ([dropNode class] == [PithosContainerNode class]))) { *dropOperation = NSBrowserDropOn; result = NSDragOperationCopy; } } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { // For a drop above, the drop is redirected to the parent item if (*dropOperation == NSBrowserDropAbove) *row = -1; // Only allow dropping in folders if (*column != -1) { PithosNode *dropNode; if (*row != -1) { // Check if the node is not a folder and if so redirect to the parent item dropNode = [browser itemAtRow:*row inColumn:*column]; if ([dropNode class] == [PithosObjectNode class]) *row = -1; } if (*row == -1) dropNode = [browser parentForItemsInColumn:*column]; if (!dropNode.shared && !dropNode.sharingAccount) { if ([info draggingSourceOperationMask] & NSDragOperationMove) { // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove if ((([dropNode class] == [PithosContainerNode class]) || dropNode.pithosObject.subdir || ![dropNode.pithosObject.name hasSuffix:@"/"]) && ![dropNode isEqualTo:draggedParentNode]) { // ![dropNode isEqualTo:draggedParentNode] && // ![draggedNodes containsObject:dropNode]) { result = NSDragOperationMove; } } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) { // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy if (([dropNode class] == [PithosContainerNode class]) || dropNode.pithosObject.subdir || ![dropNode.pithosObject.name hasSuffix:@"/"]) { result = NSDragOperationCopy; } } } } } return result; } - (BOOL)browser:(NSBrowser *)aBrowser acceptDrop:(id)info atRow:(NSInteger)row column:(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); 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); return [self uploadFiles:filenames toNode:node]; } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { NSLog(@"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); if ([info draggingSourceOperationMask] & NSDragOperationMove) return [self moveNodes:draggedNodes toNode:node]; else if ([info draggingSourceOperationMask] & NSDragOperationCopy) return [self copyNodes:draggedNodes toNode:node]; } } return NO; } #pragma mark - #pragma mark Drag and Drop methods - (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]; ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES]; 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]]; // Operation: Upload a local file __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } 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; if (operation.isCancelled) { [pool drain]; return; } ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos containerName:containerName objectName:objectName contentType:contentType blockSize:blockSize blockHash:blockHash forFile:filePath checkIfExists:YES hashes:&hashes sharingAccount:destinationNode.sharingAccount]; if (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload message:[messagePrefix stringByAppendingString:@" (0%)"] totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] currentBytes:0]; [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: containerName, @"containerName", objectName, @"objectName", contentType, @"contentType", [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", blockHash, @"blockHash", filePath, @"filePath", hashes, @"hashes", [NSArray arrayWithObject:destinationNode], @"refreshNodes", [NSNumber numberWithBool:YES], @"refresh", [NSNumber numberWithUnsignedInteger:10], @"iteration", activity, @"activity", [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", uploadNetworkQueue, @"networkQueue", @"upload", @"operationType", nil]]; if (destinationNode.sharingAccount) [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"]; [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; } [pool drain]; }]; [uploadQueue addOperation:operation]; } 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]]; // Operation: Upload a local directory and its descendants // The resulting ASIPithosObjectRequests are chained through dependencies __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; return; } ASIPithosObjectRequest *previousObjectRequest = nil; for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) { if (operation.isCancelled) { [pool drain]; return; } objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory message:messagePrefix]; [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], @"refresh", activity, @"activity", [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", uploadNetworkQueue, @"networkQueue", @"upload", @"queue", nil]]; if (previousObjectRequest) [objectRequest addDependency:previousObjectRequest]; previousObjectRequest = objectRequest; [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]]; } if (!operation.isCancelled && objectRequests) { for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) { if (operation.isCancelled) { [pool drain]; 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", @"queue", 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]; } } } } return YES; } - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode { if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) || (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) return NO; NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name]; NSString *objectNamePrefix; if ([destinationNode class] == [PithosSubdirNode class]) objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name]; else objectNamePrefix = [NSString string]; for (PithosNode *node in nodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { // Operation: Move an object or subdir/ node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName]; if ([node.pithosObject.name hasSuffix:@"/"]) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos containerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES]; if (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", [objectRequest.userInfo objectForKey:@"sourceContainerName"], [objectRequest.userInfo objectForKey:@"sourceObjectName"], [objectRequest.userInfo objectForKey:@"destinationContainerName"], [objectRequest.userInfo objectForKey:@"destinationObjectName"]]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove message:messagePrefix]; [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", activity, @"activity", [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", moveNetworkQueue, @"networkQueue", @"move", @"operationType", nil]]; [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } [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:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; 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]]; } } [pool drain]; }]; [moveQueue addOperation:operation]; } } return YES; } - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode { if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) || (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) return NO; NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name]; NSString *objectNamePrefix; if ([destinationNode class] == [PithosSubdirNode class]) objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name]; else objectNamePrefix = [NSString string]; for (PithosNode *node in nodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { // Operation: Copy an object or subdir/ node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; return; } ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos containerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES sharingAccount:node.sharingAccount]; if (!operation.isCancelled && objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", [objectRequest.userInfo objectForKey:@"sourceContainerName"], [objectRequest.userInfo objectForKey:@"sourceObjectName"], [objectRequest.userInfo objectForKey:@"destinationContainerName"], [objectRequest.userInfo objectForKey:@"destinationObjectName"]]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy message:messagePrefix]; [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", activity, @"activity", [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", copyNetworkQueue, @"networkQueue", @"copy", @"operationType", nil]]; [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } [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:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; return; } NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos: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) { [pool drain]; 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]]; } } [pool drain]; }]; [copyQueue addOperation:operation]; } } return YES; } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request { NSOperationQueue *callbackQueue; NSString *operationType = [request.userInfo objectForKey:@"operationType"]; if ([operationType isEqualToString:@"move"]) callbackQueue = moveCallbackQueue; else if ([operationType isEqualToString:@"copy"]) callbackQueue = copyCallbackQueue; else if ([operationType isEqualToString:@"delete"]) callbackQueue = deleteCallbackQueue; else if ([operationType isEqualToString:@"upload"]) callbackQueue = uploadCallbackQueue; else if ([operationType isEqualToString:@"download"]) callbackQueue = downloadCallbackQueue; else dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]]; }); // 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] autorelease]; operation.completionBlock = ^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if ([[request.userInfo objectForKey:@"operation"] isCancelled]) { dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]]; }); } [pool drain]; }; [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"]; [callbackQueue addOperation:operation]; } - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request { if (request.isCancelled) { // Request has been cancelled // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request]; } else { NSOperationQueue *callbackQueue; NSString *operationType = [request.userInfo objectForKey:@"operationType"]; if ([operationType isEqualToString:@"move"]) callbackQueue = moveCallbackQueue; else if ([operationType isEqualToString:@"copy"]) callbackQueue = copyCallbackQueue; else if ([operationType isEqualToString:@"delete"]) callbackQueue = deleteCallbackQueue; else if ([operationType isEqualToString:@"upload"]) callbackQueue = uploadCallbackQueue; else if ([operationType isEqualToString:@"download"]) callbackQueue = downloadCallbackQueue; else dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]]; }); // 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] autorelease]; operation.completionBlock = ^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if ([[request.userInfo objectForKey:@"operation"] isCancelled]) { dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]]; }); } [pool drain]; }; [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"]; [callbackQueue addOperation:operation]; } } - (void)requestFailed:(ASIPithosRequest *)request { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOperation *operation = [request.userInfo objectForKey:@"operation"]; NSLog(@"Request failed: %@", request.url); if (operation.isCancelled) { [pool drain]; return; } if (request.isCancelled) { 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]]]; } 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]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"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) && ![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]; }); } } } 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]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"Upload directory object finished: %@", objectRequest.url); if (operation.isCancelled) { [self requestFailed:objectRequest]; } else 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]; } 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]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; 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 (operation.isCancelled) { [self requestFailed:objectRequest]; } else 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); 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]; }); [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]; NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"]; 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 (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]; } [pool drain]; } - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"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]; } [pool drain]; } - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"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]; } [pool drain]; } - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"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]; } [pool drain]; } #pragma mark - #pragma mark NSSplitViewDelegate - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) return 120; else return ([horizontalSplitView bounds].size.height - 108); } - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) return 220; else 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); } } #pragma mark - #pragma mark NSOutlineViewDataSource - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (item == nil) return 2; if (item == containersNode) return containersNodeChildren.count; if (item == sharedNode) return 2; return 0; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (item == nil) return (!index ? containersNode : sharedNode); if (item == sharedNode) return (!index ? mySharedNode : othersSharedNode); return [containersNodeChildren objectAtIndex:index]; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if ((item == containersNode) || (item == sharedNode)) return YES; return NO; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { PithosNode *node = (PithosNode *)item; return node; } #pragma mark Drag and Drop destination - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView validateDrop:(id)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { NSDragOperation result = NSDragOperationNone; if ((item == nil) || (index != NSOutlineViewDropOnItemIndex)) return result; PithosNode *dropNode = (PithosNode *)item; if ([dropNode class] != [PithosContainerNode class]) return result; if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { result = NSDragOperationCopy; } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { if ([info draggingSourceOperationMask] & NSDragOperationMove) { // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove if (![dropNode isEqualTo:draggedParentNode]) result = NSDragOperationMove; } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) { // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy result = NSDragOperationCopy; } } return result; } - (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); if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) { PithosNode *node = (PithosNode *)item; NSLog(@"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); if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) { PithosNode *node = (PithosNode *)item; NSLog(@"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 NO; } #pragma mark - #pragma mark NSOutlineViewDelegate - (BOOL)outlineView:outlineView shouldSelectItem:(id)item { if ((item == containersNode) || (item == sharedNode)) return NO; return YES; } - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item { if ((item == containersNode) || (item == sharedNode)) return YES; return NO; } - (void)outlineViewSelectionDidChange:(NSNotification *)notification { PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]]; if (node) { rootNode = node; [browser loadColumnZero]; [self refresh:nil]; } } #pragma mark - #pragma mark NSMenuDelegate - (void)menuNeedsUpdate:(NSMenu *)menu { [menu removeAllItems]; NSMenuItem *menuItem; NSString *menuItemTitle; BOOL nodeContextMenu = NO; PithosNode *menuNode = nil; NSMutableArray *menuNodes; if (menu == browserMenu) { 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)]; } else { menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; } } else { // Node context menu NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row]; NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]]; if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) { for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) { [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]]; } } else { [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]]; } nodeContextMenu = YES; } } else if (menu == outlineViewMenu) { NSInteger row = [outlineView clickedRow]; if (row == -1) row = [outlineView selectedRow]; if (row == -1) return; menuNode = [outlineView itemAtRow:row]; } if (!nodeContextMenu) { // General context menu if (([menuNode class] == [PithosAccountNode class]) || ([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]; [menuItem setRepresentedObject:menuNode]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; } // Refresh menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; // Get Info menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease]; [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]; } } } } else { // Node context menu NSUInteger menuNodesCount = [menuNodes count]; PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0]; BOOL shared = firstMenuNode.shared; BOOL sharingAccount = (firstMenuNode.sharingAccount != nil); // 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]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; } } // Refresh menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; // Get Info if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) { menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class])) [menu addItem:[NSMenuItem separatorItem]]; } // Cut if (!shared && !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 setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } // Copy if ((!shared && !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 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]; } } } } } } } #pragma mark - #pragma mark Menu Actions - (void)menuNewFolder:(NSMenuItem *)sender { PithosNode *node = (PithosNode *)[sender representedObject]; if ([node class] == [PithosContainerNode class]) { // Operation: Create (upload) a new root application/directory object __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:node.pithosContainer.name subdirName:@"untitled folder"]; NSString *fileName = [safeObjectName lastPathComponent]; if (operation.isCancelled) { [pool drain]; 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]]; [pool drain]; }]; [uploadQueue addOperation:operation]; } else if (([node class] == [PithosSubdirNode class]) && (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) { // Operation: Create (upload) a new aplication/directory object __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; return; } NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:node.pithosContainer.name subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]]; NSString *fileName = [safeObjectName lastPathComponent]; if (operation.isCancelled) { [pool drain]; 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]]; [pool drain]; }]; [uploadQueue addOperation:operation]; } } - (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:@"/"])) { // Operation: Delete an object or subdir/ node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; 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]]; [pool drain]; }]; [deleteQueue addOperation:operation]; } else if ([node class] == [PithosSubdirNode class]) { // Operation: Delete a subdir node and its descendants // The resulting ASIPithosObjectRequests are chained through dependencies __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; 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]; } } } - (void)menuMoveToTrash:(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: Move to trash an object or subdir/ node __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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:); 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]]; [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:^{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (operation.isCancelled) { [pool drain]; 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) { [pool drain]; 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]]; } } } [pool drain]; }]; [moveQueue addOperation:operation]; } } } - (void)menuCut:(NSMenuItem *)sender { self.clipboardNodes = (NSArray *)[sender representedObject]; self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent; self.clipboardCopy = NO; } - (void)menuCopy:(NSMenuItem *)sender { self.clipboardNodes = (NSArray *)[sender representedObject]; self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent; self.clipboardCopy = YES; } - (void)menuPaste:(NSMenuItem *)sender { if (!clipboardNodes || ![clipboardNodes count]) return; PithosNode *dropNode = (PithosNode *)[sender representedObject]; NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes]; if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) { self.clipboardNodes = nil; self.clipboardParentNode = nil; [self moveNodes:localClipboardNodes toNode:dropNode]; } else { [self copyNodes:localClipboardNodes toNode:dropNode]; } } #pragma mark - #pragma mark PithosActivityFacilityDelegate - (void)activityUpdate:(NSDictionary *)info { NSString *message = [info objectForKey:@"message"]; NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue]; // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue]; NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue]; NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue]; NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue]; NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue]; NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes; if (runningActivitiesCount && totalBytes) { [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))]; [activityProgressIndicator startAnimation:self]; } else { [activityProgressIndicator setDoubleValue:1.0]; [activityProgressIndicator stopAnimation:self]; } if (!message) message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount]; [activityTextField setStringValue:message]; } @end