Container items in the outlineView can be drag and drop targets for upload, copy...
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Fri, 30 Sep 2011 16:48:09 +0000 (19:48 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Fri, 30 Sep 2011 16:48:09 +0000 (19:48 +0300)
pithos-macos/PithosBrowserController.h
pithos-macos/PithosBrowserController.m

index f0b1150..6bf0dfe 100644 (file)
@@ -41,7 +41,7 @@
 @class PithosSharingAccountsNode;
 @class PithosEmptyNode;
 
-@interface PithosBrowserController : NSWindowController <NSBrowserDelegate, NSSplitViewDelegate, NSOutlineViewDelegate, NSMenuDelegate> {    
+@interface PithosBrowserController : NSWindowController <NSBrowserDelegate, NSSplitViewDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate> {
     PithosNode *rootNode;
     PithosAccountNode *accountNode;
     PithosEmptyNode *containersNode;
index 58979fd..f7c3f5d 100644 (file)
@@ -97,6 +97,9 @@
 
 @interface PithosBrowserController (Private) {}
 - (void)resetContainers:(NSNotification *)notification;
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
+- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
 @end
 
 @implementation PithosBrowserController
     [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
     
+    [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
+    [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
+    [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+    
     [browser setCellClass:[PithosBrowserCell class]];
     
     browserMenu = [[NSMenu alloc] init];
@@ -648,272 +655,305 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
             else
                 node = [browser parentForItemsInColumn:column];
             NSLog(@"drag in node: %@", node.url);
-            if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
-                return NO;
-            
-            NSFileManager *defaultManager = [NSFileManager defaultManager];
-            NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
-            NSString *objectNamePrefix;
-            if ([node class] == [PithosSubdirNode class])
-                objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if ((column != -1) && (draggedNodes != nil)) {
+            PithosNode *node = nil;
+            if (row != -1)
+                node = [browser itemAtRow:row inColumn:column];
             else
-                objectNamePrefix = [NSString string];
-            NSUInteger blockSize = node.pithosContainer.blockSize;
-            NSString *blockHash = node.pithosContainer.blockHash;
-            
-            for (NSString *filePath in filenames) {
-                BOOL isDirectory;
-                if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
-                    if (!isDirectory) {
-                        // Upload file
-                        NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSError *error = nil;
-                            NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
-                            if (contentType == nil)
-                                contentType = @"application/octet-stream";
-                            if (error)
-                                NSLog(@"contentType detection error: %@", error);
-                            NSArray *hashes = nil;
-                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                                      objectName:objectName 
-                                                                                                                     contentType:contentType 
-                                                                                                                       blockSize:blockSize 
-                                                                                                                       blockHash:blockHash 
-                                                                                                                         forFile:filePath 
-                                                                                                                   checkIfExists:YES 
-                                                                                                                          hashes:&hashes 
-                                                                                                                  sharingAccount:node.sharingAccount];
-                            if (objectRequest) {
+                node = [browser parentForItemsInColumn:column];
+            NSLog(@"drag local node: %@", node.url);
+            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self copyNodes:draggedNodes toNode:node];
+        }
+    }
+    return NO;
+}
+
+#pragma mark -
+#pragma mark Drag and Drop methods
+
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
+    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+        return NO;
+    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+    if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
+        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithContainerName:containerName];
+        [containerRequest startSynchronous];
+        if ([containerRequest error]) {
+            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
+            return NO;
+        } else if (containerRequest.responseStatusCode != 200) {
+            [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+            return NO;
+        }
+        destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
+        destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
+    }    
+    NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
+    NSString *blockHash = destinationNode.pithosContainer.blockHash;
+    
+    for (NSString *filePath in filenames) {
+        BOOL isDirectory;
+        if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+            if (!isDirectory) {
+                // Upload file
+                NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
+                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+                dispatch_async(queue, ^{
+                    NSError *error = nil;
+                    NSString *contentType = [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 
+                                                                                                          sharingAccount:destinationNode.sharingAccount];
+                    if (objectRequest) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+                        objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+                        NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                         containerName, @"containerName", 
+                                                         objectName, @"objectName", 
+                                                         contentType, @"contentType", 
+                                                         [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
+                                                         blockHash, @"blockHash", 
+                                                         filePath, @"filePath", 
+                                                         hashes, @"hashes", 
+                                                         destinationNode, @"node", 
+                                                         [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+                                                         nil];
+                        if (destinationNode.sharingAccount)
+                            [userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+                        objectRequest.userInfo = userInfo;
+                        [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 
+                                                                                                 sharingAccount:destinationNode.sharingAccount];
+                        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:);
                                 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                                  containerName, @"containerName", 
-                                                                 objectName, @"objectName", 
-                                                                 contentType, @"contentType", 
+                                                                 [objectNames objectAtIndex:i], @"objectName", 
+                                                                 [contentTypes objectAtIndex:i], @"contentType", 
                                                                  [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
                                                                  blockHash, @"blockHash", 
-                                                                 filePath, @"filePath", 
-                                                                 hashes, @"hashes", 
-                                                                 node, @"node", 
+                                                                 [filePaths objectAtIndex:i], @"filePath", 
+                                                                 [hashesArrays objectAtIndex:i], @"hashes", 
                                                                  [NSNumber numberWithUnsignedInteger:10], @"iteration", 
                                                                  nil];
-                                if (node.sharingAccount)
-                                    [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
+                                if (destinationNode.sharingAccount)
+                                    [userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
                                 objectRequest.userInfo = userInfo;
                                 [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 
-                                                                                                         sharingAccount:node.sharingAccount];
-                                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:);
-                                        NSMutableDictionary *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];
-                                        if (node.sharingAccount)
-                                            [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
-                                        objectRequest.userInfo = userInfo;
-                                        [objectRequest startAsynchronous];
-                                    }
-                                }
-                            });
                         }
-                    }
+                    });
                 }
-                
             }
-            return YES;
         }
