'my shared' nodes can be copied.
[pithos-macos] / pithos-macos / PithosBrowserController.m
index a1d4cd8..832014e 100644 (file)
@@ -51,7 +51,6 @@
 #import "ASIPithosObject.h"
 #import "PithosFileUtilities.h"
 
-//@interface PithosBrowserCell : NSBrowserCell {}
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
 
@@ -60,6 +59,7 @@
 - (id)init {
     if ((self = [super init])) {
         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
+        [self setEditable:YES];
     }
     return self;
 }
@@ -69,9 +69,6 @@
         PithosNode *node = (PithosNode *)object;
         [self setStringValue:node.displayName];
         [self setImage:node.icon];
-//        // All cells are set as leafs because a branchingImage is already set!
-//        // Maybe this cell is already inside an NSBrowserCell
-//        [self setLeaf:YES];
     } else {
         [super setObjectValue:object];
     }
 @end
 
 @interface PithosBrowserController (Private) {}
-- (void)resetContainers;
-- (void)getInfo:(NSMenuItem *)sender;
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest;
+- (void)resetContainers:(NSNotification *)notification;
 @end
 
 @implementation PithosBrowserController
-@synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
+@synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
+@synthesize draggedNodes, draggedParentNode;
+@synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
 
 #pragma mark -
 #pragma Object Lifecycle
 
 - (void)dealloc {
     [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [clipboardParentNode release];
+    [clipboardNodes release];
+    [draggedParentNode release];
+    [draggedNodes release];
     [browserMenu release];
     [sharedPreviewController release];
-    [outlineViewDataSourceArray release];
+    [othersSharedNode release];
+    [mySharedNode release];
+    [sharedNode release];
+    [containersNodeChildren release];
+    [containersNode release];
     [accountNode release];
     [rootNode release];
     [super dealloc];
 - (void)awakeFromNib {
     [super awakeFromNib];
     
-    [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
-    [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
+    [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
+    [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
     
     [browser setCellClass:[PithosBrowserCell class]];
     [browser setMenu:browserMenu];
 }
 
-- (void)resetContainers {
+- (void)resetContainers:(NSNotification *)notification {
     rootNode = nil;
     [browser loadColumnZero];
-    self.outlineViewDataSourceArray = nil;
-    
-    // Create the outlineView tree
-    // CONTAINERS
-       NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
-                            [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
-//    // CONTAINERS/pithos
-//     [[containersTreeNode mutableChildNodes] addObject:
-//     [NSTreeNode treeNodeWithRepresentedObject:
-//      [[[PithosContainerNode alloc] initWithContainerName:@"pithos" 
-//                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
-//        ] autorelease]]];
-//    // CONTAINERS/trash
-//     [[containersTreeNode mutableChildNodes] addObject:
-//     [NSTreeNode treeNodeWithRepresentedObject:
-//      [[[PithosContainerNode alloc] initWithContainerName:@"trash"
-//                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
-//        ] autorelease]]];
-    // SHARED
-       NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
-                                      [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
-    // SHARED/my shared
-       [[sharedTreeNode mutableChildNodes] addObject:
-     [NSTreeNode treeNodeWithRepresentedObject:
-      [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
-                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
-        ] autorelease]]];
-    // SHARED/others shared
-       [[sharedTreeNode mutableChildNodes] addObject:
-     [NSTreeNode treeNodeWithRepresentedObject:
-      [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
-                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
-        ] autorelease]]];
-    
-    self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
+    [outlineView reloadData];
     
        // Expand the folder outline view
     [outlineView expandItem:nil expandChildren:YES];
        [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
     
-    // Create accountNode and trigger a refresh
-    accountNode = [[PithosAccountNode alloc] init];
-    accountNode.children;
+    // Refresh account
+    [accountNode refresh];
 }
 
 - (void)windowDidLoad {
     [super windowDidLoad];
     
+    accountNode = [[PithosAccountNode alloc] init];
+    containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
+    containersNodeChildren = [[NSMutableArray alloc] init];
+    sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
+    mySharedNode = [[PithosAccountNode alloc] init];
+    mySharedNode.displayName = @"my shared";
+    mySharedNode.shared = YES;
+    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
+    othersSharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"others shared" 
+                                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]];
+    
     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
     
     // Register for updates
     [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(pithosAccountNodeChildrenUpdated:) 
                                                  name:@"PithosAccountNodeChildrenUpdated" 
-                                               object:nil];
+                                               object:accountNode];
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosNodeChildrenUpdated:) 
+                                                 name:@"PithosAccountNodeChildrenUpdated" 
+                                               object:mySharedNode];
     [[NSNotificationCenter defaultCenter] addObserver:self 
-                                             selector:@selector(resetContainers) 
+                                             selector:@selector(resetContainers:) 
                                                  name:@"PithosAuthenticationCredentialsUpdated" 
                                                object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosBrowserRefreshNeeded:) 
+                                                 name:@"PithosBrowserRefreshNeeeded" 
+                                               object:nil];    
 }
 
 #pragma mark -
-#pragma Observers
+#pragma mark Observers
 
 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
     PithosNode *node = (PithosNode *)[notification object];
+    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
     NSInteger lastColumn = [browser lastColumn];
     for (NSInteger column = lastColumn; column >= 0; column--) {
         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
             [browser reloadColumn:column];
-            if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
-                // This reloads the preview column
-                [browser setLastColumn:column];
-                [browser addColumn];
-            }
             return;
         }
     }
 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
     BOOL containerPithosFound = NO;
     BOOL containerTrashFound = NO;
