Implemented menu delete and move to trash.
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Sat, 17 Sep 2011 20:36:07 +0000 (23:36 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Sat, 17 Sep 2011 20:36:07 +0000 (23:36 +0300)
Other fixes and improvements.

13 files changed:
pithos-apple-common
pithos-macos/PithosAccountNode.m
pithos-macos/PithosBrowserController.h
pithos-macos/PithosBrowserController.m
pithos-macos/PithosContainerNode.m
pithos-macos/PithosFileUtilities.h
pithos-macos/PithosFileUtilities.m
pithos-macos/PithosNode.h
pithos-macos/PithosNode.m
pithos-macos/PithosNodeInfoController.m
pithos-macos/PithosPreferencesController.h
pithos-macos/PithosPreferencesController.m
pithos-macos/PithosSubdirNode.m

index 9e1f103..de233d9 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 9e1f10387cee938c9844d337577d9884ee42381d
+Subproject commit de233d93db69b1631f8fdb0ae24be920ee3989d2
index 9e90730..049869e 100644 (file)
@@ -91,7 +91,6 @@
         accountRequest = nil;
         [containers release];
         containers = nil;
-        // XXX sort children based on preferences
         @synchronized(self) {
             freshness = PithosNodeStateRefreshFinished;
         }
index a791ec1..7e0531f 100644 (file)
     NSViewController *sharedPreviewController;
     
     NSMenu *browserMenu;
+    
+    NSSplitView *splitView;
+    NSOutlineView *outlineView;
+    NSBrowser *browser;
 }
 
 @property (nonatomic, retain) NSMutableArray *outlineViewDataSourceArray;
index 8452bbc..55a1c9f 100644 (file)
@@ -51,7 +51,6 @@
 #import "ASIPithosObject.h"
 #import "PithosFileUtilities.h"
 
-//@interface PithosBrowserCell : NSBrowserCell {}
 @interface PithosBrowserCell : FileSystemBrowserCell {}
 @end
 
@@ -69,9 +68,6 @@
         PithosNode *node = (PithosNode *)object;
         [self setStringValue:node.displayName];
         [self setImage:node.icon];
-//        // All cells are set as leafs because a branchingImage is already set!
-//        // Maybe this cell is already inside an NSBrowserCell
-//        [self setLeaf:YES];
     } else {
         [super setObjectValue:object];
     }
@@ -98,7 +94,7 @@
 @end
 
 @interface PithosBrowserController (Private) {}
-- (void)resetContainers;
+- (void)resetContainers:(NSNotification *)notification;
 - (void)getInfo:(NSMenuItem *)sender;
 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
     [browser setMenu:browserMenu];
 }
 
-- (void)resetContainers {
+- (void)resetContainers:(NSNotification *)notification {
     rootNode = nil;
     [browser loadColumnZero];
     self.outlineViewDataSourceArray = nil;
     // CONTAINERS
        NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
-//    // CONTAINERS/pithos
-//     [[containersTreeNode mutableChildNodes] addObject:
-//     [NSTreeNode treeNodeWithRepresentedObject:
-//      [[[PithosContainerNode alloc] initWithContainerName:@"pithos" 
-//                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
-//        ] autorelease]]];
-//    // CONTAINERS/trash
-//     [[containersTreeNode mutableChildNodes] addObject:
-//     [NSTreeNode treeNodeWithRepresentedObject:
-//      [[[PithosContainerNode alloc] initWithContainerName:@"trash"
-//                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
-//        ] autorelease]]];
+
     // SHARED
        NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
                                                  name:@"PithosAccountNodeChildrenUpdated" 
                                                object:nil];
     [[NSNotificationCenter defaultCenter] addObserver:self 
-                                             selector:@selector(resetContainers) 
+                                             selector:@selector(resetContainers:) 
                                                  name:@"PithosAuthenticationCredentialsUpdated" 
                                                object:nil];
 }
 
 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
     PithosNode *node = (PithosNode *)[notification object];