-    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
-        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
-        if ((column != -1) && (draggedNodes != nil)) {
-            PithosNode *dropNode = nil;
-            if (row != -1)
-                dropNode = [browser itemAtRow:row inColumn:column];
-            else
-                dropNode = [browser parentForItemsInColumn:column];
-            NSLog(@"drag local node: %@", dropNode.url);
-            if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
-                return NO;
-            
-            NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
-            NSString *objectNamePrefix;
-            if ([dropNode class] == [PithosSubdirNode class])
-                objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
-            else
-                objectNamePrefix = [NSString string];
-            
-            if ([info draggingSourceOperationMask] & NSDragOperationMove) {
-                for (PithosNode *node in draggedNodes) {
-                    if (([node class] == [PithosObjectNode class]) || 
-                        (([node class] == [PithosSubdirNode class]) && 
-                         !node.pithosObject.subdir &&
-                         [node.pithosObject.name hasSuffix:@"/"])) {
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                            if ([node.pithosObject.name hasSuffix:@"/"])
-                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
-                                                                                                                 objectName:node.pithosObject.name 
-                                                                                                   destinationContainerName:containerName 
-                                                                                                      destinationObjectName:destinationObjectName 
-                                                                                                              checkIfExists:YES];
-                            if (objectRequest) {
-                                objectRequest.delegate = self;
-                                objectRequest.didFinishSelector = @selector(moveFinished:);
-                                objectRequest.didFailSelector = @selector(moveFailed:);
-                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                          node.parent, @"node", 
-                                                          dropNode, @"dropNode", 
-                                                          nil];
-                                [objectRequest startAsynchronous];
-                            }
-                        });
-                    } else if ([node class] == [PithosSubdirNode class]) {
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                            if (node.pithosObject.subdir)
-                                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                             objectName:node.pithosObject.name 
-                                                                                               destinationContainerName:containerName 
-                                                                                                  destinationObjectName:destinationObjectName 
-                                                                                                          checkIfExists:YES];
-                            if (objectRequests) {
-                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                                    objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(moveFinished:);
-                                    objectRequest.didFailSelector = @selector(moveFailed:);
-                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                              node.parent, @"node", 
-                                                              dropNode, @"dropNode", 
-                                                              nil];
-                                    [objectRequest startAsynchronous];
-                                }
-                            }
-                        });
+    }
+    return YES;
+}
+
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && 
+             !node.pithosObject.subdir &&
+             [node.pithosObject.name hasSuffix:@"/"])) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                if ([node.pithosObject.name hasSuffix:@"/"])
+                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                ASIPithosObjectRequest *objectRequest = [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", 
+                                              destinationNode, @"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", 
+                                                  destinationNode, @"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 
-                                                                                                             sharingAccount:nil];
-                            if (objectRequest) {
-                                objectRequest.delegate = self;
-                                objectRequest.didFinishSelector = @selector(copyFinished:);
-                                objectRequest.didFailSelector = @selector(copyFailed:);
-                                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                          dropNode, @"dropNode", 
-                                                          nil];
-                                [objectRequest startAsynchronous];
-                            }
-                        });
-                    } else if ([node class] == [PithosSubdirNode class]) {
-                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-                        dispatch_async(queue, ^{
-                            NSString *destinationObjectName;
-                            if (![dropNode isEqualTo:node.parent]) {
-                                destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
-                                if (node.pithosObject.subdir)
-                                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
-                            } else {
-                                destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
-                                                                                                 subdirName:node.pithosObject.name];
-                            }
-                            NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
-                                                                                                             objectName:node.pithosObject.name 
-                                                                                               destinationContainerName:containerName 
-                                                                                                  destinationObjectName:destinationObjectName 
-                                                                                                          checkIfExists:YES 
-                                                                                                         sharingAccount:nil];
-                            if (objectRequests) {
-                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
-                                    objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(copyFinished:);
-                                    objectRequest.didFailSelector = @selector(copyFailed:);
-                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                                              dropNode, @"dropNode", 
-                                                              nil];
-                                    [objectRequest startAsynchronous];
-                                }
-                            }
-                        });
+            });
+        }
+    }
+    return YES;
+}
 
+- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
+    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+        return NO;
+    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+    NSString *objectNamePrefix;
+    if ([destinationNode class] == [PithosSubdirNode class])
+        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+    else
+        objectNamePrefix = [NSString string];
+    
+    for (PithosNode *node in nodes) {
+        if (([node class] == [PithosObjectNode class]) || 
+            (([node class] == [PithosSubdirNode class]) && 
+             !node.pithosObject.subdir &&
+             [node.pithosObject.name hasSuffix:@"/"])) {
+            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+            dispatch_async(queue, ^{
+                NSString *destinationObjectName;
+                if (![destinationNode isEqualTo:node.parent]) {
+                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if ([node.pithosObject.name hasSuffix:@"/"])
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                } else {
+                    destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
+                                                                                     objectName:node.pithosObject.name];
+                }
+                ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                     objectName:node.pithosObject.name 
+                                                                                       destinationContainerName:containerName 
+                                                                                          destinationObjectName:destinationObjectName 
+                                                                                                  checkIfExists:YES 
+                                                                                                 sharingAccount:nil];
+                if (objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(copyFinished:);
+                    objectRequest.didFailSelector = @selector(copyFailed:);
+                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                              destinationNode, @"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 (![destinationNode isEqualTo:node.parent]) {
+                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+                    if (node.pithosObject.subdir)
+                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+                } else {
+                    destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
+                                                                                     subdirName:node.pithosObject.name];
+                }
+                NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                                 objectName:node.pithosObject.name 
+                                                                                   destinationContainerName:containerName 
+                                                                                      destinationObjectName:destinationObjectName 
+                                                                                              checkIfExists:YES 
+                                                                                             sharingAccount:nil];
+                if (objectRequests) {
+                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                        objectRequest.delegate = self;
+                        objectRequest.didFinishSelector = @selector(copyFinished:);
+                        objectRequest.didFailSelector = @selector(copyFailed:);
+                        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                                  destinationNode, @"dropNode", 
+                                                  nil];
+                        [objectRequest startAsynchronous];
                     }
                 }
-            }
-            return YES;
+            });
         }
     }
-    return NO;
+    return YES;
 }
 
 #pragma mark -
@@ -1157,6 +1197,56 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     return node;    
 }
 
+#pragma mark Drag and Drop destination
+
+- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
+                  validateDrop:(id<NSDraggingInfo>)info 
+                  proposedItem:(id)item 
+            proposedChildIndex:(NSInteger)index {
+    NSDragOperation result = NSDragOperationNone;
+    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
+        return result;
+    PithosNode *dropNode = (PithosNode *)item;
+    if ([dropNode class] != [PithosContainerNode class])
+        return result;
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        result = NSDragOperationCopy;
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        if ([info draggingSourceOperationMask] & NSDragOperationMove) {
+            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
+            if (![dropNode isEqualTo:draggedParentNode])
+                result = NSDragOperationMove;
+        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
+            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
+            result = NSDragOperationCopy;
+        }
+    }
+   return result;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            NSLog(@"drag in node: %@", node.url);
+            return [self uploadFiles:filenames toNode:node];
+        }
+    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
+            PithosNode *node = (PithosNode *)item;
+            NSLog(@"drag local node: %@", node.url);
+            if ([info draggingSourceOperationMask] & NSDragOperationMove)
+                return [self moveNodes:draggedNodes toNode:node];
+            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+                return [self copyNodes:draggedNodes toNode:node];
+        }
+    }
+    return NO;
+}
+
 #pragma mark -
 #pragma mark NSOutlineViewDelegate