Drag and drop move and copy internally is implemented.
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Thu, 22 Sep 2011 14:09:07 +0000 (17:09 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Thu, 22 Sep 2011 14:09:07 +0000 (17:09 +0300)
Improved the algorithm for creating a safe name.
Other minor changes.

pithos-macos/PithosBrowserController.h
pithos-macos/PithosBrowserController.m
pithos-macos/PithosFileUtilities.h
pithos-macos/PithosFileUtilities.m

index 7e0531f..e14af41 100644 (file)
@@ -52,6 +52,9 @@
     NSSplitView *splitView;
     NSOutlineView *outlineView;
     NSBrowser *browser;
+    
+    NSArray *draggedNodes;
+    PithosNode *draggedParentNode;
 }
 
 @property (nonatomic, retain) NSMutableArray *outlineViewDataSourceArray;
@@ -60,6 +63,9 @@
 @property (nonatomic, assign) IBOutlet NSOutlineView *outlineView;
 @property (nonatomic, assign) IBOutlet NSBrowser *browser;
 
+@property (nonatomic, retain) NSArray *draggedNodes;
+@property (nonatomic, retain) PithosNode *draggedParentNode;
+
 - (IBAction)refresh:(id)sender;
 
 @end
index d7138e0..f8a45af 100644 (file)
 @end
 
 @implementation PithosBrowserController
-@synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
+@synthesize outlineViewDataSourceArray, splitView, outlineView, browser, draggedNodes, draggedParentNode;
 
 #pragma mark -
 #pragma Object Lifecycle
 
 - (void)dealloc {
     [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [draggedParentNode release];
+    [draggedNodes release];
     [browserMenu release];
     [sharedPreviewController release];
     [outlineViewDataSourceArray release];
 - (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]];
 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
    toPasteboard:(NSPasteboard *)pasteboard {
     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
+    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;
 }
 
@@ -409,23 +414,57 @@ 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] containsObject:NSFilenamesPboardType]) {
-        // For a between drop, we let the user drop "on" the parent item
+        // 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 ([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;
 }
 
@@ -434,132 +473,271 @@ 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]];
-                    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) {
+    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, ^{
-                            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) {
+                            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];
+                                    }
+                                }
+                            });
+                        }
+                    }
+                }
+                
+            }
+            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.name hasSuffix:@"/"])
+                                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(uploadDirectoryObjectFinished:);
-                                objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
+                                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.name hasSuffix:@"/"])
+                                    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 (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
-                                    ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
+                                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
                                     objectRequest.delegate = self;
-                                    objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-                                    objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
+                                    objectRequest.didFinishSelector = @selector(copyFinished:);
+                                    objectRequest.didFailSelector = @selector(copyFailed:);
                                     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", 
-                                                              [NSNull null], @"node", 
-                                                              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+                                                              dropNode, @"dropNode", 
                                                               nil];
                                     [objectRequest startAsynchronous];
                                 }
                             }
                         });
+
                     }
                 }
             }
-            
+            return YES;
         }
-        return YES;
     }
-
     return NO;
 }
 
@@ -613,9 +791,9 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (objectRequest.responseStatusCode == 201) {
         NSLog(@"Object created: %@", [objectRequest url]);
         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        if (node != (id)[NSNull null]) {
-            [node.parent invalidateChildren];
-            node.parent.children;
+        if (node) {
+            [node invalidateChildren];
+            node.children;
         } else {
             [self refresh:nil];
         }
@@ -704,6 +882,51 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     [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 invalidateChildren];
+            node.children;
+        }
+        if (dropNode) {
+            [dropNode invalidateChildren];
+            dropNode.children;
+        }
+        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 invalidateChildren];
+            dropNode.children;
+        } else {
+            [self refresh:nil];
+        }
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Copy object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
 #pragma mark -
 #pragma mark NSSplitViewDelegate
 