+    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
     NSInteger lastColumn = [browser lastColumn];
     for (NSInteger column = lastColumn; column >= 0; column--) {
         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
             [browser reloadColumn:column];
-            if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
-                // This reloads the preview column
-                [browser setLastColumn:column];
-                [browser addColumn];
-            }
+            // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
+            // Furthermore it caused problems on delete, because a non-existing parent was asked for
+            //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
+            //    // This reloads the preview column
+            //    [browser setLastColumn:column];
+            //    [browser addColumn];
+            //}
             return;
         }
     }
 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
     BOOL containerPithosFound = NO;
     BOOL containerTrashFound = NO;
-    //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
     NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
     for (PithosContainerNode *containerNode in accountNode.children) {
         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
         [containerRequest startSynchronous];
         if ([containerRequest error]) {
-            NSLog(@"error:%@", [containerRequest error]);
-            // XXX do something on error
+            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
         [containerRequest startSynchronous];
         if ([containerRequest error]) {
-            NSLog(@"error:%@", [containerRequest error]);
-            // XXX do something on error
+            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
         } else {
             refreshAccountNode = YES;
         }
@@ -422,7 +407,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
              dropOperation:(NSBrowserDropOperation *)dropOperation {
     NSDragOperation result = NSDragOperationNone;
     // Files from the finder are accepted
-    if ([[[info draggingPasteboard] types] indexOfObject:NSFilenamesPboardType] != -1) {
+    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
         // For a between drop, we let the user drop "on" the parent item
         if (*dropOperation == NSBrowserDropAbove)
             *row = -1;
@@ -715,6 +700,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     if (node) {
         rootNode = node;
         [browser loadColumnZero];
+        [self refresh:nil];                                  
     }
 }
 
@@ -725,12 +711,25 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     NSInteger column = [browser clickedColumn];
     NSInteger row = [browser clickedRow];
     [menu removeAllItems];
+    NSMenuItem *menuItem;
     if ((column == -1) || (row == -1)) {
-        // General context menu has 0
+        // General context menu
     } else {
-        // PithosNode menu has 1 items
+        // Move to Trash (pithos container only)
+        // Delete
+        if ([rootNode class] == [PithosContainerNode class]) {
+            if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
+                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
+                [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
+                [menu addItem:menuItem];
+            }
+            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
+            [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
+            [menu addItem:menuItem];
+            [menu addItem:[NSMenuItem separatorItem]];
+        }
         // Get Info
-        NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
+        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
         [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
         [menu addItem:menuItem];
     }
@@ -743,4 +742,108 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];
 }
 
+- (void)deleteObject:(NSMenuItem *)sender {
+    PithosNode *node = (PithosNode *)[sender representedObject];
+    if (([node class] == [PithosObjectNode class]) || 
+        (([node class] == [PithosSubdirNode class]) && 
+         !node.pithosObject.subdir &&
+         [node.pithosObject.name hasSuffix:@"/"])) {
+        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
+                                                                                                  objectName:node.pithosObject.name];
+        objectRequest.delegate = self;
+        objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
+        objectRequest.didFailSelector = @selector(deleteObjectFailed:);
+        [objectRequest startAsynchronous];
+    } else if ([node class] == [PithosSubdirNode class]) {
+        NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                           objectName:node.pithosObject.name];
+        if (objectRequests) {
+            for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                objectRequest.delegate = self;
+                objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
+                objectRequest.didFailSelector = @selector(deleteObjectFailed:);
+                [objectRequest startAsynchronous];
+            }
+        }
+        
+    }    
+}
+
+- (void)moveToTrash:(NSMenuItem *)sender {
+    PithosNode *node = (PithosNode *)[sender representedObject];
+    if (([node class] == [PithosObjectNode class]) || 
+        (([node class] == [PithosSubdirNode class]) && 
+         !node.pithosObject.subdir &&
+         [node.pithosObject.name hasSuffix:@"/"])) {
+        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];
+        }
+    } else if ([node class] == [PithosSubdirNode class]) {
+        NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
+                                                                            subdirName:node.pithosObject.name];
+        if (safeObjectName) {
+            NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
+                                                                                             objectName:node.pithosObject.name 
+                                                                               destinationContainerName:@"trash" 
+                                                                                  destinationObjectName:safeObjectName];
+            if (objectRequests) {
+                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
+                    objectRequest.didFailSelector = @selector(moveToTrashFailed:);
+                    [objectRequest startAsynchronous];
+                }
+            }
+        }        
+    }
+}
+
+#pragma mark -
+#pragma mark Menu Actions ASIHTTPRequestDelegate
+
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    if (objectRequest.responseStatusCode == 204) {
+        NSLog(@"Object deleted: %@", [objectRequest url]);
+        [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Delete of object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+- (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
+    if (objectRequest.responseStatusCode == 201) {
+        NSLog(@"Object moved: %@", [objectRequest url]);
+        [self refresh:nil];
+    } else {
+        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
+    }
+}
+
+- (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Move of object failed");
+    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
+}
+
+
 @end
\ No newline at end of file
index 583a308..25c26a3 100644 (file)
@@ -114,13 +114,13 @@ static NSImage *sharedIcon = nil;
             // Save new children
             NSLog(@"using newChildren");
             newChildren = [[NSMutableArray alloc] init];
+            NSArray *objectNames = [objects valueForKey:@"name"];
+            NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
             for (ASIPithosObject *object in objects) {
                 if (object.subdir) {
-                    PithosNode *previousNode = (PithosNode *)[newChildren lastObject];
-                    if (!previousNode ||
-                        ([previousNode class] != [PithosSubdirNode class]) ||
-                        (![((PithosSubdirNode *)previousNode).pithosObject.contentType isEqualToString:@"application/directory"]) ||
-                        (![[((PithosSubdirNode *)previousNode).pithosObject.name stringByAppendingString:@"/"] isEqualToString:object.name])) {
+                    NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
+                    if ((sameNameObjectIndex == NSNotFound) ||
+                        ![[[objects objectAtIndex:sameNameObjectIndex] contentType] isEqualToString:@"application/directory"]) {
                         PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
                         node.parent = self;
                         if (children) {
@@ -130,48 +130,58 @@ static NSImage *sharedIcon = nil;
                                 node = [children objectAtIndex:oldIndex];
                                 node.pithosContainer = pithosContainer;
                                 node.pithosObject = object;
+                                [keptNodes addIndex:oldIndex];
                             }
                         }
                         [newChildren addObject:node];
                     }
-                } else if ([object.contentType isEqualToString:@"application/directory"]) {
-                    PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
-                    node.parent = self;
-                    if (children) {
-                        NSUInteger oldIndex = [children indexOfObject:node];
-                        if (oldIndex != NSNotFound) {
-                            // Use the same pointer value, if possible
-                            node = [children objectAtIndex:oldIndex];
-                            node.pithosContainer = pithosContainer;
-                            node.pithosObject = object;
-                        }
-                    }
-                    [newChildren addObject:node];
                 } else if (([self class] != [PithosSubdirNode class]) || 
                            ![[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"] isEqualToString:object.name]) {
-                    // XXX the if above removes false objects due to trailing slash
-                    // XXX this will change in the server, but it is fixed in the client for now
-                    PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
-                    node.parent = self;
-                    if (children) {
-                        NSUInteger oldIndex = [children indexOfObject:node];
-                        if (oldIndex != NSNotFound) {
-                            // Use the same pointer value, if possible
-                            node = [children objectAtIndex:oldIndex];
-                            node.pithosContainer = pithosContainer;
-                            node.pithosObject = object;
+                    // The check above removes false objects due to trailing slash
+                    // This may change in the server, but it is fixed in the client for now
+                    if ([object.contentType isEqualToString:@"application/directory"]) {
+                        PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
+                        node.parent = self;
+                        if (children) {
+                            NSUInteger oldIndex = [children indexOfObject:node];
+                            if (oldIndex != NSNotFound) {
+                                // Use the same pointer value, if possible
+                                node = [children objectAtIndex:oldIndex];
+                                node.pithosContainer = pithosContainer;
+                                node.pithosObject = object;
+                                [keptNodes addIndex:oldIndex];
+                            }
+                        }
+                        [newChildren addObject:node];
+                    } else {
+                        PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
+                        node.parent = self;
+                        if (children) {
+                            NSUInteger oldIndex = [children indexOfObject:node];
+                            if (oldIndex != NSNotFound) {
+                                // Use the same pointer value, if possible
+                                node = [children objectAtIndex:oldIndex];
+                                node.pithosContainer = pithosContainer;
+                                node.pithosObject = object;
+                                [keptNodes addIndex:oldIndex];
+                            }
                         }
+                        [newChildren addObject:node];                                
                     }
-                    [newChildren addObject:node];                                
                 }
             }
+            [[children objectsAtIndexes:
+              [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
+                if ([keptNodes containsIndex:idx])
+                    return NO;
+                return YES;
+            }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
         }
         // Else cache was used and all results were fetched during this request, so existing children can be reused
         [containerRequest release];
         containerRequest = nil;
         [objects release];
         objects = nil;
-        // XXX sort children based on preferences
         @synchronized(self) {
             freshness = PithosNodeStateRefreshFinished;
         }
index 9f17b40..71628c5 100644 (file)
@@ -60,7 +60,6 @@
 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashesResponse:(NSString *)missingHashesResponse;
 + (ASIPithosObjectRequest *)updateObjectDataRequestWithContainerName:(NSString *)containerName 
                                                           objectName:(NSString *)objectName 
-                                                         contentType:(NSString *)contentType 
                                                            blockSize:(NSUInteger)blockSize 
                                                              forFile:(NSString *)filePath 
                                                               hashes:(NSArray *)hashes 
                                             filePaths:(NSMutableArray **)filePaths 
                                          hashesArrays:(NSMutableArray **)hashesArrays;
 
++ (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName;
++ (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
+                                               objectName:(NSString *)objectName 
+                                 destinationContainerName:(NSString *)destinationContainerName 
+                                    destinationObjectName:(NSString *)destinationObjectName;
 
 + (NSUInteger)bytesOfFile:(NSString *)filePath;
 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error;
 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
                      responseString:(NSString **)responseString error:(NSError **)error isDirectory:(BOOL *)isDirectory;
-+ (int)deleteObjectAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
-             responseStatusMessage:(NSString **)responseStatusMessage error:(NSError **)error;
++ (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
+                            delimiter:(NSString *)delimiter;
++ (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName
+                                     delimiter:(NSString *)delimiter;
++ (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName;
++ (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName;
 
 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request;
 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request;
index 8ed40e5..83c2ad4 100644 (file)
             return nil;
     }
     
-    NSMutableArray *objects = [NSMutableArray array];
-    NSString *marker = nil;
-    do {
-        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
-                                                                                                               limit:0 
-                                                                                                              marker:marker 
-                                                                                                              prefix:objectName 
-                                                                                                           delimiter:nil 
-                                                                                                                path:nil 
-                                                                                                                meta:nil 
-                                                                                                              shared:NO 
-                                                                                                               until:nil];
-        [containerRequest startSynchronous];
-        if ([containerRequest error]) {
-            NSLog(@"HTTP Request Error: %@\nResponse String: %@", [containerRequest error], [containerRequest responseString]);
-            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-            [alert setMessageText:@"HTTP Request Error"];
-            [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@", 
-                                       [containerRequest error], [containerRequest responseString]]];
-            [alert addButtonWithTitle:@"OK"];
-            [alert runModal];        
-            return nil;
-        }
-        NSArray *someObjects = [containerRequest objects];
-        [objects addObjectsFromArray:someObjects];
-        if ([someObjects count] < 10000)
-            marker = nil;
-        else
-            marker = [[someObjects lastObject] name];
-    } while (marker);
+    NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil];
+    if (objects == nil)
+        return nil;
     
     NSFileManager *defaultManager = [NSFileManager defaultManager];
     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
     NSUInteger subdirPrefixLength = [objectName length];
     for (ASIPithosObject *object in objects) {
-        if (object.subdir || [object.contentType isEqualToString:@"application/directory"]) {
+        if ([object.contentType isEqualToString:@"application/directory"]) {
             NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
             subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
             
         if (![line length])
             break;
         NSUInteger missingBlock = [hashes indexOfObject:line];
-        if (missingBlock != -1)
+        if (missingBlock != NSNotFound)
             [missingBlocks addIndex:missingBlock];
     }
     return missingBlocks;
     return [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
                                                                 objectName:objectName 
                                                                       eTag:nil
-                                                               contentType:@"application/octet-stream" 
+                                                               contentType:@"application/octet-stream"  
                                                            contentEncoding:nil 
                                                         contentDisposition:nil 
                                                                   manifest:nil 
 
 + (ASIPithosObjectRequest *)updateObjectDataRequestWithContainerName:(NSString *)containerName 
                                                           objectName:(NSString *)objectName 
-                                                         contentType:(NSString *)contentType 
                                                            blockSize:(NSUInteger)blockSize 
                                                              forFile:(NSString *)filePath 
                                                    missingBlockIndex:(NSUInteger)missingBlockIndex {
     return [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
                                                                 objectName:objectName 
                                                                       eTag:nil
-                                                               contentType:contentType 
+                                                               contentType:@"application/octet-stream" 
                                                            contentEncoding:nil 
                                                         contentDisposition:nil 
                                                                   manifest:nil 
                     error = nil;
                     contentType = [self contentTypeOfFile:filePath error:&error];
                     if (contentType == nil)
-                        contentType = @"application/binary";
+                        contentType = @"application/octet-stream";
                     if (error)
                         NSLog(@"contentType detection error: %@", error);
                     [objectRequests addObject:[ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
                     [*hashesArrays addObject:hashes];
                 }
                 
-            }//XXX else
+            }//XXX else upload directory
         }
     }
     
 }
 
 #pragma mark -
+#pragma mark Move to Trash / Delete
+
++ (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName {
+    NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil];
+    if (objects == nil)
+        return nil;
+
+    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
+    
+    if (![objectName hasSuffix:@"/"])
+        [objectRequests addObject:[ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:objectName]];
+    for (ASIPithosObject *object in objects) {
+        [objectRequests addObject:[ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:object.name]];
+    }
+    
+    if ([objectRequests count] == 0)
+        return nil;
+    return objectRequests;
+}
+
++ (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName 
+                                               objectName:(NSString *)objectName 
+                                 destinationContainerName:(NSString *)destinationContainerName 
+                                 destinationObjectName:(NSString *)destinationObjectName {
+    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 moveObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:objectName 
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:objectName]];
+        for (ASIPithosObject *object in objects) {
+            [objectRequests addObject:[ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:object.name 
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               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 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:newObjectName]];
+        }
+        for (ASIPithosObject *object in objects) {
+            newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
+                                                                   withString:destinationObjectName
+                                                                      options:NSAnchoredSearch
+                                                                        range:prefixRange];
+            [objectRequests addObject:[ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName 
+                                                                                          objectName:object.name
+                                                                                         contentType:nil 
+                                                                                     contentEncoding:nil 
+                                                                                  contentDisposition:nil 
+                                                                                            manifest:nil 
+                                                                                             sharing:nil 
+                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                            metadata:nil 
+                                                                            destinationContainerName:destinationContainerName 
+                                                                               destinationObjectName:newObjectName]];
+        }
+    }
+     
+    if ([objectRequests count] == 0)
+        return nil;
+    return objectRequests;
+}
+
+#pragma mark -
 #pragma mark Helper Methods
 
 + (NSUInteger)bytesOfFile:(NSString *)filePath {
     return NO;
 }
 
-+ (int)deleteObjectAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
-             responseStatusMessage:(NSString **)responseStatusMessage error:(NSError **)error {
-    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteContainerRequestWithContainerName:containerName 
-                                                                                                 objectName:objectName];
-    [objectRequest startSynchronous];
-    *error = [objectRequest error];
-    *responseStatusMessage = [NSString stringWithString:objectRequest.responseStatusMessage];
-    return objectRequest.responseStatusCode;
++ (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
+                            delimiter:(NSString *)delimiter {
+    NSMutableArray *objects = [NSMutableArray array];
+    NSString *marker = nil;
+    do {
+        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
+                                                                                                               limit:0 
+                                                                                                              marker:marker 
+                                                                                                              prefix:objectNamePrefix 
+                                                                                                           delimiter:delimiter 
+                                                                                                                path:nil 
+                                                                                                                meta:nil 
+                                                                                                              shared:NO 
+                                                                                                               until:nil];
+        [containerRequest startSynchronous];
+        if ([containerRequest error]) {
+            [self httpRequestErrorAlertWithRequest:containerRequest];
+            return nil;
+        }
+        NSArray *someObjects = [containerRequest objects];
+        [objects addObjectsFromArray:someObjects];
+        if ([someObjects count] < 10000)
+            marker = nil;
+        else
+            marker = [[someObjects lastObject] name];
+    } while (marker);
+    return objects;
+}
+
++ (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName 
+                                     delimiter:(NSString *)delimiter {
+    NSString *subdirNamePrefix = [NSString stringWithString:objectName];
+    if (![subdirNamePrefix hasSuffix:@"/"])
+        subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
+    return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix delimiter:delimiter];
+}
+
++ (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)];
+        objectNameExtraSuffix = [NSString stringWithString:@"/"];
+    }
+    NSArray *objects = [self objectsWithContainerName:containerName objectNamePrefix:objectNamePrefix delimiter:nil];
+    if (objects == nil)
+        return nil;
+    if ([objects count] == 0)
+        return objectName;
+    NSArray *objectNames = [objects valueForKey:@"name"];
+    if (![objectNames containsObject:objectName])
+        return objectName;
+    NSUInteger objectNameSuffix = 1;
+    NSString *safeObjectName;
+    do {
+        safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
+        objectNameSuffix++;
+    } while ([objectNames containsObject:safeObjectName]);
+    return safeObjectName;    
+}
+
++ (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)];
+        subdirNameExtraSuffix = [NSString stringWithString:@"/"];
+    }
+    NSArray *objects = [self objectsWithContainerName:containerName 
+                                     objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
+                                            delimiter:@"/"];
+    if (objects == nil)
+        return nil;
+    if ([objects count] == 0)
+        return subdirName;
+    NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
+                                   [[objects objectsAtIndexes:
+                                     [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
+        if (pithosObject.subdir)
+            return NO;
+        return YES;
+    }]] valueForKey:@"name"]];
+    for (NSString *name in [[objects objectsAtIndexes:
+                             [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
+        if (pithosObject.subdir)
+            return YES;
+        return NO;
+    }]] valueForKey:@"name"]) {
+        [objectNames addObject:[name substringToIndex:([name length] - 1)]];
+    }
+    if (![objectNames containsObject:subdirNamePrefix])
+        return subdirName;
+    NSUInteger subdirNameSuffix = 1;
+    NSString *safeSubdirName;
+    do {
+        safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
+        subdirNameSuffix++;
+    } while ([objectNames containsObject:safeSubdirName]);
+    return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
 }
 
 #pragma mark -
 
 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
     NSString *message = [NSString stringWithFormat:
-                         @"HTTP request error: %@\n%@ URL: %@\nResponse String: %@", 
+                         @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
                          [request error], 
                          request.requestMethod, 
                          request.url, 
+                         [request requestHeaders], 
+                         [request responseHeaders], 
                          [request responseString]];
     NSLog(@"%@", message);
     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 
 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
     NSString *message = [NSString stringWithFormat:
-                         @"Unexpected response status %d: %@\n%@ URL: %@\nResponse String: %@", 
+                         @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
                          request.responseStatusCode, 
                          request.responseStatusMessage, 
                          request.requestMethod, 
                          request.url, 
+                         [request requestHeaders], 
+                         [request responseHeaders], 
                          [request responseString]];
     NSLog(@"%@", message);
     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
