All requests made asynchronous.
[pithos-macos] / pithos-macos / PithosBrowserController.m
index f2cace6..fce0de3 100644 (file)
 #import "PithosContainerNode.h"
 #import "PithosSubdirNode.h"
 #import "PithosObjectNode.h"
+#import "PithosSharingAccountsNode.h"
 #import "PithosEmptyNode.h"
 #import "ImageAndTextCell.h"
 #import "FileSystemBrowserCell.h"
 #import "ASIPithosRequest.h"
 #import "ASIPithosContainerRequest.h"
 #import "ASIPithosObjectRequest.h"
+#import "ASIPithosAccount.h"
 #import "ASIPithosContainer.h"
 #import "ASIPithosObject.h"
-#import "PithosFileUtilities.h"
+#import "PithosUtilities.h"
+#import "BytesSizeTransformer.h"
 
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
 
 @interface PithosBrowserController (Private) {}
 - (void)resetContainers:(NSNotification *)notification;
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
+- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
 @end
 
 @implementation PithosBrowserController
-@synthesize outlineViewDataSourceArray, verticalSplitView, horizontalSplitView, leftTopView, leftBottomView,
-            outlineView, browser;
+@synthesize accountNode;
+@synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
 @synthesize draggedNodes, draggedParentNode;
 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
+@synthesize activityTextField, activityProgressIndicator;
 
 #pragma mark -
 #pragma Object Lifecycle
     [clipboardNodes release];
     [draggedParentNode release];
     [draggedNodes release];
-    [browserMenu release];
     [sharedPreviewController release];
-    [outlineViewDataSourceArray release];
-    [mySharedRootNode release];
+    [othersSharedNode release];
+    [mySharedNode release];
+    [sharedNode release];
+    [containersNodeChildren release];
+    [containersNode release];
     [accountNode release];
     [rootNode release];
     [super dealloc];
     [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
     
-    [browser setCellClass:[PithosBrowserCell class]];
+    [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
+    [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
+    [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
     
-    browserMenu = [[NSMenu alloc] init];
-    [browserMenu setDelegate:self];
-    [browser setMenu:browserMenu];
+    [browser setCellClass:[PithosBrowserCell class]];
 }
 
 - (void)resetContainers:(NSNotification *)notification {
     rootNode = nil;
     [browser loadColumnZero];
-    self.outlineViewDataSourceArray = nil;
-    
-    // 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:mySharedRootNode]];
-    // SHARED/others shared
-       [[sharedTreeNode mutableChildNodes] addObject:
-     [NSTreeNode treeNodeWithRepresentedObject:
-      [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
-                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
-        ] autorelease]]];
-    
-    self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
-    
+    [containersNodeChildren removeAllObjects];
+    [outlineView reloadData];
        // Expand the folder outline view
     [outlineView expandItem:nil expandChildren:YES];
        [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
     
     // Refresh account
     [accountNode refresh];
+    [mySharedNode refresh];
+    [othersSharedNode refresh];
+    
+    [activityFacility reset];
 }
 
 - (void)windowDidLoad {
     [super windowDidLoad];
     
+    [activityProgressIndicator setUsesThreadedAnimation:YES];
+    [activityProgressIndicator setMinValue:0.0];
+    [activityProgressIndicator setMaxValue:1.0];
+    activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
+    activityFacility.delegate = self;
+    
     accountNode = [[PithosAccountNode alloc] init];
-    mySharedRootNode = [[PithosAccountNode alloc] init];
-    mySharedRootNode.displayName = @"my shared";
-    mySharedRootNode.shared = YES;
-    mySharedRootNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
+    containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
+    containersNodeChildren = [[NSMutableArray alloc] init];
+    sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
+    mySharedNode = [[PithosAccountNode alloc] init];
+    mySharedNode.displayName = @"my shared";
+    mySharedNode.shared = YES;
+    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
+    othersSharedNode = [[PithosSharingAccountsNode alloc] init];
+    othersSharedNode.displayName = @"others shared";
+    othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
     
     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
     
     // Register for updates
+    // PithosContainerNode updates browser nodes
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosNodeChildrenUpdated:) 
                                                  name:@"PithosContainerNodeChildrenUpdated" 
                                                object:nil];
+    // PithosSubdirNode updates browser nodes
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosNodeChildrenUpdated:) 
                                                  name:@"PithosSubdirNodeChildrenUpdated" 
                                                object:nil];
+    // PithosAccountNode accountNode updates outlineView container nodes 
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosAccountNodeChildrenUpdated:) 
                                                  name:@"PithosAccountNodeChildrenUpdated" 
                                                object:accountNode];
+    // PithosAccountNode other than accountNode updates nodes 
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosNodeChildrenUpdated:) 
                                                  name:@"PithosAccountNodeChildrenUpdated" 
-                                               object:mySharedRootNode];
+                                               object:nil];
+    // PithosSharingAccountsNode othersSharedNode updates browser nodes 
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosNodeChildrenUpdated:) 
+                                                 name:@"PithosSharingAccountsNodeChildrenUpdated" 
+                                               object:othersSharedNode];
+    // Updated authentication credentials reset containers in the outline view 
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(resetContainers:) 
                                                  name:@"PithosAuthenticationCredentialsUpdated" 
                                                object:nil];
+    // Request for browser refresh 
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosBrowserRefreshNeeded:) 
+                                                 name:@"PithosBrowserRefreshNeeeded" 
+                                               object:nil];    
 }
 
 #pragma mark -