-    //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
-    NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
+    NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
+    for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
+        if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
+            [removedContainersNodeChildren addIndex:i];
+    }
+    [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
     for (PithosContainerNode *containerNode in accountNode.children) {
         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
-            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
-            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
+            if (![containersNodeChildren containsObject:containerNode])
+                [containersNodeChildren insertObject:containerNode atIndex:0];
             containerPithosFound = YES;
         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
-            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];            
             NSUInteger insertIndex = 1;
             if (!containerPithosFound)
                 insertIndex = 0;
-            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
+            if (![containersNodeChildren containsObject:containerNode])
+                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
             containerTrashFound = YES;
-        } else {
-            [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
+        } else if (![containersNodeChildren containsObject:containerNode]) {
+            [containersNodeChildren addObject:containerNode];
         }
     }
     BOOL refreshAccountNode = NO;
     if (!containerPithosFound) {
-        // create pithos
+        // Create pithos node
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
         [containerRequest startSynchronous];
         if ([containerRequest error]) {
-            NSLog(@"error:%@", [containerRequest error]);
-            // XXX do something on error
+            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     }
     if (!containerTrashFound) {
-        // create trash
+        // Create trash node
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
         [containerRequest startSynchronous];
         if ([containerRequest error]) {
-            NSLog(@"error:%@", [containerRequest error]);
-            // XXX do something on error
+            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
     }
-    if (refreshAccountNode) {
-        [accountNode invalidateChildren];
-        accountNode.children;
-    } else {
-        [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
-        self.outlineViewDataSourceArray = outlineViewDataSourceArray;
-        
-        // Expand the folder outline view
-        [outlineView expandItem:nil expandChildren:YES];
-        [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
-        
-        [self refresh:nil];
+    
+    if (refreshAccountNode)
+        [accountNode refresh];
+    
+    [outlineView reloadData];
+    
+    // Expand the folder outline view
+    [outlineView expandItem:nil expandChildren:YES];
+    
+    if ((rootNode == containersNode) || (rootNode == sharedNode)) {
+        rootNode = [containersNodeChildren objectAtIndex:0];
+        [browser loadColumnZero];
     }
+    
+    if (notification)
+        [self refresh:nil];
+}
+
+- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
+    [self refresh:nil];
 }
 
 #pragma mark -
-#pragma Actions
+#pragma mark Actions
 
 - (IBAction)refresh:(id)sender {
+    if (sender)
+        [accountNode refresh];
     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
         [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
     }
 }
 
 #pragma mark -
-#pragma NSBrowserDelegate
+#pragma mark NSBrowserDelegate
 
 - (id)rootItemForBrowser:(NSBrowser *)browser {
     return rootNode;    
     return NO;
 }
 
+#pragma mark Editing
+
+- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    if (node.shared || ([node class] == [PithosContainerNode class]))
+        return NO;
+    return YES;
+}
+
+- (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    NSString *newName = (NSString *)object;
+    NSUInteger newNameLength = [newName length];
+    NSRange firstSlashRange = [newName rangeOfString:@"/"];
+    if ((newNameLength == 0) || 
+        ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
+        ([newName isEqualToString:node.displayName])) {
+        return;
+    }
+    if (([node class] == [PithosObjectNode class]) || 
+        (([node class] == [PithosSubdirNode class]) && 
+         !node.pithosObject.subdir &&
+         [node.pithosObject.name hasSuffix:@"/"])) {
+        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+        dispatch_async(queue, ^{
+            NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
+            if ([newName hasSuffix:@"/"])
+                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+            NSError *error = nil;
+            BOOL isDirectory;
+            if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
+                                                      objectName:destinationObjectName 
+                                                           error:&error 
+                                                     isDirectory:&isDirectory]) {
+                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                [alert setMessageText:@"Name Taken"];
+                [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+                [alert addButtonWithTitle:@"OK"];
+                [alert runModal];
+                return;
+            } else if (error) {
+                return;
+            }
+            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities 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];
+            }
+        });
+    } else if ([node class] == [PithosSubdirNode class]) {
+        if (firstSlashRange.length == 1)
+            return;
+        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+        dispatch_async(queue, ^{
+            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]) {
+                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                [alert setMessageText:@"Name Taken"];
+                [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+                [alert addButtonWithTitle:@"OK"];
+                [alert runModal];
+                return;
+            } else if (error) {
+                return;
+            }
+            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];
+            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];
+                }
+            }
+        });
+    }
+}
+
 #pragma mark Drag and Drop source
 
+- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
+      withEvent:(NSEvent *)event {
+    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
+    __block BOOL result = YES;
+    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
+        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
+        if ([node class] == [PithosContainerNode class]) {
+            result = NO;
+            *stop = YES;
+        }
+    }];
+    return result;
+}
+
 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
    toPasteboard:(NSPasteboard *)pasteboard {
     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
-    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; 
-    for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
-        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
+    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
+    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
+    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
+        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
         [propertyList addObject:[node.pithosObject.name pathExtension]];
-    }
+        [nodes addObject:node];
+    }];
 
     [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
     [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
-    
+    self.draggedNodes = nodes;
+    self.draggedParentNode = [browser parentForItemsInColumn:column];
     return YES;
 }
 
 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
-    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; 
-    for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
-        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
+    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
+    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
+        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
         
         // If the node is a subdir ask if the whole tree should be downloaded
-        if (node.pithosObject.subdir) {
+        if ([node class] == [PithosSubdirNode class]) {
             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
             [alert setMessageText:@"Download directory"];
             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
@@ -380,23 +491,22 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             [alert addButtonWithTitle:@"Cancel"];
             NSInteger choice = [alert runModal];
             if (choice == NSAlertFirstButtonReturn) {
-                NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                 objectName:node.pithosObject.name 
-                                                                                                toDirectory:[dropDestination path] 
-                                                                                              checkIfExists:YES];
-                if (objectRequests) {
-                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                        [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
-                        // XXX set delegates and queue
-                        [objectRequest setCompletionBlock:^{
-                            NSLog(@"dl completed: %@", [objectRequest url]);
-                        }];
-                        [objectRequest setFailedBlock:^{
-                            NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
-                        }];
-                        [objectRequest startAsynchronous];
+                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                dispatch_async(queue, ^{
+                    NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                                    toDirectory:[dropDestination path] 
+                                                                                                  checkIfExists:YES];
+                    if (objectRequests) {
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
+                            objectRequest.didFailSelector = @selector(downloadObjectFailed:);
+                            [objectRequest startAsynchronous];
+                        }
                     }
-                }
+                });
             }
         } else {
             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
@@ -405,17 +515,13 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                                                                                               checkIfExists:YES];
             if (objectRequest) {
                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
-                // XXX set delegates and queue
-                [objectRequest setCompletionBlock:^{
-                    NSLog(@"dl completed: %@", [objectRequest url]);
-                }];
-                [objectRequest setFailedBlock:^{
-                    NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
-                }];
+                objectRequest.delegate = self;
+                objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
+                objectRequest.didFailSelector = @selector(downloadObjectFailed:);
                 [objectRequest startAsynchronous];
             }
         }
