Close node info windows when changing accounts in the browser
[pithos-macos] / pithos-macos / PithosBrowserController.m
index d7138e0..e8f0743 100644 (file)
@@ -2,7 +2,7 @@
 //  PithosBrowserController.m
 //  pithos-macos
 //
-// Copyright 2011 GRNET S.A. All rights reserved.
+// 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
 #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 "PithosFileUtilities.h"
+#import "PithosUtilities.h"
+#import "UsingSizeTransformer.h"
+
+#define REFRESH_TIMER_INTERVAL 5
 
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
@@ -59,6 +66,7 @@
 - (id)init {
     if ((self = [super init])) {
         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
+        [self setEditable:YES];
     }
     return self;
 }
 
 @end
 
-@interface PithosBrowserController (Private) {}
-- (void)resetContainers:(NSNotification *)notification;
-- (void)getInfo:(NSMenuItem *)sender;
-- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
+@interface PithosBrowserController (Private)
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
+- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
 @end
 
 @implementation PithosBrowserController
-@synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
+@synthesize pithos;
+@synthesize pithosAccountManager, accountNode;
+@synthesize draggedNodes, draggedParentNode;
+@synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
 
 #pragma mark -
 #pragma Object Lifecycle
     return [super initWithWindowNibName:@"PithosBrowserController"];
 }
 
-- (void)dealloc {
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [browserMenu release];
-    [sharedPreviewController release];
-    [outlineViewDataSourceArray release];
-    [accountNode release];
-    [rootNode release];
-    [super dealloc];
+- (void)windowDidLoad {
+    [super windowDidLoad];
+    if (browser && !browserInitialized) {
+        browserInitialized = YES;
+        [self initBrowser];
+    }
 }
 
-- (void)awakeFromNib {
-    [super awakeFromNib];
-    
-    [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
-    [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
+- (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];
+    [browser setDoubleAction:@selector(browserDoubleAction:)];
     
-    browserMenu = [[NSMenu alloc] init];
-    [browserMenu setDelegate:self];
-    [browser setMenu:browserMenu];
-}
-
-- (void)resetContainers:(NSNotification *)notification {
-    rootNode = nil;
-    [browser loadColumnZero];
-    self.outlineViewDataSourceArray = nil;
+    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;
     
-    // Create the outlineView tree
-    // CONTAINERS
-       NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
-                            [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
-
-    // SHARED
-       NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
-                                      [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
-    // SHARED/my shared
-       [[sharedTreeNode mutableChildNodes] addObject:
-     [NSTreeNode treeNodeWithRepresentedObject:
-      [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
-                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
-        ] autorelease]]];
-    // SHARED/others shared
-       [[sharedTreeNode mutableChildNodes] addObject:
-     [NSTreeNode treeNodeWithRepresentedObject:
-      [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
-                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
-        ] autorelease]]];
+    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;
     
-    self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
+    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;
     
-       // Expand the folder outline view
-    [outlineView expandItem:nil expandChildren:YES];
-       [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
+    [activityProgressIndicator setUsesThreadedAnimation:YES];
+    [activityProgressIndicator setMinValue:0.0];
+    [activityProgressIndicator setMaxValue:1.0];
+    activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
     
-    // Create accountNode and trigger a refresh
-    accountNode = [[PithosAccountNode alloc] init];
-    accountNode.children;
-}
-
-- (void)windowDidLoad {
-    [super windowDidLoad];
+    self.accountNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+    containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
+    containersNodeChildren = [[NSMutableArray alloc] init];
+    sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
+    mySharedNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+    mySharedNode.displayName = @"shared by me";
+    mySharedNode.shared = YES;
+    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
+    othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+    othersSharedNode.displayName = @"shared with me";
+    othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
     
-    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
+    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]];
     
     // Register for updates
+    // PithosAccountNode accountNode updates outlineView container nodes 
     [[NSNotificationCenter defaultCenter] addObserver:self 
-                                             selector:@selector(pithosNodeChildrenUpdated:) 
-                                                 name:@"PithosContainerNodeChildrenUpdated" 
-                                               object:nil];
+                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
+                                                 name:@"PithosNodeChildrenUpdated" 
+                                               object:accountNode];
+    // PithosNode updates browser nodes
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosNodeChildrenUpdated:) 
-                                                 name:@"PithosSubdirNodeChildrenUpdated" 
+                                                 name:@"PithosNodeChildrenUpdated" 
                                                object:nil];
+    // Request for browser refresh 
     [[NSNotificationCenter defaultCenter] addObserver:self 
-                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
-                                                 name:@"PithosAccountNodeChildrenUpdated" 
-                                               object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:self 
-                                             selector:@selector(resetContainers:) 
-                                                 name:@"PithosAuthenticationCredentialsUpdated" 
+                                             selector:@selector(pithosBrowserRefreshNeeded:) 
+                                                 name:@"PithosBrowserRefreshNeeded" 
                                                object:nil];
 }
 
+- (void)resetBrowser {
+    @synchronized(self) {
+        if (!browserActive)
+            return;
+    }
+
+    [refreshTimer invalidate];
+    
+    [moveNetworkQueue reset];
+    [copyNetworkQueue reset];
+    [deleteNetworkQueue reset];
+    [uploadNetworkQueue reset];
+    [downloadNetworkQueue reset];
+    
+    [moveQueue cancelAllOperations];
+    [moveQueue setSuspended:YES];
+    [copyQueue cancelAllOperations];
+    [copyQueue setSuspended:YES];
+    [deleteQueue cancelAllOperations];
+    [deleteQueue setSuspended:YES];
+    [uploadQueue cancelAllOperations];
+    [uploadQueue setSuspended:YES];
+    [downloadQueue cancelAllOperations];
+    [downloadQueue setSuspended:YES];
+
+    [moveCallbackQueue cancelAllOperations];
+    [moveCallbackQueue setSuspended:YES];
+    [copyCallbackQueue cancelAllOperations];
+    [copyCallbackQueue setSuspended:YES];
+    [deleteCallbackQueue cancelAllOperations];
+    [deleteCallbackQueue setSuspended:YES];
+    [uploadCallbackQueue cancelAllOperations];
+    [uploadCallbackQueue setSuspended:YES];
+    [downloadCallbackQueue cancelAllOperations];
+    [downloadCallbackQueue setSuspended:YES];
+
+    [accountNode pithosNodeWillBeRemoved];
+    [mySharedNode pithosNodeWillBeRemoved];
+    [othersSharedNode pithosNodeWillBeRemoved];
+    
+    rootNode = nil;
+    [browser loadColumnZero];
+    [containersNodeChildren removeAllObjects];
+    [outlineView reloadData];
+    // Expand the folder outline view
+    [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.pithosAccountManager = pithosAccountManager;
+    [accountNode forceRefresh];
+    mySharedNode.pithos = pithos;
+    mySharedNode.pithosAccountManager = pithosAccountManager;
+    [mySharedNode forceRefresh];
+    othersSharedNode.pithos = pithos;
+    othersSharedNode.pithosAccountManager = pithosAccountManager;
+    [othersSharedNode forceRefresh];
+            
+//    [activityFacility reset];
+    activityFacility.delegate = self;
+            
+    refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL 
+                                                     target:self 
+                                                   selector:@selector(forceRefresh:) 
+                                                   userInfo:self 
+                                                    repeats:YES];
+    @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];
+}
+
+- (void)setPithos:(ASIPithos *)aPithos {
+    if (aPithos) {
+        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
+            ![aPithos.authToken isEqualToString:pithos.authToken] || 
+            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
+            ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
+            [self resetBrowser];
+            pithos = aPithos;
+            [self startBrowser];
+        } else {
+            [self startBrowser];
+        }
+    }
+}
+
+
 #pragma mark -
-#pragma Observers
+#pragma mark Observers
 
 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
+    if (![NSThread isMainThread]) {
+        [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
+        return;
+    }
     PithosNode *node = (PithosNode *)[notification object];
-    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
+    if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
+        return;
+    DLog(@"pithosNodeChildrenUpdated:%@", node.url);
     NSInteger lastColumn = [browser lastColumn];
     for (NSInteger column = lastColumn; column >= 0; column--) {
         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
             [browser reloadColumn:column];
-            // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
-            // Furthermore it caused problems on delete, because a non-existing parent was asked for
-            //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
-            //    // This reloads the preview column
-            //    [browser setLastColumn:column];
-            //    [browser addColumn];
-            //}
             return;
         }
     }
 }
 
 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