-#pragma Observers
+#pragma mark Observers
 
 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
     PithosNode *node = (PithosNode *)[notification object];
+    if (node == accountNode)
+        return;
     NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
     NSInteger lastColumn = [browser lastColumn];
     for (NSInteger column = lastColumn; column >= 0; column--) {
 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
     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"]) {
-            [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"]) {
             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 node
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
-        [containerRequest startSynchronous];
+        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        while (![containerRequest isFinished]) {
+            sleep(1);
+        }
         if ([containerRequest error]) {
-            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     if (!containerTrashFound) {
         // Create trash node
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
-        [containerRequest startSynchronous];
+        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        while (![containerRequest isFinished]) {
+            sleep(1);
+        }
         if ([containerRequest error]) {
-            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     }
-    if (refreshAccountNode) {
+    
+    if (refreshAccountNode)
         [accountNode refresh];
-    } 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];
+    
+    [outlineView reloadData];
+    
+    // Expand the folder outline view
+    [outlineView expandItem:nil expandChildren:YES];
+    
+    if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
+        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 (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 ([[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;    
 
 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
     PithosNode *node = (PithosNode *)item;
-    if (node.shared || ([node class] == [PithosContainerNode class]))
+    if (node.shared || node.sharingAccount || 
+        ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
         return NO;
     return YES;
 }
         return;
     }
     if (([node class] == [PithosObjectNode class]) || 
-        (([node class] == [PithosSubdirNode class]) && 
-         !node.pithosObject.subdir &&
-         [node.pithosObject.name hasSuffix:@"/"])) {
+        (([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 *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
             NSError *error = nil;
             BOOL isDirectory;
-            if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
-                                                      objectName:destinationObjectName 
-                                                           error:&error 
-                                                     isDirectory:&isDirectory]) {
+            if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name 
+                                                  objectName:destinationObjectName 
+                                                       error:&error 
+                                                 isDirectory:&isDirectory 
+                                              sharingAccount:nil]) {
                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                 [alert setMessageText:@"Name Taken"];
                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
             } else if (error) {
                 return;
             }
-            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                 objectName:node.pithosObject.name 
-                                                                                   destinationContainerName:node.pithosContainer.name 
-                                                                                      destinationObjectName:destinationObjectName 
-                                                                                              checkIfExists:NO];
+            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                             objectName:node.pithosObject.name 
+                                                                               destinationContainerName:node.pithosContainer.name 
+                                                                                  destinationObjectName:destinationObjectName 
+                                                                                          checkIfExists:NO];
             if (objectRequest) {
                 objectRequest.delegate = self;
                 objectRequest.didFinishSelector = @selector(moveFinished:);
                 objectRequest.didFailSelector = @selector(moveFailed:);
-                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                          node.parent, @"node", 
-                                          nil];
-                [objectRequest startAsynchronous];
+                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                           message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                                                    [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                    [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                    [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                    [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                 [NSDictionary dictionaryWithObjectsAndKeys:
+                  [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                  [NSNumber numberWithBool:YES], @"refresh", 
+                  activity, @"activity", 
+                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                  nil]];
+                [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
             }
         });
     } else if ([node class] == [PithosSubdirNode class]) {
             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
             NSError *error = nil;
             BOOL isDirectory;
-            if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
-                                                      objectName:destinationObjectName 
-                                                           error:&error 
-                                                     isDirectory:&isDirectory]) {
+            if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name 
+                                                  objectName:destinationObjectName 
+                                                       error:&error 
+                                                 isDirectory:&isDirectory 
+                                              sharingAccount:nil]) {
                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                 [alert setMessageText:@"Name Taken"];
                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
             }
             if (node.pithosObject.subdir)
                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-            NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                             objectName:node.pithosObject.name 
-                                                                               destinationContainerName:node.pithosContainer.name 
-                                                                                  destinationObjectName:destinationObjectName 
-                                                                                          checkIfExists:NO];
+            NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                         objectName:node.pithosObject.name 
+                                                                           destinationContainerName:node.pithosContainer.name 
+                                                                              destinationObjectName:destinationObjectName 
+                                                                                      checkIfExists:NO];
             if (objectRequests) {
                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
                     objectRequest.delegate = self;
                     objectRequest.didFinishSelector = @selector(moveFinished:);
                     objectRequest.didFailSelector = @selector(moveFailed:);
-                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                              node.parent, @"node", 
-                                              nil];
-                    [objectRequest startAsynchronous];
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                               message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                     [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                      [NSNumber numberWithBool:YES], @"refresh", 
+                      activity, @"activity", 
+                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                      nil]];
+                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                 }
             }
         });
     __block BOOL result = YES;
     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
