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