+    if (![NSThread isMainThread]) {
+        [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
+        return;
+    }
     BOOL containerPithosFound = NO;
     BOOL containerTrashFound = NO;
-    NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
+    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"]) {
-            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
-            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
+            if (![containersNodeChildren containsObject:containerNode])
+                [containersNodeChildren insertObject:containerNode atIndex:0];
             containerPithosFound = YES;
         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
-            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];            
             NSUInteger insertIndex = 1;
             if (!containerPithosFound)
                 insertIndex = 0;
-            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
+            if (![containersNodeChildren containsObject:containerNode])
+                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
             containerTrashFound = YES;
-        } else {
-            [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
+        } else if (![containersNodeChildren containsObject:containerNode]) {
+            [containersNodeChildren addObject:containerNode];
         }
     }
     BOOL refreshAccountNode = NO;
     if (!containerPithosFound) {
-        // create pithos
-        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
-        [containerRequest startSynchronous];
+        // Create pithos node
+        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
+                                                                                                            containerName:@"pithos"];
+        [PithosUtilities startAndWaitForRequest:containerRequest];
         if ([containerRequest error]) {
-            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     }
     if (!containerTrashFound) {
-        // create trash
-        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
-        [containerRequest startSynchronous];
+        // Create trash node
+        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
+                                                                                                            containerName:@"trash"];
+        [PithosUtilities startAndWaitForRequest:containerRequest];
         if ([containerRequest error]) {
-            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     }
-    if (refreshAccountNode) {
-        [accountNode invalidateChildren];
-        accountNode.children;
-    } else {
-        [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
-        self.outlineViewDataSourceArray = outlineViewDataSourceArray;
-        
-        // Expand the folder outline view
-        [outlineView expandItem:nil expandChildren:YES];
-        [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
-        
-        [self refresh:nil];
+    
+    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 Actions
+#pragma mark Actions
 
-- (IBAction)refresh:(id)sender {
+- (IBAction)forceRefresh:(id)sender {
+    if (![NSThread isMainThread]) {
+        [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO];
+        return;
+    }
+    if (editingItem)
+        return;
+    if (sender)
+        [accountNode forceRefresh];
     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
-        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
+        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
+        node.forcedRefresh = YES;
+        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
     }
     [browser validateVisibleColumns];
 }
 
+- (IBAction)refresh:(id)sender {
+    if (![NSThread isMainThread]) {
+        [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO];
+        return;
+    }
+    if (editingItem)
+        return;
+    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
+        [self forceRefresh:sender];
+    } else {
+        if (sender)
+            [accountNode refresh];
+        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
+            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
+        }
+        [browser validateVisibleColumns];
+    }
+}
+
 #pragma mark -
-#pragma NSBrowserDelegate
+#pragma mark NSBrowserDelegate
 
 - (id)rootItemForBrowser:(NSBrowser *)browser {
     return rootNode;    
     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;
+    editingItem = YES;
+    return YES;
+}
+
+- (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
+    editingItem = NO;
+    PithosNode *node = (PithosNode *)item;
+    NSString *newName = (NSString *)object;
+    NSUInteger newNameLength = [newName length];
+    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:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
+                if ([newName hasSuffix:@"/"])
+                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                NSError *error = nil;
+                BOOL isDirectory;
+                if ([PithosUtilities objectExistsAtPithos:pithos 
+                                            containerName:node.pithosContainer.name 
+                                               objectName:destinationObjectName 
+                                                    error:&error 
+                                              isDirectory:&isDirectory 
+                                           sharingAccount:nil]) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        NSAlert *alert = [[NSAlert alloc] init];
+                        [alert setMessageText:@"Name Taken"];
+                        [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+                        [alert addButtonWithTitle:@"OK"];
+                        [alert runModal];
+                    });
+                    return;
+                } else if (error) {
+                    return;
+                }
+                if (operation.isCancelled)
+                    return;
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+                                                                                       containerName:node.pithosContainer.name 
+                                                                                          objectName:node.pithosObject.name 
+                                                                            destinationContainerName:node.pithosContainer.name 
+                                                                               destinationObjectName:destinationObjectName 
+                                                                                       checkIfExists:NO];
+                if (!operation.isCancelled && objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                    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]];
+                }
+            }
+        }];
+        [moveQueue addOperation:operation];
+    } else if ([node class] == [PithosSubdirNode class]) {
+        if (firstSlashRange.length == 1)
+            return;
+        // Operation: Rename (move) a subdir node and its descendants
+        // The resulting ASIPithosObjectRequests are chained through dependencies
+        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
+                NSError *error = nil;
+                BOOL isDirectory;
+                if ([PithosUtilities objectExistsAtPithos:pithos 
+                                            containerName:node.pithosContainer.name 
+                                               objectName:destinationObjectName 
+                                                    error:&error 
+                                              isDirectory:&isDirectory 
+                                           sharingAccount:nil]) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        NSAlert *alert = [[NSAlert alloc] init];
+                        [alert setMessageText:@"Name Taken"];
+                        [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+                        [alert addButtonWithTitle:@"OK"];
+                        [alert runModal];
+                    });
+                    return;
+                } else if (error) {
+                    return;
+                }
+                if (operation.isCancelled)
+                    return;
+                if (node.pithosObject.subdir)
+                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
+                                                                                   containerName:node.pithosContainer.name 
+                                                                                      objectName:node.pithosObject.name 
+                                                                        destinationContainerName:node.pithosContainer.name 
+                                                                           destinationObjectName:destinationObjectName 
+                                                                                   checkIfExists:NO];
+                if (!operation.isCancelled && objectRequests) {
+                    ASIPithosObjectRequest *previousObjectRequest = nil;
+                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        if (operation.isCancelled)
+                            return;
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                                   message:messagePrefix];
+                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                         [NSDictionary dictionaryWithObjectsAndKeys:
+                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                          [NSNumber numberWithBool:YES], @"refresh", 
+                          activity, @"activity", 
+                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
+                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                          moveNetworkQueue, @"networkQueue", 
+                          @"move", @"operationType", 
+                          nil]];
+                        if (previousObjectRequest)
+                            [objectRequest addDependency:previousObjectRequest];
+                        previousObjectRequest = objectRequest;
+                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                    }
+                }
+            }
+        }];
+        [moveQueue addOperation:operation];
+    }
+}
+
 #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:[rowIndexes count]];
-    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
-    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
-        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
-        
+    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
+    for (PithosNode *node in draggedNodes) {
+        [names addObject:node.displayName];
         // If the node is a subdir ask if the whole tree should be downloaded
         if ([node class] == [PithosSubdirNode class]) {
-            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+            NSAlert *alert = [[NSAlert alloc] init];
             [alert setMessageText:@"Download directory"];
             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
             [alert addButtonWithTitle:@"OK"];
             [alert addButtonWithTitle:@"Cancel"];
             NSInteger choice = [alert runModal];
-            if (choice == NSAlertFirstButtonReturn) {
-                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                dispatch_async(queue, ^{
-                    NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                     objectName:node.pithosObject.name 
-                                                                                                    toDirectory:[dropDestination path] 
-                                                                                                  checkIfExists:YES];
-                    if (objectRequests) {
-                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                            [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
-                            objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
-                            objectRequest.didFailSelector = @selector(downloadObjectFailed:);
-                            [objectRequest startAsynchronous];
-                        }
-                    }
-                });
-            }
+            if (choice == NSAlertFirstButtonReturn)
+                [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
         } else {
-            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
-                                                                                                 objectName:node.pithosObject.name 
-                                                                                                toDirectory:[dropDestination path] 
-                                                                                              checkIfExists:YES];
-            if (objectRequest) {
-                [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
-                objectRequest.delegate = self;
-                objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
-                objectRequest.didFailSelector = @selector(downloadObjectFailed:);
-                [objectRequest startAsynchronous];
-            }
+            [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
         }
-    }];
+    }
     return names;
 }
 
@@ -409,23 +815,68 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     column:(NSInteger *)column 
              dropOperation:(NSBrowserDropOperation *)dropOperation {
     NSDragOperation result = NSDragOperationNone;
-    // Files from the finder are accepted
     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
-        // For a between drop, we let the user drop "on" the parent item
+        // 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) {
-                PithosNode *node = [browser itemAtRow:*row inColumn:*column];
-                if ([node class] != [PithosSubdirNode class])
+                // 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;
             }