-        if ([node class] == [PithosContainerNode class]) {
+        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
             result = NO;
             *stop = 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) {        
         // 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];
@@ -484,35 +563,67 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             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 
+                    NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
                                                                                                      objectName:node.pithosObject.name 
                                                                                                     toDirectory:[dropDestination path] 
-                                                                                                  checkIfExists:YES];
+                                                                                                  checkIfExists:YES 
+                                                                                                 sharingAccount:node.sharingAccount];
                     if (objectRequests) {
-                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
                             [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
                             objectRequest.delegate = self;
                             objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
                             objectRequest.didFailSelector = @selector(downloadObjectFailed:);
-                            [objectRequest startAsynchronous];
+                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
+                                                                                       message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo objectForKey:@"fileName"]] 
+                                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
+                                                                                  currentBytes:0];
+                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+                             [NSDictionary dictionaryWithObjectsAndKeys:
+                              activity, @"activity", 
+                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                              nil]];
+                            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+                                [activityFacility updateActivity:activity 
+                                                     withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                                      totalBytes:activity.totalBytes 
+                                                    currentBytes:(activity.currentBytes + size)];
+                            }];
+                            [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
                         }
                     }
                 });
             }
         } else {
-            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
-                                                                                                 objectName:node.pithosObject.name 
-                                                                                                toDirectory:[dropDestination path] 
-                                                                                              checkIfExists:YES];
+            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                                    toDirectory:[dropDestination path] 
+                                                                                                  checkIfExists:YES 
+                                                                                                 sharingAccount:node.sharingAccount];
             if (objectRequest) {
                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
                 objectRequest.delegate = self;
                 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
                 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
-                [objectRequest startAsynchronous];
+                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
+                                                                           message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
+                                                                        totalBytes:node.pithosObject.bytes 
+                                                                      currentBytes:0];
+                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+                 [NSDictionary dictionaryWithObjectsAndKeys:
+                  activity, @"activity", 
+                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                  nil]];
+                [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+                    [activityFacility updateActivity:activity 
+                                         withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                          totalBytes:activity.totalBytes 
+                                        currentBytes:(activity.currentBytes + size)];
+                }];
+                [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
             }
         }
-    }];
+    }
     return names;
 }
 
@@ -530,13 +641,23 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             *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
-                if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
+                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
@@ -548,13 +669,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             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] != [PithosSubdirNode class])
+                if ([dropNode class] == [PithosObjectNode class])
                     *row = -1;
             }
             if (*row == -1)
                 dropNode = [browser parentForItemsInColumn:*column];
             
-            if (!dropNode.shared) {
+            if (!dropNode.shared && !dropNode.sharingAccount) {
                 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
                     // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
                     if ((([dropNode class] == [PithosContainerNode class]) || 
@@ -588,280 +709,378 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
         NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
         if ((column != -1) && (filenames != nil)) {
-            PithosNode *node = nil;
+            PithosNode *node;
             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]))
-                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];
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if ((column != -1) && (draggedNodes != nil)) {
+            PithosNode *node;
+            if (row != -1)
+                node = [browser itemAtRow:row inColumn:column];
             else
-                objectNamePrefix = [NSString string];
-            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, ^{
-                            NSError *error = nil;
-                            NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
-                            if (contentType == nil)
-                                contentType = @"application/octet-stream";
-                            if (error)
-                                NSLog(@"contentType detection error: %@", error);
-                            NSArray *hashes = nil;
-                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                                      objectName:objectName 
-                                                                                                                     contentType:contentType 
-                                                                                                                       blockSize:blockSize 
-                                                                                                                       blockHash:blockHash 
-                                                                                                                         forFile:filePath 
-                                                                                                                   checkIfExists:YES 
-                                                                                                                          hashes:&hashes];
-                            if (objectRequest) {
+                node = [browser parentForItemsInColumn:column];
+            NSLog(@"drag local node: %@", node.url);
+            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self copyNodes:draggedNodes toNode:node];
+        }
+    }
+    return NO;
+}
+
+#pragma mark -
+#pragma mark Drag and Drop methods
+
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
+    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+        return NO;
+    NSFileManager *defaultManager = [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 containerMetadataRequestWithContainerName:containerName];
+        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        while (![containerRequest isFinished]) {
+            sleep(1);
+        }
+        if ([containerRequest error]) {
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            return NO;
+        } else if (containerRequest.responseStatusCode != 200) {
+            [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 ([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, ^{
+                    NSError *error = nil;
+                    NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
+                    if (contentType == nil)
+                        contentType = @"application/octet-stream";
+                    if (error)
+                        NSLog(@"contentType detection error: %@", error);
+                    NSArray *hashes = nil;
+                    ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
+                                                                                                          objectName:objectName 
+                                                                                                         contentType:contentType 
+                                                                                                           blockSize:blockSize 
+                                                                                                           blockHash:blockHash 
+                                                                                                             forFile:filePath 
+                                                                                                       checkIfExists:YES 
+                                                                                                              hashes:&hashes 
+                                                                                                      sharingAccount:destinationNode.sharingAccount];
+                    if (objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+                        objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
+                                                                                   message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
+                                                                                totalBytes:[[objectRequest.userInfo valueForKey:@"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", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          nil]];
+                        if (destinationNode.sharingAccount)
+                            [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+                        [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
+                    }
+                });
+            } 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, ^{
+                        NSMutableArray *objectNames = nil;
+                        NSMutableArray *contentTypes = nil;
+                        NSMutableArray *filePaths = nil;
+                        NSMutableArray *hashesArrays = nil;
+                        NSMutableArray *directoryObjectRequests = nil;
+                        NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithContainerName:containerName 
+                                                                                                     objectName:objectName 
+                                                                                                      blockSize:blockSize 
+                                                                                                      blockHash:blockHash 
+                                                                                                   forDirectory:filePath 
+                                                                                                  checkIfExists:YES 
+                                                                                                    objectNames:&objectNames
+                                                                                                   contentTypes:&contentTypes
+                                                                                                      filePaths:&filePaths
+                                                                                                   hashesArrays:&hashesArrays 
+                                                                                        directoryObjectRequests:&directoryObjectRequests 
+                                                                                                 sharingAccount:destinationNode.sharingAccount];
+                        for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
+                            objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
+                            [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
+                        }
+                        if (objectRequests) {
+                            for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
+                                ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
                                 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];
+                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
+                                                                                           message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
+                                                                                        totalBytes:[[objectRequest.userInfo valueForKey:@"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", 
+                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                  nil]];
+                                if (destinationNode.sharingAccount)
+                                    [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+                                [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
                             }
-                        });
-                    } 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, ^{
-                                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];
-                                for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
-                                    objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
-                                    objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
-                                    [objectRequest startAsynchronous];
-                                }
-                                if (objectRequests) {
-                                    for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
-                                        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", 
-                                                                  [NSNumber numberWithUnsignedInteger:10], @"iteration", 
-                                                                  nil];
-                                        [objectRequest startAsynchronous];
-                                    }
-                                }
-                            });
                         }