-    }
+    }];
     return names;
 }
 
@@ -427,23 +533,58 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     column:(NSInteger *)column 
              dropOperation:(NSBrowserDropOperation *)dropOperation {
     NSDragOperation result = NSDragOperationNone;
-    // Files from the finder are accepted
-    if ([[[info draggingPasteboard] types] indexOfObject:NSFilenamesPboardType] != -1) {
-        // For a between drop, we let the user drop "on" the parent item
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        // For a drop above, the drop is redirected to the parent item
         if (*dropOperation == NSBrowserDropAbove)
             *row = -1;
         // Only allow dropping in folders
         if (*column != -1) {
             if (*row != -1) {
-                PithosNode *node = [browser itemAtRow:*row inColumn:*column];
-                if ([node class] != [PithosSubdirNode class])
+                // Check if the node is not a folder and if so redirect to the parent item
+                if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
                     *row = -1;
+            }
             *dropOperation = NSBrowserDropOn;
             result = NSDragOperationCopy;
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        // For a drop above, the drop is redirected to the parent item
+        if (*dropOperation == NSBrowserDropAbove) 
+            *row = -1;
+        // Only allow dropping in folders
+        if (*column != -1) {
+            PithosNode *dropNode;
+            if (*row != -1) {
+                // Check if the node is not a folder and if so redirect to the parent item
+                dropNode = [browser itemAtRow:*row inColumn:*column];
+                if ([dropNode class] != [PithosSubdirNode class])
+                    *row = -1;
+            }
+            if (*row == -1)
+                dropNode = [browser parentForItemsInColumn:*column];
+            
+            if (!dropNode.shared) {
+                if ([info draggingSourceOperationMask] & NSDragOperationMove) {
+                    // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
+                    if ((([dropNode class] == [PithosContainerNode class]) || 
+                         dropNode.pithosObject.subdir || 
+                         ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
+                        ![dropNode isEqualTo:draggedParentNode]) { 
+    //                    ![dropNode isEqualTo:draggedParentNode] && 
+    //                    ![draggedNodes containsObject:dropNode]) {                
+                        result = NSDragOperationMove;
+                    }
+                } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
+                    // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
+                    if (([dropNode class] == [PithosContainerNode class]) || 
+                        dropNode.pithosObject.subdir || 
+                        ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
+                        result = NSDragOperationCopy;
+                    }
+                }
             }
         }
     }
-    // XXX else local file promises
     return result;
 }
 
@@ -452,206 +593,535 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
           atRow:(NSInteger)row 
          column:(NSInteger)column 
   dropOperation:(NSBrowserDropOperation)dropOperation {
-    NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
-    NSLog(@"drag in filenames: %@", filenames);
-    PithosNode *node = nil;
-    if ((column != -1) && (filenames != nil)) {
-        if (row != -1)
-            node = [browser itemAtRow:row inColumn:column];
-        else
-            node = [browser parentForItemsInColumn:column];
-        NSLog(@"drag in node: %@", node.url);
-        if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
-            return NO;
-        
-        NSFileManager *defaultManager = [NSFileManager defaultManager];
-        NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
-        NSString *objectNamePrefix;
-        if ([node class] == [PithosSubdirNode class])
-            objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
-        else
-            objectNamePrefix = [NSString stringWithString:@""];
-        NSUInteger blockSize = node.pithosContainer.blockSize;
-        NSString *blockHash = node.pithosContainer.blockHash;
-        
-        for (NSString *filePath in filenames) {
-            BOOL isDirectory;
-            if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
-                if (!isDirectory) {
-                    // Upload file
-                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
-                    // XXX request
-                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                    dispatch_async(queue, ^{
-                        NSError *error = nil;
-                        NSURLResponse *response = nil;
-                        [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath] 
-                                                                                 cachePolicy:NSURLCacheStorageNotAllowed 
-                                                                             timeoutInterval:.1] 
-                                              returningResponse:&response 
-                                                          error:&error];
-                        NSString *contentType = [response MIMEType];
-                        if (contentType == nil)
-                            contentType = @"application/binary";
-                        if (error)
-                            NSLog(@"contentType detection error: %@", error);
-                        NSArray *hashes = nil;
-                        ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                                  objectName:objectName 
-                                                                                                                 contentType:contentType 
-                                                                                                                   blockSize:blockSize 
-                                                                                                                   blockHash:blockHash 
-                                                                                                                     forFile:filePath 
-                                                                                                               checkIfExists:YES 
-                                                                                                                      hashes:&hashes];
-                        if (objectRequest) {
-                            // XXX set delegates and queue
-                            objectRequest.delegate = self;
-                            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-                            objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                      containerName, @"containerName", 
-                                                      objectName, @"objectName", 
-                                                      contentType, @"contentType", 
-                                                      [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
-                                                      blockHash, @"blockHash", 
-                                                      filePath, @"filePath", 
-                                                      hashes, @"hashes", 
-                                                      [NSNumber numberWithBool:YES], @"checkIfExists", 
-                                                      node, @"node", 
-                                                      [NSNumber numberWithUnsignedInteger:0], @"iteration", 
-                                                      nil];
-                            [objectRequest startAsynchronous];
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+        if ((column != -1) && (filenames != nil)) {
+            PithosNode *node = nil;
+            if (row != -1)
+                node = [browser itemAtRow:row inColumn:column];
+            else
+                node = [browser parentForItemsInColumn:column];
+            NSLog(@"drag in node: %@", node.url);
+            if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
+                return NO;
+            
+            NSFileManager *defaultManager = [NSFileManager defaultManager];
+            NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
+            NSString *objectNamePrefix;
+            if ([node class] == [PithosSubdirNode class])
+                objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
+            else
+                objectNamePrefix = [NSString string];
+            NSUInteger blockSize = node.pithosContainer.blockSize;
+            NSString *blockHash = node.pithosContainer.blockHash;
+            
+            for (NSString *filePath in filenames) {
+                BOOL isDirectory;
+                if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+                    if (!isDirectory) {
+                        // Upload file
+                        NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
+                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                        dispatch_async(queue, ^{
+                            NSError *error = nil;
+                            NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
+                            if (contentType == nil)
+                                contentType = @"application/octet-stream";
+                            if (error)
+                                NSLog(@"contentType detection error: %@", error);
+                            NSArray *hashes = nil;
+                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
+                                                                                                                      objectName:objectName 
+                                                                                                                     contentType:contentType 
+                                                                                                                       blockSize:blockSize 
+                                                                                                                       blockHash:blockHash 
+                                                                                                                         forFile:filePath 
+                                                                                                                   checkIfExists:YES 
+                                                                                                                          hashes:&hashes];
+                            if (objectRequest) {
+                                objectRequest.delegate = self;
+                                objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+                                objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                          containerName, @"containerName", 
+                                                          objectName, @"objectName", 
+                                                          contentType, @"contentType", 
+                                                          [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
+                                                          blockHash, @"blockHash", 
+                                                          filePath, @"filePath", 
+                                                          hashes, @"hashes", 
+                                                          node, @"node", 
+                                                          [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+                                                          nil];
+                                [objectRequest startAsynchronous];
+                            }
+                        });
+                    } else {
+                        // Upload directory, confirm first
+                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                        [alert setMessageText:@"Upload directory"];
+                        [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
+                        [alert addButtonWithTitle:@"OK"];
+                        [alert addButtonWithTitle:@"Cancel"];
+                        NSInteger choice = [alert runModal];
+                        if (choice == NSAlertFirstButtonReturn) {
+                            NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
+                            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                            dispatch_async(queue, ^{
+                                NSMutableArray *objectNames = nil;
+                                NSMutableArray *contentTypes = nil;
+                                NSMutableArray *filePaths = nil;
+                                NSMutableArray *hashesArrays = nil;
+                                NSMutableArray *directoryObjectRequests = nil;
+                                NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
+                                                                                                             objectName:objectName 
+                                                                                                              blockSize:blockSize 
+                                                                                                              blockHash:blockHash 
+                                                                                                           forDirectory:filePath 
+                                                                                                          checkIfExists:YES 
+                                                                                                            objectNames:&objectNames
+                                                                                                           contentTypes:&contentTypes
+                                                                                                              filePaths:&filePaths
+                                                                                                             hashesArrays:&hashesArrays 
+                                                                                                directoryObjectRequests:&directoryObjectRequests];
+                                for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
+                                    objectRequest.delegate = self;
+                                    objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
+                                    objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
+                                    [objectRequest startAsynchronous];
+                                }
+                                if (objectRequests) {
+                                    for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
+                                        ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
+                                        objectRequest.delegate = self;
+                                        objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+                                        objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+                                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                                  containerName, @"containerName", 
+                                                                  [objectNames objectAtIndex:i], @"objectName", 
+                                                                  [contentTypes objectAtIndex:i], @"contentType", 
+                                                                  [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
+                                                                  blockHash, @"blockHash", 
+                                                                  [filePaths objectAtIndex:i], @"filePath", 
+                                                                  [hashesArrays objectAtIndex:i], @"hashes", 
+                                                                  [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+                                                                  nil];
+                                        [objectRequest startAsynchronous];
+                                    }
+                                }
+                            });
                         }
-                        // XXX else show alert?
-                    });
-                } else {
-                    // Upload directory, confirm first
-                    // XXX implement this
+                    }
                 }
+                
             }
+            return YES;
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if ((column != -1) && (draggedNodes != nil)) {
+            PithosNode *dropNode = nil;
+            if (row != -1)
+                dropNode = [browser itemAtRow:row inColumn:column];
+            else
+                dropNode = [browser parentForItemsInColumn:column];
+            NSLog(@"drag local node: %@", dropNode.url);
+            if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
+                return NO;
+            
+            NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
+            NSString *objectNamePrefix;
+            if ([dropNode class] == [PithosSubdirNode class])
+                objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
+            else
+                objectNamePrefix = [NSString string];
             
+            if ([info draggingSourceOperationMask] & NSDragOperationMove) {
+                for (PithosNode *node in draggedNodes) {
+                    if (([node class] == [PithosObjectNode class]) || 
+                        (([node class] == [PithosSubdirNode class]) && 
+                         !node.pithosObject.subdir &&
+                         [node.pithosObject.name hasSuffix:@"/"])) {
+                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                        dispatch_async(queue, ^{
+                            NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                            if ([node.pithosObject.name hasSuffix:@"/"])
+                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                                 objectName:node.pithosObject.name 
+                                                                                                   destinationContainerName:containerName 
+                                                                                                      destinationObjectName:destinationObjectName 
+                                                                                                              checkIfExists:YES];
+                            if (objectRequest) {
+                                objectRequest.delegate = self;
+                                objectRequest.didFinishSelector = @selector(moveFinished:);
+                                objectRequest.didFailSelector = @selector(moveFailed:);
+                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                          node.parent, @"node", 
+                                                          dropNode, @"dropNode", 
+                                                          nil];
+                                [objectRequest startAsynchronous];
+                            }
+                        });
+                    } else if ([node class] == [PithosSubdirNode class]) {
+                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                        dispatch_async(queue, ^{
+                            NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                            if (node.pithosObject.subdir)
+                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                            NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                             objectName:node.pithosObject.name 
+                                                                                               destinationContainerName:containerName 
+                                                                                                  destinationObjectName:destinationObjectName 
+                                                                                                          checkIfExists:YES];
+                            if (objectRequests) {
+                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                                    objectRequest.delegate = self;
+                                    objectRequest.didFinishSelector = @selector(moveFinished:);
+                                    objectRequest.didFailSelector = @selector(moveFailed:);
+                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                              node.parent, @"node", 
+                                                              dropNode, @"dropNode", 
+                                                              nil];
+                                    [objectRequest startAsynchronous];
+                                }
+                            }
+                        });
+                    }
+                }
+            } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
+                for (PithosNode *node in draggedNodes) {
+                    if (([node class] == [PithosObjectNode class]) || 
+                        (([node class] == [PithosSubdirNode class]) && 
+                         !node.pithosObject.subdir &&
+                         [node.pithosObject.name hasSuffix:@"/"])) {
+                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                        dispatch_async(queue, ^{
+                            NSString *destinationObjectName;
+                            if (![dropNode isEqualTo:node.parent]) {
+                                destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                                if ([node.pithosObject.name hasSuffix:@"/"])
+                                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                            } else {
+                                destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
+                                                                                                    objectName:node.pithosObject.name];
+                            }
+                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                                 objectName:node.pithosObject.name 
+                                                                                                   destinationContainerName:containerName 
+                                                                                                      destinationObjectName:destinationObjectName 
+                                                                                                              checkIfExists:YES];
+                            if (objectRequest) {
+                                objectRequest.delegate = self;
+                                objectRequest.didFinishSelector = @selector(copyFinished:);
+                                objectRequest.didFailSelector = @selector(copyFailed:);
+                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                          dropNode, @"dropNode", 
+                                                          nil];
+                                [objectRequest startAsynchronous];
+                            }
+                        });
+                    } else if ([node class] == [PithosSubdirNode class]) {
+                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                        dispatch_async(queue, ^{
+                            NSString *destinationObjectName;
+                            if (![dropNode isEqualTo:node.parent]) {
+                                destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                                if (node.pithosObject.subdir)
+                                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                            } else {
+                                destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
+                                                                                                 subdirName:node.pithosObject.name];
+                            }
+                            NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                             objectName:node.pithosObject.name 
+                                                                                               destinationContainerName:containerName 
+                                                                                                  destinationObjectName:destinationObjectName 
+                                                                                                          checkIfExists:YES];
+                            if (objectRequests) {
+                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                                    objectRequest.delegate = self;
+                                    objectRequest.didFinishSelector = @selector(copyFinished:);
+                                    objectRequest.didFailSelector = @selector(copyFailed:);
+                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                              dropNode, @"dropNode", 
+                                                              nil];
+                                    [objectRequest startAsynchronous];
+                                }
+                            }
+                        });
+
+                    }
+                }
+            }
+            return YES;
         }
