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