fac1f5d97e8edde5c7590e6aac032c59a4d67e2d
[pithos-macos] / pithos-macos / PithosBrowserController.m
1 //
2 //  PithosBrowserController.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 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 "ASINetworkQueue.h"
49 #import "ASIPithosRequest.h"
50 #import "ASIPithos.h"
51 #import "ASIPithosContainerRequest.h"
52 #import "ASIPithosObjectRequest.h"
53 #import "ASIPithosAccount.h"
54 #import "ASIPithosContainer.h"
55 #import "ASIPithosObject.h"
56 #import "PithosUtilities.h"
57 #import "UsingSizeTransformer.h"
58
59 #define REFRESH_TIMER_INTERVAL 5
60
61 @interface PithosBrowserCell : FileSystemBrowserCell {}
62 @end
63
64 @implementation PithosBrowserCell
65
66 - (id)init {
67     if ((self = [super init])) {
68         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
69         [self setEditable:YES];
70     }
71     return self;
72 }
73
74 - (void)setObjectValue:(id)object {
75     if ([object isKindOfClass:[PithosNode class]]) {
76         PithosNode *node = (PithosNode *)object;
77         [self setStringValue:node.displayName];
78         [self setImage:node.icon];
79     } else {
80         [super setObjectValue:object];
81     }
82 }
83
84 @end
85
86 @interface PithosOutlineViewCell : ImageAndTextCell {}
87 @end
88
89 @implementation PithosOutlineViewCell
90
91 - (void)setObjectValue:(id)object {
92     if ([object isKindOfClass:[PithosNode class]]) {
93         PithosNode *node = (PithosNode *)object;
94         [self setStringValue:node.displayName];
95         [self setImage:node.icon];
96         [self setEditable:NO];
97     } else {
98         [super setObjectValue:object];
99     }
100 }
101
102 @end
103
104 @interface PithosBrowserController (Private)
105 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
106 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
107 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
108 @end
109
110 @implementation PithosBrowserController
111 @synthesize pithos;
112 @synthesize accountNode;
113 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
114 @synthesize draggedNodes, draggedParentNode;
115 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
116 @synthesize activityTextField, activityProgressIndicator;
117
118 #pragma mark -
119 #pragma Object Lifecycle
120
121 - (id)init {
122     return [super initWithWindowNibName:@"PithosBrowserController"];
123 }
124
125 - (void)windowDidLoad {
126     [super windowDidLoad];
127     if (browser && !browserInitialized) {
128         browserInitialized = YES;
129         [self initBrowser];
130     }
131 }
132
133 - (void)initBrowser {
134     [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
135     [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
136     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
137     
138     [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
139     [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
140     [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
141     
142     [browser setCellClass:[PithosBrowserCell class]];
143     [browser setAllowsBranchSelection:YES];
144     [browser setAllowsMultipleSelection:YES];
145     [browser setAllowsEmptySelection:YES];
146     [browser setAllowsTypeSelect:YES];
147     
148     moveNetworkQueue = [[ASINetworkQueue alloc] init];
149     moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
150 //    moveNetworkQueue.maxConcurrentOperationCount = 1;
151     copyNetworkQueue = [[ASINetworkQueue alloc] init];
152     copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
153 //    copyNetworkQueue.maxConcurrentOperationCount = 1;
154     deleteNetworkQueue = [[ASINetworkQueue alloc] init];
155     deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
156 //    deleteNetworkQueue.maxConcurrentOperationCount = 1;
157     uploadNetworkQueue = [[ASINetworkQueue alloc] init];
158     uploadNetworkQueue.showAccurateProgress = YES;
159     uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
160 //    uploadNetworkQueue.maxConcurrentOperationCount = 1;
161     downloadNetworkQueue = [[ASINetworkQueue alloc] init];
162     downloadNetworkQueue.showAccurateProgress = YES;
163     downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
164 //    downloadNetworkQueue.maxConcurrentOperationCount = 1;
165     
166     moveQueue = [[NSOperationQueue alloc] init];
167     [moveQueue setSuspended:YES];
168     moveQueue.name = @"gr.grnet.pithos.MoveQueue";
169 //    moveQueue.maxConcurrentOperationCount = 1;
170     copyQueue = [[NSOperationQueue alloc] init];
171     [copyQueue setSuspended:YES];
172     copyQueue.name = @"gr.grnet.pithos.CopyQueue";
173 //    copyQueue.maxConcurrentOperationCount = 1;
174     deleteQueue = [[NSOperationQueue alloc] init];
175     [deleteQueue setSuspended:YES];
176     deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
177 //    deleteQueue.maxConcurrentOperationCount = 1;
178     uploadQueue = [[NSOperationQueue alloc] init];
179     [uploadQueue setSuspended:YES];
180     uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
181 //    uploadQueue.maxConcurrentOperationCount = 1;
182     downloadQueue = [[NSOperationQueue alloc] init];
183     [downloadQueue setSuspended:YES];
184     downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
185 //    downloadQueue.maxConcurrentOperationCount = 1;
186     
187     moveCallbackQueue = [[NSOperationQueue alloc] init];
188     [moveCallbackQueue setSuspended:YES];
189     moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
190 //    moveCallbackQueue.maxConcurrentOperationCount = 1;
191     copyCallbackQueue = [[NSOperationQueue alloc] init];
192     [copyCallbackQueue setSuspended:YES];
193     copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
194 //    copyCallbackQueue.maxConcurrentOperationCount = 1;
195     deleteCallbackQueue = [[NSOperationQueue alloc] init];
196     [deleteCallbackQueue setSuspended:YES];
197     deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
198 //    deleteCallbackQueue.maxConcurrentOperationCount = 1;
199     uploadCallbackQueue = [[NSOperationQueue alloc] init];
200     [uploadCallbackQueue setSuspended:YES];
201     uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
202 //    uploadCallbackQueue.maxConcurrentOperationCount = 1;
203     downloadCallbackQueue = [[NSOperationQueue alloc] init];
204     [downloadCallbackQueue setSuspended:YES];
205     downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
206 //    downloadCallbackQueue.maxConcurrentOperationCount = 1;
207     
208     [activityProgressIndicator setUsesThreadedAnimation:YES];
209     [activityProgressIndicator setMinValue:0.0];
210     [activityProgressIndicator setMaxValue:1.0];
211     activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
212     
213     self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
214     containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
215     containersNodeChildren = [[NSMutableArray alloc] init];
216     sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
217     mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
218     mySharedNode.displayName = @"shared by me";
219     mySharedNode.shared = YES;
220     mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
221     othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
222     othersSharedNode.displayName = @"shared to me";
223     othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
224     
225     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
226     
227     // Register for updates
228     // PithosAccountNode accountNode updates outlineView container nodes 
229     [[NSNotificationCenter defaultCenter] addObserver:self 
230                                              selector:@selector(pithosAccountNodeChildrenUpdated:) 
231                                                  name:@"PithosNodeChildrenUpdated" 
232                                                object:accountNode];
233     // PithosNode updates browser nodes
234     [[NSNotificationCenter defaultCenter] addObserver:self 
235                                              selector:@selector(pithosNodeChildrenUpdated:) 
236                                                  name:@"PithosNodeChildrenUpdated" 
237                                                object:nil];
238     // Request for browser refresh 
239     [[NSNotificationCenter defaultCenter] addObserver:self 
240                                              selector:@selector(pithosBrowserRefreshNeeded:) 
241                                                  name:@"PithosBrowserRefreshNeeeded" 
242                                                object:nil];
243 }
244
245 - (void)resetBrowser {
246     @synchronized(self) {
247         if (!browserActive)
248             return;
249     }
250
251     [refreshTimer invalidate];
252     [refreshTimer release];
253     
254     [moveNetworkQueue reset];
255     [copyNetworkQueue reset];
256     [deleteNetworkQueue reset];
257     [uploadNetworkQueue reset];
258     [downloadNetworkQueue reset];
259     
260     [moveQueue cancelAllOperations];
261     [moveQueue setSuspended:YES];
262     [copyQueue cancelAllOperations];
263     [copyQueue setSuspended:YES];
264     [deleteQueue cancelAllOperations];
265     [deleteQueue setSuspended:YES];
266     [uploadQueue cancelAllOperations];
267     [uploadQueue setSuspended:YES];
268     [downloadQueue cancelAllOperations];
269     [downloadQueue setSuspended:YES];
270
271     [moveCallbackQueue cancelAllOperations];
272     [moveCallbackQueue setSuspended:YES];
273     [copyCallbackQueue cancelAllOperations];
274     [copyCallbackQueue setSuspended:YES];
275     [deleteCallbackQueue cancelAllOperations];
276     [deleteCallbackQueue setSuspended:YES];
277     [uploadCallbackQueue cancelAllOperations];
278     [uploadCallbackQueue setSuspended:YES];
279     [downloadCallbackQueue cancelAllOperations];
280     [downloadCallbackQueue setSuspended:YES];
281
282     rootNode = nil;
283     [browser loadColumnZero];
284     [containersNodeChildren removeAllObjects];
285     [outlineView reloadData];
286     // Expand the folder outline view
287     [outlineView expandItem:nil expandChildren:YES];
288     [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
289     
290     activityFacility.delegate = nil;
291     [activityProgressIndicator setDoubleValue:1.0];
292     [activityProgressIndicator stopAnimation:self];
293     
294     @synchronized(self) {
295         browserActive = NO;
296     }
297 }
298
299 - (void)startBrowser {
300     @synchronized(self) {
301         if (browserActive)
302             return;
303     }
304     
305     // In the improbable case of leftover operations
306     [moveNetworkQueue reset];
307     [copyNetworkQueue reset];
308     [deleteNetworkQueue reset];
309     [uploadNetworkQueue reset];
310     [downloadNetworkQueue reset];
311     [moveQueue cancelAllOperations];
312     [copyQueue cancelAllOperations];
313     [deleteQueue cancelAllOperations];
314     [uploadQueue cancelAllOperations];
315     [downloadQueue cancelAllOperations];
316     [moveCallbackQueue cancelAllOperations];
317     [copyCallbackQueue cancelAllOperations];
318     [deleteCallbackQueue cancelAllOperations];
319     [uploadCallbackQueue cancelAllOperations];
320     [downloadCallbackQueue cancelAllOperations];
321
322     [moveNetworkQueue go];
323     [copyNetworkQueue go];
324     [deleteNetworkQueue go];
325     [uploadNetworkQueue go];
326     [downloadNetworkQueue go];
327     [moveQueue setSuspended:NO];
328     [copyQueue setSuspended:NO];
329     [deleteQueue setSuspended:NO];
330     [uploadQueue setSuspended:NO];
331     [downloadQueue setSuspended:NO];
332     [moveCallbackQueue setSuspended:NO];
333     [copyCallbackQueue setSuspended:NO];
334     [deleteCallbackQueue setSuspended:NO];
335     [uploadCallbackQueue setSuspended:NO];
336     [downloadCallbackQueue setSuspended:NO];
337
338     accountNode.pithos = pithos;
339     [accountNode forceRefresh];
340     mySharedNode.pithos = pithos;
341     [mySharedNode forceRefresh];
342     othersSharedNode.pithos = pithos;
343     [othersSharedNode forceRefresh];
344             
345 //    [activityFacility reset];
346     activityFacility.delegate = self;
347             
348     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL 
349                                                      target:self 
350                                                    selector:@selector(forceRefresh:) 
351                                                    userInfo:self 
352                                                     repeats:YES] retain];
353     @synchronized(self) {
354         browserActive = YES;
355     }
356 }
357
358 - (BOOL)operationsPending {
359     return ([moveNetworkQueue operationCount] || 
360             [copyNetworkQueue operationCount] || 
361             [deleteNetworkQueue operationCount] || 
362             [uploadNetworkQueue operationCount] || 
363             [downloadNetworkQueue operationCount] || 
364             [moveQueue operationCount] || 
365             [copyQueue operationCount] || 
366             [deleteQueue operationCount] || 
367             [uploadQueue operationCount] || 
368             [downloadQueue operationCount] || 
369             [moveCallbackQueue operationCount] || 
370             [copyCallbackQueue operationCount] || 
371             [deleteCallbackQueue operationCount] || 
372             [uploadCallbackQueue operationCount] || 
373             [downloadCallbackQueue operationCount]);
374 }
375
376 - (void)dealloc {
377     [[NSNotificationCenter defaultCenter] removeObserver:self];
378     [self resetBrowser];
379     [moveQueue release];
380     [copyQueue release];
381     [deleteQueue release];
382     [uploadQueue release];
383     [downloadQueue release];
384     [moveNetworkQueue release];
385     [copyNetworkQueue release];
386     [deleteNetworkQueue release];
387     [uploadNetworkQueue release];
388     [downloadNetworkQueue release];
389     [clipboardParentNode release];
390     [clipboardNodes release];
391     [draggedParentNode release];
392     [draggedNodes release];
393     [sharedPreviewController release];
394     [othersSharedNode release];
395     [mySharedNode release];
396     [sharedNode release];
397     [containersNodeChildren release];
398     [containersNode release];
399     [accountNode release];
400     [rootNode release];
401     [pithos release];
402     [super dealloc];
403 }
404
405 - (void)setPithos:(ASIPithos *)aPithos {
406     if (aPithos) {
407         if (![aPithos.authUser isEqualToString:pithos.authUser] || 
408             ![aPithos.authToken isEqualToString:pithos.authToken] || 
409             ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
410             ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
411             [self resetBrowser];
412             [pithos release];
413             pithos = [aPithos retain];
414             [self startBrowser];
415         } else {
416             [self startBrowser];
417         }
418     }
419 }
420
421
422 #pragma mark -
423 #pragma mark Observers
424
425 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
426     PithosNode *node = (PithosNode *)[notification object];
427     if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
428         return;
429     NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
430     NSInteger lastColumn = [browser lastColumn];
431     for (NSInteger column = lastColumn; column >= 0; column--) {
432         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
433             [browser reloadColumn:column];
434             return;
435         }
436     }
437 }
438
439 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
440     BOOL containerPithosFound = NO;
441     BOOL containerTrashFound = NO;
442     NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
443     for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
444         if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
445             [removedContainersNodeChildren addIndex:i];
446     }
447     [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
448     for (PithosContainerNode *containerNode in accountNode.children) {
449         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
450             if (![containersNodeChildren containsObject:containerNode])
451                 [containersNodeChildren insertObject:containerNode atIndex:0];
452             containerPithosFound = YES;
453         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
454             NSUInteger insertIndex = 1;
455             if (!containerPithosFound)
456                 insertIndex = 0;
457             if (![containersNodeChildren containsObject:containerNode])
458                 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
459             containerTrashFound = YES;
460         } else if (![containersNodeChildren containsObject:containerNode]) {
461             [containersNodeChildren addObject:containerNode];
462         }
463     }
464     BOOL refreshAccountNode = NO;
465     if (!containerPithosFound) {
466         // Create pithos node
467         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
468                                                                                                             containerName:@"pithos"];
469         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
470         [networkQueue go];
471         [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
472         if ([containerRequest error]) {
473             dispatch_async(dispatch_get_main_queue(), ^{
474                 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
475             });
476         } else {
477             refreshAccountNode = YES;
478         }
479     }
480     if (!containerTrashFound) {
481         // Create trash node
482         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
483                                                                                                             containerName:@"trash"];
484         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
485         [networkQueue go];
486         [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
487         if ([containerRequest error]) {
488             dispatch_async(dispatch_get_main_queue(), ^{
489                 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
490             });
491         } else {
492             refreshAccountNode = YES;
493         }
494     }
495     
496     if (refreshAccountNode)
497         [accountNode refresh];
498     
499     [outlineView reloadData];
500     
501     // Expand the folder outline view
502     [outlineView expandItem:nil expandChildren:YES];
503     
504     if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
505         rootNode = [containersNodeChildren objectAtIndex:0];
506         [browser loadColumnZero];
507     }
508     
509     if (notification)
510         [self refresh:nil];
511 }
512
513 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
514     [self refresh:nil];
515 }
516
517 #pragma mark -
518 #pragma mark Actions
519
520 - (IBAction)forceRefresh:(id)sender {
521     if (editingItem)
522         return;
523     if (sender)
524         [accountNode forceRefresh];
525     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
526         PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
527         node.forcedRefresh = YES;
528         [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
529     }
530     [browser validateVisibleColumns];
531 }
532
533 - (IBAction)refresh:(id)sender {
534     if (editingItem)
535         return;
536     if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
537         [self forceRefresh:sender];
538     } else {
539         if (sender)
540             [accountNode refresh];
541         for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
542             [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
543         }
544         [browser validateVisibleColumns];
545     }
546 }
547
548 #pragma mark -
549 #pragma mark NSBrowserDelegate
550
551 - (id)rootItemForBrowser:(NSBrowser *)browser {
552     return rootNode;    
553 }
554
555 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
556     PithosNode *node = (PithosNode *)item;
557     return node.children.count;
558 }
559
560 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
561     PithosNode *node = (PithosNode *)item;
562     return [node.children objectAtIndex:index];
563 }
564
565 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
566     PithosNode *node = (PithosNode *)item;
567     return node.isLeafItem;
568 }
569
570 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
571     PithosNode *node = (PithosNode *)item;
572     return node;
573 }
574
575 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
576     if (sharedPreviewController == nil)
577         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
578     return sharedPreviewController;
579 }
580
581 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
582 //    if (!forUserResize) {
583 //        id item = [browser parentForItemsInColumn:columnIndex]; 
584 //        if ([self browser:browser isLeafItem:item]) {
585 //            suggestedWidth = 200; 
586 //        }
587 //    }
588 //    return suggestedWidth;
589 //}
590
591 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
592     return NO;
593 }
594
595 #pragma mark Editing
596
597 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
598     PithosNode *node = (PithosNode *)item;
599     if (node.shared || node.sharingAccount || 
600         ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
601         return NO;
602     editingItem = YES;
603     return YES;
604 }
605
606 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
607     editingItem = NO;
608     PithosNode *node = (PithosNode *)item;
609     NSString *newName = (NSString *)object;
610     NSUInteger newNameLength = [newName length];
611     NSRange firstSlashRange = [newName rangeOfString:@"/"];
612     if ((newNameLength == 0) || 
613         ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
614         ([newName isEqualToString:node.displayName])) {
615         return;
616     }
617     if (([node class] == [PithosObjectNode class]) || 
618         (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
619         // Operation: Rename (move) an object or subdir/ node
620         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
621             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
622             if (operation.isCancelled) {
623                 [pool drain];
624                 return;
625             }
626             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
627             if ([newName hasSuffix:@"/"])
628                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
629             NSError *error = nil;
630             BOOL isDirectory;
631             if ([PithosUtilities objectExistsAtPithos:pithos 
632                                         containerName:node.pithosContainer.name 
633                                            objectName:destinationObjectName 
634                                                 error:&error 
635                                           isDirectory:&isDirectory 
636                                        sharingAccount:nil]) {
637                 dispatch_async(dispatch_get_main_queue(), ^{
638                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
639                     [alert setMessageText:@"Name Taken"];
640                     [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
641                     [alert addButtonWithTitle:@"OK"];
642                     [alert runModal];
643                 });
644                 [pool drain];
645                 return;
646             } else if (error) {
647                 [pool drain];
648                 return;
649             }
650             if (operation.isCancelled) {
651                 [pool drain];
652                 return;
653             }
654             ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
655                                                                                    containerName:node.pithosContainer.name 
656                                                                                       objectName:node.pithosObject.name 
657                                                                         destinationContainerName:node.pithosContainer.name 
658                                                                            destinationObjectName:destinationObjectName 
659                                                                                    checkIfExists:NO];
660             if (!operation.isCancelled && objectRequest) {
661                 objectRequest.delegate = self;
662                 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
663                 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
664                 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
665                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
666                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
667                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
668                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
669                 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
670                                                                            message:messagePrefix];
671                 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
672                  [NSDictionary dictionaryWithObjectsAndKeys:
673                   [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
674                   [NSNumber numberWithBool:YES], @"refresh", 
675                   activity, @"activity", 
676                   [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
677                   [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
678                   [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
679                   [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
680                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
681                   NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
682                   NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
683                   moveNetworkQueue, @"networkQueue", 
684                   @"move", @"operationType", 
685                   nil]];
686                 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
687             }
688             [pool drain];
689         }];
690         [moveQueue addOperation:operation];
691     } else if ([node class] == [PithosSubdirNode class]) {
692         if (firstSlashRange.length == 1)
693             return;
694         // Operation: Rename (move) a subdir node and its descendants
695         // The resulting ASIPithosObjectRequests are chained through dependencies
696         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
697             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
698             if (operation.isCancelled) {
699                 [pool drain];
700                 return;
701             }
702             NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
703             NSError *error = nil;
704             BOOL isDirectory;
705             if ([PithosUtilities objectExistsAtPithos:pithos 
706                                         containerName:node.pithosContainer.name 
707                                            objectName:destinationObjectName 
708                                                 error:&error 
709                                           isDirectory:&isDirectory 
710                                        sharingAccount:nil]) {
711                 dispatch_async(dispatch_get_main_queue(), ^{
712                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
713                     [alert setMessageText:@"Name Taken"];
714                     [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
715                     [alert addButtonWithTitle:@"OK"];
716                     [alert runModal];
717                 });
718                 [pool drain];
719                 return;
720             } else if (error) {
721                 [pool drain];
722                 return;
723             }
724             if (operation.isCancelled) {
725                 [pool drain];
726                 return;
727             }
728             if (node.pithosObject.subdir)
729                 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
730             NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
731                                                                                containerName:node.pithosContainer.name 
732                                                                                   objectName:node.pithosObject.name 
733                                                                     destinationContainerName:node.pithosContainer.name 
734                                                                        destinationObjectName:destinationObjectName 
735                                                                                checkIfExists:NO];
736             if (!operation.isCancelled && objectRequests) {
737                 ASIPithosObjectRequest *previousObjectRequest = nil;
738                 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
739                     if (operation.isCancelled) {
740                         [pool drain];
741                         return;
742                     }
743                     objectRequest.delegate = self;
744                     objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
745                     objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
746                     NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
747                                                [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
748                                                [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
749                                                [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
750                                                [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
751                     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
752                                                                                message:messagePrefix];
753                     [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
754                      [NSDictionary dictionaryWithObjectsAndKeys:
755                       [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
756                       [NSNumber numberWithBool:YES], @"refresh", 
757                       activity, @"activity", 
758                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
759                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
760                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
761                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
762                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
763                       NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
764                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
765                       moveNetworkQueue, @"networkQueue", 
766                       @"move", @"operationType", 
767                       nil]];
768                     if (previousObjectRequest)
769                         [objectRequest addDependency:previousObjectRequest];
770                     previousObjectRequest = objectRequest;
771                     [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
772                 }
773             }
774             [pool drain];
775         }];
776         [moveQueue addOperation:operation];
777     }
778 }
779
780 #pragma mark Drag and Drop source
781
782 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
783       withEvent:(NSEvent *)event {
784     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
785     __block BOOL result = YES;
786     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
787         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
788         if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
789             result = NO;
790             *stop = YES;
791         }
792     }];
793     return result;
794 }
795
796 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
797    toPasteboard:(NSPasteboard *)pasteboard {
798     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
799     NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
800     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
801     [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
802         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
803         [propertyList addObject:[node.pithosObject.name pathExtension]];
804         [nodes addObject:node];
805     }];
806
807     [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
808     [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
809     self.draggedNodes = nodes;
810     self.draggedParentNode = [browser parentForItemsInColumn:column];
811     return YES;
812 }
813
814 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
815 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
816     NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
817     for (PithosNode *node in draggedNodes) {
818         [names addObject:node.displayName];
819         // If the node is a subdir ask if the whole tree should be downloaded
820         if ([node class] == [PithosSubdirNode class]) {
821             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
822             [alert setMessageText:@"Download directory"];
823             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
824             [alert addButtonWithTitle:@"OK"];
825             [alert addButtonWithTitle:@"Cancel"];
826             NSInteger choice = [alert runModal];
827             if (choice == NSAlertFirstButtonReturn)
828                 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
829         } else {
830             [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
831         }
832     }
833     return names;
834 }
835
836 #pragma mark Drag and Drop destination
837
838 - (NSDragOperation)browser:aBrowser 
839               validateDrop:(id<NSDraggingInfo>)info 
840                proposedRow:(NSInteger *)row 
841                     column:(NSInteger *)column 
842              dropOperation:(NSBrowserDropOperation *)dropOperation {
843     NSDragOperation result = NSDragOperationNone;
844     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
845         // For a drop above, the drop is redirected to the parent item
846         if (*dropOperation == NSBrowserDropAbove)
847             *row = -1;
848         // Only allow dropping in folders
849         if (*column != -1) {
850             PithosNode *dropNode;
851             if (*row != -1) {
852                 // Check if the node is not a folder and if so redirect to the parent item
853                 dropNode = [browser itemAtRow:*row inColumn:*column];
854                 if ([dropNode class] == [PithosObjectNode class])
855                     *row = -1;
856             }
857             if (*row == -1)
858                 dropNode = [browser parentForItemsInColumn:*column];
859             
860             if (!dropNode.shared && 
861                 (!dropNode.sharingAccount || 
862                  ([dropNode class] == [PithosSubdirNode class]) || 
863                  ([dropNode class] == [PithosContainerNode class]))) {
864                 *dropOperation = NSBrowserDropOn;
865                 result = NSDragOperationCopy;
866             }
867         }
868     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
869         // For a drop above, the drop is redirected to the parent item
870         if (*dropOperation == NSBrowserDropAbove) 
871             *row = -1;
872         // Only allow dropping in folders
873         if (*column != -1) {
874             PithosNode *dropNode;
875             if (*row != -1) {
876                 // Check if the node is not a folder and if so redirect to the parent item
877                 dropNode = [browser itemAtRow:*row inColumn:*column];
878                 if ([dropNode class] == [PithosObjectNode class])
879                     *row = -1;
880             }
881             if (*row == -1)
882                 dropNode = [browser parentForItemsInColumn:*column];
883             
884             if (!dropNode.shared && !dropNode.sharingAccount) {
885                 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
886                     // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
887                     if ((([dropNode class] == [PithosContainerNode class]) || 
888                          dropNode.pithosObject.subdir || 
889                          ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
890                         ![dropNode isEqualTo:draggedParentNode]) { 
891     //                    ![dropNode isEqualTo:draggedParentNode] && 
892     //                    ![draggedNodes containsObject:dropNode]) {                
893                         result = NSDragOperationMove;
894                     }
895                 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
896                     // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
897                     if (([dropNode class] == [PithosContainerNode class]) || 
898                         dropNode.pithosObject.subdir || 
899                         ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
900                         result = NSDragOperationCopy;
901                     }
902                 }
903             }
904         }
905     }
906     return result;
907 }
908
909 - (BOOL)browser:(NSBrowser *)aBrowser 
910      acceptDrop:(id<NSDraggingInfo>)info 
911           atRow:(NSInteger)row 
912          column:(NSInteger)column 
913   dropOperation:(NSBrowserDropOperation)dropOperation {
914     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
915         NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
916         NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
917         if ((column != -1) && (filenames != nil)) {
918             PithosNode *node;
919             if (row != -1)
920                 node = [browser itemAtRow:row inColumn:column];
921             else
922                 node = [browser parentForItemsInColumn:column];
923             NSLog(@"drag in node: %@", node.url);
924             return [self uploadFiles:filenames toNode:node];
925         }
926     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
927         NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
928         if ((column != -1) && (draggedNodes != nil)) {
929             PithosNode *node;
930             if (row != -1)
931                 node = [browser itemAtRow:row inColumn:column];
932             else
933                 node = [browser parentForItemsInColumn:column];
934             NSLog(@"drag local node: %@", node.url);
935             if ([info draggingSourceOperationMask] & NSDragOperationMove)
936                 return [self moveNodes:draggedNodes toNode:node];
937             else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
938                 return [self copyNodes:draggedNodes toNode:node];
939         }
940     }
941     return NO;
942 }
943
944 #pragma mark -
945 #pragma mark Drag and Drop methods
946
947 - (void)downloadNode:(PithosNode *)node 
948          toDirectory:(NSString *)dirPath 
949      withNewFileName:(NSString *)newFileName 
950              version:(NSString *)version {
951     if ([node class] == [PithosSubdirNode class]) {
952         // XXX newFilename and version are ignored in the case of a subdir node for now
953         // Operation: Download a subdir node and its descendants
954         // The resulting ASIPithosObjectRequests are chained through dependencies
955         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
956             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
957             if (operation.isCancelled) {
958                 [pool drain];
959                 return;
960             }
961             NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos 
962                                                                                containerName:node.pithosContainer.name 
963                                                                                   objectName:node.pithosObject.name 
964                                                                                  toDirectory:dirPath 
965                                                                                checkIfExists:YES 
966                                                                               sharingAccount:node.sharingAccount];
967             if (!operation.isCancelled && objectRequests) {
968                 ASIPithosObjectRequest *previousObjectRequest = nil;
969                 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
970                     if (operation.isCancelled) {
971                         [pool drain];
972                         return;
973                     }
974                     objectRequest.delegate = self;
975                     objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
976                     objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
977                     NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
978                     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
979                                                                                message:[messagePrefix stringByAppendingString:@" (0%)"]
980                                                                             totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
981                                                                           currentBytes:0];
982                     dispatch_async(dispatch_get_main_queue(), ^{
983                         [activityFacility updateActivity:activity withMessage:activity.message];  
984                     });
985                     [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
986                      [NSDictionary dictionaryWithObjectsAndKeys:
987                       activity, @"activity", 
988                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
989                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
990                       [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
991                       [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
992                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
993                       NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
994                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
995                       downloadNetworkQueue, @"networkQueue", 
996                       @"download", @"operationType", 
997                       nil]];
998                     [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
999                         [activityFacility updateActivity:activity 
1000                                              withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1001                                               totalBytes:activity.totalBytes 
1002                                             currentBytes:(activity.currentBytes + size)];
1003                     }];
1004                     if (previousObjectRequest)
1005                         [objectRequest addDependency:previousObjectRequest];
1006                     previousObjectRequest = objectRequest;
1007                     [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1008                 }
1009             }
1010             [pool drain];
1011         }];
1012         [downloadQueue addOperation:operation];
1013     } else if ([node class] == [PithosObjectNode class]) {
1014         // Operation: Download an object node
1015         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1016             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1017             if (operation.isCancelled) {
1018                 [pool drain];
1019                 return;
1020             }
1021             __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos 
1022                                                                                            containerName:node.pithosContainer.name 
1023                                                                                               objectName:node.pithosObject.name 
1024                                                                                                  version:version 
1025                                                                                             toDirectory:dirPath 
1026                                                                                          withNewFileName:newFileName 
1027                                                                                            checkIfExists:(version ? NO : YES) 
1028                                                                                           sharingAccount:node.sharingAccount];
1029             if (!operation.isCancelled && objectRequest) {
1030                 objectRequest.delegate = self;
1031                 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1032                 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1033                 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1034                 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
1035                                                                            message:[messagePrefix stringByAppendingString:@" (0%)"]
1036                                                                         totalBytes:node.pithosObject.bytes 
1037                                                                       currentBytes:0];
1038                 dispatch_async(dispatch_get_main_queue(), ^{
1039                     [activityFacility updateActivity:activity withMessage:activity.message];  
1040                 });
1041                 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1042                  [NSDictionary dictionaryWithObjectsAndKeys:
1043                   activity, @"activity", 
1044                   [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1045                   [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1046                   [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1047                   [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1048                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
1049                   NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
1050                   NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1051                   downloadNetworkQueue, @"networkQueue", 
1052                   @"download", @"operationType", 
1053                   nil]];
1054                 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1055                     [activityFacility updateActivity:activity 
1056                                          withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1057                                           totalBytes:activity.totalBytes 
1058                                         currentBytes:(activity.currentBytes + size)];
1059                 }];
1060                 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1061                 [pool drain];
1062             }
1063         }];
1064         [downloadQueue addOperation:operation];
1065     }
1066 }
1067
1068 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1069     if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1070         return NO;
1071     NSFileManager *fileManager = [NSFileManager defaultManager];
1072     NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1073     NSString *objectNamePrefix;
1074     if ([destinationNode class] == [PithosSubdirNode class])
1075         objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1076     else
1077         objectNamePrefix = [NSString string];
1078     if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1079         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
1080                                                                                                       containerName:containerName];
1081         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1082         [networkQueue go];
1083         [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1084         if ([containerRequest error]) {
1085             [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1086             return NO;
1087         } else if (containerRequest.responseStatusCode != 204) {
1088             [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1089             return NO;
1090         }
1091         destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1092         destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1093     }    
1094     NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1095     NSString *blockHash = destinationNode.pithosContainer.blockHash;
1096     
1097     for (NSString *filePath in filenames) {
1098         BOOL isDirectory;
1099         if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1100             if (!isDirectory) {
1101                 // Upload file
1102                 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1103                 // Operation: Upload a local file
1104                 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1105                     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1106                     if (operation.isCancelled) {
1107                         [pool drain];
1108                         return;
1109                     }
1110                     NSError *error = nil;
1111                     NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1112                     if (contentType == nil)
1113                         contentType = @"application/octet-stream";
1114                     if (error)
1115                         NSLog(@"contentType detection error: %@", error);
1116                     NSArray *hashes = nil;
1117                     if (operation.isCancelled) {
1118                         [pool drain];
1119                         return;
1120                     }
1121                     ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1122                                                                                                 containerName:containerName 
1123                                                                                                    objectName:objectName 
1124                                                                                                   contentType:contentType 
1125                                                                                                     blockSize:blockSize 
1126                                                                                                     blockHash:blockHash 
1127                                                                                                       forFile:filePath 
1128                                                                                                 checkIfExists:YES 
1129                                                                                                        hashes:&hashes 
1130                                                                                                sharingAccount:destinationNode.sharingAccount];
1131                     if (!operation.isCancelled && objectRequest) {
1132                         objectRequest.delegate = self;
1133                         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1134                         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1135                         NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1136                         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1137                                                                                    message:[messagePrefix stringByAppendingString:@" (0%)"]
1138                                                                                 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1139                                                                               currentBytes:0];
1140                         [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1141                          [NSDictionary dictionaryWithObjectsAndKeys:
1142                           containerName, @"containerName", 
1143                           objectName, @"objectName", 
1144                           contentType, @"contentType", 
1145                           [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
1146                           blockHash, @"blockHash", 
1147                           filePath, @"filePath", 
1148                           hashes, @"hashes", 
1149                           [NSArray arrayWithObject:destinationNode], @"refreshNodes", 
1150                           [NSNumber numberWithBool:YES], @"refresh", 
1151                           [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1152                           activity, @"activity", 
1153                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1154                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1155                           [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1156                           [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1157                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
1158                           NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1159                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1160                           uploadNetworkQueue, @"networkQueue", 
1161                           @"upload", @"operationType", 
1162                           nil]];
1163                         if (destinationNode.sharingAccount)
1164                             [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1165                         [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1166                     }
1167                     [pool drain];
1168                 }];
1169                 [uploadQueue addOperation:operation];
1170             } else {
1171                 // Upload directory, confirm first
1172                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1173                 [alert setMessageText:@"Upload directory"];
1174                 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1175                 [alert addButtonWithTitle:@"OK"];
1176                 [alert addButtonWithTitle:@"Cancel"];
1177                 NSInteger choice = [alert runModal];
1178                 if (choice == NSAlertFirstButtonReturn) {
1179                     NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1180                     // Operation: Upload a local directory and its descendants
1181                     // The resulting ASIPithosObjectRequests are chained through dependencies
1182                     __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1183                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1184                         if (operation.isCancelled) {
1185                             [pool drain];
1186                             return;
1187                         }
1188                         NSMutableArray *objectNames = nil;
1189                         NSMutableArray *contentTypes = nil;
1190                         NSMutableArray *filePaths = nil;
1191                         NSMutableArray *hashesArrays = nil;
1192                         NSMutableArray *directoryObjectRequests = nil;
1193                         NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos 
1194                                                                                        containerName:containerName 
1195                                                                                           objectName:objectName 
1196                                                                                            blockSize:blockSize 
1197                                                                                            blockHash:blockHash 
1198                                                                                         forDirectory:filePath 
1199                                                                                        checkIfExists:YES 
1200                                                                                          objectNames:&objectNames
1201                                                                                         contentTypes:&contentTypes
1202                                                                                            filePaths:&filePaths
1203                                                                                         hashesArrays:&hashesArrays 
1204                                                                              directoryObjectRequests:&directoryObjectRequests 
1205                                                                                       sharingAccount:destinationNode.sharingAccount];
1206                         if (operation.isCancelled) {
1207                             [pool drain];
1208                             return;
1209                         }
1210                         ASIPithosObjectRequest *previousObjectRequest = nil;
1211                         for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1212                             if (operation.isCancelled) {
1213                                 [pool drain];
1214                                 return;
1215                             }
1216                             objectRequest.delegate = self;
1217                             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1218                             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1219                             NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1220                             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1221                                                                                        message:messagePrefix];
1222                             [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1223                              [NSDictionary dictionaryWithObjectsAndKeys:
1224                               [NSNumber numberWithBool:YES], @"refresh", 
1225                               activity, @"activity", 
1226                               [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1227                               [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1228                               [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1229                               [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1230                               [NSNumber numberWithUnsignedInteger:10], @"retries", 
1231                               NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1232                               NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1233                               uploadNetworkQueue, @"networkQueue", 
1234                               @"upload", @"operationType", 
1235                               nil]];
1236                             if (previousObjectRequest)
1237                                 [objectRequest addDependency:previousObjectRequest];
1238                             previousObjectRequest = objectRequest;
1239                             [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1240                         }
1241                         if (!operation.isCancelled && objectRequests) {
1242                             for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1243                                 if (operation.isCancelled) {
1244                                     [pool drain];
1245                                     return;
1246                                 }
1247                                 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1248                                 objectRequest.delegate = self;
1249                                 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1250                                 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1251                                 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1252                                 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1253                                                                                            message:[messagePrefix stringByAppendingString:@" (0%)"]
1254                                                                                         totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1255                                                                                       currentBytes:0];
1256                                 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1257                                  [NSDictionary dictionaryWithObjectsAndKeys:
1258                                   containerName, @"containerName", 
1259                                   [objectNames objectAtIndex:i], @"objectName", 
1260                                   [contentTypes objectAtIndex:i], @"contentType", 
1261                                   [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
1262                                   blockHash, @"blockHash", 
1263                                   [filePaths objectAtIndex:i], @"filePath", 
1264                                   [hashesArrays objectAtIndex:i], @"hashes", 
1265                                   [NSNumber numberWithBool:YES], @"refresh", 
1266                                   [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1267                                   activity, @"activity", 
1268                                   [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1269                                   [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1270                                   [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1271                                   [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1272                                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
1273                                   NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1274                                   NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1275                                   uploadNetworkQueue, @"networkQueue", 
1276                                   @"upload", @"operationType", 
1277                                   nil]];
1278                                 if (destinationNode.sharingAccount)
1279                                     [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1280                                 if (previousObjectRequest)
1281                                     [objectRequest addDependency:previousObjectRequest];
1282                                 previousObjectRequest = objectRequest;
1283                                 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1284                             }
1285                         }
1286                         [pool drain];
1287                     }];
1288                     [uploadQueue addOperation:operation];
1289                 }
1290             }
1291         }
1292     }
1293     return YES;
1294 }
1295
1296 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1297     if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1298         (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1299         return NO;
1300     NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1301     NSString *objectNamePrefix;
1302     if ([destinationNode class] == [PithosSubdirNode class])
1303         objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1304     else
1305         objectNamePrefix = [NSString string];
1306
1307     for (PithosNode *node in nodes) {
1308         if (([node class] == [PithosObjectNode class]) || 
1309             (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1310             // Operation: Move an object or subdir/ node
1311             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1312                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1313                 if (operation.isCancelled) {
1314                     [pool drain];
1315                     return;
1316                 }
1317                 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1318                 if ([node.pithosObject.name hasSuffix:@"/"])
1319                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1320                 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1321                                                                                        containerName:node.pithosContainer.name 
1322                                                                                           objectName:node.pithosObject.name 
1323                                                                             destinationContainerName:containerName 
1324                                                                                destinationObjectName:destinationObjectName 
1325                                                                                        checkIfExists:YES];
1326                 if (!operation.isCancelled && objectRequest) {
1327                     objectRequest.delegate = self;
1328                     objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1329                     objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1330                     NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1331                                                [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1332                                                [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1333                                                [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1334                                                [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1335                     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1336                                                                                message:messagePrefix];
1337                     [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1338                      [NSDictionary dictionaryWithObjectsAndKeys:
1339                       [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1340                       activity, @"activity", 
1341                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1342                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1343                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1344                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1345                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
1346                       NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1347                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1348                       moveNetworkQueue, @"networkQueue", 
1349                       @"move", @"operationType", 
1350                       nil]];
1351                     [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1352                 }
1353                 [pool drain];
1354             }];
1355             [moveQueue addOperation:operation];
1356         } else if ([node class] == [PithosSubdirNode class]) {
1357             // Operation: Move a subdir node and its descendants
1358             // The resulting ASIPithosObjectRequests are chained through dependencies
1359             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1360                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1361                 if (operation.isCancelled) {
1362                     [pool drain];
1363                     return;
1364                 }
1365                 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1366                 if (node.pithosObject.subdir)
1367                     destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1368                 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
1369                                                                                    containerName:node.pithosContainer.name 
1370                                                                                       objectName:node.pithosObject.name 
1371                                                                         destinationContainerName:containerName 
1372                                                                            destinationObjectName:destinationObjectName 
1373                                                                                    checkIfExists:YES];
1374                 if (!operation.isCancelled && objectRequests) {
1375                     ASIPithosObjectRequest *previousObjectRequest = nil;
1376                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1377                         if (operation.isCancelled) {
1378                             [pool drain];
1379                             return;
1380                         }
1381                         objectRequest.delegate = self;
1382                         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1383                         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1384                         NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1385                                                    [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1386                                                    [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1387                                                    [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1388                                                    [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1389                         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1390                                                                                    message:messagePrefix];
1391                         [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1392                          [NSDictionary dictionaryWithObjectsAndKeys:
1393                           [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1394                           [NSNumber numberWithBool:YES], @"refresh", 
1395                           activity, @"activity", 
1396                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1397                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1398                           [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1399                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1400                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
1401                           NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1402                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1403                           moveNetworkQueue, @"networkQueue", 
1404                           @"move", @"operationType", 
1405                           nil]];
1406                         if (previousObjectRequest)
1407                             [objectRequest addDependency:previousObjectRequest];
1408                         previousObjectRequest = objectRequest;
1409                         [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1410                     }
1411                 }
1412                 [pool drain];
1413             }];
1414             [moveQueue addOperation:operation];
1415         }
1416     }
1417     return YES;
1418 }
1419
1420 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
1421     if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1422         (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1423         return NO;
1424     NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1425     NSString *objectNamePrefix;
1426     if ([destinationNode class] == [PithosSubdirNode class])
1427         objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1428     else
1429         objectNamePrefix = [NSString string];
1430     
1431     for (PithosNode *node in nodes) {
1432         if (([node class] == [PithosObjectNode class]) || 
1433             (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1434             // Operation: Copy an object or subdir/ node
1435             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1436                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1437                 if (operation.isCancelled) {
1438                     [pool drain];
1439                     return;
1440                 }
1441                 NSString *destinationObjectName;
1442                 if (![destinationNode isEqualTo:node.parent]) {
1443                     destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1444                     if ([node.pithosObject.name hasSuffix:@"/"])
1445                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1446                 } else {
1447                     destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
1448                                                                        containerName:containerName 
1449                                                                           objectName:node.pithosObject.name];
1450                 }
1451                 if (operation.isCancelled) {
1452                     [pool drain];
1453                     return;
1454                 }
1455                 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos 
1456                                                                                        containerName:node.pithosContainer.name 
1457                                                                                           objectName:node.pithosObject.name 
1458                                                                             destinationContainerName:containerName 
1459                                                                                destinationObjectName:destinationObjectName 
1460                                                                                        checkIfExists:YES 
1461                                                                                       sharingAccount:node.sharingAccount];
1462                 if (!operation.isCancelled && objectRequest) {
1463                     objectRequest.delegate = self;
1464                     objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1465                     objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1466                     NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1467                                                [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1468                                                [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1469                                                [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1470                                                [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1471                     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1472                                                                                message:messagePrefix];
1473                     [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1474                      [NSDictionary dictionaryWithObjectsAndKeys:
1475                       [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1476                       activity, @"activity", 
1477                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1478                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1479                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1480                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1481                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
1482                       NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1483                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1484                       copyNetworkQueue, @"networkQueue", 
1485                       @"copy", @"operationType", 
1486                       nil]];
1487                     [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1488                 }
1489                 [pool drain];
1490             }];
1491             [copyQueue addOperation:operation];
1492         } else if ([node class] == [PithosSubdirNode class]) {
1493             // Operation: Copy a subdir node and its descendants
1494             // The resulting ASIPithosObjectRequests are chained through dependencies
1495             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1496                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1497                 if (operation.isCancelled) {
1498                     [pool drain];
1499                     return;
1500                 }
1501                 NSString *destinationObjectName;
1502                 if (![destinationNode isEqualTo:node.parent]) {
1503                     destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1504                     if (node.pithosObject.subdir)
1505                         destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1506                 } else {
1507                     destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
1508                                                                        containerName:containerName 
1509                                                                           subdirName:node.pithosObject.name];
1510                 }
1511                 if (operation.isCancelled) {
1512                     [pool drain];
1513                     return;
1514                 }
1515                 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos 
1516                                                                                    containerName:node.pithosContainer.name 
1517                                                                                       objectName:node.pithosObject.name 
1518                                                                         destinationContainerName:containerName 
1519                                                                            destinationObjectName:destinationObjectName 
1520                                                                                    checkIfExists:YES 
1521                                                                                   sharingAccount:node.sharingAccount];
1522                 if (!operation.isCancelled && objectRequests) {
1523                     ASIPithosObjectRequest *previousObjectRequest = nil;
1524                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1525                         if (operation.isCancelled) {
1526                             [pool drain];
1527                             return;
1528                         }
1529                         objectRequest.delegate = self;
1530                         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1531                         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1532                         NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1533                                                    [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1534                                                    [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1535                                                    [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1536                                                    [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1537                         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1538                                                                                    message:messagePrefix];
1539                         [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1540                          [NSDictionary dictionaryWithObjectsAndKeys:
1541                           [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1542                           activity, @"activity", 
1543                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1544                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1545                           [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1546                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1547                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
1548                           NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1549                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1550                           copyNetworkQueue, @"networkQueue", 
1551                           @"copy", @"operationType", 
1552                           nil]];
1553                         if (previousObjectRequest)
1554                             [objectRequest addDependency:previousObjectRequest];
1555                         previousObjectRequest = objectRequest;
1556                         [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1557                     }
1558                 }
1559                 [pool drain];
1560             }];
1561             [copyQueue addOperation:operation];
1562         }
1563     }
1564     return YES;
1565 }
1566
1567 #pragma mark -
1568 #pragma mark ASIHTTPRequestDelegate
1569
1570 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1571     NSOperationQueue *callbackQueue;
1572     NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1573     if ([operationType isEqualToString:@"move"])
1574         callbackQueue = moveCallbackQueue;
1575     else if ([operationType isEqualToString:@"copy"])
1576         callbackQueue = copyCallbackQueue;
1577     else if ([operationType isEqualToString:@"delete"])
1578         callbackQueue = deleteCallbackQueue;
1579     else if ([operationType isEqualToString:@"upload"])
1580         callbackQueue = uploadCallbackQueue;
1581     else if ([operationType isEqualToString:@"download"])
1582         callbackQueue = downloadCallbackQueue;
1583     else {
1584         dispatch_async(dispatch_get_main_queue(), ^{
1585             [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1586                               withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1587         });
1588         return;
1589     }
1590     // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1591     NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1592                                                                              selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1593                                                                                object:request] autorelease];
1594     operation.completionBlock = ^{
1595         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1596         if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1597             dispatch_async(dispatch_get_main_queue(), ^{
1598                 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1599                                   withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1600             });
1601         }
1602         [pool drain];
1603     };
1604     [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1605     [callbackQueue addOperation:operation];
1606 }
1607
1608 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1609     if (request.isCancelled) {
1610         // Request has been cancelled 
1611         // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1612         [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1613                    withObject:request];
1614     } else {
1615         NSOperationQueue *callbackQueue;
1616         NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1617         if ([operationType isEqualToString:@"move"])
1618             callbackQueue = moveCallbackQueue;
1619         else if ([operationType isEqualToString:@"copy"])
1620             callbackQueue = copyCallbackQueue;
1621         else if ([operationType isEqualToString:@"delete"])
1622             callbackQueue = deleteCallbackQueue;
1623         else if ([operationType isEqualToString:@"upload"])
1624             callbackQueue = uploadCallbackQueue;
1625         else if ([operationType isEqualToString:@"download"])
1626             callbackQueue = downloadCallbackQueue;
1627         else {
1628             dispatch_async(dispatch_get_main_queue(), ^{
1629                 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1630                                   withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1631             });
1632             return;
1633         }
1634         // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1635         NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1636                                                                                  selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1637                                                                                    object:request] autorelease];
1638         operation.completionBlock = ^{
1639             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1640             if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1641                 dispatch_async(dispatch_get_main_queue(), ^{
1642                     [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1643                                       withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1644                 });
1645             }
1646             [pool drain];
1647         };
1648         [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1649         [callbackQueue addOperation:operation];
1650     }
1651 }
1652
1653 - (void)requestFailed:(ASIPithosRequest *)request {
1654     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1655     NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1656     NSLog(@"Request failed: %@", request.url);
1657     if (operation.isCancelled) {
1658         [pool drain];
1659         return;        
1660     }
1661     if (request.isCancelled) {
1662         dispatch_async(dispatch_get_main_queue(), ^{
1663             [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1664                               withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1665         });
1666         [pool drain];
1667         return;
1668     }
1669     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1670     if (retries > 0) {
1671         ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1672         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1673         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1674         [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1675     } else {
1676         dispatch_async(dispatch_get_main_queue(), ^{
1677             [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1678                               withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1679             if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1680                 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1681             else
1682                 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1683         });
1684     }
1685     [pool drain];
1686 }
1687
1688 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1689     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1690     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1691     NSLog(@"Download finished: %@", objectRequest.url);
1692     if (operation.isCancelled) {
1693         [self requestFailed:objectRequest];
1694     } else if (objectRequest.responseStatusCode == 200) {
1695         NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1696         PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1697         NSUInteger totalBytes = activity.totalBytes;
1698         
1699         // XXX change contentLength to objectContentLength if it is fixed in the server
1700         if ([objectRequest contentLength] == 0) {
1701             // The check above was:
1702             // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1703             // I checked for directory content types in order not to create a file in place of a directory,
1704             // but this callback method is not called in the case of a directory download.
1705             // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1706             // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1707             NSLog(@"Downloaded  0 bytes");
1708             NSFileManager *fileManager = [NSFileManager defaultManager];
1709             if (![fileManager fileExistsAtPath:filePath]) {
1710                 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1711                     dispatch_async(dispatch_get_main_queue(), ^{
1712                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1713                         [alert setMessageText:@"Create File Error"];
1714                         [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1715                         [alert addButtonWithTitle:@"OK"];
1716                         [alert runModal];
1717                     });
1718                 }
1719             }
1720         }
1721         
1722         NSUInteger currentBytes = [objectRequest objectContentLength];
1723         if (currentBytes == 0)
1724             currentBytes = totalBytes;
1725         dispatch_async(dispatch_get_main_queue(), ^{
1726             [activityFacility endActivity:activity 
1727                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1728                                totalBytes:totalBytes 
1729                              currentBytes:currentBytes];
1730         });
1731     } else {
1732         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1733         [self requestFailed:objectRequest];
1734     }
1735     [pool drain];
1736 }
1737
1738 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1739     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1740     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1741     NSLog(@"Upload directory object finished: %@", objectRequest.url);
1742     if (operation.isCancelled) {
1743         [self requestFailed:objectRequest];
1744     } else if (objectRequest.responseStatusCode == 201) {
1745         NSLog(@"Directory object created: %@", objectRequest.url);
1746         dispatch_async(dispatch_get_main_queue(), ^{
1747             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1748                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1749             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1750                 [node forceRefresh];
1751             }
1752             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1753                 [node refresh];
1754             }
1755             if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1756                 [self forceRefresh:self];
1757             else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1758                 [self refresh:self];
1759         });
1760     } else {
1761         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1762         [self requestFailed:objectRequest];
1763     }
1764     [pool drain];
1765 }
1766
1767 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1768     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1769     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1770     NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1771     NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1772     PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1773     NSUInteger totalBytes = activity.totalBytes;
1774     NSUInteger currentBytes = activity.currentBytes;
1775     if (operation.isCancelled) {
1776         [self requestFailed:objectRequest];
1777     } else if (objectRequest.responseStatusCode == 201) {
1778         NSLog(@"Object created: %@", objectRequest.url);
1779         dispatch_async(dispatch_get_main_queue(), ^{
1780             [activityFacility endActivity:activity 
1781                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1782                                totalBytes:totalBytes 
1783                              currentBytes:totalBytes];
1784             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1785                 [node forceRefresh];
1786             }
1787             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1788                 [node refresh];
1789             }
1790             if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1791                 [self forceRefresh:self];
1792             else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1793                 [self refresh:self];        
1794         });
1795     } else if (objectRequest.responseStatusCode == 409) {
1796         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1797         if (iteration == 0) {
1798             NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1799             dispatch_async(dispatch_get_main_queue(), ^{
1800                 [activityFacility endActivity:activity 
1801                                   withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]]; 
1802                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1803                 [alert setMessageText:@"Upload Timeout"];
1804                 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
1805                                            [objectRequest.userInfo objectForKey:@"objectName"]]];
1806                 [alert addButtonWithTitle:@"OK"];
1807                 [alert runModal];
1808             });
1809             [pool drain];
1810             return;
1811         }
1812         NSLog(@"object is missing hashes: %@", objectRequest.url);
1813         NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1814                                                           withMissingHashes:[objectRequest hashes]];
1815         NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1816         if (totalBytes >= [missingBlocks count]*blockSize)
1817             currentBytes = totalBytes - [missingBlocks count]*blockSize;
1818         dispatch_async(dispatch_get_main_queue(), ^{
1819             [activityFacility updateActivity:activity 
1820                                  withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)] 
1821                                   totalBytes:totalBytes 
1822                                 currentBytes:currentBytes];
1823         });
1824         NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1825         __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1826                                                                                                          containerName:[objectRequest.userInfo objectForKey:@"containerName"] 
1827                                                                                                              blockSize:blockSize 
1828                                                                                                                forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1829                                                                                                      missingBlockIndex:missingBlockIndex 
1830                                                                                                         sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1831         newContainerRequest.delegate = self;
1832         newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1833         newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1834         newContainerRequest.userInfo = objectRequest.userInfo;
1835         [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1836         [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1837         [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1838         [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1839         [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1840         [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1841             [activityFacility updateActivity:activity 
1842                                  withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1843                                   totalBytes:activity.totalBytes 
1844                                 currentBytes:(activity.currentBytes + size)];
1845         }];
1846         [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1847     } else {
1848         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1849         [self requestFailed:objectRequest];
1850     }
1851     [pool drain];
1852 }
1853
1854 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1855     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1856     NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1857     NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1858     NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1859     NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1860     PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1861     if (operation.isCancelled) {
1862         [self requestFailed:containerRequest];
1863     } else if (containerRequest.responseStatusCode == 202) {
1864         NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1865         NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1866         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1867         if (missingBlockIndex == NSNotFound) {
1868             NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1869             ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1870                                                                                            containerName:[containerRequest.userInfo objectForKey:@"containerName"] 
1871                                                                                               objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
1872                                                                                              contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
1873                                                                                                blockSize:blockSize 
1874                                                                                                blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1875                                                                                                  forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1876                                                                                            checkIfExists:NO 
1877                                                                                                   hashes:&hashes 
1878                                                                                           sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1879             newObjectRequest.delegate = self;
1880             newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1881             newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1882             newObjectRequest.userInfo = containerRequest.userInfo;
1883             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1884             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1885             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1886             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1887             [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1888         } else {
1889             __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1890                                                                                                              containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1891                                                                                                                  blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1892                                                                                                                    forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1893                                                                                                          missingBlockIndex:missingBlockIndex 
1894                                                                                                             sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1895             newContainerRequest.delegate = self;
1896             newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1897             newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1898             newContainerRequest.userInfo = containerRequest.userInfo;
1899             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1900             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1901             [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1902                 [activityFacility updateActivity:activity 
1903                                      withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1904                                       totalBytes:activity.totalBytes 
1905                                     currentBytes:(activity.currentBytes + size)];
1906             }];
1907             [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1908         }
1909     } else {
1910         [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1911         [self requestFailed:containerRequest];
1912     }
1913     [pool drain];
1914 }
1915
1916 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1917     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1918     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1919     NSLog(@"Move object finished: %@", objectRequest.url);
1920     if (operation.isCancelled) {
1921         [self requestFailed:objectRequest];
1922     } else if (objectRequest.responseStatusCode == 201) {
1923         dispatch_async(dispatch_get_main_queue(), ^{
1924             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1925                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1926             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1927                 [node forceRefresh];
1928             }
1929             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1930                 [node refresh];
1931             }
1932             if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1933                 [self forceRefresh:self];
1934             else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1935                 [self refresh:self];
1936         });
1937     } else {
1938         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1939         [self requestFailed:objectRequest];
1940     }
1941     [pool drain];
1942 }
1943
1944 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1945     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1946     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1947     NSLog(@"Copy object finished: %@", objectRequest.url);
1948     if (operation.isCancelled) {
1949         [self requestFailed:objectRequest];
1950     } else if (objectRequest.responseStatusCode == 201) {
1951         dispatch_async(dispatch_get_main_queue(), ^{
1952             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1953                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1954             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1955                 [node forceRefresh];
1956             }
1957             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1958                 [node refresh];
1959             }
1960             if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1961                 [self forceRefresh:self];
1962             else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1963                 [self refresh:self];
1964         });
1965     } else {
1966         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1967         [self requestFailed:objectRequest];
1968     }
1969     [pool drain];
1970 }
1971
1972 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1973     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1974     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1975     NSLog(@"Delete object finished: %@", objectRequest.url);
1976     if (operation.isCancelled) {
1977         [self requestFailed:objectRequest];
1978     } else if (objectRequest.responseStatusCode == 204) {
1979         dispatch_async(dispatch_get_main_queue(), ^{
1980             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1981                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1982             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1983                 [node forceRefresh];
1984             }
1985             for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1986                 [node refresh];
1987             }
1988             if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1989                 [self forceRefresh:self];
1990             else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1991                 [self refresh:self];
1992         });
1993     } else {
1994         [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1995         [self requestFailed:objectRequest];
1996     }
1997     [pool drain];
1998 }
1999
2000 #pragma mark -
2001 #pragma mark NSSplitViewDelegate
2002
2003 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
2004     if (splitView == verticalSplitView)
2005         return 120;
2006     else
2007         return ([horizontalSplitView bounds].size.height - 108);
2008 }
2009
2010 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2011     if (splitView == verticalSplitView)
2012         return 220;
2013     else
2014         return ([horizontalSplitView bounds].size.height - 108);
2015 }
2016
2017 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2018     if (splitView == verticalSplitView) {
2019         if (proposedPosition < 120)
2020             return 120;
2021         else if (proposedPosition > 220)
2022             return 220;
2023         else
2024             return proposedPosition;
2025     } else {
2026         return ([horizontalSplitView bounds].size.height - 108);
2027     }
2028 }
2029
2030 #pragma mark -
2031 #pragma mark NSOutlineViewDataSource
2032
2033 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2034     if (!browserInitialized)
2035         return 0;
2036     if (item == nil)
2037         return 2;
2038     if (item == containersNode)
2039         return containersNodeChildren.count;
2040     if (item == sharedNode)
2041         return 2;
2042     return 0;
2043 }
2044
2045 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2046     if (!browserInitialized)
2047         return nil;
2048     if (item == nil)
2049         return (!index ? containersNode : sharedNode);
2050     if (item == sharedNode)
2051         return (!index ? mySharedNode : othersSharedNode);
2052     return [containersNodeChildren objectAtIndex:index];
2053 }
2054
2055 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2056     if ((item == containersNode) || (item == sharedNode))
2057         return YES;
2058     return NO;
2059 }
2060
2061 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2062     PithosNode *node = (PithosNode *)item;
2063     return node;    
2064 }
2065
2066 #pragma mark Drag and Drop destination
2067
2068 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2069                   validateDrop:(id<NSDraggingInfo>)info 
2070                   proposedItem:(id)item 
2071             proposedChildIndex:(NSInteger)index {
2072     NSDragOperation result = NSDragOperationNone;
2073     if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2074         return result;
2075     PithosNode *dropNode = (PithosNode *)item;
2076     if ([dropNode class] != [PithosContainerNode class])
2077         return result;
2078     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2079         result = NSDragOperationCopy;
2080     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2081         if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2082             ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2083             // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2084             if (![dropNode isEqualTo:draggedParentNode])
2085                 result = NSDragOperationMove;
2086         } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2087             // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2088             result = NSDragOperationCopy;
2089         }
2090     }
2091    return result;
2092 }
2093
2094 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2095     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2096         NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2097         NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2098         if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2099             PithosNode *node = (PithosNode *)item;
2100             NSLog(@"drag in node: %@", node.url);
2101             return [self uploadFiles:filenames toNode:node];
2102         }
2103     } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2104         NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2105         if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2106             PithosNode *node = (PithosNode *)item;
2107             NSLog(@"drag local node: %@", node.url);
2108             if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2109                 ([info draggingSourceOperationMask] & NSDragOperationMove))
2110                 return [self moveNodes:draggedNodes toNode:node];
2111             else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2112                 return [self copyNodes:draggedNodes toNode:node];
2113         }
2114     }
2115     return NO;
2116 }
2117
2118 #pragma mark -
2119 #pragma mark NSOutlineViewDelegate
2120
2121 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2122     if ((item == containersNode) || (item == sharedNode))
2123         return NO;
2124     return YES;
2125 }
2126
2127 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2128     if ((item == containersNode) || (item == sharedNode))
2129         return YES;
2130     return NO;
2131 }
2132
2133 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2134     PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2135     if (node) {
2136         rootNode = node;
2137         [browser loadColumnZero];
2138         [self refresh:nil];
2139     }
2140 }
2141
2142 #pragma mark -
2143 #pragma mark NSMenuDelegate
2144
2145 - (void)menuNeedsUpdate:(NSMenu *)menu {
2146     [menu removeAllItems];
2147     NSMenuItem *menuItem;
2148     NSString *menuItemTitle;
2149     BOOL nodeContextMenu = NO;
2150     PithosNode *menuNode = nil;
2151     NSMutableArray *menuNodes;
2152     if (menu == browserMenu) {
2153         NSInteger column = [browser clickedColumn];
2154         NSInteger row = [browser clickedRow];
2155         if ((column == -1) || (row == -1)) {
2156             // General context menu
2157             NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2158             if ([menuNodesIndexPaths count] == 0) {
2159                 menuNode = [browser parentForItemsInColumn:0];
2160             } else if (([menuNodesIndexPaths count] != 1) || 
2161                        ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2162                 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2163             } else {
2164                 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2165             }
2166         } else {
2167             // Node context menu
2168             NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2169             NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2170             menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2171             if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2172                 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2173                     [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2174                 }
2175             } else {
2176                 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2177             }
2178             nodeContextMenu = YES;
2179         }
2180     } else if (menu == outlineViewMenu) {
2181         NSInteger row = [outlineView clickedRow];
2182         if (row == -1)
2183             row = [outlineView selectedRow];
2184         if (row == -1)
2185             return;
2186         menuNode = [outlineView itemAtRow:row];
2187     }
2188
2189     if (!nodeContextMenu) {
2190         // General context menu
2191         if (([menuNode class] == [PithosAccountNode class]) || 
2192             ([menuNode class] == [PithosSharingAccountsNode class]) ||
2193             ([menuNode class] == [PithosEmptyNode class]))
2194             return;
2195         BOOL shared = menuNode.shared;
2196         BOOL sharingAccount = (menuNode.sharingAccount != nil);
2197         // New Folder
2198         if (!shared && !sharingAccount) {
2199             menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2200             [menuItem setRepresentedObject:menuNode];
2201             [menu addItem:menuItem];
2202             [menu addItem:[NSMenuItem separatorItem]];
2203         }
2204         // Refresh
2205         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2206         [menu addItem:menuItem];
2207         [menu addItem:[NSMenuItem separatorItem]];
2208         // Get Info
2209         menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
2210                                                action:@selector(menuGetInfo:) 
2211                                         keyEquivalent:@""] autorelease];
2212         [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2213         [menu addItem:menuItem];
2214         // Paste
2215         if (!shared && !sharingAccount) {
2216             if (clipboardNodes) {
2217                 NSUInteger clipboardNodesCount = [clipboardNodes count];
2218                 if (clipboardNodesCount == 0) {
2219                     self.clipboardNodes = nil;
2220                 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2221                     if (clipboardNodesCount == 1)
2222                         menuItemTitle = [NSString stringWithString:@"Paste Item"];
2223                     else
2224                         menuItemTitle = [NSString stringWithString:@"Paste Items"];
2225                     [menu addItem:[NSMenuItem separatorItem]];
2226                     menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2227                     [menuItem setRepresentedObject:menuNode];
2228                     [menu addItem:menuItem];
2229                 }
2230             }
2231         }
2232     } else {
2233         // Node context menu
2234         NSUInteger menuNodesCount = [menuNodes count];
2235         PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
2236         BOOL shared = firstMenuNode.shared;
2237         BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
2238         // Move to Trash (pithos container only)
2239         // Delete
2240         if (!shared && !sharingAccount) {
2241             if ([rootNode class] == [PithosContainerNode class]) {
2242                 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2243                     menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
2244                     [menuItem setRepresentedObject:menuNodes];
2245                     [menu addItem:menuItem];
2246                 }
2247                 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2248                 [menuItem setRepresentedObject:menuNodes];
2249                 [menu addItem:menuItem];
2250                 [menu addItem:[NSMenuItem separatorItem]];
2251             }
2252         }
2253         // Refresh
2254         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2255         [menu addItem:menuItem];
2256         // Get Info
2257         if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2258             [menu addItem:[NSMenuItem separatorItem]];
2259             menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
2260                                                    action:@selector(menuGetInfo:) 
2261                                             keyEquivalent:@""] autorelease];
2262             [menuItem setRepresentedObject:menuNodes];
2263             [menu addItem:menuItem];
2264             
2265             if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2266                 [menu addItem:[NSMenuItem separatorItem]];
2267         }
2268         // Cut
2269         if (!shared && !sharingAccount) {
2270             if (menuNodesCount == 1)
2271                 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2272             else 
2273                 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2274             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2275             [menuItem setRepresentedObject:menuNodes];
2276             [menu addItem:menuItem];
2277         }
2278         // Copy
2279         if ((!shared && !sharingAccount) || 
2280             (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2281             if (menuNodesCount == 1)
2282                 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2283             else 
2284                 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2285             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2286             [menuItem setRepresentedObject:menuNodes];
2287             [menu addItem:menuItem];
2288         }
2289         // Paste
2290         if (!shared && !sharingAccount) {
2291             if (menuNodesCount == 1) {
2292                 PithosNode *menuNode = [menuNodes objectAtIndex:0];
2293                 if (([menuNode class] == [PithosSubdirNode class]) && 
2294                     (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
2295                     if (clipboardNodes) {
2296                         NSUInteger clipboardNodesCount = [clipboardNodes count];
2297                         if (clipboardNodesCount == 0) {
2298                             self.clipboardNodes = nil;
2299                         } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2300                             if (clipboardNodesCount == 1)
2301                                 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2302                             else
2303                                 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2304                             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2305                             [menuItem setRepresentedObject:menuNode];
2306                             [menu addItem:menuItem];
2307                         }
2308                     }
2309                 }
2310             }
2311         }
2312     }
2313 }
2314
2315 #pragma mark -
2316 #pragma mark Menu Actions
2317
2318 - (void)menuNewFolder:(NSMenuItem *)sender {
2319     PithosNode *node = (PithosNode *)[sender representedObject];
2320     if ([node class] == [PithosContainerNode class]) {
2321         // Operation: Create (upload) a new root application/directory object
2322         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2323             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2324             if (operation.isCancelled) {
2325                 [pool drain];
2326                 return;
2327             }
2328             NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2329                                                                   containerName:node.pithosContainer.name 
2330                                                                      subdirName:@"untitled folder"];
2331             NSString *fileName = [safeObjectName lastPathComponent];
2332             if (operation.isCancelled) {
2333                 [pool drain];
2334                 return;
2335             }
2336             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
2337                                                                                                containerName:node.pithosContainer.name 
2338                                                                                                   objectName:safeObjectName 
2339                                                                                                         eTag:nil 
2340                                                                                                  contentType:@"application/directory" 
2341                                                                                              contentEncoding:nil 
2342                                                                                           contentDisposition:nil 
2343                                                                                                     manifest:nil 
2344                                                                                                      sharing:nil 
2345                                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
2346                                                                                                     metadata:nil 
2347                                                                                                         data:[NSData data]];
2348             objectRequest.delegate = self;
2349             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2350             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2351             NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2352             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2353                                                                        message:messagePrefix];
2354             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2355                                       fileName, @"fileName", 
2356                                       [NSArray arrayWithObject:node], @"refreshNodes", 
2357                                       [NSNumber numberWithBool:YES], @"refresh", 
2358                                       activity, @"activity", 
2359                                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2360                                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2361                                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2362                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2363                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
2364                                       NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2365                                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2366                                       uploadNetworkQueue, @"networkQueue", 
2367                                       @"upload", @"operationType", 
2368                                       nil];
2369             [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2370             [pool drain];
2371         }];
2372         [uploadQueue addOperation:operation];
2373     } else if (([node class] == [PithosSubdirNode class]) && 
2374                (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2375         // Operation: Create (upload) a new aplication/directory object
2376         __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2377             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2378             if (operation.isCancelled) {
2379                 [pool drain];
2380                 return;
2381             }
2382             NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2383                                                                   containerName:node.pithosContainer.name 
2384                                                                      subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2385             NSString *fileName = [safeObjectName lastPathComponent];
2386             if (operation.isCancelled) {
2387                 [pool drain];
2388                 return;
2389             }
2390             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
2391                                                                                                containerName:node.pithosContainer.name 
2392                                                                                                   objectName:safeObjectName 
2393                                                                                                         eTag:nil 
2394                                                                                                  contentType:@"application/directory" 
2395                                                                                              contentEncoding:nil 
2396                                                                                           contentDisposition:nil 
2397                                                                                                     manifest:nil 
2398                                                                                                      sharing:nil 
2399                                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
2400                                                                                                     metadata:nil 
2401                                                                                                         data:[NSData data]];
2402             objectRequest.delegate = self;
2403             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2404             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2405             NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2406             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2407                                                                        message:messagePrefix];
2408             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2409                                       fileName, @"fileName", 
2410                                       [NSArray arrayWithObject:node], @"refreshNodes", 
2411                                       [NSNumber numberWithBool:YES], @"refresh", 
2412                                       activity, @"activity", 
2413                                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2414                                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2415                                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2416                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2417                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
2418                                       NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2419                                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2420                                       uploadNetworkQueue, @"networkQueue", 
2421                                       @"upload", @"operationType", 
2422                                       nil];
2423             [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2424             [pool drain];
2425         }];
2426         [uploadQueue addOperation:operation];
2427     }
2428 }
2429
2430 - (void)menuGetInfo:(NSMenuItem *)sender {
2431     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2432         [node showPithosNodeInfo:sender];
2433     }
2434 }
2435
2436 - (void)menuDelete:(NSMenuItem *)sender {
2437     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2438         if (([node class] == [PithosObjectNode class]) || 
2439             (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2440             // Operation: Delete an object or subdir/ node
2441             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2442                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2443                 if (operation.isCancelled) {
2444                     [pool drain];
2445                     return;
2446                 }
2447                 NSString *fileName = [node.pithosObject.name lastPathComponent];
2448                 if ([node.pithosObject.name hasSuffix:@"/"])
2449                     fileName = [fileName stringByAppendingString:@"/"];
2450                 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
2451                                                                                                 containerName:node.pithosContainer.name 
2452                                                                                                    objectName:node.pithosObject.name];
2453                 if (operation.isCancelled) {
2454                     [pool drain];
2455                     return;
2456                 }
2457                 objectRequest.delegate = self;
2458                 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2459                 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2460                 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2461                 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2462                                                                            message:messagePrefix];
2463                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2464                                           fileName, @"fileName", 
2465                                           [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2466                                           activity, @"activity", 
2467                                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2468                                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2469                                           [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2470                                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2471                                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
2472                                           NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2473                                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2474                                           deleteNetworkQueue, @"networkQueue", 
2475                                           @"delete", @"operationType", 
2476                                           nil];
2477                 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2478                 [pool drain];
2479             }];
2480             [deleteQueue addOperation:operation];
2481         } else if ([node class] == [PithosSubdirNode class]) {
2482             // Operation: Delete a subdir node and its descendants
2483             // The resulting ASIPithosObjectRequests are chained through dependencies
2484             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2485                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2486                 if (operation.isCancelled) {
2487                     [pool drain];
2488                     return;
2489                 }
2490                 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos 
2491                                                                                      containerName:node.pithosContainer.name 
2492                                                                                         objectName:node.pithosObject.name];
2493                 if (!operation.isCancelled && objectRequests) {
2494                     ASIPithosObjectRequest *previousObjectRequest = nil;
2495                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2496                         if (operation.isCancelled) {
2497                             [pool drain];
2498                             return;
2499                         }
2500                         objectRequest.delegate = self;
2501                         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2502                         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2503                         NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2504                         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2505                                                                                    message:messagePrefix];
2506                         [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2507                          [NSDictionary dictionaryWithObjectsAndKeys:
2508                           [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2509                           activity, @"activity", 
2510                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2511                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2512                           [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2513                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2514                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
2515                           NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2516                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2517                           deleteNetworkQueue, @"networkQueue", 
2518                           @"delete", @"operationType", 
2519                           nil]];
2520                         if (previousObjectRequest)
2521                             [objectRequest addDependency:previousObjectRequest];
2522                         previousObjectRequest = objectRequest;
2523                         [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2524                     }
2525                 }
2526                 [pool drain];
2527             }];
2528             [deleteQueue addOperation:operation];
2529         }
2530     }
2531 }
2532
2533 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2534     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2535         if (([node class] == [PithosObjectNode class]) || 
2536             (([node class] == [PithosSubdirNode class]) && 
2537              !node.pithosObject.subdir &&
2538              [node.pithosObject.name hasSuffix:@"/"])) {
2539             // Operation: Move to trash an object or subdir/ node
2540             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2541                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2542                 if (operation.isCancelled) {
2543                     [pool drain];
2544                     return;
2545                 }
2546                 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
2547                                                                       containerName:@"trash" 
2548                                                                          objectName:node.pithosObject.name];
2549                 if (!operation.isCancelled && safeObjectName) {
2550                     ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
2551                                                                                            containerName:node.pithosContainer.name 
2552                                                                                               objectName:node.pithosObject.name 
2553                                                                                 destinationContainerName:@"trash" 
2554                                                                                    destinationObjectName:safeObjectName 
2555                                                                                            checkIfExists:NO];
2556                     if (!operation.isCancelled && objectRequest) {
2557                         objectRequest.delegate = self;
2558                         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2559                         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2560                         NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2561                                                    [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2562                                                    [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2563                                                    [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2564                                                    [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2565                         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2566                                                                                    message:messagePrefix];
2567                         [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2568                          [NSDictionary dictionaryWithObjectsAndKeys:
2569                           [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2570                           activity, @"activity", 
2571                           [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2572                           [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2573                           [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2574                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2575                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
2576                           NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2577                           NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2578                           moveNetworkQueue, @"networkQueue", 
2579                           @"move", @"operationType", 
2580                           nil]];
2581                         [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2582                     }
2583                 }
2584                 [pool drain];
2585             }];
2586             [moveQueue addOperation:operation];
2587         } else if ([node class] == [PithosSubdirNode class]) {
2588             // Operation: Move to trash a subdir node and its descendants
2589             // The resulting ASIPithosObjectRequests are chained through dependencies
2590             __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2591                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2592                 if (operation.isCancelled) {
2593                     [pool drain];
2594                     return;
2595                 }
2596                 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2597                                                                       containerName:@"trash" 
2598                                                                          subdirName:node.pithosObject.name];
2599                 if (!operation.isCancelled && safeObjectName) {
2600                     NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
2601                                                                                        containerName:node.pithosContainer.name 
2602                                                                                           objectName:node.pithosObject.name 
2603                                                                             destinationContainerName:@"trash" 
2604                                                                                destinationObjectName:safeObjectName 
2605                                                                                        checkIfExists:NO];
2606                     if (!operation.isCancelled && objectRequests) {
2607                         ASIPithosObjectRequest *previousObjectRequest = nil;
2608                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2609                             if (operation.isCancelled) {
2610                                 [pool drain];
2611                                 return;
2612                             }
2613                             objectRequest.delegate = self;
2614                             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2615                             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2616                             NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2617                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2618                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2619                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2620                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2621                             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2622                                                                                        message:messagePrefix];
2623                             [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2624                              [NSDictionary dictionaryWithObjectsAndKeys:
2625                               [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2626                               activity, @"activity", 
2627                               [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2628                               [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2629                               [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2630                               [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2631                               [NSNumber numberWithUnsignedInteger:10], @"retries", 
2632                               NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2633                               NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2634                               moveNetworkQueue, @"networkQueue", 
2635                               @"move", @"operationType", 
2636                               nil]];
2637                             if (previousObjectRequest)
2638                                 [objectRequest addDependency:previousObjectRequest];
2639                             previousObjectRequest = objectRequest;
2640                             [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2641                         }
2642                     }
2643                 }
2644                 [pool drain];
2645             }];
2646             [moveQueue addOperation:operation];
2647         }
2648     }
2649 }
2650
2651 - (void)menuCut:(NSMenuItem *)sender {
2652     self.clipboardNodes = (NSArray *)[sender representedObject];
2653     self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2654     self.clipboardCopy = NO;
2655 }
2656
2657 - (void)menuCopy:(NSMenuItem *)sender {
2658     self.clipboardNodes = (NSArray *)[sender representedObject];
2659     self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2660     self.clipboardCopy = YES;
2661 }
2662
2663 - (void)menuPaste:(NSMenuItem *)sender {
2664     if (!clipboardNodes || ![clipboardNodes count])
2665         return;
2666     PithosNode *dropNode = (PithosNode *)[sender representedObject];
2667     NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2668     if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2669         self.clipboardNodes = nil;
2670         self.clipboardParentNode = nil;
2671         [self moveNodes:localClipboardNodes toNode:dropNode];
2672     } else {
2673         [self copyNodes:localClipboardNodes toNode:dropNode];
2674     }
2675 }
2676     
2677 #pragma mark -
2678 #pragma mark PithosActivityFacilityDelegate
2679
2680 - (void)activityUpdate:(NSDictionary *)info {
2681     NSString *message = [info objectForKey:@"message"];
2682     NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2683 //    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2684     NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2685     NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2686     NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2687     NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2688     NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2689     if (runningActivitiesCount && totalBytes) {
2690         [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2691         [activityProgressIndicator startAnimation:self];
2692     } else {
2693         [activityProgressIndicator setDoubleValue:1.0];
2694         [activityProgressIndicator stopAnimation:self];
2695     }
2696     
2697     if (!message)
2698         message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2699     [activityTextField setStringValue:message];
2700 }
2701
2702 @end