-        return YES;
     }
-
     return NO;
 }
 
 #pragma mark -
 #pragma mark ASIHTTPRequestDelegate
 
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {    
-    NSLog(@"upload using hashmap completed: %@", [objectRequest url]);
+- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Download completed: %@", [objectRequest url]);
+    if (objectRequest.responseStatusCode == 200) {
+        if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
+            NSLog(@"Downloaded  0 bytes");
+            NSFileManager *defaultManager = [NSFileManager defaultManager];
+            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+            if (![defaultManager fileExistsAtPath:filePath]) {
+                if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
+                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                    [alert setMessageText:@"Create File Error"];
+                    [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
+                    [alert addButtonWithTitle:@"OK"];
+                    [alert runModal];
+                }
+            }
+        }
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Download failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Upload directory object completed: %@", [objectRequest url]);
+    if (objectRequest.responseStatusCode == 201) {
+        NSLog(@"Directory object created: %@", [objectRequest url]);
+        [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Upload directory object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
     if (objectRequest.responseStatusCode == 201) {
-        NSLog(@"object created: %@", [objectRequest url]);
+        NSLog(@"Object created: %@", [objectRequest url]);
         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        [node invalidateChildren];
-        node.children;
+        if (node)
+            [node refresh];
+        else
+            [self refresh:nil];
     } else if (objectRequest.responseStatusCode == 409) {
-        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] + 1;
-        if (iteration > 10) {
-            NSLog(@"upload iteration limit reached: %@", [objectRequest url]);
-            // XXX show alert
+        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
+        if (iteration == 0) {
+            NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
+            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+            [alert setMessageText:@"Upload Timeout"];
+            [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
+                                       [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
+            [alert addButtonWithTitle:@"OK"];
+            [alert runModal];
             return;
         }
         NSLog(@"object is missing hashes: %@", [objectRequest url]);
+        NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+                                                      withMissingHashesResponse:[objectRequest responseString]];
+        NSUInteger missingBlockIndex = [missingBlocks firstIndex];
         ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
                                                                                                       objectName:@".upload" 
-                                                                                                     contentType:[objectRequest.userInfo objectForKey:@"contentType"]
                                                                                                        blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
-                                                                                                         forFile:[objectRequest.userInfo objectForKey:@"filePath"]
-                                                                                                          hashes:[objectRequest.userInfo objectForKey:@"hashes"] 
-                                                                                           missingHashesResponse:[objectRequest responseString] 
-                                                                                                   checkIfExists:[[objectRequest.userInfo objectForKey:@"checkIfExists"] boolValue]];
-        newObjectRequest.shouldAttemptPersistentConnection = NO;
+                                                                                                         forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                               missingBlockIndex:missingBlockIndex];
         newObjectRequest.delegate = self;
-        newObjectRequest.didFinishSelector = @selector(uploadMissingHashesFinished:);
-        newObjectRequest.didFailSelector = @selector(uploadMissingHashesFailed:);
+        newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
+        newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
         newObjectRequest.userInfo = objectRequest.userInfo;
-        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithBool:NO] forKey:@"checkIfExists"];
         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
+        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
         [newObjectRequest startAsynchronous];
     } else {
-        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-        [alert setMessageText:@"Unexpected Response Status"];
-        [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]];
-        [alert addButtonWithTitle:@"OK"];
-        [alert runModal];
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"upload failed: %@, error: %@", [objectRequest url], [objectRequest error]);
-    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-    [alert setMessageText:@"HTTP Request Error"];
-    [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]];
-    [alert addButtonWithTitle:@"OK"];
-    [alert runModal];
-}
-
-- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest {
-    NSLog(@"upload of missing hashes completed: %@", [objectRequest url]);
-    if ((objectRequest.responseStatusCode == 201) || (objectRequest.responseStatusCode == 204)) {
-        NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
-        ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
-                                                                                                     objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
-                                                                                                    contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
-                                                                                                      blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
-                                                                                                      blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
-                                                                                                        forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                  checkIfExists:NO 
-                                                                                                         hashes:&hashes];
-        newObjectRequest.delegate = self;
-        newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-        newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
-        newObjectRequest.userInfo = objectRequest.userInfo;
-        [newObjectRequest startAsynchronous];
+    NSLog(@"Upload using hashmap failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
+    if (objectRequest.responseStatusCode == 201) {
+        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
+        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+        if (missingBlockIndex == NSNotFound) {
+            NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
+            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
+                                                                                                         objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
+                                                                                                        contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
+                                                                                                          blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
+                                                                                                          blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
+                                                                                                            forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                      checkIfExists:NO 
+                                                                                                             hashes:&hashes];
+            newObjectRequest.delegate = self;
+            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+            newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+            newObjectRequest.userInfo = objectRequest.userInfo;
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
+            [newObjectRequest startAsynchronous];
+        } else {
+            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
+                                                                                                          objectName:@".upload" 
+                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
+                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                   missingBlockIndex:missingBlockIndex];
+            newObjectRequest.delegate = self;
+            newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
+            newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
+            newObjectRequest.userInfo = objectRequest.userInfo;
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+            [newObjectRequest startAsynchronous];
+        }
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Upload of missing block failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Move object completed: %@", [objectRequest url]);
+    if (objectRequest.responseStatusCode == 201) {
+        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
+        PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
+        if (node)
+            [node refresh];
+        if (dropNode)
+            [dropNode refresh];
+        if (!node || !dropNode)
+            [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Move object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Copy object completed: %@", [objectRequest url]);
+    if (objectRequest.responseStatusCode == 201) {
+        PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
+        if (dropNode)
+            [dropNode refresh];
+        else
+            [self refresh:nil];
     } else {
-        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-        [alert setMessageText:@"Unexpected Response Status"];
-        [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]];
-        [alert addButtonWithTitle:@"OK"];
-        [alert runModal];
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
 }
 
-- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest {    
-    NSLog(@"upload of missing hashes failed: %@, error: %@", [objectRequest url], [objectRequest error]);
-    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-    [alert setMessageText:@"HTTP Request Error"];
-    [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]];
-    [alert addButtonWithTitle:@"OK"];
-    [alert runModal];
+- (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Copy object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
 }
 
 #pragma mark -
 #pragma mark NSSplitViewDelegate
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
-    return 120;
+    if (splitView == verticalSplitView)
+        return 120;
+    else
+        return ([horizontalSplitView bounds].size.height - 87);
 }
 
 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
-    return 220;
+    if (splitView == verticalSplitView)
+        return 220;
+    else
+        return ([horizontalSplitView bounds].size.height - 87);
+}
+
+- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
+    if (splitView == verticalSplitView) {
+        if (proposedPosition < 120)
+            return 120;
+        else if (proposedPosition > 220)
+            return 220;
+        else
+            return proposedPosition;
+    } else {
+        return ([horizontalSplitView bounds].size.height - 87);
+    }
+}
+
+#pragma mark -
+#pragma mark NSOutlineViewDataSource
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+    if (item == nil)
+        return 2;
+    if (item == containersNode)
+        return containersNodeChildren.count;
+    if (item == sharedNode)
+        return 2;
+    return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+    if (item == nil)
+        return (!index ? containersNode : sharedNode);
+    if (item == sharedNode)
+        return (!index ? mySharedNode : othersSharedNode);
+    return [containersNodeChildren objectAtIndex:index];
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+    if ((item == containersNode) || (item == sharedNode))
+        return YES;
+    return NO;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return node;    
 }
 
 #pragma mark -
 #pragma mark NSOutlineViewDelegate
 
 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