-                    }
+                    });
                 }
-                
             }
-            return YES;
         }
-    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
-        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
-        if ((column != -1) && (draggedNodes != nil)) {
-            PithosNode *dropNode = nil;
-            if (row != -1)
-                dropNode = [browser itemAtRow:row inColumn:column];
-            else
-                dropNode = [browser parentForItemsInColumn:column];
-            NSLog(@"drag local node: %@", dropNode.url);
-            if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
-                return NO;
-            
-            NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
-            NSString *objectNamePrefix;
-            if ([dropNode class] == [PithosSubdirNode class])
-                objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
-            else
-                objectNamePrefix = [NSString string];
-            
-            if ([info draggingSourceOperationMask] & NSDragOperationMove) {
-                for (PithosNode *node in draggedNodes) {
-                    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 *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                            if ([node.pithosObject.name hasSuffix:@"/"])
-                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                                 objectName:node.pithosObject.name 
-                                                                                                   destinationContainerName:containerName 
-                                                                                                      destinationObjectName:destinationObjectName 
-                                                                                                              checkIfExists:YES];
-                            if (objectRequest) {
-                                objectRequest.delegate = self;
-                                objectRequest.didFinishSelector = @selector(moveFinished:);
-                                objectRequest.didFailSelector = @selector(moveFailed:);
-                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                          node.parent, @"node", 
-                                                          dropNode, @"dropNode", 
-                                                          nil];
-                                [objectRequest startAsynchronous];
-                            }
-                        });
-                    } else if ([node class] == [PithosSubdirNode class]) {
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                            if (node.pithosObject.subdir)
-                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                             objectName:node.pithosObject.name 
-                                                                                               destinationContainerName:containerName 
-                                                                                                  destinationObjectName:destinationObjectName 
-                                                                                                          checkIfExists:YES];
-                            if (objectRequests) {
-                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                                    objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(moveFinished:);
-                                    objectRequest.didFailSelector = @selector(moveFailed:);
-                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                              node.parent, @"node", 
-                                                              dropNode, @"dropNode", 
-                                                              nil];
-                                    [objectRequest startAsynchronous];
-                                }
-                            }
-                        });
+    }
+    return YES;
+}
+
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                if ([node.pithosObject.name hasSuffix:@"/"])
+                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                       destinationContainerName:containerName 
+                                                                                          destinationObjectName:destinationObjectName 
+                                                                                                  checkIfExists:YES];
+                if (objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(moveFinished:);
+                    objectRequest.didFailSelector = @selector(moveFailed:);
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                               message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                     [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
+                      activity, @"activity", 
+                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                      nil]];
+                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+                }
+            });
+        } else if ([node class] == [PithosSubdirNode class]) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                if (node.pithosObject.subdir)
+                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                 objectName:node.pithosObject.name 
+                                                                                   destinationContainerName:containerName 
+                                                                                      destinationObjectName:destinationObjectName 
+                                                                                              checkIfExists:YES];
+                if (objectRequests) {
+                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(moveFinished:);
+                        objectRequest.didFailSelector = @selector(moveFailed:);
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
+                                                                                   message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
+                                                                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                         [NSDictionary dictionaryWithObjectsAndKeys:
+                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
+                          [NSNumber numberWithBool:YES], @"refresh", 
+                          activity, @"activity", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          nil]];
+                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                     }
                 }
-            } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
-                for (PithosNode *node in draggedNodes) {
-                    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 *destinationObjectName;
-                            if (![dropNode isEqualTo:node.parent]) {
-                                destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                                if ([node.pithosObject.name hasSuffix:@"/"])
-                                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            } else {
-                                destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
-                                                                                                    objectName:node.pithosObject.name];
-                            }
-                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                                 objectName:node.pithosObject.name 
-                                                                                                   destinationContainerName:containerName 
-                                                                                                      destinationObjectName:destinationObjectName 
-                                                                                                              checkIfExists:YES];
-                            if (objectRequest) {
-                                objectRequest.delegate = self;
-                                objectRequest.didFinishSelector = @selector(copyFinished:);
-                                objectRequest.didFailSelector = @selector(copyFailed:);
-                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                          dropNode, @"dropNode", 
-                                                          nil];
-                                [objectRequest startAsynchronous];
-                            }
-                        });
-                    } else if ([node class] == [PithosSubdirNode class]) {
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSString *destinationObjectName;
-                            if (![dropNode isEqualTo:node.parent]) {
-                                destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                                if (node.pithosObject.subdir)
-                                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            } else {
-                                destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
-                                                                                                 subdirName:node.pithosObject.name];
-                            }
-                            NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                             objectName:node.pithosObject.name 
-                                                                                               destinationContainerName:containerName 
-                                                                                                  destinationObjectName:destinationObjectName 
-                                                                                                          checkIfExists:YES];
-                            if (objectRequests) {
-                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                                    objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(copyFinished:);
-                                    objectRequest.didFailSelector = @selector(copyFailed:);
-                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                              dropNode, @"dropNode", 
-                                                              nil];
-                                    [objectRequest startAsynchronous];
-                                }
-                            }
-                        });
+            });
+        }
+    }
+    return YES;
+}
 
+- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
+    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+    
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName;
+                if (![destinationNode isEqualTo:node.parent]) {
+                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if ([node.pithosObject.name hasSuffix:@"/"])
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                } else {
+                    destinationObjectName = [PithosUtilities safeObjectNameForContainerName:containerName 
+                                                                                 objectName:node.pithosObject.name];
+                }
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                       destinationContainerName:containerName 
+                                                                                          destinationObjectName:destinationObjectName 
+                                                                                                  checkIfExists:YES 
+                                                                                                 sharingAccount:node.sharingAccount];
+                if (objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(copyFinished:);
+                    objectRequest.didFailSelector = @selector(copyFailed:);
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
+                                                                               message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                     [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
+                      activity, @"activity", 
+                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                      nil]];
+                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+                }
+            });
+        } else if ([node class] == [PithosSubdirNode class]) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName;
+                if (![destinationNode isEqualTo:node.parent]) {
+                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if (node.pithosObject.subdir)
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                } else {
+                    destinationObjectName = [PithosUtilities safeSubdirNameForContainerName:containerName 
+                                                                                     subdirName:node.pithosObject.name];
+                }
+                NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                 objectName:node.pithosObject.name 
+                                                                                   destinationContainerName:containerName 
+                                                                                      destinationObjectName:destinationObjectName 
+                                                                                              checkIfExists:YES 
+                                                                                             sharingAccount:node.sharingAccount];
+                if (objectRequests) {
+                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(copyFinished:);
+                        objectRequest.didFailSelector = @selector(copyFailed:);
+                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
+                                                                                   message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
+                                                                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                                                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+                         [NSDictionary dictionaryWithObjectsAndKeys:
+                          [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
+                          activity, @"activity", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          nil]];
+                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                     }
                 }
-            }
-            return YES;
+            });
         }
     }
-    return NO;
+    return YES;
 }
 
 #pragma mark -
 #pragma mark ASIHTTPRequestDelegate
 
 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download completed: %@", [objectRequest url]);
+    NSLog(@"Download finished: %@", objectRequest.url);
     if (objectRequest.responseStatusCode == 200) {
+        NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+        NSUInteger totalBytes = activity.totalBytes;
+        NSUInteger currentBytes = activity.currentBytes;
+        
+        // XXX change contentLength to objectContentLength if it is fixed in the server
         if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
             NSLog(@"Downloaded  0 bytes");
             NSFileManager *defaultManager = [NSFileManager defaultManager];
-            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
             if (![defaultManager fileExistsAtPath:filePath]) {
                 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
@@ -872,14 +1091,43 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 }
             }
         }
+        
+        currentBytes = [objectRequest objectContentLength];
+        if (currentBytes == 0)
+            currentBytes = totalBytes;
+        [activityFacility endActivity:activity 
+                          withMessage:[NSString stringWithFormat:@"Downloading '%@' (100%%)", 
+                                       [objectRequest.userInfo objectForKey:@"fileName"]] 
+                           totalBytes:totalBytes 
+                         currentBytes:currentBytes];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
+        } else {
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)", 
+                                           [objectRequest.userInfo objectForKey:@"fileName"]]];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        }
     }
 }
 
 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    NSLog(@"Download failed: %@", objectRequest.url);
+    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+        [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
+    } else {
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)", 
+                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
+        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    }
 }
 
 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
@@ -888,146 +1136,304 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         NSLog(@"Directory object created: %@", [objectRequest url]);
         [self refresh:nil];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
     NSLog(@"Upload directory object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
 }
 
 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
+    NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
+    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
+    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+    NSUInteger totalBytes = activity.totalBytes;
+    NSUInteger currentBytes = activity.currentBytes;
     if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"Object created: %@", [objectRequest url]);
-        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        if (node)
+        NSLog(@"Object created: %@", objectRequest.url);
+        [activityFacility endActivity:activity 
+                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (100%%)", fileName] 
+                           totalBytes:totalBytes 
+                         currentBytes:totalBytes];
+        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+            [node forceRefresh];
+        }
+        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
             [node refresh];
-        else
-            [self refresh:nil];
+        }
+        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] - 1;
+        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
         if (iteration == 0) {
-            NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
+            NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
+            [activityFacility endActivity:activity 
+                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]]; 
             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"]]];
+                                       [objectRequest.userInfo objectForKey:@"objectName"]]];
             [alert addButtonWithTitle:@"OK"];
             [alert runModal];
             return;
         }
-        NSLog(@"object is missing hashes: %@", [objectRequest url]);
-        NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
-                                                      withMissingHashesResponse:[objectRequest responseString]];
+        NSLog(@"object is missing hashes: %@", objectRequest.url);
+        NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+                                                  withMissingHashesResponse:[objectRequest responseString]];
+        NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+        if (totalBytes >= [missingBlocks count]*blockSize)
+            currentBytes = totalBytes - [missingBlocks count]*blockSize;
+        [activityFacility updateActivity:activity 
+                             withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
+                              totalBytes:totalBytes 
+                            currentBytes:currentBytes];
         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];
