Object or subdir rename is supported through browser node edit.
[pithos-macos] / pithos-macos / PithosBrowserController.m
1 //
2 //  PithosBrowserController.m
3 //  pithos-macos
4 //
5 // Copyright 2011 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
15 //   2. Redistributions in binary form must reproduce the above
16 //      copyright notice, this list of conditions and the following
17 //      disclaimer in the documentation and/or other materials
18 //      provided with the distribution.
19 // 
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 // 
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
37
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosEmptyNode.h"
45 #import "ImageAndTextCell.h"
46 #import "FileSystemBrowserCell.h"
47 #import "ASIPithosRequest.h"
48 #import "ASIPithosContainerRequest.h"
49 #import "ASIPithosObjectRequest.h"
50 #import "ASIPithosContainer.h"
51 #import "ASIPithosObject.h"
52 #import "PithosFileUtilities.h"
53
54 @interface PithosBrowserCell : FileSystemBrowserCell {}
55 @end
56
57 @implementation PithosBrowserCell
58
59 - (id)init {
60     if ((self = [super init])) {
61         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
62         [self setEditable:YES];
63     }
64     return self;
65 }
66
67 - (void)setObjectValue:(id)object {
68     if ([object isKindOfClass:[PithosNode class]]) {
69         PithosNode *node = (PithosNode *)object;
70         [self setStringValue:node.displayName];
71         [self setImage:node.icon];
72     } else {
73         [super setObjectValue:object];
74     }
75 }
76
77 @end
78
79 @interface PithosOutlineViewCell : ImageAndTextCell {}
80 @end
81
82 @implementation PithosOutlineViewCell
83
84 - (void)setObjectValue:(id)object {
85     if ([object isKindOfClass:[PithosNode class]]) {
86         PithosNode *node = (PithosNode *)object;
87         [self setStringValue:node.displayName];
88         [self setImage:node.icon];
89         [self setEditable:NO];
90     } else {
91         [super setObjectValue:object];
92     }
93 }
94
95 @end
96
97 @interface PithosBrowserController (Private) {}
98 - (void)resetContainers:(NSNotification *)notification;
99 - (void)getInfo:(NSMenuItem *)sender;
100 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
101 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
102 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
103 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
104 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
105 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
106 @end
107
108 @implementation PithosBrowserController
109 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser, draggedNodes, draggedParentNode;
110
111 #pragma mark -
112 #pragma Object Lifecycle
113
114 - (id)init {
115     return [super initWithWindowNibName:@"PithosBrowserController"];
116 }
117
118 - (void)dealloc {
119     [[NSNotificationCenter defaultCenter] removeObserver:self];
120     [draggedParentNode release];
121     [draggedNodes release];
122     [browserMenu release];
123     [sharedPreviewController release];
124     [outlineViewDataSourceArray release];
125     [accountNode release];
126     [rootNode release];
127     [super dealloc];
128 }
129
130 - (void)awakeFromNib {
131     [super awakeFromNib];
132     
133     [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
134     [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
135     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
136     
137     [browser setCellClass:[PithosBrowserCell class]];
138     
139     browserMenu = [[NSMenu alloc] init];
140     [browserMenu setDelegate:self];
141     [browser setMenu:browserMenu];
142 }
143
144 - (void)resetContainers:(NSNotification *)notification {
145     rootNode = nil;
146     [browser loadColumnZero];
147     self.outlineViewDataSourceArray = nil;
148     
149     // Create the outlineView tree
150     // CONTAINERS
151         NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
152                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
153
154     // SHARED
155         NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
156                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
157     // SHARED/my shared
158         [[sharedTreeNode mutableChildNodes] addObject:
159      [NSTreeNode treeNodeWithRepresentedObject:
160       [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
161                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
162         ] autorelease]]];
163     // SHARED/others shared
164         [[sharedTreeNode mutableChildNodes] addObject:
165      [NSTreeNode treeNodeWithRepresentedObject:
166       [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
167                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
168         ] autorelease]]];
169     
170     self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
171     
172         // Expand the folder outline view
173     [outlineView expandItem:nil expandChildren:YES];
174         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
175     
176     // Create accountNode and trigger a refresh
177     accountNode = [[PithosAccountNode alloc] init];
178     accountNode.children;
179 }
180
181 - (void)windowDidLoad {
182     [super windowDidLoad];
183     
184     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
185     
186     // Register for updates
187     [[NSNotificationCenter defaultCenter] addObserver:self 
188                                              selector:@selector(pithosNodeChildrenUpdated:) 
189                                                  name:@"PithosContainerNodeChildrenUpdated" 
190                                                object:nil];
191     [[NSNotificationCenter defaultCenter] addObserver:self 
192                                              selector:@selector(pithosNodeChildrenUpdated:) 
193                                                  name:@"PithosSubdirNodeChildrenUpdated" 
194                                                object:nil];
195     [[NSNotificationCenter defaultCenter] addObserver:self 
196                                              selector:@selector(pithosAccountNodeChildrenUpdated:) 
197                                                  name:@"PithosAccountNodeChildrenUpdated" 
198                                                object:nil];
199     [[NSNotificationCenter defaultCenter] addObserver:self 
200                                              selector:@selector(resetContainers:) 
201                                                  name:@"PithosAuthenticationCredentialsUpdated" 
202                                                object:nil];
203 }
204
205 #pragma mark -
206 #pragma Observers
207
208 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
209     PithosNode *node = (PithosNode *)[notification object];
210     NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
211     NSInteger lastColumn = [browser lastColumn];
212     for (NSInteger column = lastColumn; column >= 0; column--) {
213         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
214             [browser reloadColumn:column];
215             // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
216             // Furthermore it caused problems on delete, because a non-existing parent was asked for
217             //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
218             //    // This reloads the preview column
219             //    [browser setLastColumn:column];
220             //    [browser addColumn];
221             //}
222             return;
223         }
224     }
225 }
226
227 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
228     BOOL containerPithosFound = NO;
229     BOOL containerTrashFound = NO;
230     NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
231     for (PithosContainerNode *containerNode in accountNode.children) {
232         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
233             containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
234             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
235             containerPithosFound = YES;
236         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
237             containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];            
238             NSUInteger insertIndex = 1;
239             if (!containerPithosFound)
240                 insertIndex = 0;
241             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
242             containerTrashFound = YES;
243         } else {
244             [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
245         }
246     }
247     BOOL refreshAccountNode = NO;
248     if (!containerPithosFound) {
249         // create pithos
250         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
251         [containerRequest startSynchronous];
252         if ([containerRequest error]) {
253             [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
254         } else {
255             refreshAccountNode = YES;
256         }
257     }
258     if (!containerTrashFound) {
259         // create trash
260         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
261         [containerRequest startSynchronous];
262         if ([containerRequest error]) {
263             [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
264         } else {
265             refreshAccountNode = YES;
266         }
267     }
268     if (refreshAccountNode) {
269         [accountNode invalidateChildren];
270         accountNode.children;
271     } else {
272         [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
273         self.outlineViewDataSourceArray = outlineViewDataSourceArray;
274         
275         // Expand the folder outline view
276         [outlineView expandItem:nil expandChildren:YES];
277         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
278         
279         [self refresh:nil];
280     }
281 }
282
283 #pragma mark -
284 #pragma Actions
285
286 - (IBAction)refresh:(id)sender {
287     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
288         [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
289     }
290     [browser validateVisibleColumns];
291 }
292
293 #pragma mark -
294 #pragma NSBrowserDelegate
295
296 - (id)rootItemForBrowser:(NSBrowser *)browser {
297     return rootNode;    
298 }
299
300 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
301     PithosNode *node = (PithosNode *)item;
302     return node.children.count;
303 }
304
305 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
306     PithosNode *node = (PithosNode *)item;
307     return [node.children objectAtIndex:index];
308 }
309
310 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
311     PithosNode *node = (PithosNode *)item;
312     return node.isLeafItem;
313 }
314
315 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
316     PithosNode *node = (PithosNode *)item;
317     return node;
318 }
319
320 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
321     if (sharedPreviewController == nil)
322         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
323     return sharedPreviewController;
324 }
325
326 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
327 //    if (!forUserResize) {
328 //        id item = [browser parentForItemsInColumn:columnIndex]; 
329 //        if ([self browser:browser isLeafItem:item]) {
330 //            suggestedWidth = 200; 
331 //        }
332 //    }
333 //    return suggestedWidth;
334 //}
335
336 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
337     return NO;
338 }
339
340 #pragma mark Editing
341
342 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
343     return YES;
344 }
345
346 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
347     PithosNode *node = (PithosNode *)item;
348     NSString *newName = (NSString *)object;
349     NSUInteger newNameLength = [newName length];
350     NSRange firstSlashRange = [newName rangeOfString:@"/"];
351     if ((newNameLength == 0) || 
352         ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
353         ([newName isEqualToString:node.displayName])) {
354         return;
355     }
356     if (([node class] == [PithosObjectNode class]) || 
357         (([node class] == [PithosSubdirNode class]) && 
358          !node.pithosObject.subdir &&
359          [node.pithosObject.name hasSuffix:@"/"])) {
360         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
361         dispatch_async(queue, ^{
362             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
363             if ([newName hasSuffix:@"/"])
364                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
365             NSError *error = nil;
366             BOOL isDirectory;
367             if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
368                                                       objectName:destinationObjectName 
369                                                            error:&error 
370                                                      isDirectory:&isDirectory]) {
371                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
372                 [alert setMessageText:@"Name Taken"];
373                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
374                 [alert addButtonWithTitle:@"OK"];
375                 [alert runModal];
376                 return;
377             } else if (error) {
378                 return;
379             }
380             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
381                                                                                                  objectName:node.pithosObject.name 
382                                                                                    destinationContainerName:node.pithosContainer.name 
383                                                                                       destinationObjectName:destinationObjectName 
384                                                                                               checkIfExists:NO];
385             if (objectRequest) {
386                 objectRequest.delegate = self;
387                 objectRequest.didFinishSelector = @selector(moveFinished:);
388                 objectRequest.didFailSelector = @selector(moveFailed:);
389                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
390                                           node.parent, @"node", 
391                                           nil];
392                 [objectRequest startAsynchronous];
393             }
394         });
395     } else if ([node class] == [PithosSubdirNode class]) {
396         if (firstSlashRange.length == 1)
397             return;
398         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
399         dispatch_async(queue, ^{
400             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
401             NSError *error = nil;
402             BOOL isDirectory;
403             if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
404                                                       objectName:destinationObjectName 
405                                                            error:&error 
406                                                      isDirectory:&isDirectory]) {
407                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
408                 [alert setMessageText:@"Name Taken"];
409                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
410                 [alert addButtonWithTitle:@"OK"];
411                 [alert runModal];
412                 return;
413             } else if (error) {
414                 return;
415             }
416             if (node.pithosObject.subdir)
417                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
418             NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
419                                                                                              objectName:node.pithosObject.name 
420                                                                                destinationContainerName:node.pithosContainer.name 
421                                                                                   destinationObjectName:destinationObjectName 
422                                                                                           checkIfExists:NO];
423             if (objectRequests) {
424                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
425                     objectRequest.delegate = self;
426                     objectRequest.didFinishSelector = @selector(moveFinished:);
427                     objectRequest.didFailSelector = @selector(moveFailed:);
428                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
429                                               node.parent, @"node", 
430                                               nil];
431                     [objectRequest startAsynchronous];
432                 }
433             }
434         });
435     }
436 }
437
438 #pragma mark Drag and Drop source
439
440 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
441    toPasteboard:(NSPasteboard *)pasteboard {
442     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
443     NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
444     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
445     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
446         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
447         [propertyList addObject:[node.pithosObject.name pathExtension]];
448         [nodes addObject:node];
449     }];
450
451     [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
452     [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
453     self.draggedNodes = nodes;
454     self.draggedParentNode = [browser parentForItemsInColumn:column];
455     return YES;
456 }
457
458 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
459 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
460     NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
461     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
462     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
463         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
464         
465         // If the node is a subdir ask if the whole tree should be downloaded
466         if ([node class] == [PithosSubdirNode class]) {
467             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
468             [alert setMessageText:@"Download directory"];
469             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
470             [alert addButtonWithTitle:@"OK"];
471             [alert addButtonWithTitle:@"Cancel"];
472             NSInteger choice = [alert runModal];
473             if (choice == NSAlertFirstButtonReturn) {
474                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
475                 dispatch_async(queue, ^{
476                     NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
477                                                                                                      objectName:node.pithosObject.name 
478                                                                                                     toDirectory:[dropDestination path] 
479                                                                                                   checkIfExists:YES];
480                     if (objectRequests) {
481                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
482                             [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
483                             objectRequest.delegate = self;
484                             objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
485                             objectRequest.didFailSelector = @selector(downloadObjectFailed:);
486                             [objectRequest startAsynchronous];
487                         }
488                     }
489                 });
490             }
491         } else {
492             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
493                                                                                                  objectName:node.pithosObject.name 
494                                                                                                 toDirectory:[dropDestination path] 
495                                                                                               checkIfExists:YES];
496             if (objectRequest) {
497                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
498                 objectRequest.delegate = self;
499                 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
500                 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
501                 [objectRequest startAsynchronous];
502             }
503         }
504     }];
505     return names;
506 }
507
508 #pragma mark Drag and Drop destination
509
510 - (NSDragOperation)browser:aBrowser 
511               validateDrop:(id<NSDraggingInfo>)info 
512                proposedRow:(NSInteger *)row 
513                     column:(NSInteger *)column 
514              dropOperation:(NSBrowserDropOperation *)dropOperation {
515     NSDragOperation result = NSDragOperationNone;
516     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
517         // For a drop above, the drop is redirected to the parent item
518         if (*dropOperation == NSBrowserDropAbove)
519             *row = -1;
520         // Only allow dropping in folders
521         if (*column != -1) {
522             if (*row != -1) {
523                 // Check if the node is not a folder and if so redirect to the parent item
524                 if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
525                     *row = -1;
526             }
527             *dropOperation = NSBrowserDropOn;
528             result = NSDragOperationCopy;
529         }
530     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
531         // For a drop above, the drop is redirected to the parent item
532         if (*dropOperation == NSBrowserDropAbove) 
533             *row = -1;
534         // Only allow dropping in folders
535         if (*column != -1) {
536             PithosNode *dropNode;
537             if (*row != -1) {
538                 // Check if the node is not a folder and if so redirect to the parent item
539                 dropNode = [browser itemAtRow:*row inColumn:*column];
540                 if ([dropNode class] != [PithosSubdirNode class])
541                     *row = -1;
542             }
543             if (*row == -1)
544                 dropNode = [browser parentForItemsInColumn:*column];
545             
546             
547             if ([info draggingSourceOperationMask] & NSDragOperationMove) {
548                 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
549                 if ((([dropNode class] == [PithosContainerNode class]) || 
550                      dropNode.pithosObject.subdir || 
551                      ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
552                     ![dropNode isEqualTo:draggedParentNode]) { 
553 //                    ![dropNode isEqualTo:draggedParentNode] && 
554 //                    ![draggedNodes containsObject:dropNode]) {                
555                     result = NSDragOperationMove;
556                 }
557             } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
558                 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
559                 if (([dropNode class] == [PithosContainerNode class]) || 
560                     dropNode.pithosObject.subdir || 
561                     ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
562                     result = NSDragOperationCopy;
563                 }
564             }
565         }
566     }
567     return result;
568 }
569
570 - (BOOL)browser:(NSBrowser *)aBrowser 
571      acceptDrop:(id<NSDraggingInfo>)info 
572           atRow:(NSInteger)row 
573          column:(NSInteger)column 
574   dropOperation:(NSBrowserDropOperation)dropOperation {
575     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
576         NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
577         NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
578         if ((column != -1) && (filenames != nil)) {
579             PithosNode *node = nil;
580             if (row != -1)
581                 node = [browser itemAtRow:row inColumn:column];
582             else
583                 node = [browser parentForItemsInColumn:column];
584             NSLog(@"drag in node: %@", node.url);
585             if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
586                 return NO;
587             
588             NSFileManager *defaultManager = [NSFileManager defaultManager];
589             NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
590             NSString *objectNamePrefix;
591             if ([node class] == [PithosSubdirNode class])
592                 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
593             else
594                 objectNamePrefix = [NSString string];
595             NSUInteger blockSize = node.pithosContainer.blockSize;
596             NSString *blockHash = node.pithosContainer.blockHash;
597             
598             for (NSString *filePath in filenames) {
599                 BOOL isDirectory;
600                 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
601                     if (!isDirectory) {
602                         // Upload file
603                         NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
604                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
605                         dispatch_async(queue, ^{
606                             NSError *error = nil;
607                             NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
608                             if (contentType == nil)
609                                 contentType = @"application/octet-stream";
610                             if (error)
611                                 NSLog(@"contentType detection error: %@", error);
612                             NSArray *hashes = nil;
613                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
614                                                                                                                       objectName:objectName 
615                                                                                                                      contentType:contentType 
616                                                                                                                        blockSize:blockSize 
617                                                                                                                        blockHash:blockHash 
618                                                                                                                          forFile:filePath 
619                                                                                                                    checkIfExists:YES 
620                                                                                                                           hashes:&hashes];
621                             if (objectRequest) {
622                                 objectRequest.delegate = self;
623                                 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
624                                 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
625                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
626                                                           containerName, @"containerName", 
627                                                           objectName, @"objectName", 
628                                                           contentType, @"contentType", 
629                                                           [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
630                                                           blockHash, @"blockHash", 
631                                                           filePath, @"filePath", 
632                                                           hashes, @"hashes", 
633                                                           node, @"node", 
634                                                           [NSNumber numberWithUnsignedInteger:10], @"iteration", 
635                                                           nil];
636                                 [objectRequest startAsynchronous];
637                             }
638                         });
639                     } else {
640                         // Upload directory, confirm first
641                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
642                         [alert setMessageText:@"Upload directory"];
643                         [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
644                         [alert addButtonWithTitle:@"OK"];
645                         [alert addButtonWithTitle:@"Cancel"];
646                         NSInteger choice = [alert runModal];
647                         if (choice == NSAlertFirstButtonReturn) {
648                             NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
649                             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
650                             dispatch_async(queue, ^{
651                                 NSMutableArray *objectNames = nil;
652                                 NSMutableArray *contentTypes = nil;
653                                 NSMutableArray *filePaths = nil;
654                                 NSMutableArray *hashesArrays = nil;
655                                 NSMutableArray *directoryObjectRequests = nil;
656                                 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
657                                                                                                              objectName:objectName 
658                                                                                                               blockSize:blockSize 
659                                                                                                               blockHash:blockHash 
660                                                                                                            forDirectory:filePath 
661                                                                                                           checkIfExists:YES 
662                                                                                                             objectNames:&objectNames
663                                                                                                            contentTypes:&contentTypes
664                                                                                                               filePaths:&filePaths
665                                                                                                              hashesArrays:&hashesArrays 
666                                                                                                 directoryObjectRequests:&directoryObjectRequests];
667                                 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
668                                     objectRequest.delegate = self;
669                                     objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
670                                     objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
671                                     [objectRequest startAsynchronous];
672                                 }
673                                 if (objectRequests) {
674                                     for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
675                                         ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
676                                         objectRequest.delegate = self;
677                                         objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
678                                         objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
679                                         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
680                                                                   containerName, @"containerName", 
681                                                                   [objectNames objectAtIndex:i], @"objectName", 
682                                                                   [contentTypes objectAtIndex:i], @"contentType", 
683                                                                   [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
684                                                                   blockHash, @"blockHash", 
685                                                                   [filePaths objectAtIndex:i], @"filePath", 
686                                                                   [hashesArrays objectAtIndex:i], @"hashes", 
687                                                                   [NSNumber numberWithUnsignedInteger:10], @"iteration", 
688                                                                   nil];
689                                         [objectRequest startAsynchronous];
690                                     }
691                                 }
692                             });
693                         }
694                     }
695                 }
696                 
697             }
698             return YES;
699         }
700     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
701         NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
702         if ((column != -1) && (draggedNodes != nil)) {
703             PithosNode *dropNode = nil;
704             if (row != -1)
705                 dropNode = [browser itemAtRow:row inColumn:column];
706             else
707                 dropNode = [browser parentForItemsInColumn:column];
708             NSLog(@"drag local node: %@", dropNode.url);
709             if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
710                 return NO;
711             
712             NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
713             NSString *objectNamePrefix;
714             if ([dropNode class] == [PithosSubdirNode class])
715                 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
716             else
717                 objectNamePrefix = [NSString string];
718             
719             if ([info draggingSourceOperationMask] & NSDragOperationMove) {
720                 for (PithosNode *node in draggedNodes) {
721                     if (([node class] == [PithosObjectNode class]) || 
722                         (([node class] == [PithosSubdirNode class]) && 
723                          !node.pithosObject.subdir &&
724                          [node.pithosObject.name hasSuffix:@"/"])) {
725                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
726                         dispatch_async(queue, ^{
727                             NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
728                             if ([node.pithosObject.name hasSuffix:@"/"])
729                                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
730                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
731                                                                                                                  objectName:node.pithosObject.name 
732                                                                                                    destinationContainerName:containerName 
733                                                                                                       destinationObjectName:destinationObjectName 
734                                                                                                               checkIfExists:YES];
735                             if (objectRequest) {
736                                 objectRequest.delegate = self;
737                                 objectRequest.didFinishSelector = @selector(moveFinished:);
738                                 objectRequest.didFailSelector = @selector(moveFailed:);
739                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
740                                                           node.parent, @"node", 
741                                                           dropNode, @"dropNode", 
742                                                           nil];
743                                 [objectRequest startAsynchronous];
744                             }
745                         });
746                     } else if ([node class] == [PithosSubdirNode class]) {
747                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
748                         dispatch_async(queue, ^{
749                             NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
750                             if (node.pithosObject.subdir)
751                                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
752                             NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
753                                                                                                              objectName:node.pithosObject.name 
754                                                                                                destinationContainerName:containerName 
755                                                                                                   destinationObjectName:destinationObjectName 
756                                                                                                           checkIfExists:YES];
757                             if (objectRequests) {
758                                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
759                                     objectRequest.delegate = self;
760                                     objectRequest.didFinishSelector = @selector(moveFinished:);
761                                     objectRequest.didFailSelector = @selector(moveFailed:);
762                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
763                                                               node.parent, @"node", 
764                                                               dropNode, @"dropNode", 
765                                                               nil];
766                                     [objectRequest startAsynchronous];
767                                 }
768                             }
769                         });
770                     }
771                 }
772             } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
773                 for (PithosNode *node in draggedNodes) {
774                     if (([node class] == [PithosObjectNode class]) || 
775                         (([node class] == [PithosSubdirNode class]) && 
776                          !node.pithosObject.subdir &&
777                          [node.pithosObject.name hasSuffix:@"/"])) {
778                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
779                         dispatch_async(queue, ^{
780                             NSString *destinationObjectName;
781                             if (![dropNode isEqualTo:node.parent]) {
782                                 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
783                                 if ([node.pithosObject.name hasSuffix:@"/"])
784                                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
785                             } else {
786                                 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
787                                                                                                     objectName:node.pithosObject.name];
788                             }
789                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
790                                                                                                                  objectName:node.pithosObject.name 
791                                                                                                    destinationContainerName:containerName 
792                                                                                                       destinationObjectName:destinationObjectName 
793                                                                                                               checkIfExists:YES];
794                             if (objectRequest) {
795                                 objectRequest.delegate = self;
796                                 objectRequest.didFinishSelector = @selector(copyFinished:);
797                                 objectRequest.didFailSelector = @selector(copyFailed:);
798                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
799                                                           dropNode, @"dropNode", 
800                                                           nil];
801                                 [objectRequest startAsynchronous];
802                             }
803                         });
804                     } else if ([node class] == [PithosSubdirNode class]) {
805                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
806                         dispatch_async(queue, ^{
807                             NSString *destinationObjectName;
808                             if (![dropNode isEqualTo:node.parent]) {
809                                 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
810                                 if (node.pithosObject.subdir)
811                                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
812                             } else {
813                                 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
814                                                                                                  subdirName:node.pithosObject.name];
815                             }
816                             NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
817                                                                                                              objectName:node.pithosObject.name 
818                                                                                                destinationContainerName:containerName 
819                                                                                                   destinationObjectName:destinationObjectName 
820                                                                                                           checkIfExists:YES];
821                             if (objectRequests) {
822                                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
823                                     objectRequest.delegate = self;
824                                     objectRequest.didFinishSelector = @selector(copyFinished:);
825                                     objectRequest.didFailSelector = @selector(copyFailed:);
826                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
827                                                               dropNode, @"dropNode", 
828                                                               nil];
829                                     [objectRequest startAsynchronous];
830                                 }
831                             }
832                         });
833
834                     }
835                 }
836             }
837             return YES;
838         }
839     }
840     return NO;
841 }
842
843 #pragma mark -
844 #pragma mark ASIHTTPRequestDelegate
845
846 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
847     NSLog(@"Download completed: %@", [objectRequest url]);
848     if (objectRequest.responseStatusCode == 200) {
849         if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
850             NSLog(@"Downloaded  0 bytes");
851             NSFileManager *defaultManager = [NSFileManager defaultManager];
852             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
853             if (![defaultManager fileExistsAtPath:filePath]) {
854                 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
855                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
856                     [alert setMessageText:@"Create File Error"];
857                     [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
858                     [alert addButtonWithTitle:@"OK"];
859                     [alert runModal];
860                 }
861             }
862         }
863     } else {
864         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
865     }
866 }
867
868 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
869     NSLog(@"Download failed");
870     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
871 }
872
873 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
874     NSLog(@"Upload directory object completed: %@", [objectRequest url]);
875     if (objectRequest.responseStatusCode == 201) {
876         NSLog(@"Directory object created: %@", [objectRequest url]);
877         [self refresh:nil];
878     } else {
879         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
880     }
881 }
882
883 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
884     NSLog(@"Upload directory object failed");
885     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
886 }
887
888 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
889     NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
890     if (objectRequest.responseStatusCode == 201) {
891         NSLog(@"Object created: %@", [objectRequest url]);
892         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
893         if (node) {
894             [node invalidateChildren];
895             node.children;
896         } else {
897             [self refresh:nil];
898         }
899     } else if (objectRequest.responseStatusCode == 409) {
900         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
901         if (iteration == 0) {
902             NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
903             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
904             [alert setMessageText:@"Upload Timeout"];
905             [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
906                                        [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
907             [alert addButtonWithTitle:@"OK"];
908             [alert runModal];
909             return;
910         }
911         NSLog(@"object is missing hashes: %@", [objectRequest url]);
912         NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
913                                                       withMissingHashesResponse:[objectRequest responseString]];
914         NSUInteger missingBlockIndex = [missingBlocks firstIndex];
915         ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
916                                                                                                       objectName:@".upload" 
917                                                                                                        blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
918                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
919                                                                                                missingBlockIndex:missingBlockIndex];
920         newObjectRequest.delegate = self;
921         newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
922         newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
923         newObjectRequest.userInfo = objectRequest.userInfo;
924         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
925         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
926         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
927         [newObjectRequest startAsynchronous];
928     } else {
929         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
930     }
931 }
932
933 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
934     NSLog(@"Upload using hashmap failed");
935     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
936 }
937
938 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
939     NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
940     if (objectRequest.responseStatusCode == 201) {
941         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
942         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
943         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
944         if (missingBlockIndex == NSNotFound) {
945             NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
946             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
947                                                                                                          objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
948                                                                                                         contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
949                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
950                                                                                                           blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
951                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
952                                                                                                       checkIfExists:NO 
953                                                                                                              hashes:&hashes];
954             newObjectRequest.delegate = self;
955             newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
956             newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
957             newObjectRequest.userInfo = objectRequest.userInfo;
958             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
959             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
960             [newObjectRequest startAsynchronous];
961         } else {
962             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
963                                                                                                           objectName:@".upload" 
964                                                                                                            blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
965                                                                                                              forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
966                                                                                                    missingBlockIndex:missingBlockIndex];
967             newObjectRequest.delegate = self;
968             newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
969             newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
970             newObjectRequest.userInfo = objectRequest.userInfo;
971             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
972             [newObjectRequest startAsynchronous];
973         }
974     } else {
975         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
976     }
977 }
978
979 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
980     NSLog(@"Upload of missing block failed");
981     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
982 }
983
984 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
985     NSLog(@"Move object completed: %@", [objectRequest url]);
986     if (objectRequest.responseStatusCode == 201) {
987         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
988         PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
989         if (node) {
990             [node invalidateChildren];
991             node.children;
992         }
993         if (dropNode) {
994             [dropNode invalidateChildren];
995             dropNode.children;
996         }
997         if (!node || !dropNode)
998             [self refresh:nil];
999     } else {
1000         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1001     }
1002 }
1003
1004 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1005     NSLog(@"Move object failed");
1006     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1007 }
1008
1009 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1010     NSLog(@"Copy object completed: %@", [objectRequest url]);
1011     if (objectRequest.responseStatusCode == 201) {
1012         PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1013         if (dropNode) {
1014             [dropNode invalidateChildren];
1015             dropNode.children;
1016         } else {
1017             [self refresh:nil];
1018         }
1019     } else {
1020         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1021     }
1022 }
1023
1024 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1025     NSLog(@"Copy object failed");
1026     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1027 }
1028
1029 #pragma mark -
1030 #pragma mark NSSplitViewDelegate
1031
1032 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1033     return 120;
1034 }
1035
1036 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1037     return 220;
1038 }
1039
1040 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1041     if (proposedPosition < 120)
1042         return 120;
1043     else if (proposedPosition > 220)
1044         return 220;
1045     else
1046         return proposedPosition;
1047 }
1048
1049 #pragma mark -
1050 #pragma mark NSOutlineViewDelegate
1051
1052 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1053     return ([[item representedObject] isLeaf]);
1054 }
1055
1056 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1057         return (![[item representedObject] isLeaf]);
1058 }
1059
1060 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1061     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
1062     if (node) {
1063         rootNode = node;
1064         [browser loadColumnZero];
1065         [self refresh:nil];                                  
1066     }
1067 }
1068
1069 #pragma mark -
1070 #pragma mark NSMenuDelegate
1071
1072 - (void)menuNeedsUpdate:(NSMenu *)menu {
1073     NSInteger column = [browser clickedColumn];
1074     NSInteger row = [browser clickedRow];
1075     [menu removeAllItems];
1076     NSMenuItem *menuItem;
1077     if ((column == -1) || (row == -1)) {
1078         // General context menu
1079         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1080         PithosNode *menuNode;
1081         if ([menuNodesIndexPaths count] == 0) {
1082             menuNode = [browser parentForItemsInColumn:0];
1083         } else if (([menuNodesIndexPaths count] != 1) || 
1084             ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1085             menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1086         } else {
1087             menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1088         }
1089         NSArray *menuNodes = [NSArray arrayWithObject:menuNode];
1090         // New Folder
1091         menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease];
1092         [menuItem setRepresentedObject:menuNodes];
1093         [menu addItem:menuItem];
1094         [menu addItem:[NSMenuItem separatorItem]];
1095         // Get Info
1096         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
1097         [menuItem setRepresentedObject:menuNodes];
1098         [menu addItem:menuItem];
1099     } else {
1100         // Node context menu
1101         NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1102         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1103         NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1104         if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1105             for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1106                 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1107             }
1108         } else {
1109             [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1110         }
1111         // Move to Trash (pithos container only)
1112         // Delete
1113         if ([rootNode class] == [PithosContainerNode class]) {
1114             if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1115                 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
1116                 [menuItem setRepresentedObject:menuNodes];
1117                 [menu addItem:menuItem];
1118             }
1119             menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
1120             [menuItem setRepresentedObject:menuNodes];
1121             [menu addItem:menuItem];
1122             [menu addItem:[NSMenuItem separatorItem]];
1123         }
1124         // Get Info
1125         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
1126         [menuItem setRepresentedObject:menuNodes];
1127         [menu addItem:menuItem];
1128     }
1129 }
1130
1131 #pragma mark -
1132 #pragma mark Menu Actions
1133
1134 - (void)newFolder:(NSMenuItem *)sender {
1135     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1136         if ([node class] == [PithosContainerNode class]) {
1137             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1138             dispatch_async(queue, ^{
1139                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1140                                                                                     subdirName:@"untitled folder"];
1141                 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1142                                                                                                              objectName:safeObjectName 
1143                                                                                                                eTag:nil 
1144                                                                                                         contentType:@"application/directory" 
1145                                                                                                     contentEncoding:nil 
1146                                                                                                  contentDisposition:nil 
1147                                                                                                            manifest:nil 
1148                                                                                                             sharing:nil 
1149                                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
1150                                                                                                            metadata:nil 
1151                                                                                                                data:[NSData data]];
1152                 objectRequest.delegate = self;
1153                 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1154                 objectRequest.didFailSelector = @selector(newFolderFailed:);
1155                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1156                                           node, @"node", 
1157                                           nil];
1158                 [objectRequest startAsynchronous];
1159             });
1160         } else if (([node class] == [PithosSubdirNode class]) && 
1161                    (node.pithosObject.subdir || 
1162                     ![node.pithosObject.name hasSuffix:@"/"])) {
1163             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1164             dispatch_async(queue, ^{
1165                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1166                                                                                    subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1167                 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1168                                                                                                             objectName:safeObjectName 
1169                                                                                                                   eTag:nil 
1170                                                                                                            contentType:@"application/directory" 
1171                                                                                                        contentEncoding:nil 
1172                                                                                                     contentDisposition:nil 
1173                                                                                                               manifest:nil 
1174                                                                                                                sharing:nil 
1175                                                                                                               isPublic:ASIPithosObjectRequestPublicIgnore 
1176                                                                                                               metadata:nil 
1177                                                                                                                   data:[NSData data]];
1178                 objectRequest.delegate = self;
1179                 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1180                 objectRequest.didFailSelector = @selector(newFolderFailed:);
1181                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1182                                          node, @"node", 
1183                                          nil];
1184                 [objectRequest startAsynchronous];
1185             });
1186         }
1187     }
1188 }
1189
1190 - (void)getInfo:(NSMenuItem *)sender {
1191     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1192         [node showPithosNodeInfo:sender];
1193     }
1194 }
1195
1196 - (void)deleteObject:(NSMenuItem *)sender {
1197     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1198         if (([node class] == [PithosObjectNode class]) || 
1199             (([node class] == [PithosSubdirNode class]) && 
1200              !node.pithosObject.subdir &&
1201              [node.pithosObject.name hasSuffix:@"/"])) {
1202             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
1203                                                                                                       objectName:node.pithosObject.name];
1204             objectRequest.delegate = self;
1205             objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1206             objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1207             [objectRequest startAsynchronous];
1208         } else if ([node class] == [PithosSubdirNode class]) {
1209             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1210             dispatch_async(queue, ^{
1211                 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1212                                                                                                    objectName:node.pithosObject.name];
1213                 if (objectRequests) {
1214                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1215                         objectRequest.delegate = self;
1216                         objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1217                         objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1218                         [objectRequest startAsynchronous];
1219                     }
1220                 }
1221             });
1222         }
1223     }
1224 }
1225
1226 - (void)moveToTrash:(NSMenuItem *)sender {
1227     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1228         if (([node class] == [PithosObjectNode class]) || 
1229             (([node class] == [PithosSubdirNode class]) && 
1230              !node.pithosObject.subdir &&
1231              [node.pithosObject.name hasSuffix:@"/"])) {
1232             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1233             dispatch_async(queue, ^{
1234                 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
1235                                                                                     objectName:node.pithosObject.name];
1236                 if (safeObjectName) {
1237                     ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
1238                                                                                                          objectName:node.pithosObject.name 
1239                                                                                            destinationContainerName:@"trash" 
1240                                                                                               destinationObjectName:safeObjectName 
1241                                                                                                       checkIfExists:NO];
1242                     if (objectRequest) {
1243                         objectRequest.delegate = self;
1244                         objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1245                         objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1246                         [objectRequest startAsynchronous];
1247                     }
1248                 }
1249             });
1250         } else if ([node class] == [PithosSubdirNode class]) {
1251             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1252             dispatch_async(queue, ^{
1253                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
1254                                                                                     subdirName:node.pithosObject.name];
1255                 if (safeObjectName) {
1256                     NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1257                                                                                                      objectName:node.pithosObject.name 
1258                                                                                        destinationContainerName:@"trash" 
1259                                                                                           destinationObjectName:safeObjectName 
1260                                                                                                   checkIfExists:NO];
1261                     if (objectRequests) {
1262                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1263                             objectRequest.delegate = self;
1264                             objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1265                             objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1266                             [objectRequest startAsynchronous];
1267                         }
1268                     }
1269                 }
1270             });
1271         }
1272     }
1273 }
1274
1275 #pragma mark -
1276 #pragma mark Menu Actions ASIHTTPRequestDelegate
1277
1278 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1279     if (objectRequest.responseStatusCode == 201) {
1280         NSLog(@"New application/directory object created: %@", [objectRequest url]);
1281         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1282         if (node) {
1283             [node invalidateChildren];
1284             node.children;
1285         } else {
1286             [self refresh:nil];
1287         }
1288     } else {
1289         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1290     }
1291 }
1292
1293 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1294     NSLog(@"Creation of new application/directory object failed");
1295     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1296 }
1297
1298 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1299     if (objectRequest.responseStatusCode == 204) {
1300         NSLog(@"Object deleted: %@", [objectRequest url]);
1301         [self refresh:nil];
1302     } else {
1303         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1304     }
1305 }
1306
1307 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1308     NSLog(@"Delete of object failed");
1309     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1310 }
1311
1312 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1313     if (objectRequest.responseStatusCode == 201) {
1314         NSLog(@"Object moved: %@", [objectRequest url]);
1315         [self refresh:nil];
1316     } else {
1317         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1318     }
1319 }
1320
1321 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1322     NSLog(@"Move of object failed");
1323     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1324 }
1325
1326
1327 @end