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