All requests made asynchronous.
[pithos-macos] / pithos-macos / PithosBrowserController.m
index dc5832d..fce0de3 100644 (file)
@@ -51,8 +51,8 @@
 #import "ASIPithosAccount.h"
 #import "ASIPithosContainer.h"
 #import "ASIPithosObject.h"
-#import "PithosFileUtilities.h"
-#import "BytesExtendedSizeTransformer.h"
+#import "PithosUtilities.h"
+#import "BytesSizeTransformer.h"
 
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
     [clipboardNodes release];
     [draggedParentNode release];
     [draggedNodes release];
-    [outlineViewMenu release];
-    [browserMenu release];
     [sharedPreviewController release];
     [othersSharedNode release];
     [mySharedNode release];
     [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
     
     [browser setCellClass:[PithosBrowserCell class]];
-    
-    browserMenu = [[NSMenu alloc] init];
-    [browserMenu setDelegate:self];
-    [browser setMenu:browserMenu];
-
-    outlineViewMenu = [[NSMenu alloc] init];
-    [outlineViewMenu setDelegate:self];
-    [outlineView setMenu:outlineViewMenu];
 }
 
 - (void)resetContainers:(NSNotification *)notification {
     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;
         }
 #pragma mark -
 #pragma mark Actions
 
+- (IBAction)forceRefresh:(id)sender {
+    if (sender)
+        [accountNode forceRefresh];
+    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
+        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
+        node.forcedRefresh = YES;
+        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
+    }
+    [browser validateVisibleColumns];
+}
+
 - (IBAction)refresh:(id)sender {
     if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
-        if (sender)
-            [accountNode forceRefresh];
-        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
-            PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
-            node.forcedRefresh = YES;
-            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
-            //[(PithosNode *)[browser parentForItemsInColumn:column] forceRefresh];
-        }        
+        [self forceRefresh:sender];
     } else {
         if (sender)
             [accountNode refresh];
         for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
             [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
         }
+        [browser validateVisibleColumns];
     }
-    [browser validateVisibleColumns];
 }
 
 #pragma mark -
         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 
-                                                  sharingAccount:nil]) {
+            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 
-                                                  sharingAccount:nil]) {
+            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];
                 }
             }
         });
@@ -545,13 +563,13 @@ 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 
                                                                                                  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:);
@@ -560,18 +578,28 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                                                                                        message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo objectForKey:@"fileName"]] 
                                                                                     totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
                                                                                   currentBytes:0];
-                            [(NSMutableDictionary *)objectRequest.userInfo setObject:activity forKey:@"activity"];
-                            [objectRequest startAsynchronous];
+                            [(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 
-                                                                                             sharingAccount:node.sharingAccount];
+            __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;
@@ -581,8 +609,18 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                                                                            message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
                                                                         totalBytes:node.pithosObject.bytes 
                                                                       currentBytes:0];
-                [(NSMutableDictionary *)objectRequest.userInfo setObject:activity forKey:@"activity"];
-                [objectRequest startAsynchronous];
+                [(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];
             }
         }
     }
@@ -671,7 +709,7 @@ 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
@@ -682,7 +720,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
         NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
         if ((column != -1) && (draggedNodes != nil)) {
-            PithosNode *node = nil;
+            PithosNode *node;
             if (row != -1)
                 node = [browser itemAtRow:row inColumn:column];
             else
@@ -712,12 +750,15 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         objectNamePrefix = [NSString string];
     if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithContainerName:containerName];
-        [containerRequest startSynchronous];
+        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        while (![containerRequest isFinished]) {
+            sleep(1);
+        }
         if ([containerRequest error]) {
-            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
             return NO;
         } else if (containerRequest.responseStatusCode != 200) {
-            [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
             return NO;
         }
         destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
@@ -735,21 +776,21 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 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];
+                    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 = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                              objectName:objectName 
-                                                                                                             contentType:contentType 
-                                                                                                               blockSize:blockSize 
-                                                                                                               blockHash:blockHash 
-                                                                                                                 forFile:filePath 
-                                                                                                           checkIfExists:YES 
-                                                                                                                  hashes:&hashes 
-                                                                                                          sharingAccount:destinationNode.sharingAccount];
+                    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:);
@@ -767,13 +808,15 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                           blockHash, @"blockHash", 
                           filePath, @"filePath", 
                           hashes, @"hashes", 
-                          destinationNode, @"node", 
+                          [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"];
-                        [objectRequest startAsynchronous];
+                        [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
                     }
                 });
             } else {
@@ -793,7 +836,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                         NSMutableArray *filePaths = nil;
                         NSMutableArray *hashesArrays = nil;
                         NSMutableArray *directoryObjectRequests = nil;
-                        NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
+                        NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithContainerName:containerName 
                                                                                                      objectName:objectName 
                                                                                                       blockSize:blockSize 
                                                                                                       blockHash:blockHash 
@@ -809,7 +852,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                             objectRequest.delegate = self;
                             objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
                             objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
-                            [objectRequest startAsynchronous];
+                            [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
                         }
                         if (objectRequests) {
                             for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
@@ -830,12 +873,14 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                                   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"];
-                                [objectRequest startAsynchronous];
+                                [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
                             }
                         }
                     });
