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