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