@@ -847,7 +892,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 }
 
 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
-    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+    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;
@@ -858,15 +904,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 
     for (PithosNode *node in nodes) {
         if (([node class] == [PithosObjectNode class]) || 
-            (([node class] == [PithosSubdirNode class]) && 
-             !node.pithosObject.subdir &&
-             [node.pithosObject.name hasSuffix:@"/"])) {
+            (([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 
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
                                                                                                      objectName:node.pithosObject.name 
                                                                                        destinationContainerName:containerName 
                                                                                           destinationObjectName:destinationObjectName 
@@ -875,11 +919,19 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     objectRequest.delegate = self;
                     objectRequest.didFinishSelector = @selector(moveFinished:);
                     objectRequest.didFailSelector = @selector(moveFailed:);
-                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                              node.parent, @"node", 
-                                              destinationNode, @"dropNode", 
-                                              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 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]) {
@@ -888,7 +940,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
                 if (node.pithosObject.subdir)
                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
                                                                                                  objectName:node.pithosObject.name 
                                                                                    destinationContainerName:containerName 
                                                                                       destinationObjectName:destinationObjectName 
@@ -898,11 +950,20 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                         objectRequest.delegate = self;
                         objectRequest.didFinishSelector = @selector(moveFinished:);
                         objectRequest.didFailSelector = @selector(moveFailed:);
-                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                  node.parent, @"node", 
-                                                  destinationNode, @"dropNode", 
-                                                  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 arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
+                          [NSNumber numberWithBool:YES], @"refresh", 
+                          activity, @"activity", 
+                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                          nil]];
+                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                     }
                 }
             });
@@ -912,7 +973,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 }
 
 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
-    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+    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;
@@ -923,9 +985,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     
     for (PithosNode *node in nodes) {
         if (([node class] == [PithosObjectNode class]) || 
-            (([node class] == [PithosSubdirNode class]) && 
-             !node.pithosObject.subdir &&
-             [node.pithosObject.name hasSuffix:@"/"])) {
+            (([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;
@@ -934,23 +994,32 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     if ([node.pithosObject.name hasSuffix:@"/"])
                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
                 } else {
-                    destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
-                                                                                     objectName:node.pithosObject.name];
+                    destinationObjectName = [PithosUtilities safeObjectNameForContainerName:containerName 
+                                                                                 objectName:node.pithosObject.name];
                 }
-                ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
                                                                                                      objectName:node.pithosObject.name 
                                                                                        destinationContainerName:containerName 
                                                                                           destinationObjectName:destinationObjectName 
                                                                                                   checkIfExists:YES 
-                                                                                                 sharingAccount:nil];
+                                                                                                 sharingAccount:node.sharingAccount];
                 if (objectRequest) {
                     objectRequest.delegate = self;
                     objectRequest.didFinishSelector = @selector(copyFinished:);
                     objectRequest.didFailSelector = @selector(copyFailed:);
-                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                              destinationNode, @"dropNode", 
-                                              nil];
-                    [objectRequest startAsynchronous];
+                    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]) {
@@ -962,24 +1031,33 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     if (node.pithosObject.subdir)
                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
                 } else {
-                    destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
+                    destinationObjectName = [PithosUtilities safeSubdirNameForContainerName:containerName 
                                                                                      subdirName:node.pithosObject.name];
                 }
-                NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
                                                                                                  objectName:node.pithosObject.name 
                                                                                    destinationContainerName:containerName 
                                                                                       destinationObjectName:destinationObjectName 
                                                                                               checkIfExists:YES 
-                                                                                             sharingAccount:nil];
+                                                                                             sharingAccount:node.sharingAccount];
                 if (objectRequests) {
                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
                         objectRequest.delegate = self;
                         objectRequest.didFinishSelector = @selector(copyFinished:);
                         objectRequest.didFailSelector = @selector(copyFailed:);
-                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                  destinationNode, @"dropNode", 
-                                                  nil];
-                        [objectRequest startAsynchronous];
+                        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];
                     }
                 }
             });
@@ -992,16 +1070,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 #pragma mark ASIHTTPRequestDelegate
 
 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download completed: %@", [objectRequest url]);
-    
-    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
-    NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
-    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
-    NSString *activityMessage;
-    NSUInteger totalBytes = activity.totalBytes;
-    NSUInteger currentBytes = activity.currentBytes;
-    
+    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");
@@ -1020,26 +1095,39 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         currentBytes = [objectRequest objectContentLength];
         if (currentBytes == 0)
             currentBytes = totalBytes;