-    return ([[item representedObject] isLeaf]);
+    if ((item == containersNode) || (item == sharedNode))
+        return NO;
+    return YES;
 }
 
 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
-       return (![[item representedObject] isLeaf]);
+    if ((item == containersNode) || (item == sharedNode))
+        return YES;
+    return NO;
 }
 
 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
-    PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
+    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
     if (node) {
         rootNode = node;
         [browser loadColumnZero];
+        [self refresh:nil];
     }
 }
 
@@ -662,22 +1132,480 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     NSInteger column = [browser clickedColumn];
     NSInteger row = [browser clickedRow];
     [menu removeAllItems];
+    NSMenuItem *menuItem;
+    NSString *menuItemTitle;
     if ((column == -1) || (row == -1)) {
-        // General context menu has 0
+        // General context menu
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        PithosNode *menuNode;
+        if ([menuNodesIndexPaths count] == 0) {
+            menuNode = [browser parentForItemsInColumn:0];
+        } else if (([menuNodesIndexPaths count] != 1) || 
+            ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
+            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+        } else {
+            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+        }
+        if ([menuNode class] == [PithosAccountNode class])
+            return;
+        BOOL shared = menuNode.shared;
+        
+        if (!shared) {
+            // New Folder
+            menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:menuNode];
+            [menu addItem:menuItem];
+            [menu addItem:[NSMenuItem separatorItem]];
+        }
+        
+        // Get Info
+        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
+        [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
+        [menu addItem:menuItem];
+        
+        if (!shared) {
+            // Paste
+            if (clipboardNodes) {
+                NSUInteger clipboardNodesCount = [clipboardNodes count];
+                if (clipboardNodesCount == 0) {
+                    self.clipboardNodes = nil;
+                } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
+                    if (clipboardNodesCount == 1)
+                        menuItemTitle = [NSString stringWithString:@"Paste Item"];
+                    else
+                        menuItemTitle = [NSString stringWithString:@"Paste Items"];
+                    [menu addItem:[NSMenuItem separatorItem]];
+                    menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
+                    [menuItem setRepresentedObject:menuNode];
+                    [menu addItem:menuItem];
+                }
+            }
+        }
     } else {
-        // PithosNode menu has 1 items
+        // Node context menu
+        NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+        if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
+            for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+                [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+            }
+        } else {
+            [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
+        }
+        NSUInteger menuNodesCount = [menuNodes count];
+        BOOL shared = ((PithosNode *)[menuNodes objectAtIndex:0]).shared;
+        
+        
+        if (!shared) {
+            // Move to Trash (pithos container only)
+            // Delete
+            if ([rootNode class] == [PithosContainerNode class]) {
+                if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
+                    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
+                    [menuItem setRepresentedObject:menuNodes];
+                    [menu addItem:menuItem];
+                }
+                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
+                [menuItem setRepresentedObject:menuNodes];
+                [menu addItem:menuItem];
+                [menu addItem:[NSMenuItem separatorItem]];
+            }
+        }
+        
         // Get Info