index 82a1ba9..870bc0c 100644 (file)
@@ -77,4 +77,6 @@
 - (void)showPithosNodeInfo:(id)sender;
 - (void)pithosNodeInfoWillClose:(id)sender;
 
+- (void)pithosNodeWillBeRemoved;
+
 @end
\ No newline at end of file
index 5795b14..2b3a6a3 100644 (file)
@@ -56,7 +56,6 @@
 }
 
 - (void)dealloc {
-    [[pithosNodeInfoController window] close];
     [pithosNodeInfoController release];
     [icon release];
     [displayName release];
     }
 }
 
+- (void)pithosNodeWillBeRemoved {
+    if (pithosNodeInfoController) {
+        pithosNodeInfoController.node = nil;
+        [[pithosNodeInfoController window] close];
+    }
+    for (PithosNode *child in children) {
+        [child pithosNodeWillBeRemoved];
+    }
+}
+
 @end
\ No newline at end of file
index 430688e..1b8dcfd 100644 (file)
@@ -52,7 +52,6 @@
 }
 
 - (void)dealloc {
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
     [super dealloc];
 }
 
index a3f1882..f9160b7 100644 (file)
 
 #import <Cocoa/Cocoa.h>
 
-@interface PithosPreferencesController : NSWindowController <NSWindowDelegate>
+@interface PithosPreferencesController : NSWindowController <NSWindowDelegate> {
+    NSUserDefaultsController *userDefaultsController;
+    
+    NSTextField *authenticationUserTextField;
+    NSTextField *authenticationTokenTextField;
+    NSButton *authenticationRenewCheckBox;
+}
 
