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