-        NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
-        [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
+        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
+        [menuItem setRepresentedObject:menuNodes];
         [menu addItem:menuItem];
+        
+        if (!shared || ([[menuNodes objectAtIndex:0] class] != [PithosContainerNode class]))
+            [menu addItem:[NSMenuItem separatorItem]];
+        
+        if (!shared) {
+            // Cut
+            
+            if (menuNodesCount == 1)
+                menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+            else 
+                menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
+            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+        }
+        
+        if (!shared || ([[menuNodes objectAtIndex:0] class] != [PithosContainerNode class])) {
+            // Copy
+            if (menuNodesCount == 1)
+                menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+            else 
+                menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
+            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+        }
+        
+        if (!shared) {
+            // Paste
+            if (menuNodesCount == 1) {
+                PithosNode *menuNode = [menuNodes objectAtIndex:0];
+                if (([menuNode class] == [PithosSubdirNode class]) && 
+                    (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
+                    if (clipboardNodes) {
+                        NSUInteger clipboardNodesCount = [clipboardNodes count];
+                        if (clipboardNodesCount == 0) {
+                            self.clipboardNodes = nil;
+                        } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
+                            if (clipboardNodesCount == 1)
+                                menuItemTitle = [NSString stringWithString:@"Paste Item"];
+                            else
+                                menuItemTitle = [NSString stringWithString:@"Paste Items"];
+                            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
+                            [menuItem setRepresentedObject:menuNode];
+                            [menu addItem:menuItem];
+                        }
+                    }
+                }
+            }
+        }
     }
 }
 
 #pragma mark -
 #pragma mark Menu Actions
 