+        __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
+                                                                                                                    blockSize:blockSize 
+                                                                                                                      forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                            missingBlockIndex:missingBlockIndex 
+                                                                                                               sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
+        newContainerRequest.delegate = self;
+        newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
+        newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
+        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"];
+        [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+            [activityFacility updateActivity:activity 
+                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                  totalBytes:activity.totalBytes 
+                                currentBytes:(activity.currentBytes + size)];
+        }];
+        [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
+        } else {
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        }
     }
 }
 
 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload using hashmap failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    NSLog(@"Upload using hashmap failed: %@", objectRequest.url);
+    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+        [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
+    } else {
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", 
+                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
+        [PithosUtilities 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];
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+    NSLog(@"Upload of missing block finished: %@", containerRequest.url);
+    NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+    NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
+    PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
+    NSUInteger totalBytes = activity.totalBytes;
+    NSUInteger currentBytes = activity.currentBytes + blockSize;
+    if (currentBytes > totalBytes)
+        currentBytes = totalBytes;
+    if (containerRequest.responseStatusCode == 202) {
+        [activityFacility updateActivity:activity 
+                             withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
+                              totalBytes:totalBytes 
+                            currentBytes:currentBytes];
+        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
+        NSUInteger missingBlockIndex = [[containerRequest.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];
+            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
+            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:[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(uploadObjectUsingHashMapFinished:);
             newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-            newObjectRequest.userInfo = objectRequest.userInfo;
+            newObjectRequest.userInfo = containerRequest.userInfo;
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
-            [newObjectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
         } 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];
+            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[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(uploadMissingBlockFinished:);
+            newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
+            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, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                      totalBytes:activity.totalBytes 
+                                    currentBytes:(activity.currentBytes + size)];
+            }];
+            [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
         }
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
+        } else {
+            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+        }
     }
 }
 
-- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Upload of missing block failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+- (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
+    NSLog(@"Upload of missing block failed: %@", containerRequest.url);
+    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
+        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+        [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
+    } else {
+        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", 
+                                       [containerRequest.userInfo objectForKey:@"fileName"]]];
+        [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
+    }
 }
 
 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Move object completed: %@", [objectRequest url]);
+    NSLog(@"Move object finished: %@", objectRequest.url);
     if (objectRequest.responseStatusCode == 201) {
-        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
-        if (node)
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (finished)", 
+                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+            [node forceRefresh];
+        }
+        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
             [node refresh];
-        if (dropNode)
-            [dropNode refresh];
-        if (!node || !dropNode)
-            [self refresh:nil];
+        }
+        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+            [self forceRefresh:self];
+        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+            [self refresh:self];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        } else {
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)", 
+                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        }
     }
 }
 
 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Move object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    NSLog(@"Move object failed: %@", objectRequest.url);
+    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+        [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+    } else {
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' failed", 
+                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    }
 }
 
 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Copy object completed: %@", [objectRequest url]);
+    NSLog(@"Copy object finished: %@", objectRequest.url);
     if (objectRequest.responseStatusCode == 201) {
-        PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
-        if (dropNode)
-            [dropNode refresh];
-        else
-            [self refresh:nil];
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (finished)", 
+                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+        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 {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+        if (retries > 0) {
+            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        } else {
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)", 
+                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        }
     }
 }
 
 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Copy object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    NSLog(@"Copy object failed: %@", objectRequest.url);
+    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+        [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+    } else {
+        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' failed", 
+                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
+                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
+        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    }
 }
 
 #pragma mark -
@@ -1037,14 +1443,14 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (splitView == verticalSplitView)
         return 120;
     else
-        return ([horizontalSplitView bounds].size.height - 87);
+        return ([horizontalSplitView bounds].size.height - 108);
 }
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
     if (splitView == verticalSplitView)
         return 220;
     else
-        return ([horizontalSplitView bounds].size.height - 87);
+        return ([horizontalSplitView bounds].size.height - 108);
 }
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
@@ -1056,27 +1462,113 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         else
             return proposedPosition;
     } else {
-        return ([horizontalSplitView bounds].size.height - 87);
+        return ([horizontalSplitView bounds].size.height - 108);
+    }
+}
+
+#pragma mark -
+#pragma mark NSOutlineViewDataSource
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+    if (item == nil)
+        return 2;
+    if (item == containersNode)
+        return containersNodeChildren.count;
+    if (item == sharedNode)
+        return 2;
+    return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+    if (item == nil)
+        return (!index ? containersNode : sharedNode);
+    if (item == sharedNode)
+        return (!index ? mySharedNode : othersSharedNode);
+    return [containersNodeChildren objectAtIndex:index];
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+    if ((item == containersNode) || (item == sharedNode))
+        return YES;
+    return NO;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return node;    
+}
+
+#pragma mark Drag and Drop destination
+
+- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
+                  validateDrop:(id<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 ([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];
+        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            NSLog(@"drag in node: %@", node.url);
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            NSLog(@"drag local node: %@", node.url);
+            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self copyNodes:draggedNodes toNode:node];
+        }
     }
+    return NO;
 }
 
 #pragma mark -
 #pragma mark NSOutlineViewDelegate
 
 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
-    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];
     }
 }
 
@@ -1084,42 +1576,70 @@ 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;
     NSString *menuItemTitle;