@@ -912,21 +1135,17 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
                                                                                     objectName:node.pithosObject.name];
                 if (safeObjectName) {
-                    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:node.pithosContainer.name 
-                                                                                                                objectName:node.pithosObject.name 
-                                                                                                               contentType:nil 
-                                                                                                           contentEncoding:nil 
-                                                                                                        contentDisposition:nil 
-                                                                                                                  manifest:nil 
-                                                                                                                   sharing:nil 
-                                                                                                                  isPublic:ASIPithosObjectRequestPublicIgnore 
-                                                                                                                  metadata:nil 
-                                                                                                  destinationContainerName:@"trash" 
-                                                                                                     destinationObjectName:safeObjectName];
-                    objectRequest.delegate = self;
-                    objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
-                    objectRequest.didFailSelector = @selector(moveToTrashFailed:);
-                    [objectRequest startAsynchronous];
+                    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]) {
@@ -938,7 +1157,8 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
                     NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
                                                                                                      objectName:node.pithosObject.name 
                                                                                        destinationContainerName:@"trash" 
-                                                                                          destinationObjectName:safeObjectName];
+                                                                                          destinationObjectName:safeObjectName 
+                                                                                                  checkIfExists:NO];
                     if (objectRequests) {
                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
                             objectRequest.delegate = self;
@@ -960,8 +1180,12 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (objectRequest.responseStatusCode == 201) {
         NSLog(@"New application/directory object created: %@", [objectRequest url]);
         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
-        [node invalidateChildren];
-        node.children;
+        if (node) {
+            [node invalidateChildren];
+            node.children;
+        } else {
+            [self refresh:nil];
+        }
     } else {
         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
     }
index 609d203..4c3d5ca 100644 (file)
                               directoryObjectRequests:(NSMutableArray **) directoryObjectRequests;
 
 + (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName;
+
++ (ASIPithosObjectRequest *)copyObjectRequestWithContainerName:(NSString *)containerName 
+                                                    objectName:(NSString *)objectName 
+                                      destinationContainerName:(NSString *)destinationContainerName 
+                                         destinationObjectName:(NSString *)destinationObjectName 
+                                                 checkIfExists:(BOOL)ifExists;
++ (NSArray *)copyObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
+                                               objectName:(NSString *)objectName 
+                                 destinationContainerName:(NSString *)destinationContainerName 
+                                    destinationObjectName:(NSString *)destinationObjectName 
+                                            checkIfExists:(BOOL)ifExists;
+
+
++ (ASIPithosObjectRequest *)moveObjectRequestWithContainerName:(NSString *)containerName 
+                                                    objectName:(NSString *)objectName 
+                                      destinationContainerName:(NSString *)destinationContainerName 
+                                         destinationObjectName:(NSString *)destinationObjectName 
+                                                 checkIfExists:(BOOL)ifExists;
 + (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
                                                objectName:(NSString *)objectName 
                                  destinationContainerName:(NSString *)destinationContainerName 
-                                    destinationObjectName:(NSString *)destinationObjectName;
+                                    destinationObjectName:(NSString *)destinationObjectName 
+                                            checkIfExists:(BOOL)ifExists;
 
 + (NSUInteger)bytesOfFile:(NSString *)filePath;
 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error;
 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
                               error:(NSError **)error isDirectory:(BOOL *)isDirectory;
++ (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName;
 + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
                             delimiter:(NSString *)delimiter;
 + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName
index 549a8f2..20ba66a 100644 (file)
                                                             forFile:(NSString *)filePath 
                                                       checkIfExists:(BOOL)ifExists 
                                                              hashes:(NSArray **)hashes {
-    if (ifExists) {
-        NSError *error;
-        BOOL isDirectory;
-        BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName
-                                                        error:&error isDirectory:&isDirectory];
-        if (error) {
-            return nil;
-        } else if (objectExists) {
-            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-            if (isDirectory) {
-                [alert setMessageText:@"Directory Exists"];
-                [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
-            } else {
-                [alert setMessageText:@"Object Exists"];
-                [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
-            }
-            [alert addButtonWithTitle:@"OK"];
-            [alert addButtonWithTitle:@"Cancel"];
-            NSInteger choice = [alert runModal];
-            if (choice == NSAlertSecondButtonReturn)
-                return nil;
-        }
-    }
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName])
+        return nil;
     
     if (*hashes == nil)
         *hashes = [HashMapHash calculateObjectHashMap:filePath withBlockHash:blockHash andBlockSize:blockSize];
                                             filePaths:(NSMutableArray **)filePaths 
                                          hashesArrays:(NSMutableArray **)hashesArrays 
                               directoryObjectRequests:(NSMutableArray **) directoryObjectRequests {
-    if (ifExists) {
-        NSError *error = nil;
-        BOOL isDirectory;
-        BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName
-                                                        error:&error isDirectory:&isDirectory];
-        if (error) {
-            return nil;
-        } else if (objectExists) {
-            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-            if (isDirectory) {
-                [alert setMessageText:@"Directory Exists"];
-                [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
-            } else {
-                [alert setMessageText:@"Object Exists"];
-                [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
-            }
-            [alert addButtonWithTitle:@"OK"];
-            [alert addButtonWithTitle:@"Cancel"];
-            NSInteger choice = [alert runModal];
-            if (choice == NSAlertSecondButtonReturn)
-                return nil;
-        }
-    }
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName])
+        return nil;
 
     NSFileManager *defaultManager = [NSFileManager defaultManager];
     NSError *error = nil;
 }
 
 #pragma mark -
-#pragma mark Move to Trash / Delete
+#pragma mark Delete
 
 + (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName {
     NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil];
     return objectRequests;
 }
 
+#pragma mark -
+#pragma mark Copy
+
++ (ASIPithosObjectRequest *)copyObjectRequestWithContainerName:(NSString *)containerName 
+                                                    objectName:(NSString *)objectName 
+                                      destinationContainerName:(NSString *)destinationContainerName 
+                                         destinationObjectName:(NSString *)destinationObjectName 
+                                                 checkIfExists:(BOOL)ifExists {
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName])
+        return nil;
+    
+    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName 
+                                                                                                objectName:objectName 
+                                                                                               contentType:nil 
+                                                                                           contentEncoding:nil 
+                                                                                        contentDisposition:nil 
+                                                                                                  manifest:nil 
+                                                                                                   sharing:nil 
+                                                                                                  isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                  metadata:nil 
+                                                                                  destinationContainerName:destinationContainerName 
+                                                                                     destinationObjectName:destinationObjectName 
+                                                                                             sourceVersion:nil];
+    return objectRequest;
+}
+
++ (NSArray *)copyObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
+                                               objectName:(NSString *)objectName 
+                                 destinationContainerName:(NSString *)destinationContainerName 
+                                    destinationObjectName:(NSString *)destinationObjectName 
+                                            checkIfExists:(BOOL)ifExists {
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName])
+        return nil;
+    
+    NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil];
+    if (objects == nil)
+        return nil;
+    
+    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
+    if ([objectName isEqualToString:destinationObjectName]) {
+        if (![objectName hasSuffix:@"/"])
+            [objectRequests addObject:[ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:objectName 
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:objectName 
+                                                                                       sourceVersion:nil]];
+        for (ASIPithosObject *object in objects) {
+            [objectRequests addObject:[ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:object.name 
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:object.name 
+                                                                                       sourceVersion:nil]];
+        }
+    } else {
+        if (![objectName hasSuffix:@"/"]) {
+            [objectRequests addObject:[ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:objectName 
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:destinationObjectName 
+                                                                                       sourceVersion:nil]];
+        }
+        NSRange prefixRange = NSMakeRange(0, [objectName length]);
+        NSString *newObjectName;
+        for (ASIPithosObject *object in objects) {
+            newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
+                                                                   withString:destinationObjectName
+                                                                      options:NSAnchoredSearch
+                                                                        range:prefixRange];
+            [objectRequests addObject:[ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:object.name
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:newObjectName 
+                                                                                       sourceVersion:nil]];
+        }
+    }
+    
+    if ([objectRequests count] == 0)
+        return nil;
+    return objectRequests;
+}
+
+#pragma mark -
+#pragma mark Move
+
++ (ASIPithosObjectRequest *)moveObjectRequestWithContainerName:(NSString *)containerName 
+                                     objectName:(NSString *)objectName 
+                       destinationContainerName:(NSString *)destinationContainerName 
+                          destinationObjectName:(NSString *)destinationObjectName 
+                                  checkIfExists:(BOOL)ifExists {
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName])
+        return nil;
+    
+    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName 
+                                                                                                objectName:objectName 
+                                                                                               contentType:nil 
+                                                                                           contentEncoding:nil 
+                                                                                        contentDisposition:nil 
+                                                                                                  manifest:nil 
+                                                                                                   sharing:nil 
+                                                                                                  isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                  metadata:nil 
+                                                                                  destinationContainerName:destinationContainerName 
+                                                                                     destinationObjectName:destinationObjectName];
+    return objectRequest;
+}
+
 + (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
                                                objectName:(NSString *)objectName 
                                  destinationContainerName:(NSString *)destinationContainerName 
-                                 destinationObjectName:(NSString *)destinationObjectName {
+                                    destinationObjectName:(NSString *)destinationObjectName 
+                                            checkIfExists:(BOOL)ifExists {
+    if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName])
+        return nil;
+    
     NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil];
     if (objects == nil)
         return nil;
                                                                                destinationObjectName:object.name]];
         }
     } else {
-        NSRange prefixRange = NSMakeRange(0, [objectName length]);
-        NSString *newObjectName;
         if (![objectName hasSuffix:@"/"]) {
-            newObjectName = [objectName stringByReplacingOccurrencesOfString:objectName
-                                                                  withString:destinationObjectName
-                                                                     options:NSAnchoredSearch
-                                                                       range:prefixRange];
             [objectRequests addObject:[ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName 
                                                                                           objectName:objectName 
                                                                                          contentType:nil 
                                                                                             isPublic:ASIPithosObjectRequestPublicIgnore 
                                                                                             metadata:nil 
                                                                             destinationContainerName:destinationContainerName 
-                                                                               destinationObjectName:newObjectName]];
+                                                                               destinationObjectName:destinationObjectName]];
         }