-- (void)getInfo:(NSMenuItem *)sender {
-    [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];
+- (void)menuNewFolder:(NSMenuItem *)sender {
+    PithosNode *node = (PithosNode *)[sender representedObject];
+    if ([node class] == [PithosContainerNode class]) {
+        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+        dispatch_async(queue, ^{
+            NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
+                                                                                subdirName:@"untitled folder"];
+            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
+                                                                                                         objectName:safeObjectName 
+                                                                                                           eTag:nil 
+                                                                                                    contentType:@"application/directory" 
+                                                                                                contentEncoding:nil 
+                                                                                             contentDisposition:nil 
+                                                                                                       manifest:nil 
+                                                                                                        sharing:nil 
+                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                       metadata:nil 
+                                                                                                           data:[NSData data]];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(newFolderFinished:);
+            objectRequest.didFailSelector = @selector(newFolderFailed:);
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                      node, @"node", 
+                                      nil];
+            [objectRequest startAsynchronous];
+        });
+    } else if (([node class] == [PithosSubdirNode class]) && 
+               (node.pithosObject.subdir || 
+                ![node.pithosObject.name hasSuffix:@"/"])) {
+        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+        dispatch_async(queue, ^{
+            NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
+                                                                               subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
+            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
+                                                                                                        objectName:safeObjectName 
+                                                                                                              eTag:nil 
+                                                                                                       contentType:@"application/directory" 
+                                                                                                   contentEncoding:nil 
+                                                                                                contentDisposition:nil 
+                                                                                                          manifest:nil 
+                                                                                                           sharing:nil 
+                                                                                                          isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                          metadata:nil 
+                                                                                                              data:[NSData data]];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(newFolderFinished:);
+            objectRequest.didFailSelector = @selector(newFolderFailed:);
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                     node, @"node", 
+                                     nil];
+            [objectRequest startAsynchronous];
+        });
+    }
 }
 