-    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)];
+    BOOL nodeContextMenu = NO;
+    PithosNode *menuNode;
+    NSMutableArray *menuNodes;
+    if (menu == browserMenu) {
+        NSInteger column = [browser clickedColumn];
+        NSInteger row = [browser clickedRow];
+        if ((column == -1) || (row == -1)) {
+            // General context menu
+            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+            if ([menuNodesIndexPaths count] == 0) {
+                menuNode = [browser parentForItemsInColumn:0];
+            } else if (([menuNodesIndexPaths count] != 1) || 
+                       ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
+                menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+            } else {
+                menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+            }
         } else {
-            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;
         }
-        if ([menuNode class] == [PithosAccountNode class])
+    } else if (menu == outlineViewMenu) {
+        NSInteger row = [outlineView clickedRow];
+        if (row == -1)
+            row = [outlineView selectedRow];
+        if (row == -1)
+            return;
+        menuNode = [outlineView itemAtRow:row];
+    }
+
+    if (!nodeContextMenu) {
+        // General context menu
+        if (([menuNode class] == [PithosAccountNode class]) || 
+            ([menuNode class] == [PithosSharingAccountsNode class]) ||
+            ([menuNode class] == [PithosEmptyNode class]))
             return;
         BOOL shared = menuNode.shared;
-        
-        if (!shared) {
-            // New Folder
+        BOOL sharingAccount = (menuNode.sharingAccount != nil);
+        // New Folder
+        if (!shared && !sharingAccount) {
             menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
             [menuItem setRepresentedObject:menuNode];
             [menu addItem:menuItem];
             [menu addItem:[NSMenuItem separatorItem]];
         }
-        
         // Get Info
         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
         [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
         [menu addItem:menuItem];
-        
-        if (!shared) {
-            // Paste
+        // Paste
+        if (!shared && !sharingAccount) {
             if (clipboardNodes) {
                 NSUInteger clipboardNodesCount = [clipboardNodes count];
                 if (clipboardNodesCount == 0) {
@@ -1138,22 +1658,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         }
     } 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];
-        BOOL shared = ((PithosNode *)[menuNodes objectAtIndex:0]).shared;
-        
-        if (!shared) {
-            // Move to Trash (pithos container only)
-            // Delete
+        PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
+        BOOL shared = firstMenuNode.shared;
+        BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
+        // Move to Trash (pithos container only)
+        // Delete
+        if (!shared && !sharingAccount) {
             if ([rootNode class] == [PithosContainerNode class]) {
                 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
                     menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
@@ -1166,15 +1677,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 [menu addItem:[NSMenuItem separatorItem]];
             }
         }
-        
         // Get Info
-        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
-        [menuItem setRepresentedObject:menuNodes];
-        [menu addItem:menuItem];
-        
-        if (!shared) {
-            // Cut
-            [menu addItem:[NSMenuItem separatorItem]];
+        if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
+            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+            
+            if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
+                [menu addItem:[NSMenuItem separatorItem]];
+        }
+        // Cut
+        if (!shared && !sharingAccount) {
             if (menuNodesCount == 1)
                 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
             else 
@@ -1182,7 +1695,10 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
             [menuItem setRepresentedObject:menuNodes];
             [menu addItem:menuItem];
-            // Copy
+        }
+        // Copy
+        if ((!shared && !sharingAccount) || 
+            (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
             if (menuNodesCount == 1)
                 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
             else 
@@ -1190,7 +1706,9 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
             [menuItem setRepresentedObject:menuNodes];
             [menu addItem:menuItem];
-            // Paste
+        }
+        // Paste
+        if (!shared && !sharingAccount) {
             if (menuNodesCount == 1) {
                 PithosNode *menuNode = [menuNodes objectAtIndex:0];
                 if (([menuNode class] == [PithosSubdirNode class]) && 
@@ -1223,7 +1741,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     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 
+            NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
                                                                                 subdirName:@"untitled folder"];
             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
                                                                                                          objectName:safeObjectName 
@@ -1242,14 +1760,14 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                       node, @"node", 
                                       nil];
-            [objectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] 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 
+            NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
                                                                                subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
                                                                                                         objectName:safeObjectName 
@@ -1268,7 +1786,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                      node, @"node", 
                                      nil];
-            [objectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
         });
     }
 }
@@ -1290,18 +1808,18 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             objectRequest.delegate = self;
             objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
             objectRequest.didFailSelector = @selector(deleteObjectFailed:);
-            [objectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
         } 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 
+                NSArray *objectRequests = [PithosUtilities 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];
+                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                     }
                 }
             });
@@ -1317,10 +1835,10 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
              [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" 
+                NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
                                                                                     objectName:node.pithosObject.name];
                 if (safeObjectName) {
-                    ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
+                    ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
                                                                                                          objectName:node.pithosObject.name 
                                                                                            destinationContainerName:@"trash" 
                                                                                               destinationObjectName:safeObjectName 
@@ -1329,17 +1847,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                         objectRequest.delegate = self;
                         objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
                         objectRequest.didFailSelector = @selector(moveToTrashFailed:);
-                        [objectRequest startAsynchronous];
+                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                     }
                 }
             });
         } 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" 
+                NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:@"trash" 
                                                                                     subdirName:node.pithosObject.name];
                 if (safeObjectName) {
-                    NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                    NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
                                                                                                      objectName:node.pithosObject.name 
                                                                                        destinationContainerName:@"trash" 
                                                                                           destinationObjectName:safeObjectName 
@@ -1349,7 +1867,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                             objectRequest.delegate = self;
                             objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
                             objectRequest.didFailSelector = @selector(moveToTrashFailed:);
-                            [objectRequest startAsynchronous];
+                            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                         }
                     }
                 }