-            *dropOperation = NSBrowserDropOn;
-            result = NSDragOperationCopy;
+            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;
+                    }
+                }
+            }
         }
     }
-    // XXX else local file promises
     return result;
 }
 
@@ -434,313 +885,1211 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
           atRow:(NSInteger)row 
          column:(NSInteger)column 
   dropOperation:(NSBrowserDropOperation)dropOperation {
-    NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
-    NSLog(@"drag in filenames: %@", filenames);
-    PithosNode *node = nil;
-    if ((column != -1) && (filenames != nil)) {
-        if (row != -1)
-            node = [browser itemAtRow:row inColumn:column];
-        else
-            node = [browser parentForItemsInColumn:column];
-        NSLog(@"drag in node: %@", node.url);
-        if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+        if ((column != -1) && (filenames != nil)) {
+            PithosNode *node;
+            if (row != -1)
+                node = [browser itemAtRow:row inColumn:column];
+            else
+                node = [browser parentForItemsInColumn:column];
+            DLog(@"drag in node: %@", node.url);
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if ((column != -1) && (draggedNodes != nil)) {
+            PithosNode *node;
+            if (row != -1)
+                node = [browser itemAtRow:row inColumn:column];
+            else
+                node = [browser parentForItemsInColumn:column];
+            DLog(@"drag local node: %@", node.url);
+            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self cpyNodes:draggedNodes toNode:node];
+        }
+    }
+    return NO;
+}
+
+#pragma mark -
+#pragma mark NSBrowser Actions
+
+- (void)browserDoubleAction:(id)sender {
+    NSInteger column = [browser clickedColumn];
+    NSInteger row = [browser clickedRow];
+    if ((column == -1) || (row == -1))
+        return;
+    NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
+    NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+    NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+    if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
+        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+        }
+    } else {
+        [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
+    }
+    NSMenuItem *menuItem = [[NSMenuItem alloc] init];
+    menuItem.representedObject = menuNodes;
+    [self menuDownload:menuItem];
+}
+
+#pragma mark -
+#pragma mark Drag and Drop methods
+
+- (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName 
+             version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
+    if ([node class] == [PithosSubdirNode class]) {
+        // XXX newFilename and version are ignored in the case of a subdir node for now
+        // Operation: Download a subdir node and its descendants
+        // The resulting ASIPithosObjectRequests are chained through dependencies
+        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos 
+                                                                                   containerName:node.pithosContainer.name 
+                                                                                      objectName:node.pithosObject.name 
+                                                                                     toDirectory:dirPath 
+                                                                                   checkIfExists:checkIfExists 
+                                                                                  sharingAccount:node.sharingAccount];
+                if (!operation.isCancelled && objectRequests) {
+                    ASIPithosObjectRequest *previousObjectRequest = nil;
+                    for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        if (operation.isCancelled)
+                            return;
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                        NSString *messagePrefix = [NSString stringWithFormat:@"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]];
+                    }
+                }
+            }
+        }];
+        [downloadQueue addOperation:operation];
+    } else if ([node class] == [PithosObjectNode class]) {
+        // Operation: Download an object node
+        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
+                                                                                               containerName:node.pithosContainer.name 
+                                                                                                  objectName:node.pithosObject.name 
+                                                                                                     version:version 
+                                                                                                toDirectory:dirPath 
+                                                                                             withNewFileName:newFileName 
+                                                                                               checkIfExists:checkIfExists 
+                                                                                              sharingAccount:node.sharingAccount];
+                if (!operation.isCancelled && objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                    NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
+                                                                               message:[messagePrefix stringByAppendingString:@" (0%)"]
+                                                                            totalBytes:node.pithosObject.bytes 
+                                                                          currentBytes:0];
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [activityFacility updateActivity:activity withMessage:activity.message];  
+                    });
+                    [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+                     [NSDictionary dictionaryWithObjectsAndKeys:
+                      activity, @"activity", 
+                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                      [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
+                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
+                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                      NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
+                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                      downloadNetworkQueue, @"networkQueue", 
+                      @"download", @"operationType", 
+                      nil]];
+                    [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+                        [activityFacility updateActivity:activity 
+                                             withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
+                                              totalBytes:activity.totalBytes 
+                                            currentBytes:(activity.currentBytes + size)];
+                    }];
+                    [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+                }
+            }
+        }];
+        [downloadQueue addOperation:operation];
+    }
+}
+
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
+    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+        return NO;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+    if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
+        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
+                                                                                                      containerName:containerName];
+        [PithosUtilities startAndWaitForRequest:containerRequest];
+        if ([containerRequest error]) {
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
             return NO;
-        
-        NSFileManager *defaultManager = [NSFileManager defaultManager];
-        NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
-        NSString *objectNamePrefix;
-        if ([node class] == [PithosSubdirNode class])
-            objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
-        else
-            objectNamePrefix = [NSString stringWithString:@""];
-        NSUInteger blockSize = node.pithosContainer.blockSize;
-        NSString *blockHash = node.pithosContainer.blockHash;
-        
-        for (NSString *filePath in filenames) {
-            BOOL isDirectory;
-            if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
-                if (!isDirectory) {
-                    // Upload file
-                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
-                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                    dispatch_async(queue, ^{
+        } else if (containerRequest.responseStatusCode != 204) {
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+            return NO;
+        }
+        destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
+        destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
+    }    
+    NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
+    NSString *blockHash = destinationNode.pithosContainer.blockHash;
+    
+    for (NSString *filePath in filenames) {
+        BOOL isDirectory;
+        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+            if (!isDirectory) {
+                // Upload file
+                NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]] 
+                                        precomposedStringWithCanonicalMapping];
+                // Operation: Upload a local file
+                __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                    @autoreleasepool {
+                        if (operation.isCancelled)
+                            return;
                         NSError *error = nil;
-                        NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
+                        NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
                         if (contentType == nil)
                             contentType = @"application/octet-stream";
+                        #if DEBUG_PITHOS
                         if (error)
-                            NSLog(@"contentType detection error: %@", error);
+                            DLog(@"contentType detection error: %@", error);
+                        #endif
                         NSArray *hashes = nil;
-                        ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                                  objectName:objectName 
-                                                                                                                 contentType:contentType 
-                                                                                                                   blockSize:blockSize 
-                                                                                                                   blockHash:blockHash 
-                                                                                                                     forFile:filePath 
-                                                                                                               checkIfExists:YES 
-                                                                                                                      hashes:&hashes];
-                        if (objectRequest) {
+                        if (operation.isCancelled)
+                            return;
+                        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+                                                                                                    containerName:containerName 
+                                                                                                       objectName:objectName 
+                                                                                                      contentType:contentType 
+                                                                                                        blockSize:blockSize 
+                                                                                                        blockHash:blockHash 
+                                                                                                          forFile:filePath 
+                                                                                                    checkIfExists:YES 
+                                                                                                           hashes:&hashes 
+                                                                                                   sharingAccount:destinationNode.sharingAccount];
+                        if (!operation.isCancelled && objectRequest) {
                             objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-                            objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                      containerName, @"containerName", 
-                                                      objectName, @"objectName", 
-                                                      contentType, @"contentType", 
-                                                      [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
-                                                      blockHash, @"blockHash", 
-                                                      filePath, @"filePath", 
-                                                      hashes, @"hashes", 
-                                                      node, @"node", 
-                                                      [NSNumber numberWithUnsignedInteger:10], @"iteration", 
-                                                      nil];
-                            [objectRequest startAsynchronous];
+                            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]];
                         }
