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