+- (void)menuGetInfo:(NSMenuItem *)sender {
+    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+        [node showPithosNodeInfo:sender];
+    }
+}
+
+- (void)menuDelete:(NSMenuItem *)sender {
+    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && 
+             !node.pithosObject.subdir &&
+             [node.pithosObject.name hasSuffix:@"/"])) {
+            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                      objectName:node.pithosObject.name];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
+            objectRequest.didFailSelector = @selector(deleteObjectFailed:);
+            [objectRequest 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 
+                                                                                                   objectName:node.pithosObject.name];
+                if (objectRequests) {
+                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
+                        objectRequest.didFailSelector = @selector(deleteObjectFailed:);
+                        [objectRequest startAsynchronous];
+                    }
+                }
+            });
+        }
+    }
+}
+
+- (void)menuMoveToTrash:(NSMenuItem *)sender {
+    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && 
+             !node.pithosObject.subdir &&
+             [node.pithosObject.name hasSuffix:@"/"])) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
+                                                                                    objectName:node.pithosObject.name];
+                if (safeObjectName) {
+                    ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                         objectName:node.pithosObject.name 
+                                                                                           destinationContainerName:@"trash" 
+                                                                                              destinationObjectName:safeObjectName 
+                                                                                                      checkIfExists:NO];
+                    if (objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
+                        objectRequest.didFailSelector = @selector(moveToTrashFailed:);
+                        [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 *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
+                                                                                    subdirName:node.pithosObject.name];
+                if (safeObjectName) {
+                    NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                       destinationContainerName:@"trash" 
+                                                                                          destinationObjectName:safeObjectName 
+                                                                                                  checkIfExists:NO];
+                    if (objectRequests) {
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
+                            objectRequest.didFailSelector = @selector(moveToTrashFailed:);
+                            [objectRequest startAsynchronous];
+                        }
+                    }
+                }
+            });
+        }
+    }
+}
+
+- (void)menuCut:(NSMenuItem *)sender {
+    self.clipboardNodes = (NSArray *)[sender representedObject];
+    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+    self.clipboardCopy = NO;
+}
+
+- (void)menuCopy:(NSMenuItem *)sender {
+    self.clipboardNodes = (NSArray *)[sender representedObject];
+    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+    self.clipboardCopy = YES;
+}
+
+- (void)menuPaste:(NSMenuItem *)sender {
+    if (!clipboardNodes || ![clipboardNodes count])
+        return;
+    PithosNode *dropNode = (PithosNode *)[sender representedObject];
+    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];
+                        }
+                    }
+                });
+            }
+        }
+    } else {
+        for (PithosNode *node in localClipboardNodes) {
+            if (([node class] == [PithosObjectNode class]) || 
+                (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                dispatch_async(queue, ^{
+                    NSString *destinationObjectName;
+                    if (![dropNode isEqualTo:node.parent]) {
+                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                        if ([node.pithosObject.name hasSuffix:@"/"])
+                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    } else {
+                        destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
+                                                                                         objectName:node.pithosObject.name];
+                    }
+                    ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                         objectName:node.pithosObject.name 
+                                                                                           destinationContainerName:containerName 
+                                                                                              destinationObjectName:destinationObjectName 
+                                                                                                      checkIfExists:YES];
+                    if (objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(copyFinished:);
+                        objectRequest.didFailSelector = @selector(copyFailed:);
+                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                  dropNode, @"dropNode", 
+                                                  nil];
+                        [objectRequest startAsynchronous];
+                    }
+                });
+            } else if ([node class] == [PithosSubdirNode class]) {
+                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                dispatch_async(queue, ^{
+                    NSString *destinationObjectName;
+                    if (![dropNode isEqualTo:node.parent]) {
+                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                        if (node.pithosObject.subdir)
+                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                    } else {
+                        destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
+                                                                                         subdirName:node.pithosObject.name];
+                    }
+                    NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                       destinationContainerName:containerName 
+                                                                                          destinationObjectName:destinationObjectName 
+                                                                                                  checkIfExists:YES];
+                    if (objectRequests) {
+                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                            objectRequest.delegate = self;
+                            objectRequest.didFinishSelector = @selector(copyFinished:);
+                            objectRequest.didFailSelector = @selector(copyFailed:);
+                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                      dropNode, @"dropNode", 
+                                                      nil];
+                            [objectRequest startAsynchronous];
+                        }
+                    }
+                });                
+            }
+        }
+    }
+}
+    
+#pragma mark -
+#pragma mark Menu Actions ASIHTTPRequestDelegate
+
+- (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
+    if (objectRequest.responseStatusCode == 201) {
+        NSLog(@"New application/directory object created: %@", [objectRequest url]);
+        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
+        if (node)
+            [node refresh];
+        else
+            [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Creation of new application/directory object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    if (objectRequest.responseStatusCode == 204) {
+        NSLog(@"Object deleted: %@", [objectRequest url]);
+        [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Delete of object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
+    if (objectRequest.responseStatusCode == 201) {
+        NSLog(@"Object moved: %@", [objectRequest url]);
+        [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Move of object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+
 @end
\ No newline at end of file