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