Initial implementation of 'my shared'.
[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 @end
100
101 @implementation PithosBrowserController
102 @synthesize outlineViewDataSourceArray, verticalSplitView, horizontalSplitView, leftTopView, leftBottomView,
103             outlineView, browser;
104 @synthesize draggedNodes, draggedParentNode;
105 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
106
107 #pragma mark -
108 #pragma Object Lifecycle
109
110 - (id)init {
111     return [super initWithWindowNibName:@"PithosBrowserController"];
112 }
113
114 - (void)dealloc {
115     [[NSNotificationCenter defaultCenter] removeObserver:self];
116     [clipboardParentNode release];
117     [clipboardNodes release];
118     [draggedParentNode release];
119     [draggedNodes release];
120     [browserMenu release];
121     [sharedPreviewController release];
122     [outlineViewDataSourceArray release];
123     [mySharedRootNode release];
124     [accountNode release];
125     [rootNode release];
126     [super dealloc];
127 }
128
129 - (void)awakeFromNib {
130     [super awakeFromNib];
131     
132     [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
133     [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
134     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
135     
136     [browser setCellClass:[PithosBrowserCell class]];
137     
138     browserMenu = [[NSMenu alloc] init];
139     [browserMenu setDelegate:self];
140     [browser setMenu:browserMenu];
141 }
142
143 - (void)resetContainers:(NSNotification *)notification {
144     rootNode = nil;
145     [browser loadColumnZero];
146     self.outlineViewDataSourceArray = nil;
147     
148     // Create the outlineView tree
149     // CONTAINERS
150         NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
151                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
152
153     // SHARED
154         NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
155                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
156     // SHARED/my shared
157         [[sharedTreeNode mutableChildNodes] addObject:[NSTreeNode treeNodeWithRepresentedObject:mySharedRootNode]];
158     // SHARED/others shared
159         [[sharedTreeNode mutableChildNodes] addObject:
160      [NSTreeNode treeNodeWithRepresentedObject:
161       [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
162                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
163         ] autorelease]]];
164     
165     self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
166     
167         // Expand the folder outline view
168     [outlineView expandItem:nil expandChildren:YES];
169         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
170     
171     // Refresh account
172     [accountNode refresh];
173 }
174
175 - (void)windowDidLoad {
176     [super windowDidLoad];
177     
178     accountNode = [[PithosAccountNode alloc] init];
179     mySharedRootNode = [[PithosAccountNode alloc] init];
180     mySharedRootNode.displayName = @"my shared";
181     mySharedRootNode.shared = YES;
182     mySharedRootNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
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:accountNode];
199     [[NSNotificationCenter defaultCenter] addObserver:self 
200                                              selector:@selector(pithosNodeChildrenUpdated:) 
201                                                  name:@"PithosAccountNodeChildrenUpdated" 
202                                                object:mySharedRootNode];
203     [[NSNotificationCenter defaultCenter] addObserver:self 
204                                              selector:@selector(resetContainers:) 
205                                                  name:@"PithosAuthenticationCredentialsUpdated" 
206                                                object:nil];
207 }
208
209 #pragma mark -
210 #pragma Observers
211
212 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
213     PithosNode *node = (PithosNode *)[notification object];
214     NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
215     NSInteger lastColumn = [browser lastColumn];
216     for (NSInteger column = lastColumn; column >= 0; column--) {
217         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
218             [browser reloadColumn:column];
219             return;
220         }
221     }
222 }
223
224 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
225     BOOL containerPithosFound = NO;
226     BOOL containerTrashFound = NO;
227     NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
228     for (PithosContainerNode *containerNode in accountNode.children) {
229         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
230             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
231             containerPithosFound = YES;
232         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
233             NSUInteger insertIndex = 1;
234             if (!containerPithosFound)
235                 insertIndex = 0;
236             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
237             containerTrashFound = YES;
238         } else {
239             [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
240         }
241     }
242     BOOL refreshAccountNode = NO;
243     if (!containerPithosFound) {
244         // Create pithos node
245         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
246         [containerRequest startSynchronous];
247         if ([containerRequest error]) {
248             [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
249         } else {
250             refreshAccountNode = YES;
251         }
252     }
253     if (!containerTrashFound) {
254         // Create trash node
255         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
256         [containerRequest startSynchronous];
257         if ([containerRequest error]) {
258             [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
259         } else {
260             refreshAccountNode = YES;
261         }
262     }
263     if (refreshAccountNode) {
264         [accountNode refresh];
265     } else {
266         [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
267         self.outlineViewDataSourceArray = outlineViewDataSourceArray;
268         
269         // Expand the folder outline view
270         [outlineView expandItem:nil expandChildren:YES];
271         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
272         
273         [self refresh:nil];
274     }
275 }
276
277 #pragma mark -
278 #pragma Actions
279
280 - (IBAction)refresh:(id)sender {
281     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
282         [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
283     }
284     [browser validateVisibleColumns];
285 }
286
287 #pragma mark -
288 #pragma NSBrowserDelegate
289
290 - (id)rootItemForBrowser:(NSBrowser *)browser {
291     return rootNode;    
292 }
293
294 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
295     PithosNode *node = (PithosNode *)item;
296     return node.children.count;
297 }
298
299 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
300     PithosNode *node = (PithosNode *)item;
301     return [node.children objectAtIndex:index];
302 }
303
304 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
305     PithosNode *node = (PithosNode *)item;
306     return node.isLeafItem;
307 }
308
309 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
310     PithosNode *node = (PithosNode *)item;
311     return node;
312 }
313
314 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
315     if (sharedPreviewController == nil)
316         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
317     return sharedPreviewController;
318 }
319
320 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
321 //    if (!forUserResize) {
322 //        id item = [browser parentForItemsInColumn:columnIndex]; 
323 //        if ([self browser:browser isLeafItem:item]) {
324 //            suggestedWidth = 200; 
325 //        }
326 //    }
327 //    return suggestedWidth;
328 //}
329
330 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
331     return NO;
332 }
333
334 #pragma mark Editing
335
336 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
337     PithosNode *node = (PithosNode *)item;
338     if (node.shared || ([node class] == [PithosContainerNode class]))
339         return NO;
340     return YES;
341 }
342
343 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
344     PithosNode *node = (PithosNode *)item;
345     NSString *newName = (NSString *)object;
346     NSUInteger newNameLength = [newName length];
347     NSRange firstSlashRange = [newName rangeOfString:@"/"];
348     if ((newNameLength == 0) || 
349         ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
350         ([newName isEqualToString:node.displayName])) {
351         return;
352     }
353     if (([node class] == [PithosObjectNode class]) || 
354         (([node class] == [PithosSubdirNode class]) && 
355          !node.pithosObject.subdir &&
356          [node.pithosObject.name hasSuffix:@"/"])) {
357         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
358         dispatch_async(queue, ^{
359             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
360             if ([newName hasSuffix:@"/"])
361                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
362             NSError *error = nil;
363             BOOL isDirectory;
364             if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
365                                                       objectName:destinationObjectName 
366                                                            error:&error 
367                                                      isDirectory:&isDirectory]) {
368                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
369                 [alert setMessageText:@"Name Taken"];
370                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
371                 [alert addButtonWithTitle:@"OK"];
372                 [alert runModal];
373                 return;
374             } else if (error) {
375                 return;
376             }
377             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
378                                                                                                  objectName:node.pithosObject.name 
379                                                                                    destinationContainerName:node.pithosContainer.name 
380                                                                                       destinationObjectName:destinationObjectName 
381                                                                                               checkIfExists:NO];
382             if (objectRequest) {
383                 objectRequest.delegate = self;
384                 objectRequest.didFinishSelector = @selector(moveFinished:);
385                 objectRequest.didFailSelector = @selector(moveFailed:);
386                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
387                                           node.parent, @"node", 
388                                           nil];
389                 [objectRequest startAsynchronous];
390             }
391         });
392     } else if ([node class] == [PithosSubdirNode class]) {
393         if (firstSlashRange.length == 1)
394             return;
395         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
396         dispatch_async(queue, ^{
397             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
398             NSError *error = nil;
399             BOOL isDirectory;
400             if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name 
401                                                       objectName:destinationObjectName 
402                                                            error:&error 
403                                                      isDirectory:&isDirectory]) {
404                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
405                 [alert setMessageText:@"Name Taken"];
406                 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
407                 [alert addButtonWithTitle:@"OK"];
408                 [alert runModal];
409                 return;
410             } else if (error) {
411                 return;
412             }
413             if (node.pithosObject.subdir)
414                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
415             NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
416                                                                                              objectName:node.pithosObject.name 
417                                                                                destinationContainerName:node.pithosContainer.name 
418                                                                                   destinationObjectName:destinationObjectName 
419                                                                                           checkIfExists:NO];
420             if (objectRequests) {
421                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
422                     objectRequest.delegate = self;
423                     objectRequest.didFinishSelector = @selector(moveFinished:);
424                     objectRequest.didFailSelector = @selector(moveFailed:);
425                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
426                                               node.parent, @"node", 
427                                               nil];
428                     [objectRequest startAsynchronous];
429                 }
430             }
431         });
432     }
433 }
434
435 #pragma mark Drag and Drop source
436
437 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
438       withEvent:(NSEvent *)event {
439     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
440     __block BOOL result = YES;
441     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
442         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
443         if ([node class] == [PithosContainerNode class]) {
444             result = NO;
445             *stop = YES;
446         }
447     }];
448     return result;
449 }
450
451 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
452    toPasteboard:(NSPasteboard *)pasteboard {
453     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
454     NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
455     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
456     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
457         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
458         [propertyList addObject:[node.pithosObject.name pathExtension]];
459         [nodes addObject:node];
460     }];
461
462     [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
463     [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
464     self.draggedNodes = nodes;
465     self.draggedParentNode = [browser parentForItemsInColumn:column];
466     return YES;
467 }
468
469 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
470 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
471     NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
472     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
473     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
474         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
475         
476         // If the node is a subdir ask if the whole tree should be downloaded
477         if ([node class] == [PithosSubdirNode class]) {
478             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
479             [alert setMessageText:@"Download directory"];
480             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
481             [alert addButtonWithTitle:@"OK"];
482             [alert addButtonWithTitle:@"Cancel"];
483             NSInteger choice = [alert runModal];
484             if (choice == NSAlertFirstButtonReturn) {
485                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
486                 dispatch_async(queue, ^{
487                     NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
488                                                                                                      objectName:node.pithosObject.name 
489                                                                                                     toDirectory:[dropDestination path] 
490                                                                                                   checkIfExists:YES];
491                     if (objectRequests) {
492                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
493                             [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
494                             objectRequest.delegate = self;
495                             objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
496                             objectRequest.didFailSelector = @selector(downloadObjectFailed:);
497                             [objectRequest startAsynchronous];
498                         }
499                     }
500                 });
501             }
502         } else {
503             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
504                                                                                                  objectName:node.pithosObject.name 
505                                                                                                 toDirectory:[dropDestination path] 
506                                                                                               checkIfExists:YES];
507             if (objectRequest) {
508                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
509                 objectRequest.delegate = self;
510                 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
511                 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
512                 [objectRequest startAsynchronous];
513             }
514         }
515     }];
516     return names;
517 }
518
519 #pragma mark Drag and Drop destination
520
521 - (NSDragOperation)browser:aBrowser 
522               validateDrop:(id<NSDraggingInfo>)info 
523                proposedRow:(NSInteger *)row 
524                     column:(NSInteger *)column 
525              dropOperation:(NSBrowserDropOperation *)dropOperation {
526     NSDragOperation result = NSDragOperationNone;
527     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
528         // For a drop above, the drop is redirected to the parent item
529         if (*dropOperation == NSBrowserDropAbove)
530             *row = -1;
531         // Only allow dropping in folders
532         if (*column != -1) {
533             if (*row != -1) {
534                 // Check if the node is not a folder and if so redirect to the parent item
535                 if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
536                     *row = -1;
537             }
538             *dropOperation = NSBrowserDropOn;
539             result = NSDragOperationCopy;
540         }
541     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
542         // For a drop above, the drop is redirected to the parent item
543         if (*dropOperation == NSBrowserDropAbove) 
544             *row = -1;
545         // Only allow dropping in folders
546         if (*column != -1) {
547             PithosNode *dropNode;
548             if (*row != -1) {
549                 // Check if the node is not a folder and if so redirect to the parent item
550                 dropNode = [browser itemAtRow:*row inColumn:*column];
551                 if ([dropNode class] != [PithosSubdirNode class])
552                     *row = -1;
553             }
554             if (*row == -1)
555                 dropNode = [browser parentForItemsInColumn:*column];
556             
557             if (!dropNode.shared) {
558                 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
559                     // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
560                     if ((([dropNode class] == [PithosContainerNode class]) || 
561                          dropNode.pithosObject.subdir || 
562                          ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
563                         ![dropNode isEqualTo:draggedParentNode]) { 
564     //                    ![dropNode isEqualTo:draggedParentNode] && 
565     //                    ![draggedNodes containsObject:dropNode]) {                
566                         result = NSDragOperationMove;
567                     }
568                 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
569                     // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
570                     if (([dropNode class] == [PithosContainerNode class]) || 
571                         dropNode.pithosObject.subdir || 
572                         ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
573                         result = NSDragOperationCopy;
574                     }
575                 }
576             }
577         }
578     }
579     return result;
580 }
581
582 - (BOOL)browser:(NSBrowser *)aBrowser 
583      acceptDrop:(id<NSDraggingInfo>)info 
584           atRow:(NSInteger)row 
585          column:(NSInteger)column 
586   dropOperation:(NSBrowserDropOperation)dropOperation {
587     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
588         NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
589         NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
590         if ((column != -1) && (filenames != nil)) {
591             PithosNode *node = nil;
592             if (row != -1)
593                 node = [browser itemAtRow:row inColumn:column];
594             else
595                 node = [browser parentForItemsInColumn:column];
596             NSLog(@"drag in node: %@", node.url);
597             if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
598                 return NO;
599             
600             NSFileManager *defaultManager = [NSFileManager defaultManager];
601             NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
602             NSString *objectNamePrefix;
603             if ([node class] == [PithosSubdirNode class])
604                 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
605             else
606                 objectNamePrefix = [NSString string];
607             NSUInteger blockSize = node.pithosContainer.blockSize;
608             NSString *blockHash = node.pithosContainer.blockHash;
609             
610             for (NSString *filePath in filenames) {
611                 BOOL isDirectory;
612                 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
613                     if (!isDirectory) {
614                         // Upload file
615                         NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
616                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
617                         dispatch_async(queue, ^{
618                             NSError *error = nil;
619                             NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
620                             if (contentType == nil)
621                                 contentType = @"application/octet-stream";
622                             if (error)
623                                 NSLog(@"contentType detection error: %@", error);
624                             NSArray *hashes = nil;
625                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
626                                                                                                                       objectName:objectName 
627                                                                                                                      contentType:contentType 
628                                                                                                                        blockSize:blockSize 
629                                                                                                                        blockHash:blockHash 
630                                                                                                                          forFile:filePath 
631                                                                                                                    checkIfExists:YES 
632                                                                                                                           hashes:&hashes];
633                             if (objectRequest) {
634                                 objectRequest.delegate = self;
635                                 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
636                                 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
637                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
638                                                           containerName, @"containerName", 
639                                                           objectName, @"objectName", 
640                                                           contentType, @"contentType", 
641                                                           [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
642                                                           blockHash, @"blockHash", 
643                                                           filePath, @"filePath", 
644                                                           hashes, @"hashes", 
645                                                           node, @"node", 
646                                                           [NSNumber numberWithUnsignedInteger:10], @"iteration", 
647                                                           nil];
648                                 [objectRequest startAsynchronous];
649                             }
650                         });
651                     } else {
652                         // Upload directory, confirm first
653                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
654                         [alert setMessageText:@"Upload directory"];
655                         [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
656                         [alert addButtonWithTitle:@"OK"];
657                         [alert addButtonWithTitle:@"Cancel"];
658                         NSInteger choice = [alert runModal];
659                         if (choice == NSAlertFirstButtonReturn) {
660                             NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
661                             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
662                             dispatch_async(queue, ^{
663                                 NSMutableArray *objectNames = nil;
664                                 NSMutableArray *contentTypes = nil;
665                                 NSMutableArray *filePaths = nil;
666                                 NSMutableArray *hashesArrays = nil;
667                                 NSMutableArray *directoryObjectRequests = nil;
668                                 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
669                                                                                                              objectName:objectName 
670                                                                                                               blockSize:blockSize 
671                                                                                                               blockHash:blockHash 
672                                                                                                            forDirectory:filePath 
673                                                                                                           checkIfExists:YES 
674                                                                                                             objectNames:&objectNames
675                                                                                                            contentTypes:&contentTypes
676                                                                                                               filePaths:&filePaths
677                                                                                                              hashesArrays:&hashesArrays 
678                                                                                                 directoryObjectRequests:&directoryObjectRequests];
679                                 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
680                                     objectRequest.delegate = self;
681                                     objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
682                                     objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
683                                     [objectRequest startAsynchronous];
684                                 }
685                                 if (objectRequests) {
686                                     for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
687                                         ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
688                                         objectRequest.delegate = self;
689                                         objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
690                                         objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
691                                         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
692                                                                   containerName, @"containerName", 
693                                                                   [objectNames objectAtIndex:i], @"objectName", 
694                                                                   [contentTypes objectAtIndex:i], @"contentType", 
695                                                                   [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
696                                                                   blockHash, @"blockHash", 
697                                                                   [filePaths objectAtIndex:i], @"filePath", 
698                                                                   [hashesArrays objectAtIndex:i], @"hashes", 
699                                                                   [NSNumber numberWithUnsignedInteger:10], @"iteration", 
700                                                                   nil];
701                                         [objectRequest startAsynchronous];
702                                     }
703                                 }
704                             });
705                         }
706                     }
707                 }
708                 
709             }
710             return YES;
711         }
712     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
713         NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
714         if ((column != -1) && (draggedNodes != nil)) {
715             PithosNode *dropNode = nil;
716             if (row != -1)
717                 dropNode = [browser itemAtRow:row inColumn:column];
718             else
719                 dropNode = [browser parentForItemsInColumn:column];
720             NSLog(@"drag local node: %@", dropNode.url);
721             if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
722                 return NO;
723             
724             NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
725             NSString *objectNamePrefix;
726             if ([dropNode class] == [PithosSubdirNode class])
727                 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
728             else
729                 objectNamePrefix = [NSString string];
730             
731             if ([info draggingSourceOperationMask] & NSDragOperationMove) {
732                 for (PithosNode *node in draggedNodes) {
733                     if (([node class] == [PithosObjectNode class]) || 
734                         (([node class] == [PithosSubdirNode class]) && 
735                          !node.pithosObject.subdir &&
736                          [node.pithosObject.name hasSuffix:@"/"])) {
737                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
738                         dispatch_async(queue, ^{
739                             NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
740                             if ([node.pithosObject.name hasSuffix:@"/"])
741                                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
742                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
743                                                                                                                  objectName:node.pithosObject.name 
744                                                                                                    destinationContainerName:containerName 
745                                                                                                       destinationObjectName:destinationObjectName 
746                                                                                                               checkIfExists:YES];
747                             if (objectRequest) {
748                                 objectRequest.delegate = self;
749                                 objectRequest.didFinishSelector = @selector(moveFinished:);
750                                 objectRequest.didFailSelector = @selector(moveFailed:);
751                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
752                                                           node.parent, @"node", 
753                                                           dropNode, @"dropNode", 
754                                                           nil];
755                                 [objectRequest startAsynchronous];
756                             }
757                         });
758                     } else if ([node class] == [PithosSubdirNode class]) {
759                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
760                         dispatch_async(queue, ^{
761                             NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
762                             if (node.pithosObject.subdir)
763                                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
764                             NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
765                                                                                                              objectName:node.pithosObject.name 
766                                                                                                destinationContainerName:containerName 
767                                                                                                   destinationObjectName:destinationObjectName 
768                                                                                                           checkIfExists:YES];
769                             if (objectRequests) {
770                                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
771                                     objectRequest.delegate = self;
772                                     objectRequest.didFinishSelector = @selector(moveFinished:);
773                                     objectRequest.didFailSelector = @selector(moveFailed:);
774                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
775                                                               node.parent, @"node", 
776                                                               dropNode, @"dropNode", 
777                                                               nil];
778                                     [objectRequest startAsynchronous];
779                                 }
780                             }
781                         });
782                     }
783                 }
784             } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
785                 for (PithosNode *node in draggedNodes) {
786                     if (([node class] == [PithosObjectNode class]) || 
787                         (([node class] == [PithosSubdirNode class]) && 
788                          !node.pithosObject.subdir &&
789                          [node.pithosObject.name hasSuffix:@"/"])) {
790                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
791                         dispatch_async(queue, ^{
792                             NSString *destinationObjectName;
793                             if (![dropNode isEqualTo:node.parent]) {
794                                 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
795                                 if ([node.pithosObject.name hasSuffix:@"/"])
796                                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
797                             } else {
798                                 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
799                                                                                                     objectName:node.pithosObject.name];
800                             }
801                             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
802                                                                                                                  objectName:node.pithosObject.name 
803                                                                                                    destinationContainerName:containerName 
804                                                                                                       destinationObjectName:destinationObjectName 
805                                                                                                               checkIfExists:YES];
806                             if (objectRequest) {
807                                 objectRequest.delegate = self;
808                                 objectRequest.didFinishSelector = @selector(copyFinished:);
809                                 objectRequest.didFailSelector = @selector(copyFailed:);
810                                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
811                                                           dropNode, @"dropNode", 
812                                                           nil];
813                                 [objectRequest startAsynchronous];
814                             }
815                         });
816                     } else if ([node class] == [PithosSubdirNode class]) {
817                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
818                         dispatch_async(queue, ^{
819                             NSString *destinationObjectName;
820                             if (![dropNode isEqualTo:node.parent]) {
821                                 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
822                                 if (node.pithosObject.subdir)
823                                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
824                             } else {
825                                 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
826                                                                                                  subdirName:node.pithosObject.name];
827                             }
828                             NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
829                                                                                                              objectName:node.pithosObject.name 
830                                                                                                destinationContainerName:containerName 
831                                                                                                   destinationObjectName:destinationObjectName 
832                                                                                                           checkIfExists:YES];
833                             if (objectRequests) {
834                                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
835                                     objectRequest.delegate = self;
836                                     objectRequest.didFinishSelector = @selector(copyFinished:);
837                                     objectRequest.didFailSelector = @selector(copyFailed:);
838                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
839                                                               dropNode, @"dropNode", 
840                                                               nil];
841                                     [objectRequest startAsynchronous];
842                                 }
843                             }
844                         });
845
846                     }
847                 }
848             }
849             return YES;
850         }
851     }
852     return NO;
853 }
854
855 #pragma mark -
856 #pragma mark ASIHTTPRequestDelegate
857
858 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
859     NSLog(@"Download completed: %@", [objectRequest url]);
860     if (objectRequest.responseStatusCode == 200) {
861         if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
862             NSLog(@"Downloaded  0 bytes");
863             NSFileManager *defaultManager = [NSFileManager defaultManager];
864             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
865             if (![defaultManager fileExistsAtPath:filePath]) {
866                 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
867                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
868                     [alert setMessageText:@"Create File Error"];
869                     [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
870                     [alert addButtonWithTitle:@"OK"];
871                     [alert runModal];
872                 }
873             }
874         }
875     } else {
876         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
877     }
878 }
879
880 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
881     NSLog(@"Download failed");
882     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
883 }
884
885 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
886     NSLog(@"Upload directory object completed: %@", [objectRequest url]);
887     if (objectRequest.responseStatusCode == 201) {
888         NSLog(@"Directory object created: %@", [objectRequest url]);
889         [self refresh:nil];
890     } else {
891         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
892     }
893 }
894
895 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
896     NSLog(@"Upload directory object failed");
897     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
898 }
899
900 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
901     NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
902     if (objectRequest.responseStatusCode == 201) {
903         NSLog(@"Object created: %@", [objectRequest url]);
904         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
905         if (node)
906             [node refresh];
907         else
908             [self refresh:nil];
909     } else if (objectRequest.responseStatusCode == 409) {
910         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
911         if (iteration == 0) {
912             NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
913             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
914             [alert setMessageText:@"Upload Timeout"];
915             [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
916                                        [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
917             [alert addButtonWithTitle:@"OK"];
918             [alert runModal];
919             return;
920         }
921         NSLog(@"object is missing hashes: %@", [objectRequest url]);
922         NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
923                                                       withMissingHashesResponse:[objectRequest responseString]];
924         NSUInteger missingBlockIndex = [missingBlocks firstIndex];
925         ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
926                                                                                                       objectName:@".upload" 
927                                                                                                        blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
928                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
929                                                                                                missingBlockIndex:missingBlockIndex];
930         newObjectRequest.delegate = self;
931         newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
932         newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
933         newObjectRequest.userInfo = objectRequest.userInfo;
934         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
935         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
936         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
937         [newObjectRequest startAsynchronous];
938     } else {
939         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
940     }
941 }
942
943 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
944     NSLog(@"Upload using hashmap failed");
945     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
946 }
947
948 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
949     NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
950     if (objectRequest.responseStatusCode == 201) {
951         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
952         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
953         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
954         if (missingBlockIndex == NSNotFound) {
955             NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
956             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
957                                                                                                          objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
958                                                                                                         contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
959                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
960                                                                                                           blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
961                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
962                                                                                                       checkIfExists:NO 
963                                                                                                              hashes:&hashes];
964             newObjectRequest.delegate = self;
965             newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
966             newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
967             newObjectRequest.userInfo = objectRequest.userInfo;
968             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
969             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
970             [newObjectRequest startAsynchronous];
971         } else {
972             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
973                                                                                                           objectName:@".upload" 
974                                                                                                            blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
975                                                                                                              forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
976                                                                                                    missingBlockIndex:missingBlockIndex];
977             newObjectRequest.delegate = self;
978             newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
979             newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
980             newObjectRequest.userInfo = objectRequest.userInfo;
981             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
982             [newObjectRequest startAsynchronous];
983         }
984     } else {
985         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
986     }
987 }
988
989 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
990     NSLog(@"Upload of missing block failed");
991     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
992 }
993
994 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
995     NSLog(@"Move object completed: %@", [objectRequest url]);
996     if (objectRequest.responseStatusCode == 201) {
997         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
998         PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
999         if (node)
1000             [node refresh];
1001         if (dropNode)
1002             [dropNode refresh];
1003         if (!node || !dropNode)
1004             [self refresh:nil];
1005     } else {
1006         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1007     }
1008 }
1009
1010 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1011     NSLog(@"Move object failed");
1012     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1013 }
1014
1015 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1016     NSLog(@"Copy object completed: %@", [objectRequest url]);
1017     if (objectRequest.responseStatusCode == 201) {
1018         PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1019         if (dropNode)
1020             [dropNode refresh];
1021         else
1022             [self refresh:nil];
1023     } else {
1024         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1025     }
1026 }
1027
1028 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1029     NSLog(@"Copy object failed");
1030     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1031 }
1032
1033 #pragma mark -
1034 #pragma mark NSSplitViewDelegate
1035
1036 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1037     if (splitView == verticalSplitView)
1038         return 120;
1039     else
1040         return ([horizontalSplitView bounds].size.height - 87);
1041 }
1042
1043 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1044     if (splitView == verticalSplitView)
1045         return 220;
1046     else
1047         return ([horizontalSplitView bounds].size.height - 87);
1048 }
1049
1050 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1051     if (splitView == verticalSplitView) {
1052         if (proposedPosition < 120)
1053             return 120;
1054         else if (proposedPosition > 220)
1055             return 220;
1056         else
1057             return proposedPosition;
1058     } else {
1059         return ([horizontalSplitView bounds].size.height - 87);
1060     }
1061 }
1062
1063 #pragma mark -
1064 #pragma mark NSOutlineViewDelegate
1065
1066 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1067     return ([[item representedObject] isLeaf]);
1068 }
1069
1070 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1071         return (![[item representedObject] isLeaf]);
1072 }
1073
1074 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1075     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
1076     if (node) {
1077         rootNode = node;
1078         [browser loadColumnZero];
1079         [self refresh:nil];                                  
1080     }
1081 }
1082
1083 #pragma mark -
1084 #pragma mark NSMenuDelegate
1085
1086 - (void)menuNeedsUpdate:(NSMenu *)menu {
1087     NSInteger column = [browser clickedColumn];
1088     NSInteger row = [browser clickedRow];
1089     [menu removeAllItems];
1090     NSMenuItem *menuItem;
1091     NSString *menuItemTitle;
1092     if ((column == -1) || (row == -1)) {
1093         // General context menu
1094         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1095         PithosNode *menuNode;
1096         if ([menuNodesIndexPaths count] == 0) {
1097             menuNode = [browser parentForItemsInColumn:0];
1098         } else if (([menuNodesIndexPaths count] != 1) || 
1099             ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1100             menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1101         } else {
1102             menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1103         }
1104         if ([menuNode class] == [PithosAccountNode class])
1105             return;
1106         BOOL shared = menuNode.shared;
1107         
1108         if (!shared) {
1109             // New Folder
1110             menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1111             [menuItem setRepresentedObject:menuNode];
1112             [menu addItem:menuItem];
1113             [menu addItem:[NSMenuItem separatorItem]];
1114         }
1115         
1116         // Get Info
1117         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1118         [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1119         [menu addItem:menuItem];
1120         
1121         if (!shared) {
1122             // Paste
1123             if (clipboardNodes) {
1124                 NSUInteger clipboardNodesCount = [clipboardNodes count];
1125                 if (clipboardNodesCount == 0) {
1126                     self.clipboardNodes = nil;
1127                 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1128                     if (clipboardNodesCount == 1)
1129                         menuItemTitle = [NSString stringWithString:@"Paste Item"];
1130                     else
1131                         menuItemTitle = [NSString stringWithString:@"Paste Items"];
1132                     [menu addItem:[NSMenuItem separatorItem]];
1133                     menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1134                     [menuItem setRepresentedObject:menuNode];
1135                     [menu addItem:menuItem];
1136                 }
1137             }
1138         }
1139     } else {
1140         // Node context menu
1141         NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1142         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1143         NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1144         if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1145             for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1146                 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1147             }
1148         } else {
1149             [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1150         }
1151         NSUInteger menuNodesCount = [menuNodes count];
1152         BOOL shared = ((PithosNode *)[menuNodes objectAtIndex:0]).shared;
1153         
1154         if (!shared) {
1155             // Move to Trash (pithos container only)
1156             // Delete
1157             if ([rootNode class] == [PithosContainerNode class]) {
1158                 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1159                     menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1160                     [menuItem setRepresentedObject:menuNodes];
1161                     [menu addItem:menuItem];
1162                 }
1163                 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1164                 [menuItem setRepresentedObject:menuNodes];
1165                 [menu addItem:menuItem];
1166                 [menu addItem:[NSMenuItem separatorItem]];
1167             }
1168         }
1169         
1170         // Get Info
1171         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1172         [menuItem setRepresentedObject:menuNodes];
1173         [menu addItem:menuItem];
1174         
1175         if (!shared) {
1176             // Cut
1177             [menu addItem:[NSMenuItem separatorItem]];
1178             if (menuNodesCount == 1)
1179                 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1180             else 
1181                 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1182             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1183             [menuItem setRepresentedObject:menuNodes];
1184             [menu addItem:menuItem];
1185             // Copy
1186             if (menuNodesCount == 1)
1187                 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1188             else 
1189                 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1190             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1191             [menuItem setRepresentedObject:menuNodes];
1192             [menu addItem:menuItem];
1193             // Paste
1194             if (menuNodesCount == 1) {
1195                 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1196                 if (([menuNode class] == [PithosSubdirNode class]) && 
1197                     (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1198                     if (clipboardNodes) {
1199                         NSUInteger clipboardNodesCount = [clipboardNodes count];
1200                         if (clipboardNodesCount == 0) {
1201                             self.clipboardNodes = nil;
1202                         } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1203                             if (clipboardNodesCount == 1)
1204                                 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1205                             else
1206                                 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1207                             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1208                             [menuItem setRepresentedObject:menuNode];
1209                             [menu addItem:menuItem];
1210                         }
1211                     }
1212                 }
1213             }
1214         }
1215     }
1216 }
1217
1218 #pragma mark -
1219 #pragma mark Menu Actions
1220
1221 - (void)menuNewFolder:(NSMenuItem *)sender {
1222     PithosNode *node = (PithosNode *)[sender representedObject];
1223     if ([node class] == [PithosContainerNode class]) {
1224         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1225         dispatch_async(queue, ^{
1226             NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1227                                                                                 subdirName:@"untitled folder"];
1228             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1229                                                                                                          objectName:safeObjectName 
1230                                                                                                            eTag:nil 
1231                                                                                                     contentType:@"application/directory" 
1232                                                                                                 contentEncoding:nil 
1233                                                                                              contentDisposition:nil 
1234                                                                                                        manifest:nil 
1235                                                                                                         sharing:nil 
1236                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
1237                                                                                                        metadata:nil 
1238                                                                                                            data:[NSData data]];
1239             objectRequest.delegate = self;
1240             objectRequest.didFinishSelector = @selector(newFolderFinished:);
1241             objectRequest.didFailSelector = @selector(newFolderFailed:);
1242             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1243                                       node, @"node", 
1244                                       nil];
1245             [objectRequest startAsynchronous];
1246         });
1247     } else if (([node class] == [PithosSubdirNode class]) && 
1248                (node.pithosObject.subdir || 
1249                 ![node.pithosObject.name hasSuffix:@"/"])) {
1250         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1251         dispatch_async(queue, ^{
1252             NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1253                                                                                subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1254             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1255                                                                                                         objectName:safeObjectName 
1256                                                                                                               eTag:nil 
1257                                                                                                        contentType:@"application/directory" 
1258                                                                                                    contentEncoding:nil 
1259                                                                                                 contentDisposition:nil 
1260                                                                                                           manifest:nil 
1261                                                                                                            sharing:nil 
1262                                                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
1263                                                                                                           metadata:nil 
1264                                                                                                               data:[NSData data]];
1265             objectRequest.delegate = self;
1266             objectRequest.didFinishSelector = @selector(newFolderFinished:);
1267             objectRequest.didFailSelector = @selector(newFolderFailed:);
1268             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1269                                      node, @"node", 
1270                                      nil];
1271             [objectRequest startAsynchronous];
1272         });
1273     }
1274 }
1275
1276 - (void)menuGetInfo:(NSMenuItem *)sender {
1277     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1278         [node showPithosNodeInfo:sender];
1279     }
1280 }
1281
1282 - (void)menuDelete:(NSMenuItem *)sender {
1283     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1284         if (([node class] == [PithosObjectNode class]) || 
1285             (([node class] == [PithosSubdirNode class]) && 
1286              !node.pithosObject.subdir &&
1287              [node.pithosObject.name hasSuffix:@"/"])) {
1288             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
1289                                                                                                       objectName:node.pithosObject.name];
1290             objectRequest.delegate = self;
1291             objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1292             objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1293             [objectRequest startAsynchronous];
1294         } else if ([node class] == [PithosSubdirNode class]) {
1295             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1296             dispatch_async(queue, ^{
1297                 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1298                                                                                                    objectName:node.pithosObject.name];
1299                 if (objectRequests) {
1300                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1301                         objectRequest.delegate = self;
1302                         objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1303                         objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1304                         [objectRequest startAsynchronous];
1305                     }
1306                 }
1307             });
1308         }
1309     }
1310 }
1311
1312 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1313     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1314         if (([node class] == [PithosObjectNode class]) || 
1315             (([node class] == [PithosSubdirNode class]) && 
1316              !node.pithosObject.subdir &&
1317              [node.pithosObject.name hasSuffix:@"/"])) {
1318             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1319             dispatch_async(queue, ^{
1320                 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
1321                                                                                     objectName:node.pithosObject.name];
1322                 if (safeObjectName) {
1323                     ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
1324                                                                                                          objectName:node.pithosObject.name 
1325                                                                                            destinationContainerName:@"trash" 
1326                                                                                               destinationObjectName:safeObjectName 
1327                                                                                                       checkIfExists:NO];
1328                     if (objectRequest) {
1329                         objectRequest.delegate = self;
1330                         objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1331                         objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1332                         [objectRequest startAsynchronous];
1333                     }
1334                 }
1335             });
1336         } else if ([node class] == [PithosSubdirNode class]) {
1337             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1338             dispatch_async(queue, ^{
1339                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
1340                                                                                     subdirName:node.pithosObject.name];
1341                 if (safeObjectName) {
1342                     NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1343                                                                                                      objectName:node.pithosObject.name 
1344                                                                                        destinationContainerName:@"trash" 
1345                                                                                           destinationObjectName:safeObjectName 
1346                                                                                                   checkIfExists:NO];
1347                     if (objectRequests) {
1348                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1349                             objectRequest.delegate = self;
1350                             objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1351                             objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1352                             [objectRequest startAsynchronous];
1353                         }
1354                     }
1355                 }
1356             });
1357         }
1358     }
1359 }
1360
1361 - (void)menuCut:(NSMenuItem *)sender {
1362     self.clipboardNodes = (NSArray *)[sender representedObject];
1363     self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1364     self.clipboardCopy = NO;
1365 }
1366
1367 - (void)menuCopy:(NSMenuItem *)sender {
1368     self.clipboardNodes = (NSArray *)[sender representedObject];
1369     self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1370     self.clipboardCopy = YES;
1371 }
1372
1373 - (void)menuPaste:(NSMenuItem *)sender {
1374     if (!clipboardNodes || ![clipboardNodes count])
1375         return;
1376     PithosNode *dropNode = (PithosNode *)[sender representedObject];
1377     if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) || 
1378         (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"])) 
1379         return;
1380     
1381     NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1382     NSString *objectNamePrefix;
1383     if ([dropNode class] == [PithosSubdirNode class])
1384         objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1385     else
1386         objectNamePrefix = [NSString string];
1387     
1388     NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1389     if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1390         self.clipboardNodes = nil;
1391         self.clipboardParentNode = nil;
1392         for (PithosNode *node in localClipboardNodes) {
1393             if (([node class] == [PithosObjectNode class]) || 
1394                 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1395                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1396                 dispatch_async(queue, ^{
1397                     NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1398                     if ([node.pithosObject.name hasSuffix:@"/"])
1399                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1400                     ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
1401                                                                                                          objectName:node.pithosObject.name 
1402                                                                                            destinationContainerName:containerName 
1403                                                                                               destinationObjectName:destinationObjectName 
1404                                                                                                       checkIfExists:YES];
1405                     if (objectRequest) {
1406                         objectRequest.delegate = self;
1407                         objectRequest.didFinishSelector = @selector(moveFinished:);
1408                         objectRequest.didFailSelector = @selector(moveFailed:);
1409                         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1410                                                   node.parent, @"node", 
1411                                                   dropNode, @"dropNode", 
1412                                                   nil];
1413                         [objectRequest startAsynchronous];
1414                     }
1415                 });
1416             } else if ([node class] == [PithosSubdirNode class]) {
1417                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1418                 dispatch_async(queue, ^{
1419                     NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1420                     if (node.pithosObject.subdir)
1421                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1422                     NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1423                                                                                                      objectName:node.pithosObject.name 
1424                                                                                        destinationContainerName:containerName 
1425                                                                                           destinationObjectName:destinationObjectName 
1426                                                                                                   checkIfExists:YES];
1427                     if (objectRequests) {
1428                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1429                             objectRequest.delegate = self;
1430                             objectRequest.didFinishSelector = @selector(moveFinished:);
1431                             objectRequest.didFailSelector = @selector(moveFailed:);
1432                             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1433                                                       node.parent, @"node", 
1434                                                       dropNode, @"dropNode", 
1435                                                       nil];
1436                             [objectRequest startAsynchronous];
1437                         }
1438                     }
1439                 });
1440             }
1441         }
1442     } else {
1443         for (PithosNode *node in localClipboardNodes) {
1444             if (([node class] == [PithosObjectNode class]) || 
1445                 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1446                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1447                 dispatch_async(queue, ^{
1448                     NSString *destinationObjectName;
1449                     if (![dropNode isEqualTo:node.parent]) {
1450                         destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1451                         if ([node.pithosObject.name hasSuffix:@"/"])
1452                             destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1453                     } else {
1454                         destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName 
1455                                                                                          objectName:node.pithosObject.name];
1456                     }
1457                     ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
1458                                                                                                          objectName:node.pithosObject.name 
1459                                                                                            destinationContainerName:containerName 
1460                                                                                               destinationObjectName:destinationObjectName 
1461                                                                                                       checkIfExists:YES];
1462                     if (objectRequest) {
1463                         objectRequest.delegate = self;
1464                         objectRequest.didFinishSelector = @selector(copyFinished:);
1465                         objectRequest.didFailSelector = @selector(copyFailed:);
1466                         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1467                                                   dropNode, @"dropNode", 
1468                                                   nil];
1469                         [objectRequest startAsynchronous];
1470                     }
1471                 });
1472             } else if ([node class] == [PithosSubdirNode class]) {
1473                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1474                 dispatch_async(queue, ^{
1475                     NSString *destinationObjectName;
1476                     if (![dropNode isEqualTo:node.parent]) {
1477                         destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1478                         if (node.pithosObject.subdir)
1479                             destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1480                     } else {
1481                         destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName 
1482                                                                                          subdirName:node.pithosObject.name];
1483                     }
1484                     NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1485                                                                                                      objectName:node.pithosObject.name 
1486                                                                                        destinationContainerName:containerName 
1487                                                                                           destinationObjectName:destinationObjectName 
1488                                                                                                   checkIfExists:YES];
1489                     if (objectRequests) {
1490                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1491                             objectRequest.delegate = self;
1492                             objectRequest.didFinishSelector = @selector(copyFinished:);
1493                             objectRequest.didFailSelector = @selector(copyFailed:);
1494                             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1495                                                       dropNode, @"dropNode", 
1496                                                       nil];
1497                             [objectRequest startAsynchronous];
1498                         }
1499                     }
1500                 });                
1501             }
1502         }
1503     }
1504 }
1505     
1506 #pragma mark -
1507 #pragma mark Menu Actions ASIHTTPRequestDelegate
1508
1509 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1510     if (objectRequest.responseStatusCode == 201) {
1511         NSLog(@"New application/directory object created: %@", [objectRequest url]);
1512         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1513         if (node)
1514             [node refresh];
1515         else
1516             [self refresh:nil];
1517     } else {
1518         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1519     }
1520 }
1521
1522 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1523     NSLog(@"Creation of new application/directory object failed");
1524     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1525 }
1526
1527 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1528     if (objectRequest.responseStatusCode == 204) {
1529         NSLog(@"Object deleted: %@", [objectRequest url]);
1530         [self refresh:nil];
1531     } else {
1532         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1533     }
1534 }
1535
1536 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1537     NSLog(@"Delete of object failed");
1538     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1539 }
1540
1541 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1542     if (objectRequest.responseStatusCode == 201) {
1543         NSLog(@"Object moved: %@", [objectRequest url]);
1544         [self refresh:nil];
1545     } else {
1546         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1547     }
1548 }
1549
1550 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1551     NSLog(@"Move of object failed");
1552     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1553 }
1554
1555
1556 @end