Use NFC for local files that are uploaded. Fix menu labels. Update version.
[pithos-macos] / pithos-macos / PithosBrowserController.m
index 0456e6e..08812f3 100644 (file)
@@ -56,6 +56,8 @@
 #import "PithosUtilities.h"
 #import "UsingSizeTransformer.h"
 
+#define REFRESH_TIMER_INTERVAL 5
+
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
 
     containersNodeChildren = [[NSMutableArray alloc] init];
     sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
     mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
-    mySharedNode.displayName = @"my shared";
+    mySharedNode.displayName = @"shared by me";
     mySharedNode.shared = YES;
     mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
     othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
-    othersSharedNode.displayName = @"others shared";
+    othersSharedNode.displayName = @"shared to me";
     othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
     
     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
 //    [activityFacility reset];
     activityFacility.delegate = self;
             
-    refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 
+    refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL 
                                                      target:self 
                                                    selector:@selector(forceRefresh:) 
                                                    userInfo:self 
 #pragma mark Actions
 
 - (IBAction)forceRefresh:(id)sender {
+    if (editingItem)
+        return;
     if (sender)
         [accountNode forceRefresh];
     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
 }
 
 - (IBAction)refresh:(id)sender {
+    if (editingItem)
+        return;
     if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
         [self forceRefresh:sender];
     } else {
     if (node.shared || node.sharingAccount || 
         ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
         return NO;
+    editingItem = YES;
     return YES;
 }
 
 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
+    editingItem = NO;
     PithosNode *node = (PithosNode *)item;
     NSString *newName = (NSString *)object;
     NSUInteger newNameLength = [newName length];
@@ -1091,7 +1099,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
             if (!isDirectory) {
                 // Upload file
-                NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
+                NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]] 
+                                        precomposedStringWithCanonicalMapping];
                 // Operation: Upload a local file
                 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@@ -1168,7 +1177,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 [alert addButtonWithTitle:@"Cancel"];
                 NSInteger choice = [alert runModal];
                 if (choice == NSAlertFirstButtonReturn) {
-                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
+                    NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]] 
+                                            precomposedStringWithCanonicalMapping];
                     // Operation: Upload a local directory and its descendants
                     // The resulting ASIPithosObjectRequests are chained through dependencies
                     __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
@@ -1223,7 +1233,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                               NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
                               NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
                               uploadNetworkQueue, @"networkQueue", 
-                              @"upload", @"queue", 
+                              @"upload", @"operationType", 
                               nil]];
                             if (previousObjectRequest)
                                 [objectRequest addDependency:previousObjectRequest];
@@ -1265,7 +1275,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                                   NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
                                   NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
                                   uploadNetworkQueue, @"networkQueue", 
-                                  @"upload", @"queue", 
+                                  @"upload", @"operationType", 
                                   nil]];
                                 if (destinationNode.sharingAccount)
                                     [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
@@ -1738,17 +1748,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         dispatch_async(dispatch_get_main_queue(), ^{
             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                [node forceRefresh];
+            }
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                [node refresh];
+            }
+            if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                [self forceRefresh:self];
+            else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                [self refresh:self];
         });
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
-            [node forceRefresh];
-        }
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
-            [node refresh];
-        }
-        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
-            [self forceRefresh:self];
-        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
-            [self refresh:self];
     } else {
         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
         [self requestFailed:objectRequest];
@@ -1773,17 +1783,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
                                totalBytes:totalBytes 
                              currentBytes:totalBytes];
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                [node forceRefresh];
+            }
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                [node refresh];
+            }
+            if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                [self forceRefresh:self];
+            else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                [self refresh:self];        
         });
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
-            [node forceRefresh];
-        }
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
-            [node refresh];
-        }
-        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
-            [self forceRefresh:self];
-        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
-            [self refresh:self];        
     } else if (objectRequest.responseStatusCode == 409) {
         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
         if (iteration == 0) {
@@ -1915,17 +1925,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         dispatch_async(dispatch_get_main_queue(), ^{
             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                [node forceRefresh];
+            }
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                [node refresh];
+            }
+            if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                [self forceRefresh:self];
+            else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                [self refresh:self];
         });
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
-            [node forceRefresh];
-        }
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
-            [node refresh];
-        }
-        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
-            [self forceRefresh:self];
-        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
-            [self refresh:self];
     } else {
         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
         [self requestFailed:objectRequest];
@@ -1943,17 +1953,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         dispatch_async(dispatch_get_main_queue(), ^{
             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                [node forceRefresh];
+            }
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                [node refresh];
+            }
+            if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                [self forceRefresh:self];
+            else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                [self refresh:self];
         });
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
-            [node forceRefresh];
-        }
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
-            [node refresh];
-        }
-        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
-            [self forceRefresh:self];
-        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
-            [self refresh:self];
     } else {
         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
         [self requestFailed:objectRequest];
@@ -1971,17 +1981,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         dispatch_async(dispatch_get_main_queue(), ^{
             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+                [node forceRefresh];
+            }
+            for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+                [node refresh];
+            }
+            if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+                [self forceRefresh:self];
+            else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+                [self refresh:self];
         });
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
-            [node forceRefresh];
-        }
-        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
-            [node refresh];
-        }
-        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
-            [self forceRefresh:self];
-        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
-            [self refresh:self];
     } else {
         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
         [self requestFailed:objectRequest];
@@ -2023,6 +2033,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 #pragma mark NSOutlineViewDataSource
 
 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+    if (!browserInitialized)
+        return 0;
     if (item == nil)
         return 2;
     if (item == containersNode)
@@ -2033,6 +2045,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 }
 
 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+    if (!browserInitialized)