-                    });
-                } else {
-                    // Upload directory, confirm first
-                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-                    [alert setMessageText:@"Upload directory"];
-                    [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
-                    [alert addButtonWithTitle:@"OK"];
-                    [alert addButtonWithTitle:@"Cancel"];
-                    NSInteger choice = [alert runModal];
-                    if (choice == NSAlertFirstButtonReturn) {
-                        NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
+                    }
+                }];
+                [uploadQueue addOperation:operation];
+            } else {
+                // Upload directory, confirm first
+                NSAlert *alert = [[NSAlert alloc] init];
+                [alert setMessageText:@"Upload directory"];
+                [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
+                [alert addButtonWithTitle:@"OK"];
+                [alert addButtonWithTitle:@"Cancel"];
+                NSInteger choice = [alert runModal];
+                if (choice == NSAlertFirstButtonReturn) {
+                    NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]] 
+                                            precomposedStringWithCanonicalMapping];
+                    // Operation: Upload a local directory and its descendants
+                    // The resulting ASIPithosObjectRequests are chained through dependencies
+                    __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                        @autoreleasepool {
+                            if (operation.isCancelled)
+                                return;
                             NSMutableArray *objectNames = nil;
                             NSMutableArray *contentTypes = nil;
                             NSMutableArray *filePaths = nil;
                             NSMutableArray *hashesArrays = nil;
                             NSMutableArray *directoryObjectRequests = nil;
-                            NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
-                                                                                                         objectName:objectName 
-                                                                                                          blockSize:blockSize 
-                                                                                                          blockHash:blockHash 
-                                                                                                       forDirectory:filePath 
-                                                                                                      checkIfExists:YES 
-                                                                                                        objectNames:&objectNames
-                                                                                                       contentTypes:&contentTypes
-                                                                                                          filePaths:&filePaths
-                                                                                                         hashesArrays:&hashesArrays 
-                                                                                            directoryObjectRequests:&directoryObjectRequests];
+                            NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos 
+                                                                                           containerName:containerName 
+                                                                                              objectName:objectName 
+                                                                                               blockSize:blockSize 
+                                                                                               blockHash:blockHash 
+                                                                                            forDirectory:filePath 
+                                                                                           checkIfExists:YES 
+                                                                                             objectNames:&objectNames
+                                                                                            contentTypes:&contentTypes
+                                                                                               filePaths:&filePaths
+                                                                                            hashesArrays:&hashesArrays 
+                                                                                 directoryObjectRequests:&directoryObjectRequests 
+                                                                                          sharingAccount:destinationNode.sharingAccount];
+                            if (operation.isCancelled)
+                                return;
+                            ASIPithosObjectRequest *previousObjectRequest = nil;
                             for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
+                                if (operation.isCancelled)
+                                    return;
                                 objectRequest.delegate = self;
-                                objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
-                                objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
-                                [objectRequest startAsynchronous];
+                                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                                NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
+                                                                                           message:messagePrefix];
+                                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+                                 [NSDictionary dictionaryWithObjectsAndKeys:
+                                  [NSNumber numberWithBool:YES], @"refresh", 
+                                  activity, @"activity", 
+                                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                                  [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                                  [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
+                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
+                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                  uploadNetworkQueue, @"networkQueue", 
+                                  @"upload", @"operationType", 
+                                  nil]];
+                                if (previousObjectRequest)
+                                    [objectRequest addDependency:previousObjectRequest];
+                                previousObjectRequest = objectRequest;
+                                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
                             }
-                            if (objectRequests) {
+                            if (!operation.isCancelled && objectRequests) {
                                 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
+                                    if (operation.isCancelled)
+                                        return;
                                     ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
                                     objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-                                    objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-                                    objectRequest.userInfo = [NSMutableDictionary 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", 
-                                                              [NSNull null], @"node", 
-                                                              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
-                                                              nil];
-                                    [objectRequest startAsynchronous];
+                                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                                    NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+                                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
+                                                                                               message:[messagePrefix stringByAppendingString:@" (0%)"]
+                                                                                            totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+                                                                                          currentBytes:0];
+                                    [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+                                     [NSDictionary dictionaryWithObjectsAndKeys:
+                                      containerName, @"containerName", 
+                                      [objectNames objectAtIndex:i], @"objectName", 
+                                      [contentTypes objectAtIndex:i], @"contentType", 
+                                      [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
+                                      blockHash, @"blockHash", 
+                                      [filePaths objectAtIndex:i], @"filePath", 
+                                      [hashesArrays objectAtIndex:i], @"hashes", 
+                                      [NSNumber numberWithBool:YES], @"refresh", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+                                      activity, @"activity", 
+                                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                                      [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
+                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                      NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
+                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                      uploadNetworkQueue, @"networkQueue", 
+                                      @"upload", @"operationType", 
+                                      nil]];
+                                    if (destinationNode.sharingAccount)
+                                        [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+                                    if (previousObjectRequest)
+                                        [objectRequest addDependency:previousObjectRequest];
+                                    previousObjectRequest = objectRequest;
+                                    [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
                                 }
                             }
-                        });
-                    }
+                        }
+                    }];
+                    [uploadQueue addOperation:operation];
                 }
             }
-            
         }
-        return YES;
     }
+    return YES;
+}
 
-    return NO;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            // Operation: Move an object or subdir/ node
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if ([node.pithosObject.name hasSuffix:@"/"])
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
+                                                                                           containerName:node.pithosContainer.name 
+                                                                                              objectName:node.pithosObject.name 
+                                                                                destinationContainerName:containerName 
+                                                                                   destinationObjectName:destinationObjectName 
+                                                                                           checkIfExists:YES];
+                    if (!operation.isCancelled && objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                                   message:messagePrefix];
+                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                         [NSDictionary dictionaryWithObjectsAndKeys:
+                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
+                          activity, @"activity", 
+                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
+                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                          moveNetworkQueue, @"networkQueue", 
+                          @"move", @"operationType", 
+                          nil]];
+                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                    }
+                }
+            }];
+            [moveQueue addOperation:operation];
+        } else if ([node class] == [PithosSubdirNode class]) {
+            // Operation: Move a subdir node and its descendants
+            // The resulting ASIPithosObjectRequests are chained through dependencies
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if (node.pithosObject.subdir)
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
+                                                                                       containerName:node.pithosContainer.name 
+                                                                                          objectName:node.pithosObject.name 
+                                                                            destinationContainerName:containerName 
+                                                                               destinationObjectName:destinationObjectName 
+                                                                                       checkIfExists:YES];
+                    if (!operation.isCancelled && objectRequests) {
+                        ASIPithosObjectRequest *previousObjectRequest = nil;
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            if (operation.isCancelled)
+                                return;
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                            NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                                       message:messagePrefix];
+                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                             [NSDictionary dictionaryWithObjectsAndKeys:
+                              [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
+                              [NSNumber numberWithBool:YES], @"refresh", 
+                              activity, @"activity", 
+                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                              NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
+                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                              moveNetworkQueue, @"networkQueue", 
+                              @"move", @"operationType", 
+                              nil]];
+                            if (previousObjectRequest)
+                                [objectRequest addDependency:previousObjectRequest];
+                            previousObjectRequest = objectRequest;
+                            [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                        }
+                    }
+                }
+            }];
+            [moveQueue addOperation:operation];
+        }
+    }
+    return YES;
+}
+
+- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
+    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+    
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            // Operation: Copy an object or subdir/ node
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *destinationObjectName;
+                    if (![destinationNode isEqualTo:node.parent]) {
+                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                        if ([node.pithosObject.name hasSuffix:@"/"])
+                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    } else {
+                        destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
+                                                                           containerName:containerName 
+                                                                              objectName:node.pithosObject.name];
+                    }
+                    if (operation.isCancelled)
+                        return;
+                    ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
+                                                                                           containerName:node.pithosContainer.name 
+                                                                                              objectName:node.pithosObject.name 
+                                                                                destinationContainerName:containerName 
+                                                                                   destinationObjectName:destinationObjectName 
+                                                                                           checkIfExists:YES 
+                                                                                          sharingAccount:node.sharingAccount];
+                    if (!operation.isCancelled && objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                        NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
+                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
+                                                                                   message:messagePrefix];
+                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                         [NSDictionary dictionaryWithObjectsAndKeys:
+                          [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
+                          activity, @"activity", 
+                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
+                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                          copyNetworkQueue, @"networkQueue", 
+                          @"copy", @"operationType", 
+                          nil]];
+                        [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                    }
+                }
+            }];
+            [copyQueue addOperation:operation];
+        } else if ([node class] == [PithosSubdirNode class]) {
+            // Operation: Copy a subdir node and its descendants
+            // The resulting ASIPithosObjectRequests are chained through dependencies
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *destinationObjectName;
+                    if (![destinationNode isEqualTo:node.parent]) {
+                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                        if (node.pithosObject.subdir)
+                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    } else {
+                        destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
+                                                                           containerName:containerName 
+                                                                              subdirName:node.pithosObject.name];
+                    }
+                    if (operation.isCancelled)
+                        return;
+                    NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
+                                                                                       containerName:node.pithosContainer.name 
+                                                                                          objectName:node.pithosObject.name 
+                                                                            destinationContainerName:containerName 
+                                                                               destinationObjectName:destinationObjectName 
+                                                                                       checkIfExists:YES 
+                                                                                      sharingAccount:node.sharingAccount];
+                    if (!operation.isCancelled && objectRequests) {
+                        ASIPithosObjectRequest *previousObjectRequest = nil;
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            if (operation.isCancelled)
+                                return;
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                            NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
+                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
+                                                                                       message:messagePrefix];
+                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                             [NSDictionary dictionaryWithObjectsAndKeys:
+                              [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
+                              activity, @"activity", 
+                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                              NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
+                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                              copyNetworkQueue, @"networkQueue", 
+                              @"copy", @"operationType", 
+                              nil]];
+                            if (previousObjectRequest)
+                                [objectRequest addDependency:previousObjectRequest];
+                            previousObjectRequest = objectRequest;
+                            [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                        }
+                    }
+                }
+            }];
+            [copyQueue addOperation:operation];
+        }
+    }
+    return YES;
 }
 
 #pragma mark -
 #pragma mark ASIHTTPRequestDelegate
 
+- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
+    NSOperationQueue *callbackQueue;
+    NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+    if ([operationType isEqualToString:@"move"])
+        callbackQueue = moveCallbackQueue;
+    else if ([operationType isEqualToString:@"copy"])
+        callbackQueue = copyCallbackQueue;
+    else if ([operationType isEqualToString:@"delete"])
+        callbackQueue = deleteCallbackQueue;
+    else if ([operationType isEqualToString:@"upload"])
+        callbackQueue = uploadCallbackQueue;
+    else if ([operationType isEqualToString:@"download"])
+        callbackQueue = downloadCallbackQueue;
+    else {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+        });
+        return;
+    }
+    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
+                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
+                                                                               object:request];
+    operation.completionBlock = ^{
+        @autoreleasepool {
+            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+                });
+            }
+        }
+    };
+    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+    [callbackQueue addOperation:operation];
+}
+
+- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
+    if (request.isCancelled) {
+        // Request has been cancelled 
+        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
+        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
+                   withObject:request];
+    } else {
+        NSOperationQueue *callbackQueue;
+        NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+        if ([operationType isEqualToString:@"move"])
+            callbackQueue = moveCallbackQueue;
+        else if ([operationType isEqualToString:@"copy"])
+            callbackQueue = copyCallbackQueue;
+        else if ([operationType isEqualToString:@"delete"])
+            callbackQueue = deleteCallbackQueue;
+        else if ([operationType isEqualToString:@"upload"])
+            callbackQueue = uploadCallbackQueue;
+        else if ([operationType isEqualToString:@"download"])
+            callbackQueue = downloadCallbackQueue;
+        else {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+            });
+            return;
+        }
+        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
+                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
+                                                                                   object:request];
+        operation.completionBlock = ^{
+            @autoreleasepool {
+                if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+                    });
+                }
+            }
+        };
+        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+        [callbackQueue addOperation:operation];
+    }
+}
+
+- (void)requestFailed:(ASIPithosRequest *)request {
+    @autoreleasepool {
+        NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+        DLog(@"Request failed: %@", request.url);
+        if (operation.isCancelled)
+            return;
+        if (request.isCancelled) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+            });
+            return;
+        }
+        NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
+            [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+        } else {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                  withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+            });
+            if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
+                [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
+            else
+                [PithosUtilities httpRequestErrorAlertWithRequest:request];
+        }
+    }
+}
+
 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download completed: %@", [objectRequest url]);
-    if (objectRequest.responseStatusCode == 200) {
-        if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
-            NSLog(@"Downloaded  0 bytes");
-            NSFileManager *defaultManager = [NSFileManager defaultManager];
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Download finished: %@", objectRequest.url);
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 200) {
             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
-            if (![defaultManager fileExistsAtPath:filePath]) {
-                if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
-                    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];
+            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+            NSUInteger totalBytes = activity.totalBytes;
+            
+            // XXX change contentLength to objectContentLength if it is fixed in the server
+            if ([objectRequest contentLength] == 0) {
+                // The check above was:
+                // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
+                // I checked for directory content types in order not to create a file in place of a directory,
+                // but this callback method is not called in the case of a directory download.
+                // It maybe the case though, when downloading an old version of an object, is of a directory content type.
+                // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
+                DLog(@"Downloaded  0 bytes");
+                NSFileManager *fileManager = [NSFileManager defaultManager];
+                if (![fileManager fileExistsAtPath:filePath]) {
+                    if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
+                        dispatch_async(dispatch_get_main_queue(), ^{
+                            NSAlert *alert = [[NSAlert alloc] init];
+                            [alert setMessageText:@"Create File Error"];
+                            [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
+                            [alert addButtonWithTitle:@"OK"];
+                            [alert runModal];
+                        });
+                    }
                 }
             }
+            
+            NSUInteger currentBytes = [objectRequest objectContentLength];
+            if (currentBytes == 0)
+                currentBytes = totalBytes;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:activity 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
+                                   totalBytes:totalBytes 
+                                 currentBytes:currentBytes];
+            });
+        } else {
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
         }
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
-- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Upload directory object finished: %@", objectRequest.url);
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 201) {
+            DLog(@"Directory object created: %@", objectRequest.url);
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                    [node forceRefresh];
+                }
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                    [node refresh];
+                }
+                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                    [self forceRefresh:self];
+                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                    [self refresh:self];
+            });
+        } else {
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
+        }
+    }
 }
 
-- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload directory object completed: %@", [objectRequest url]);
-    if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"Directory object created: %@", [objectRequest url]);
-        [self refresh:nil];
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Upload using hashmap finished: %@", objectRequest.url);
+        NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
+        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+        NSUInteger totalBytes = activity.totalBytes;
+        NSUInteger currentBytes = activity.currentBytes;
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 201) {
+            DLog(@"Object created: %@", objectRequest.url);
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:activity 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
+                                   totalBytes:totalBytes 
+                                 currentBytes:totalBytes];
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                    [node forceRefresh];
+                }
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                    [node refresh];
+                }
+                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                    [self forceRefresh:self];
+                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                    [self refresh:self];        
+            });
+        } else if (objectRequest.responseStatusCode == 409) {
+            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
+            if (iteration == 0) {
+                DLog(@"Upload iteration limit reached: %@", objectRequest.url);
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [activityFacility endActivity:activity 
+                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]]; 
+                    NSAlert *alert = [[NSAlert alloc] init];
+                    [alert setMessageText:@"Upload Timeout"];
+                    [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
+                                               [objectRequest.userInfo objectForKey:@"objectName"]]];
+                    [alert addButtonWithTitle:@"OK"];
+                    [alert runModal];
+                });
+                return;
+            }
+            DLog(@"object is missing hashes: %@", objectRequest.url);
+            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+                                                              withMissingHashes:[objectRequest hashes]];
+            NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+            if (totalBytes >= [missingBlocks count]*blockSize)
+                currentBytes = totalBytes - [missingBlocks count]*blockSize;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)] 
+                                      totalBytes:totalBytes 
+                                    currentBytes:currentBytes];
+            });
+            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
+                                                                                                             containerName:[objectRequest.userInfo objectForKey:@"containerName"] 
+                                                                                                                 blockSize:blockSize 
+                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                         missingBlockIndex:missingBlockIndex 
+                                                                                                            sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
+            newContainerRequest.delegate = self;
+            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+            newContainerRequest.userInfo = objectRequest.userInfo;
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
+            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
+                                      totalBytes:activity.totalBytes 
+                                    currentBytes:(activity.currentBytes + size)];
+            }];
+            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+        } else {
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
+        }
     }
 }
 