-        activityMessage = [NSString stringWithFormat:@"Downloading '%@' (100%%)", fileName];
-        
+        [activityFacility endActivity:activity 
+                          withMessage:[NSString stringWithFormat:@"Downloading '%@' (100%%)", 
+                                       [objectRequest.userInfo objectForKey:@"fileName"]] 
+                           totalBytes:totalBytes 
+                         currentBytes:currentBytes];
     } else {
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
-        
-        activityMessage = [NSString stringWithFormat:@"Downloading '%@' (failed)", fileName];
+        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];
+        }
     }
-    
-    [activityFacility endActivity:activity withMessage:activityMessage totalBytes:totalBytes currentBytes:currentBytes];
 }
 
 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"Download failed");
-    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
-    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
-    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
-    [activityFacility endActivity:activity 
-                      withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)", fileName] 
-                       totalBytes:activity.totalBytes 
-                     currentBytes:activity.currentBytes];
+    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 {
@@ -1048,40 +1136,43 @@ 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]);
+        NSLog(@"Object created: %@", objectRequest.url);
         [activityFacility endActivity:activity 
                           withMessage:[NSString stringWithFormat:@"Uploading '%@' (100%%)", fileName] 
                            totalBytes:totalBytes 
                          currentBytes:totalBytes];
-        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        if (node)
+        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] 
-                               totalBytes:totalBytes 
-                             currentBytes:currentBytes];
+                              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 '%@'", 
@@ -1090,48 +1181,68 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             [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];
-        ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
-                                                                                                                blockSize:blockSize 
-                                                                                                                  forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                        missingBlockIndex:missingBlockIndex 
-                                                                                                           sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
+        __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:(--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"];
-        [activityFacility updateActivity:activity 
-                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.2f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
-                           totalBytes:totalBytes 
-                         currentBytes:currentBytes];
-        [newContainerRequest startAsynchronous];
+        [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");
-    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
-    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
-    [activityFacility endActivity:activity 
-                      withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName] 
-                       totalBytes:activity.totalBytes 
-                     currentBytes:activity.currentBytes];
-    [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:(ASIPithosContainerRequest *)containerRequest {
-    NSLog(@"Upload of missing block completed: %@", [containerRequest url]);
+    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"];
@@ -1140,105 +1251,189 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     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 = [containerRequest.userInfo objectForKey:@"hashes"];
-            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities 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"]];
+            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 = containerRequest.userInfo;
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
-            [activityFacility updateActivity:activity 
-                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.2f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
-                                  totalBytes:totalBytes 
-                                currentBytes:currentBytes];
-            [newObjectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
         } else {
-            ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
-                                                                                                           blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
-                                                                                                             forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                   missingBlockIndex:missingBlockIndex 
-                                                                                                      sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+            __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"];
-            [activityFacility updateActivity:activity 
-                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.2f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
-                                  totalBytes:totalBytes 
-                                currentBytes:currentBytes];
-            [newContainerRequest startAsynchronous];
+            [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 {
-        [activityFacility endActivity:activity 
-                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName] 
-                           totalBytes:activity.totalBytes 
-                         currentBytes:activity.currentBytes];
-        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+        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:(ASIPithosContainerRequest *)containerRequest {
-    NSLog(@"Upload of missing block failed");
-    NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
-    PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
-    [activityFacility endActivity:activity 
-                      withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName] 
-                       totalBytes:activity.totalBytes 
-                     currentBytes:activity.currentBytes];
-    [PithosFileUtilities httpRequestErrorAlertWithRequest: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 -
@@ -1248,14 +1443,14 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (splitView == verticalSplitView)
         return 120;
     else
-        return ([horizontalSplitView bounds].size.height - 101);
+        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 - 101);
+        return ([horizontalSplitView bounds].size.height - 108);
 }
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
@@ -1267,7 +1462,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         else
             return proposedPosition;
     } else {
-        return ([horizontalSplitView bounds].size.height - 101);
+        return ([horizontalSplitView bounds].size.height - 108);
     }
 }
 
@@ -1546,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 
@@ -1565,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 
@@ -1591,7 +1786,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                      node, @"node", 
                                      nil];
-            [objectRequest startAsynchronous];
+            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
         });
     }
 }
@@ -1613,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];
                     }
                 }
             });
@@ -1640,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 
@@ -1652,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 
@@ -1672,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];
                         }
                     }
                 }
@@ -1697,134 +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 
-                                                                                                     sharingAccount:node.sharingAccount];
-                    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 
-                                                                                                 sharingAccount:node.sharingAccount];
-                    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];
     }
 }
     
@@ -1840,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 {
@@ -1854,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 {
@@ -1868,13 +1942,13 @@ 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 -
@@ -1899,7 +1973,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     
     if (!message)
         message = [NSString stringWithFormat:@"%@ used", 
-                   [[[[BytesExtendedSizeTransformer alloc] init] autorelease] transformedValue: 
+                   [[[[BytesSizeTransformer alloc] init] autorelease] transformedValue: 
                     [NSNumber numberWithUnsignedInteger:accountNode.pithosAccount.bytesUsed]]];
     [activityTextField setStringValue:message];
 }