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