-- (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload directory object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+    @autoreleasepool {
+        NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Upload of missing block finished: %@", containerRequest.url);
+        NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+        NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
+        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
+        if (operation.isCancelled) {
+            [self requestFailed:containerRequest];
+        } else if (containerRequest.responseStatusCode == 202) {
+            NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
+            NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+            missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+            if (missingBlockIndex == NSNotFound) {
+                NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
+                ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
+                                                                                               containerName:[containerRequest.userInfo objectForKey:@"containerName"] 
+                                                                                                  objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
+                                                                                                 contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
+                                                                                                   blockSize:blockSize 
+                                                                                                   blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
+                                                                                                     forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
+                                                                                               checkIfExists:NO 
+                                                                                                      hashes:&hashes 
+                                                                                              sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+                newObjectRequest.delegate = self;
+                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                newObjectRequest.userInfo = containerRequest.userInfo;
+                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
+                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
+                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+            } else {
+                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
+                                                                                                                 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
+                                                                                                                     blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
+                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                             missingBlockIndex:missingBlockIndex 
+                                                                                                                sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+                newContainerRequest.delegate = self;
+                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                newContainerRequest.userInfo = containerRequest.userInfo;
+                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+                    [activityFacility updateActivity:activity 
+                                         withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
+                                          totalBytes:activity.totalBytes 
+                                        currentBytes:(activity.currentBytes + size)];
+                }];
+                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+            }
+        } else {
+            [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:containerRequest];
+        }
+    }
 }
 
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
-    if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"Object created: %@", [objectRequest url]);
-        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        if (node != (id)[NSNull null]) {
-            [node.parent invalidateChildren];
-            node.parent.children;
+- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Move object finished: %@", objectRequest.url);
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 201) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                    [node forceRefresh];
+                }
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                    [node refresh];
+                }
+                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                    [self forceRefresh:self];
+                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                    [self refresh:self];
+            });
         } else {
-            [self refresh:nil];
-        }
-    } else if (objectRequest.responseStatusCode == 409) {
-        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
-        if (iteration == 0) {
-            NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
-            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"], [objectRequest.userInfo objectForKey:@"containerName"]]];
-            [alert addButtonWithTitle:@"OK"];
-            [alert runModal];
-            return;
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
         }
-        NSLog(@"object is missing hashes: %@", [objectRequest url]);
-        NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
-                                                      withMissingHashesResponse:[objectRequest responseString]];
-        NSUInteger missingBlockIndex = [missingBlocks firstIndex];
-        ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
-                                                                                                      objectName:@".upload" 
-                                                                                                       blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
-                                                                                                         forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                               missingBlockIndex:missingBlockIndex];
-        newObjectRequest.delegate = self;
-        newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
-        newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
-        newObjectRequest.userInfo = objectRequest.userInfo;
-        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
-        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
-        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
-        [newObjectRequest startAsynchronous];
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
-    }
-}
-
-- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload using hashmap failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
-}
-
-- (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
-    if (objectRequest.responseStatusCode == 201) {
-        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
-        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
-        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
-        if (missingBlockIndex == NSNotFound) {
-            NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
-            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
-                                                                                                         objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
-                                                                                                        contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
-                                                                                                          blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
-                                                                                                          blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
-                                                                                                            forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                      checkIfExists:NO 
-                                                                                                             hashes:&hashes];
-            newObjectRequest.delegate = self;
-            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-            newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-            newObjectRequest.userInfo = objectRequest.userInfo;
-            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
-            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
-            [newObjectRequest startAsynchronous];
+    }
+}
+
+- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Copy object finished: %@", objectRequest.url);
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 201) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                    [node forceRefresh];
+                }
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                    [node refresh];
+                }
+                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                    [self forceRefresh:self];
+                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                    [self refresh:self];
+            });
         } else {
-            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
-                                                                                                          objectName:@".upload" 
-                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
-                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                   missingBlockIndex:missingBlockIndex];
-            newObjectRequest.delegate = self;
-            newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
-            newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
-            newObjectRequest.userInfo = objectRequest.userInfo;
-            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
-            [newObjectRequest startAsynchronous];
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
         }
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
-- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload of missing block failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    @autoreleasepool {
+        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+        DLog(@"Delete object finished: %@", objectRequest.url);
+        if (operation.isCancelled) {
+            [self requestFailed:objectRequest];
+        } else if (objectRequest.responseStatusCode == 204) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                    [node forceRefresh];
+                }
+                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                    [node refresh];
+                }
+                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                    [self forceRefresh:self];
+                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                    [self refresh:self];
+            });
+        } else {
+            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+            [self requestFailed:objectRequest];
+        }
+    }
 }
 
 #pragma mark -
 #pragma mark NSSplitViewDelegate
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
-    return 120;
+    if (splitView == verticalSplitView)
+        return 140;
+    else
+        return ([horizontalSplitView bounds].size.height - 142);
 }
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
-    return 220;
-}
-
-- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
-    if (proposedPosition < 120)
-        return 120;
-    else if (proposedPosition > 220)
+    if (splitView == verticalSplitView)
         return 220;
     else
-        return proposedPosition;
+        return ([horizontalSplitView bounds].size.height - 108);
+}
+
+- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
+    if (((splitView == verticalSplitView) && (view == leftView)) ||
+        ((splitView == horizontalSplitView) && (view == leftBottomView))) {
+        return NO;
+    }
+    return YES;
+}
+
+#pragma mark -
+#pragma mark NSOutlineViewDataSource
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+    if (!browserInitialized)
+        return 0;
+    if (item == nil)
+        return 2;
+    if (item == containersNode)
+        return containersNodeChildren.count;
+    if (item == sharedNode)
+        return 2;
+    return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+    if (!browserInitialized)
+        return nil;
+    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<NSDraggingInfo>)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 (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
+            ([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<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            DLog(@"drag in node: %@", node.url);
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            DLog(@"drag local node: %@", node.url);
+            if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
+                ([info draggingSourceOperationMask] & NSDragOperationMove))
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self cpyNodes:draggedNodes toNode:node];
+        }
+    }
+    return NO;
 }
 
 #pragma mark -
 #pragma mark NSOutlineViewDelegate
 
 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
-    return ([[item representedObject] isLeaf]);
+    if ((item == containersNode) || (item == sharedNode))
+        return NO;
+    return YES;
 }
 
 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
-       return (![[item representedObject] isLeaf]);
+    if ((item == containersNode) || (item == sharedNode))
+        return YES;
+    return NO;
 }
 
 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
-    PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
+    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
     if (node) {
         rootNode = node;
         [browser loadColumnZero];
-        [self refresh:nil];                                  
+        [self refresh:nil];
     }
 }
 
@@ -748,257 +2097,647 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 #pragma mark NSMenuDelegate
 
 - (void)menuNeedsUpdate:(NSMenu *)menu {
-    NSInteger column = [browser clickedColumn];
-    NSInteger row = [browser clickedRow];
     [menu removeAllItems];
     NSMenuItem *menuItem;
-    if ((column == -1) || (row == -1)) {
-        // General context menu
-        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
-        PithosNode *menuNode;
-        if ([menuNodesIndexPaths count] == 0) {
-            menuNode = [browser parentForItemsInColumn:0];
-        } else if (([menuNodesIndexPaths count] != 1) || 
-            ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
-            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+    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)) {
+            if (column == -1) {
+                // General context menu
+                NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+                if ([menuNodesIndexPaths count] == 0) {
+                    menuNode = [browser parentForItemsInColumn:0];
+                } else if (([menuNodesIndexPaths count] != 1) || 
+                           ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
+                    menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+                } else {
+                    menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+                }
+            } else {
+                menuNode = [browser parentForItemsInColumn:column];
+                if ([menuNode class] == [PithosObjectNode class]) {
+                    // Node context menu
+                    menuNodes = [NSMutableArray arrayWithObject:menuNode];
+                    nodeContextMenu = YES;
+                }
+                // else 
+                // General context menu
+            }
         } else {
-            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+            // 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;
         }
-        NSArray *menuNodes = [NSArray arrayWithObject:menuNode];
+    } 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;
         // New Folder
-        menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease];
-        [menuItem setRepresentedObject:menuNodes];
+        if (!menuNode.shared && !menuNode.sharingAccount) {
+            menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
+            [menuItem setRepresentedObject:menuNode];
+            [menu addItem:menuItem];
+            [menu addItem:[NSMenuItem separatorItem]];
+        }
+        // Refresh
+        menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
         [menu addItem:menuItem];
         [menu addItem:[NSMenuItem separatorItem]];
         // Get Info