+        NSRange prefixRange = NSMakeRange(0, [objectName length]);
+        NSString *newObjectName;
         for (ASIPithosObject *object in objects) {
             newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
                                                                    withString:destinationObjectName
 #pragma mark -
 #pragma mark Helper Methods
 
+// Size of the file in bytes
 + (NSUInteger)bytesOfFile:(NSString *)filePath {
     NSFileManager *defaultManager = [NSFileManager defaultManager];
     NSDictionary *attributes = [defaultManager attributesOfItemAtPath:filePath error:nil];
     return [[attributes objectForKey:NSFileSize] intValue];
 }
 
+// Content type of the file or nil if it cannot be determined
 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
     NSURLResponse *response = nil;
     [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath] 
     return [response MIMEType];
 }
 
+// Returns if an object exists at the given container/object path and if this object is an application/directory
+// If an error occured an alert is shown and it is returned so the caller won't proceed
 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
                               error:(NSError **)error isDirectory:(BOOL *)isDirectory {
     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName 
     return NO;
 }
 
+// Returns if the called should proceed, after an interactive check if an object exists 
+// at the given container/object path is performed
++ (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName {
+    NSError *error = nil;
+    BOOL isDirectory;
+    BOOL objectExists = [self objectExistsAtContainerName:containerName 
+                                               objectName:objectName
+                                                    error:&error 
+                                              isDirectory:&isDirectory];
+    if (error) {
+        return NO;
+    } else if (objectExists) {
+        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+        if (isDirectory) {
+            [alert setMessageText:@"Directory Exists"];
+            [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+        } else {
+            [alert setMessageText:@"Object Exists"];
+            [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+        }
+        [alert addButtonWithTitle:@"OK"];
+        [alert addButtonWithTitle:@"Cancel"];
+        NSInteger choice = [alert runModal];
+        if (choice == NSAlertSecondButtonReturn)
+            return NO;
+    }
+    return YES;
+}
+
+
+// List of objects at the given container/object path, with prefix and or delimiter
 + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
                             delimiter:(NSString *)delimiter {
     NSMutableArray *objects = [NSMutableArray array];
     return objects;
 }
 
+// List of objects at the given container/object path, that may be a subdir or an application/directory, 
+// with prefix and or delimiter
 + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName 
                                      delimiter:(NSString *)delimiter {
     NSString *subdirNamePrefix = [NSString stringWithString:objectName];
     return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix delimiter:delimiter];
 }
 
+// A safe object name at the given container/object path
+// The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
+// If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
+// Subdirs are taken into consideration
 + (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName {
-    NSString *objectNamePrefix = [NSString stringWithString:objectName];
-    NSString *objectNameExtraSuffix = [NSString string];
-    if ([objectNamePrefix hasSuffix:@"/"]) {
-        objectNamePrefix = [objectNamePrefix substringToIndex:([objectNamePrefix length] - 1)];
+    NSString *objectNamePrefix;
+    NSString *objectNameExtraSuffix;
+    NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
+    if (lastDotRange.length == 1) {
+        objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
+        objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
+    } else if ([objectName hasSuffix:@"/"]) {
+        objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
         objectNameExtraSuffix = [NSString stringWithString:@"/"];
+    } else {
+        objectNamePrefix = [NSString stringWithString:objectName];
+        objectNameExtraSuffix = [NSString string];
     }
     NSArray *objects = [self objectsWithContainerName:containerName 
                                      objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
     }
     if (![objectNames containsObject:objectName])
         return objectName;
-    NSUInteger objectNameSuffix = 1;
+    NSUInteger objectNameSuffix = 2;
     NSString *safeObjectName;
     do {
         safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
     return safeObjectName;    
 }
 
+// A safe object name at the given container/object path that may be a subdir or application/directory
+// The original name has " %d" appended to it, for the first integer that produces a name that is free to use
+// If the original name has a "/" suffix, then it is replaced with " %d/" instead
+// Subdirs are taken into consideration
 + (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName {
-    NSString *subdirNamePrefix = [NSString stringWithString:subdirName];
-    NSString *subdirNameExtraSuffix = [NSString string];
-    if ([subdirNamePrefix hasSuffix:@"/"]) {
-        subdirNamePrefix = [subdirNamePrefix substringToIndex:([subdirNamePrefix length] - 1)];
+    NSString *subdirNamePrefix;
+    NSString *subdirNameExtraSuffix;
+    if ([subdirName hasSuffix:@"/"]) {
+        subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
         subdirNameExtraSuffix = [NSString stringWithString:@"/"];
+    } else {
+        subdirNamePrefix = [NSString stringWithString:subdirName];
+        subdirNameExtraSuffix = [NSString string];
     }
     NSArray *objects = [self objectsWithContainerName:containerName 
                                      objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
     }
     if (![objectNames containsObject:subdirNamePrefix])
         return subdirName;
-    NSUInteger subdirNameSuffix = 1;
+    NSUInteger subdirNameSuffix = 2;
     NSString *safeSubdirName;
     do {
         safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];