-@property (nonatomic, retain) IBOutlet NSUserDefaultsController *userDefaultsController;
+@property (nonatomic, assign) IBOutlet NSUserDefaultsController *userDefaultsController;
 
-@property (nonatomic, retain) IBOutlet NSTextField *authenticationUserTextField;
-@property (nonatomic, retain) IBOutlet NSTextField *authenticationTokenTextField;
-@property (nonatomic, retain) IBOutlet NSButton *authenticationRenewCheckBox;
+@property (nonatomic, assign) IBOutlet NSTextField *authenticationUserTextField;
+@property (nonatomic, assign) IBOutlet NSTextField *authenticationTokenTextField;
+@property (nonatomic, assign) IBOutlet NSButton *authenticationRenewCheckBox;
 
 - (IBAction)toolbarItemSelected:(id)sender;
 
index 23d1e95..0f59d89 100644 (file)
     return [super initWithWindowNibName:@"PithosPreferencesController"];
 }
 
-- (void)dealloc {
-    [authenticationRenewCheckBox release];
-    [authenticationTokenTextField release];
-    [authenticationUserTextField release];
-    [userDefaultsController release];
-    [super dealloc];
-}
-
 - (void)windowDidLoad {
     [super windowDidLoad];
     
index 1131441..406d58c 100644 (file)
@@ -86,8 +86,11 @@ static NSImage *sharedIcon = nil;
 }
 
 - (NSString *)displayName {
-    // XXX check if there are problems with . or other special characters
-    return [pithosObject.name lastPathComponent];
+    NSString *name = [pithosObject.name lastPathComponent];
+    if (!pithosObject.subdir && [pithosObject.name hasSuffix:@"/"]) {
+        return [name stringByAppendingString:@"/"];
+    } 
+    return name;
 }
 
 - (NSImage *)icon {
@@ -136,7 +139,7 @@ static NSImage *sharedIcon = nil;
                     [alert addButtonWithTitle:@"Cancel"];
                     NSInteger choice = [alert runModal];
                     if (choice == NSAlertFirstButtonReturn) {
-                        request = [ASIPithosObjectRequest deleteContainerRequestWithContainerName:pithosContainer.name 
+                        request = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:pithosContainer.name 
                                                                                        objectName:prefix];
                         [request startSynchronous];
                         if ([request error]) {