+        return nil;
     if (item == nil)
         return (!index ? containersNode : sharedNode);
     if (item == sharedNode)
@@ -2066,7 +2080,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
         result = NSDragOperationCopy;
     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
-        if ([info draggingSourceOperationMask] & NSDragOperationMove) {
+        if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
+            ([info draggingSourceOperationMask] & NSDragOperationMove)) {
             // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
             if (![dropNode isEqualTo:draggedParentNode])
                 result = NSDragOperationMove;
@@ -2092,7 +2107,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
             PithosNode *node = (PithosNode *)item;
             NSLog(@"drag local node: %@", node.url);
-            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+            if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
+                ([info draggingSourceOperationMask] & NSDragOperationMove))
                 return [self moveNodes:draggedNodes toNode:node];
             else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
                 return [self copyNodes:draggedNodes toNode:node];
@@ -2178,10 +2194,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             ([menuNode class] == [PithosSharingAccountsNode class]) ||
             ([menuNode class] == [PithosEmptyNode class]))
             return;
-        BOOL shared = menuNode.shared;
-        BOOL sharingAccount = (menuNode.sharingAccount != nil);
         // New Folder
-        if (!shared && !sharingAccount) {
+        if (!menuNode.shared && !menuNode.sharingAccount) {
             menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
             [menuItem setRepresentedObject:menuNode];
             [menu addItem:menuItem];
@@ -2192,63 +2206,66 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         [menu addItem:menuItem];
         [menu addItem:[NSMenuItem separatorItem]];
         // Get Info
-        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
+        menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
+                                               action:@selector(menuGetInfo:) 
+                                        keyEquivalent:@""] autorelease];
         [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
         [menu addItem:menuItem];
         // Paste
-        if (!shared && !sharingAccount) {
-            if (clipboardNodes) {
-                NSUInteger clipboardNodesCount = [clipboardNodes count];
-                if (clipboardNodesCount == 0) {
-                    self.clipboardNodes = nil;
-                } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
-                    if (clipboardNodesCount == 1)
-                        menuItemTitle = [NSString stringWithString:@"Paste Item"];
-                    else
-                        menuItemTitle = [NSString stringWithString:@"Paste Items"];
-                    [menu addItem:[NSMenuItem separatorItem]];
-                    menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
-                    [menuItem setRepresentedObject:menuNode];
-                    [menu addItem:menuItem];
-                }
+        if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount && 
+            (([menuNode class] == [PithosContainerNode class]) || 
+             (([menuNode class] == [PithosSubdirNode class]) && 
+              (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
+            NSUInteger clipboardNodesCount = [clipboardNodes count];
+            if (clipboardNodesCount == 0) {
+                self.clipboardNodes = nil;
+            } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
+                if (clipboardNodesCount == 1)
+                    menuItemTitle = [NSString stringWithString:@"Paste Item"];
+                else
+                    menuItemTitle = [NSString stringWithString:@"Paste Items"];
+                [menu addItem:[NSMenuItem separatorItem]];
+                menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
+                [menuItem setRepresentedObject:menuNode];
+                [menu addItem:menuItem];
             }
         }
     } else {
         // Node context menu
         NSUInteger menuNodesCount = [menuNodes count];
-        PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
-        BOOL shared = firstMenuNode.shared;
-        BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
+        PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
         // Move to Trash (pithos container only)
         // Delete
-        if (!shared && !sharingAccount) {
-            if ([rootNode class] == [PithosContainerNode class]) {
-                if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
-                    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
-                    [menuItem setRepresentedObject:menuNodes];
-                    [menu addItem:menuItem];
-                }
-                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
+        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([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];
-                [menu addItem:[NSMenuItem separatorItem]];
             }
+            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:menuNodes];
+            [menu addItem:menuItem];
+            [menu addItem:[NSMenuItem separatorItem]];
         }
         // Refresh
         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
         [menu addItem:menuItem];
-        [menu addItem:[NSMenuItem separatorItem]];        
         // Get Info
-        if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
-            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
+        if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
+            [menu addItem:[NSMenuItem separatorItem]];
+            menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
+                                                   action:@selector(menuGetInfo:) 
+                                            keyEquivalent:@""] autorelease];
             [menuItem setRepresentedObject:menuNodes];
             [menu addItem:menuItem];
             
