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