-        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
-        [menuItem setRepresentedObject:menuNodes];
+        menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
+                                               action:@selector(menuGetInfo:) 
+                                        keyEquivalent:@""];
+        [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
         [menu addItem:menuItem];
+        // Paste
+        if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount && 
+            (([menuNode class] == [PithosContainerNode class]) || 
+             (([menuNode class] == [PithosSubdirNode class]) && 
+              (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
+            NSUInteger clipboardNodesCount = [clipboardNodes count];
+            if (clipboardNodesCount == 0) {
+                self.clipboardNodes = nil;
+            } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
+                if (clipboardNodesCount == 1)
+                    menuItemTitle = @"Paste Item";
+                else
+                    menuItemTitle = @"Paste Items";
+                [menu addItem:[NSMenuItem separatorItem]];
+                menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
+                [menuItem setRepresentedObject:menuNode];
+                [menu addItem:menuItem];
+            }
+        }
     } else {
         // Node context menu
-        NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
-        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
-        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
-        if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
-            for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
-                [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
-            }
-        } else {
-            [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
+        NSUInteger menuNodesCount = [menuNodes count];
+        PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
+        // Download
+        if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
+            menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+            [menu addItem:[NSMenuItem separatorItem]];
         }
         // Move to Trash (pithos container only)
         // Delete
-        if ([rootNode class] == [PithosContainerNode class]) {
+        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
             if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
-                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
+                menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash" 
+                                                       action:@selector(menuMoveToTrash:) 
+                                                keyEquivalent:@""];
                 [menuItem setRepresentedObject:menuNodes];
                 [menu addItem:menuItem];
             }
-            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
+            menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
             [menuItem setRepresentedObject:menuNodes];
             [menu addItem:menuItem];
             [menu addItem:[NSMenuItem separatorItem]];
         }
-        // Get Info
-        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
-        [menuItem setRepresentedObject:menuNodes];
+        // Refresh
+        menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
         [menu addItem:menuItem];
+        // Get Info
+        if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
+            [menu addItem:[NSMenuItem separatorItem]];
+            menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
+                                                   action:@selector(menuGetInfo:) 
+                                            keyEquivalent:@""];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+            
+            if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
+                [menu addItem:[NSMenuItem separatorItem]];
+        }
+        // Cut
+        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
+            if (menuNodesCount == 1)
+                menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+            else 
+                menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
+            menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+        }
+        // Copy
+        if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || 
+            (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
+            if (menuNodesCount == 1)
+                menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+            else 
+                menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
+            menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+        }
+        // Paste
+        if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) && 
+            ([firstMenuNode class] == [PithosSubdirNode class]) && 
+            (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
+            NSUInteger clipboardNodesCount = [clipboardNodes count];
+            if (clipboardNodesCount == 0) {
+                self.clipboardNodes = nil;
+            } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
+                if (clipboardNodesCount == 1)
+                    menuItemTitle = @"Paste Item";
+                else
+                    menuItemTitle = @"Paste Items";
+                menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
+                [menuItem setRepresentedObject:firstMenuNode];
+                [menu addItem:menuItem];
+            }
+        }
+    }
+}
+
+#pragma mark -
+#pragma mark NSMenuValidation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+    if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        if ([menuNodesIndexPaths count] == 0)
+            return NO;
+        
+        PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+        if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) || 
+            ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) && 
+             (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
+            ((menuItem.action == @selector(delete:)) && 
+             (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) || 
+              ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
+            return NO;
+        
+        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+        }
+        menuItem.representedObject = menuNodes;
+    } else if (menuItem.action == @selector(paste:)) {
+        if (!clipboardNodes || ![clipboardNodes count])
+            return NO;
+        
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        PithosNode *menuNode;
+        if ([menuNodesIndexPaths count] == 0)
+            menuNode = [browser parentForItemsInColumn:0];
+        else if (([menuNodesIndexPaths count] != 1) || 
+                 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
+            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+        else
+            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+        
+        if (menuNode.shared || menuNode.sharingAccount || 
+            (([menuNode class] != [PithosContainerNode class]) && 
+             (([menuNode class] != [PithosSubdirNode class]) || 
+              (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) || 
+            (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
+            return NO;
+
+        menuItem.representedObject = menuNode;
     }
+    return YES;
+}
+
+- (void)cut:(NSMenuItem *)sender {
+    [self menuCut:sender];
+}
+
+- (void)copy:(NSMenuItem *)sender {
+    [self menuCopy:sender];
+}
+
+- (void)paste:(NSMenuItem *)sender {
+    [self menuPaste:sender];
+}
+
+- (void)delete:(NSMenuItem *)sender {
+    if (sender.tag == 0)
+        [self menuMoveToTrash:sender];
+    else
+        [self menuDelete:sender];
 }
 
 #pragma mark -
 #pragma mark Menu Actions
 
-- (void)newFolder:(NSMenuItem *)sender {
-    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
-        if ([node class] == [PithosContainerNode class]) {
-            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-            dispatch_async(queue, ^{
-                NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
-                                                                                    subdirName:@"untitled folder"];
-                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName: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]];
+- (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:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+                                                                      containerName:node.pithosContainer.name 
+                                                                         subdirName:@"untitled folder"];
+                NSString *fileName = [safeObjectName lastPathComponent];
+                if (operation.isCancelled)
+                    return;
+                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
+                                                                                                   containerName:node.pithosContainer.name 
+                                                                                                      objectName:safeObjectName 
+                                                                                                            eTag:nil 
+                                                                                                     contentType:@"application/directory" 
+                                                                                                 contentEncoding:nil 
+                                                                                              contentDisposition:nil 
+                                                                                                        manifest:nil 
+                                                                                                         sharing:nil 
+                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                        metadata:nil 
+                                                                                                            data:[NSData data]];
                 objectRequest.delegate = self;
-                objectRequest.didFinishSelector = @selector(newFolderFinished:);
-                objectRequest.didFailSelector = @selector(newFolderFailed:);
+                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:
-                                          node, @"node", 
+                                          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];
-                [objectRequest startAsynchronous];
-            });
-        } else if (([node class] == [PithosSubdirNode class]) && 
-                   (node.pithosObject.subdir || 
-                    ![node.pithosObject.name hasSuffix:@"/"])) {
-            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-            dispatch_async(queue, ^{
-                NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
-                                                                                   subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
-                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName: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]];
+                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+            }
+        }];
+        [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:^{
+            @autoreleasepool {
+                if (operation.isCancelled)
+                    return;
+                NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+                                                                      containerName:node.pithosContainer.name 
+                                                                         subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
+                NSString *fileName = [safeObjectName lastPathComponent];
+                if (operation.isCancelled)
+                    return;
+                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
+                                                                                                   containerName:node.pithosContainer.name 
+                                                                                                      objectName:safeObjectName 
+                                                                                                            eTag:nil
+                                                                                                     contentType:@"application/directory" 
+                                                                                                 contentEncoding:nil 
+                                                                                              contentDisposition:nil 
+                                                                                                        manifest:nil 
+                                                                                                         sharing:nil 
+                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                        metadata:nil 
+                                                                                                            data:[NSData data]];
                 objectRequest.delegate = self;
-                objectRequest.didFinishSelector = @selector(newFolderFinished:);
-                objectRequest.didFailSelector = @selector(newFolderFailed:);
+                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:
-                                         node, @"node", 
-                                         nil];
-                [objectRequest startAsynchronous];
-            });
-        }
+                                          fileName, @"fileName", 
+                                          [NSArray arrayWithObject:node], @"refreshNodes", 
+                                          [NSNumber numberWithBool:YES], @"refresh", 
+                                          activity, @"activity", 
+                                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                          NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
+                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                          uploadNetworkQueue, @"networkQueue", 
+                                          @"upload", @"operationType", 
+                                          nil];
+                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+            }
+        }];
+        [uploadQueue addOperation:operation];
     }
 }
 
-- (void)getInfo:(NSMenuItem *)sender {
+- (void)menuGetInfo:(NSMenuItem *)sender {
     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
         [node showPithosNodeInfo:sender];
     }
 }
 