-            if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
+            if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
                 [menu addItem:[NSMenuItem separatorItem]];
         }
         // Cut
-        if (!shared && !sharingAccount) {
+        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
             if (menuNodesCount == 1)
                 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
             else 
@@ -2258,7 +2275,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             [menu addItem:menuItem];
         }
         // Copy
-        if ((!shared && !sharingAccount) || 
+        if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || 
             (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
             if (menuNodesCount == 1)
                 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
@@ -2269,32 +2286,94 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             [menu addItem:menuItem];
         }
         // Paste
-        if (!shared && !sharingAccount) {
-            if (menuNodesCount == 1) {
-                PithosNode *menuNode = [menuNodes objectAtIndex:0];
-                if (([menuNode class] == [PithosSubdirNode class]) && 
-                    (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
-                    if (clipboardNodes) {
-                        NSUInteger clipboardNodesCount = [clipboardNodes count];
-                        if (clipboardNodesCount == 0) {
-                            self.clipboardNodes = nil;
-                        } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
-                            if (clipboardNodesCount == 1)
-                                menuItemTitle = [NSString stringWithString:@"Paste Item"];
-                            else
-                                menuItemTitle = [NSString stringWithString:@"Paste Items"];
-                            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
-                            [menuItem setRepresentedObject:menuNode];
-                            [menu addItem:menuItem];
-                        }
-                    }
-                }
+        if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) && 
+            ([firstMenuNode class] == [PithosSubdirNode class]) && 
+            (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
+            NSUInteger clipboardNodesCount = [clipboardNodes count];
+            if (clipboardNodesCount == 0) {
+                self.clipboardNodes = nil;
+            } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
+                if (clipboardNodesCount == 1)
+                    menuItemTitle = [NSString stringWithString:@"Paste Item"];
+                else
+                    menuItemTitle = [NSString stringWithString:@"Paste Items"];
+                menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
+                [menuItem setRepresentedObject:firstMenuNode];
+                [menu addItem:menuItem];
             }
         }
     }
 }
 
 #pragma mark -
+#pragma mark NSMenuValidation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+    if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        if ([menuNodesIndexPaths count] == 0)
+            return NO;
+        
+        PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+        if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) || 
+            ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) && 
+             (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
+            ((menuItem.action == @selector(delete:)) && 
+             (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) || 
+              ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
+            return NO;
+        
+        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+        }
+        menuItem.representedObject = menuNodes;
+    } else if (menuItem.action == @selector(paste:)) {
+        if (!clipboardNodes || ![clipboardNodes count])
+            return NO;
+        
+        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+        PithosNode *menuNode;
+        if ([menuNodesIndexPaths count] == 0)
+            menuNode = [browser parentForItemsInColumn:0];
+        else if (([menuNodesIndexPaths count] != 1) || 
+                 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
+            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+        else
+            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+        
+        if (menuNode.shared || menuNode.sharingAccount || 
+            (([menuNode class] != [PithosContainerNode class]) && 
+             (([menuNode class] != [PithosSubdirNode class]) || 
+              (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) || 
+            (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
+            return NO;
+
+        menuItem.representedObject = menuNode;
+    }
+    return YES;
+}
+
+- (void)cut:(NSMenuItem *)sender {
+    [self menuCut:sender];
+}
+
+- (void)copy:(NSMenuItem *)sender {
+    [self menuCopy:sender];
+}
+
+- (void)paste:(NSMenuItem *)sender {
+    [self menuPaste:sender];
+}
+
+- (void)delete:(NSMenuItem *)sender {
+    if (sender.tag == 0)
+        [self menuMoveToTrash:sender];
+    else
+        [self menuDelete:sender];
+}
+
+#pragma mark -
 #pragma mark Menu Actions
 
 - (void)menuNewFolder:(NSMenuItem *)sender {
@@ -2647,12 +2726,12 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         return;
     PithosNode *dropNode = (PithosNode *)[sender representedObject];
     NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
-    if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
+    if (clipboardCopy) {
+        [self copyNodes:localClipboardNodes toNode:dropNode];
+    } else if (![dropNode isEqualTo:clipboardParentNode]) {
         self.clipboardNodes = nil;
         self.clipboardParentNode = nil;
         [self moveNodes:localClipboardNodes toNode:dropNode];
-    } else {
-        [self copyNodes:localClipboardNodes toNode:dropNode];
     }
 }