@@ -1374,132 +1892,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (!clipboardNodes || ![clipboardNodes count])
         return;
     PithosNode *dropNode = (PithosNode *)[sender representedObject];
-    if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) || 
-        (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"])) 
-        return;
-    
-    NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
-    NSString *objectNamePrefix;
-    if ([dropNode class] == [PithosSubdirNode class])
-        objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
-    else
-        objectNamePrefix = [NSString string];
-    
     NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
     if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
         self.clipboardNodes = nil;
         self.clipboardParentNode = nil;
-        for (PithosNode *node in localClipboardNodes) {
-            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 *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                    if ([node.pithosObject.name hasSuffix:@"/"])
-                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                    ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                         objectName:node.pithosObject.name 
-                                                                                           destinationContainerName:containerName 
-                                                                                              destinationObjectName:destinationObjectName 
-                                                                                                      checkIfExists:YES];
-                    if (objectRequest) {
-                        objectRequest.delegate = self;
-                        objectRequest.didFinishSelector = @selector(moveFinished:);
-                        objectRequest.didFailSelector = @selector(moveFailed:);
-                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                  node.parent, @"node", 
-                                                  dropNode, @"dropNode", 
-                                                  nil];
-                        [objectRequest startAsynchronous];
-                    }
-                });
-            } else if ([node class] == [PithosSubdirNode class]) {
-                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                dispatch_async(queue, ^{
-                    NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                    if (node.pithosObject.subdir)
-                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                    NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                     objectName:node.pithosObject.name 
-                                                                                       destinationContainerName:containerName 
-                                                                                          destinationObjectName:destinationObjectName 
-                                                                                                  checkIfExists:YES];
-                    if (objectRequests) {
-                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                            objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(moveFinished:);
-                            objectRequest.didFailSelector = @selector(moveFailed:);
-                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                      node.parent, @"node", 
-                                                      dropNode, @"dropNode", 
-                                                      nil];
-                            [objectRequest startAsynchronous];
-                        }
-                    }
-                });
-            }
-        }
+        [self moveNodes:localClipboardNodes toNode:dropNode];
     } else {
-        for (PithosNode *node in localClipboardNodes) {
-            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 *destinationObjectName;
-                    if (![dropNode isEqualTo:node.parent]) {
-                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                        if ([node.pithosObject.name hasSuffix:@"/"])
-                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                    } else {
-                        destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
-                                                                                         objectName:node.pithosObject.name];
-                    }
-                    ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                         objectName:node.pithosObject.name 
-                                                                                           destinationContainerName:containerName 
-                                                                                              destinationObjectName:destinationObjectName 
-                                                                                                      checkIfExists:YES];
-                    if (objectRequest) {
-                        objectRequest.delegate = self;
-                        objectRequest.didFinishSelector = @selector(copyFinished:);
-                        objectRequest.didFailSelector = @selector(copyFailed:);
-                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                  dropNode, @"dropNode", 
-                                                  nil];
-                        [objectRequest startAsynchronous];
-                    }
-                });
-            } else if ([node class] == [PithosSubdirNode class]) {
-                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                dispatch_async(queue, ^{
-                    NSString *destinationObjectName;
-                    if (![dropNode isEqualTo:node.parent]) {
-                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                        if (node.pithosObject.subdir)
-                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                    } else {
-                        destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
-                                                                                         subdirName:node.pithosObject.name];
-                    }
-                    NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                     objectName:node.pithosObject.name 
-                                                                                       destinationContainerName:containerName 
-                                                                                          destinationObjectName:destinationObjectName 
-                                                                                                  checkIfExists:YES];
-                    if (objectRequests) {
-                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                            objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(copyFinished:);
-                            objectRequest.didFailSelector = @selector(copyFailed:);
-                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                      dropNode, @"dropNode", 
-                                                      nil];
-                            [objectRequest startAsynchronous];
-                        }
-                    }
-                });                
-            }
-        }
+        [self copyNodes:localClipboardNodes toNode:dropNode];
     }
 }
     
@@ -1515,13 +1914,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         else
             [self refresh:nil];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
     NSLog(@"Creation of new application/directory object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
 }
 
 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
@@ -1529,13 +1928,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         NSLog(@"Object deleted: %@", [objectRequest url]);
         [self refresh:nil];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
     NSLog(@"Delete of object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
 }
 
 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
@@ -1543,14 +1942,40 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         NSLog(@"Object moved: %@", [objectRequest url]);
         [self refresh:nil];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+        [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
     NSLog(@"Move of object failed");
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+    [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
 }
 
+#pragma mark -
+#pragma mark PithosActivityFacilityDelegate
+
+- (void)activityUpdate:(NSDictionary *)info {
+    NSString *message = [info objectForKey:@"message"];
+    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
+//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
+    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
+    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
+    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
+    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
+    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
+    if (runningActivitiesCount && totalBytes) {
+        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
+        [activityProgressIndicator startAnimation:self];
+    } else {
+        [activityProgressIndicator setDoubleValue:1.0];
+        [activityProgressIndicator stopAnimation:self];
+    }
+    
+    if (!message)
+        message = [NSString stringWithFormat:@"%@ used", 
+                   [[[[BytesSizeTransformer alloc] init] autorelease] transformedValue: 
+                    [NSNumber numberWithUnsignedInteger:accountNode.pithosAccount.bytesUsed]]];
+    [activityTextField setStringValue:message];
+}
 
 @end
\ No newline at end of file