-- (void)deleteObject:(NSMenuItem *)sender {
+- (void)menuDownload:(NSMenuItem *)sender {
+    NSArray *nodes = (NSArray *)[sender representedObject];
+    PithosNode *firstNode = [nodes objectAtIndex:0];
+    if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
+        NSSavePanel *save = [NSSavePanel savePanel];
+        save.nameFieldStringValue = firstNode.displayName;
+        NSInteger result = [save runModal];
+        if (result == NSOKButton) {
+            NSString *destinationPath = save.URL.path;
+            NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
+            NSString *newFileName = [destinationPath lastPathComponent];
+            if ([destinationPath hasSuffix:@"/"])
+                newFileName = [newFileName stringByAppendingString:@"/"];
+            if ([firstNode.displayName isEqualToString:newFileName])
+                newFileName = nil;
+            [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
+        }
+    } else {
+        NSOpenPanel *open = [NSOpenPanel openPanel];
+        open.canChooseFiles = NO;
+        open.canChooseDirectories = YES;
+        open.canCreateDirectories = YES;
+        NSInteger result = [open runModal];
+        if (result == NSOKButton) {
+            NSString *directoryPath = open.URL.path;
+            for (PithosNode *node in nodes) {
+                [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
+            }
+        }
+    }
+}
+
+- (void)menuDelete:(NSMenuItem *)sender {
     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
         if (([node class] == [PithosObjectNode class]) || 
-            (([node class] == [PithosSubdirNode class]) && 
-             !node.pithosObject.subdir &&
-             [node.pithosObject.name hasSuffix:@"/"])) {
-            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                      objectName:node.pithosObject.name];
-            objectRequest.delegate = self;
-            objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
-            objectRequest.didFailSelector = @selector(deleteObjectFailed:);
-            [objectRequest startAsynchronous];
+            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            // Operation: Delete an object or subdir/ node
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *fileName = [node.pithosObject.name lastPathComponent];
+                    if ([node.pithosObject.name hasSuffix:@"/"])
+                        fileName = [fileName stringByAppendingString:@"/"];
+                    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
+                                                                                                    containerName:node.pithosContainer.name 
+                                                                                                       objectName:node.pithosObject.name];
+                    if (operation.isCancelled)
+                        return;
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                    NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
+                                                                               message:messagePrefix];
+                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                              fileName, @"fileName", 
+                                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                                              activity, @"activity", 
+                                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                              NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
+                                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                              deleteNetworkQueue, @"networkQueue", 
+                                              @"delete", @"operationType", 
+                                              nil];
+                    [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                }
+            }];
+            [deleteQueue addOperation:operation];
         } else if ([node class] == [PithosSubdirNode class]) {
-            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-            dispatch_async(queue, ^{
-                NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                   objectName:node.pithosObject.name];
-                if (objectRequests) {
-                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                        objectRequest.delegate = self;
-                        objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
-                        objectRequest.didFailSelector = @selector(deleteObjectFailed:);
-                        [objectRequest startAsynchronous];
+            // Operation: Delete a subdir node and its descendants
+            // The resulting ASIPithosObjectRequests are chained through dependencies
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
+                                                                                         containerName:node.pithosContainer.name 
+                                                                                            objectName:node.pithosObject.name];
+                    if (!operation.isCancelled && objectRequests) {
+                        ASIPithosObjectRequest *previousObjectRequest = nil;
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            if (operation.isCancelled)
+                                return;
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                            NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
+                                                                                       message:messagePrefix];
+                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                             [NSDictionary dictionaryWithObjectsAndKeys:
+                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                              activity, @"activity", 
+                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                              NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
+                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                              deleteNetworkQueue, @"networkQueue", 
+                              @"delete", @"operationType", 
+                              nil]];
+                            if (previousObjectRequest)
+                                [objectRequest addDependency:previousObjectRequest];
+                            previousObjectRequest = objectRequest;
+                            [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                        }
                     }
                 }
-            });
+            }];
+            [deleteQueue addOperation:operation];
         }
     }
 }
 
-- (void)moveToTrash:(NSMenuItem *)sender {
+- (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:@"/"])) {
-            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-            dispatch_async(queue, ^{
-                NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
-                                                                                    objectName:node.pithosObject.name];
-                if (safeObjectName) {
-                    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:node.pithosContainer.name 
-                                                                                                                objectName:node.pithosObject.name 
-                                                                                                               contentType:nil 
-                                                                                                           contentEncoding:nil 
-                                                                                                        contentDisposition:nil 
-                                                                                                                  manifest:nil 
-                                                                                                                   sharing:nil 
-                                                                                                                  isPublic:ASIPithosObjectRequestPublicIgnore 
-                                                                                                                  metadata:nil 
-                                                                                                  destinationContainerName:@"trash" 
-                                                                                                     destinationObjectName:safeObjectName];
-                    objectRequest.delegate = self;
-                    objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
-                    objectRequest.didFailSelector = @selector(moveToTrashFailed:);
-                    [objectRequest startAsynchronous];
+            // Operation: Move to trash an object or subdir/ node
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
+                                                                          containerName:@"trash" 
+                                                                             objectName:node.pithosObject.name];
+                    if (!operation.isCancelled && safeObjectName) {
+                        ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
+                                                                                               containerName:node.pithosContainer.name 
+                                                                                                  objectName:node.pithosObject.name 
+                                                                                    destinationContainerName:@"trash" 
+                                                                                       destinationObjectName:safeObjectName 
+                                                                                               checkIfExists:NO];
+                        if (!operation.isCancelled && objectRequest) {
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                            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]];
+                        }
+                    }
                 }
-            });
+            }];
+            [moveQueue addOperation:operation];
         } else if ([node class] == [PithosSubdirNode class]) {
-            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-            dispatch_async(queue, ^{
-                NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
-                                                                                    subdirName:node.pithosObject.name];
-                if (safeObjectName) {
-                    NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                     objectName:node.pithosObject.name 
-                                                                                       destinationContainerName:@"trash" 
-                                                                                          destinationObjectName:safeObjectName];
-                    if (objectRequests) {
-                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                            objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
-                            objectRequest.didFailSelector = @selector(moveToTrashFailed:);
-                            [objectRequest startAsynchronous];
+            // Operation: Move to trash a subdir node and its descendants
+            // The resulting ASIPithosObjectRequests are chained through dependencies
+            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+                @autoreleasepool {
+                    if (operation.isCancelled)
+                        return;
+                    NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
+                                                                          containerName:@"trash" 
+                                                                             subdirName:node.pithosObject.name];
+                    if (!operation.isCancelled && safeObjectName) {
+                        NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
+                                                                                           containerName:node.pithosContainer.name 
+                                                                                              objectName:node.pithosObject.name 
+                                                                                destinationContainerName:@"trash" 
+                                                                                   destinationObjectName:safeObjectName 
+                                                                                           checkIfExists:NO];
+                        if (!operation.isCancelled && objectRequests) {
+                            ASIPithosObjectRequest *previousObjectRequest = nil;
+                            for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                                if (operation.isCancelled)
+                                    return;
+                                objectRequest.delegate = self;
+                                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                                NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                                           message:messagePrefix];
+                                [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                                 [NSDictionary dictionaryWithObjectsAndKeys:
+                                  [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                                  activity, @"activity", 
+                                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
+                                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
+                                  [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
+                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                  NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
+                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                  moveNetworkQueue, @"networkQueue", 
+                                  @"move", @"operationType", 
+                                  nil]];
+                                if (previousObjectRequest)
+                                    [objectRequest addDependency:previousObjectRequest];
+                                previousObjectRequest = objectRequest;
+                                [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                            }
                         }
                     }
                 }
-            });
+            }];
+            [moveQueue addOperation:operation];
         }
     }
 }
 
-#pragma mark -
-#pragma mark Menu Actions ASIHTTPRequestDelegate
-
-- (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
-    if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"New application/directory object created: %@", [objectRequest url]);
-        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        [node invalidateChildren];
-        node.children;
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
-    }
+- (void)menuCut:(NSMenuItem *)sender {
+    self.clipboardNodes = (NSArray *)[sender representedObject];
+    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+    self.clipboardCopy = NO;
 }
 
-- (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Creation of new application/directory object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+- (void)menuCopy:(NSMenuItem *)sender {
+    self.clipboardNodes = (NSArray *)[sender representedObject];
+    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+    self.clipboardCopy = YES;
 }
 
-- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
-    if (objectRequest.responseStatusCode == 204) {
-        NSLog(@"Object deleted: %@", [objectRequest url]);
-        [self refresh:nil];
-    } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+- (void)menuPaste:(NSMenuItem *)sender {
+    if (!clipboardNodes || ![clipboardNodes count])
+        return;
+    PithosNode *dropNode = (PithosNode *)[sender representedObject];
+    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
+    if (clipboardCopy) {
+        [self cpyNodes:localClipboardNodes toNode:dropNode];
+    } else if (![dropNode isEqualTo:clipboardParentNode]) {
+        self.clipboardNodes = nil;
+        self.clipboardParentNode = nil;
+        [self moveNodes:localClipboardNodes toNode:dropNode];
     }
 }
-
-- (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Delete of object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
-}
-
-- (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
-    if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"Object moved: %@", [objectRequest url]);
-        [self refresh:nil];
+    
+#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 {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        [activityProgressIndicator setDoubleValue:1.0];
+        [activityProgressIndicator stopAnimation:self];
     }
+    
+    if (!message)
+        message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
+    [activityTextField setStringValue:message];
 }
 
-- (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Move of object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
-}
-
-
 @end
\ No newline at end of file