Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ 544b6f52

History | View | Annotate | Download (155.7 kB)

1
//
2
//  PithosBrowserController.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

    
38
#import "PithosBrowserController.h"
39
#import "PithosNode.h"
40
#import "PithosAccountNode.h"
41
#import "PithosContainerNode.h"
42
#import "PithosSubdirNode.h"
43
#import "PithosObjectNode.h"
44
#import "PithosSharingAccountsNode.h"
45
#import "PithosEmptyNode.h"
46
#import "ImageAndTextCell.h"
47
#import "FileSystemBrowserCell.h"
48
#import "ASINetworkQueue.h"
49
#import "ASIPithosRequest.h"
50
#import "ASIPithos.h"
51
#import "ASIPithosContainerRequest.h"
52
#import "ASIPithosObjectRequest.h"
53
#import "ASIPithosAccount.h"
54
#import "ASIPithosContainer.h"
55
#import "ASIPithosObject.h"
56
#import "PithosUtilities.h"
57
#import "UsingSizeTransformer.h"
58

    
59
@interface PithosBrowserCell : FileSystemBrowserCell {}
60
@end
61

    
62
@implementation PithosBrowserCell
63

    
64
- (id)init {
65
    if ((self = [super init])) {
66
        [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
67
        [self setEditable:YES];
68
    }
69
    return self;
70
}
71

    
72
- (void)setObjectValue:(id)object {
73
    if ([object isKindOfClass:[PithosNode class]]) {
74
        PithosNode *node = (PithosNode *)object;
75
        [self setStringValue:node.displayName];
76
        [self setImage:node.icon];
77
    } else {
78
        [super setObjectValue:object];
79
    }
80
}
81

    
82
@end
83

    
84
@interface PithosOutlineViewCell : ImageAndTextCell {}
85
@end
86

    
87
@implementation PithosOutlineViewCell
88

    
89
- (void)setObjectValue:(id)object {
90
    if ([object isKindOfClass:[PithosNode class]]) {
91
        PithosNode *node = (PithosNode *)object;
92
        [self setStringValue:node.displayName];
93
        [self setImage:node.icon];
94
        [self setEditable:NO];
95
    } else {
96
        [super setObjectValue:object];
97
    }
98
}
99

    
100
@end
101

    
102
@interface PithosBrowserController (Private)
103
- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
104
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
105
- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
106
@end
107

    
108
@implementation PithosBrowserController
109
@synthesize pithos;
110
@synthesize accountNode;
111
@synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
112
@synthesize draggedNodes, draggedParentNode;
113
@synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
114
@synthesize activityTextField, activityProgressIndicator;
115

    
116
#pragma mark -
117
#pragma Object Lifecycle
118

    
119
- (id)init {
120
    return [super initWithWindowNibName:@"PithosBrowserController"];
121
}
122

    
123
- (void)windowDidLoad {
124
    [super windowDidLoad];
125
    if (browser && !browserInitialized) {
126
        browserInitialized = YES;
127
        [self initBrowser];
128
    }
129
}
130

    
131
- (void)initBrowser {
132
    [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
133
    [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
134
    [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
135
    
136
    [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
137
    [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
138
    [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
139
    
140
    [browser setCellClass:[PithosBrowserCell class]];
141
    [browser setAllowsBranchSelection:YES];
142
    [browser setAllowsMultipleSelection:YES];
143
    [browser setAllowsEmptySelection:YES];
144
    [browser setAllowsTypeSelect:YES];
145
    
146
    moveNetworkQueue = [[ASINetworkQueue alloc] init];
147
    moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
148
//    moveNetworkQueue.maxConcurrentOperationCount = 1;
149
    copyNetworkQueue = [[ASINetworkQueue alloc] init];
150
    copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
151
//    copyNetworkQueue.maxConcurrentOperationCount = 1;
152
    deleteNetworkQueue = [[ASINetworkQueue alloc] init];
153
    deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
154
//    deleteNetworkQueue.maxConcurrentOperationCount = 1;
155
    uploadNetworkQueue = [[ASINetworkQueue alloc] init];
156
    uploadNetworkQueue.showAccurateProgress = YES;
157
    uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
158
//    uploadNetworkQueue.maxConcurrentOperationCount = 1;
159
    downloadNetworkQueue = [[ASINetworkQueue alloc] init];
160
    downloadNetworkQueue.showAccurateProgress = YES;
161
    downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
162
//    downloadNetworkQueue.maxConcurrentOperationCount = 1;
163
    
164
    moveQueue = [[NSOperationQueue alloc] init];
165
    [moveQueue setSuspended:YES];
166
    moveQueue.name = @"gr.grnet.pithos.MoveQueue";
167
//    moveQueue.maxConcurrentOperationCount = 1;
168
    copyQueue = [[NSOperationQueue alloc] init];
169
    [copyQueue setSuspended:YES];
170
    copyQueue.name = @"gr.grnet.pithos.CopyQueue";
171
//    copyQueue.maxConcurrentOperationCount = 1;
172
    deleteQueue = [[NSOperationQueue alloc] init];
173
    [deleteQueue setSuspended:YES];
174
    deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
175
//    deleteQueue.maxConcurrentOperationCount = 1;
176
    uploadQueue = [[NSOperationQueue alloc] init];
177
    [uploadQueue setSuspended:YES];
178
    uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
179
//    uploadQueue.maxConcurrentOperationCount = 1;
180
    downloadQueue = [[NSOperationQueue alloc] init];
181
    [downloadQueue setSuspended:YES];
182
    downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
183
//    downloadQueue.maxConcurrentOperationCount = 1;
184
    
185
    moveCallbackQueue = [[NSOperationQueue alloc] init];
186
    [moveCallbackQueue setSuspended:YES];
187
    moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
188
//    moveCallbackQueue.maxConcurrentOperationCount = 1;
189
    copyCallbackQueue = [[NSOperationQueue alloc] init];
190
    [copyCallbackQueue setSuspended:YES];
191
    copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
192
//    copyCallbackQueue.maxConcurrentOperationCount = 1;
193
    deleteCallbackQueue = [[NSOperationQueue alloc] init];
194
    [deleteCallbackQueue setSuspended:YES];
195
    deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
196
//    deleteCallbackQueue.maxConcurrentOperationCount = 1;
197
    uploadCallbackQueue = [[NSOperationQueue alloc] init];
198
    [uploadCallbackQueue setSuspended:YES];
199
    uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
200
//    uploadCallbackQueue.maxConcurrentOperationCount = 1;
201
    downloadCallbackQueue = [[NSOperationQueue alloc] init];
202
    [downloadCallbackQueue setSuspended:YES];
203
    downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
204
//    downloadCallbackQueue.maxConcurrentOperationCount = 1;
205
    
206
    [activityProgressIndicator setUsesThreadedAnimation:YES];
207
    [activityProgressIndicator setMinValue:0.0];
208
    [activityProgressIndicator setMaxValue:1.0];
209
    activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
210
    
211
    self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
212
    containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
213
    containersNodeChildren = [[NSMutableArray alloc] init];
214
    sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
215
    mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
216
    mySharedNode.displayName = @"my shared";
217
    mySharedNode.shared = YES;
218
    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
219
    othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
220
    othersSharedNode.displayName = @"others shared";
221
    othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
222
    
223
    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
224
    
225
    // Register for updates
226
    // PithosContainerNode updates browser nodes
227
    [[NSNotificationCenter defaultCenter] addObserver:self 
228
                                             selector:@selector(pithosNodeChildrenUpdated:) 
229
                                                 name:@"PithosContainerNodeChildrenUpdated" 
230
                                               object:nil];
231
    // PithosSubdirNode updates browser nodes
232
    [[NSNotificationCenter defaultCenter] addObserver:self 
233
                                             selector:@selector(pithosNodeChildrenUpdated:) 
234
                                                 name:@"PithosSubdirNodeChildrenUpdated" 
235
                                               object:nil];
236
    // PithosAccountNode accountNode updates outlineView container nodes 
237
    [[NSNotificationCenter defaultCenter] addObserver:self 
238
                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
239
                                                 name:@"PithosAccountNodeChildrenUpdated" 
240
                                               object:accountNode];
241
    // PithosAccountNode other than accountNode updates nodes 
242
    [[NSNotificationCenter defaultCenter] addObserver:self 
243
                                             selector:@selector(pithosNodeChildrenUpdated:) 
244
                                                 name:@"PithosAccountNodeChildrenUpdated" 
245
                                               object:nil];
246
    // PithosSharingAccountsNode othersSharedNode updates browser nodes 
247
    [[NSNotificationCenter defaultCenter] addObserver:self 
248
                                             selector:@selector(pithosNodeChildrenUpdated:) 
249
                                                 name:@"PithosSharingAccountsNodeChildrenUpdated" 
250
                                               object:othersSharedNode];
251
    // Request for browser refresh 
252
    [[NSNotificationCenter defaultCenter] addObserver:self 
253
                                             selector:@selector(pithosBrowserRefreshNeeded:) 
254
                                                 name:@"PithosBrowserRefreshNeeeded" 
255
                                               object:nil];
256
}
257

    
258
- (void)resetBrowser {
259
    @synchronized(self) {
260
        if (!browserActive)
261
            return;
262
    }
263

    
264
    [refreshTimer invalidate];
265
    [refreshTimer release];
266
    
267
    [moveNetworkQueue reset];
268
    [copyNetworkQueue reset];
269
    [deleteNetworkQueue reset];
270
    [uploadNetworkQueue reset];
271
    [downloadNetworkQueue reset];
272
    
273
    [moveQueue cancelAllOperations];
274
    [moveQueue setSuspended:YES];
275
    [copyQueue cancelAllOperations];
276
    [copyQueue setSuspended:YES];
277
    [deleteQueue cancelAllOperations];
278
    [deleteQueue setSuspended:YES];
279
    [uploadQueue cancelAllOperations];
280
    [uploadQueue setSuspended:YES];
281
    [downloadQueue cancelAllOperations];
282
    [downloadQueue setSuspended:YES];
283

    
284
    [moveCallbackQueue cancelAllOperations];
285
    [moveCallbackQueue setSuspended:YES];
286
    [copyCallbackQueue cancelAllOperations];
287
    [copyCallbackQueue setSuspended:YES];
288
    [deleteCallbackQueue cancelAllOperations];
289
    [deleteCallbackQueue setSuspended:YES];
290
    [uploadCallbackQueue cancelAllOperations];
291
    [uploadCallbackQueue setSuspended:YES];
292
    [downloadCallbackQueue cancelAllOperations];
293
    [downloadCallbackQueue setSuspended:YES];
294

    
295
    rootNode = nil;
296
    [browser loadColumnZero];
297
    [containersNodeChildren removeAllObjects];
298
    [outlineView reloadData];
299
    // Expand the folder outline view
300
    [outlineView expandItem:nil expandChildren:YES];
301
    [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
302
    
303
    activityFacility.delegate = nil;
304
    [activityProgressIndicator setDoubleValue:1.0];
305
    [activityProgressIndicator stopAnimation:self];
306
    
307
    @synchronized(self) {
308
        browserActive = NO;
309
    }
310
}
311

    
312
- (void)startBrowser {
313
    @synchronized(self) {
314
        if (browserActive)
315
            return;
316
    }
317
    
318
    // In the improbable case of leftover operations
319
    [moveNetworkQueue reset];
320
    [copyNetworkQueue reset];
321
    [deleteNetworkQueue reset];
322
    [uploadNetworkQueue reset];
323
    [downloadNetworkQueue reset];
324
    [moveQueue cancelAllOperations];
325
    [copyQueue cancelAllOperations];
326
    [deleteQueue cancelAllOperations];
327
    [uploadQueue cancelAllOperations];
328
    [downloadQueue cancelAllOperations];
329
    [moveCallbackQueue cancelAllOperations];
330
    [copyCallbackQueue cancelAllOperations];
331
    [deleteCallbackQueue cancelAllOperations];
332
    [uploadCallbackQueue cancelAllOperations];
333
    [downloadCallbackQueue cancelAllOperations];
334

    
335
    [moveNetworkQueue go];
336
    [copyNetworkQueue go];
337
    [deleteNetworkQueue go];
338
    [uploadNetworkQueue go];
339
    [downloadNetworkQueue go];
340
    [moveQueue setSuspended:NO];
341
    [copyQueue setSuspended:NO];
342
    [deleteQueue setSuspended:NO];
343
    [uploadQueue setSuspended:NO];
344
    [downloadQueue setSuspended:NO];
345
    [moveCallbackQueue setSuspended:NO];
346
    [copyCallbackQueue setSuspended:NO];
347
    [deleteCallbackQueue setSuspended:NO];
348
    [uploadCallbackQueue setSuspended:NO];
349
    [downloadCallbackQueue setSuspended:NO];
350

    
351
    accountNode.pithos = pithos;
352
    [accountNode forceRefresh];
353
    mySharedNode.pithos = pithos;
354
    [mySharedNode forceRefresh];
355
    othersSharedNode.pithos = pithos;
356
    [othersSharedNode forceRefresh];
357
            
358
//    [activityFacility reset];
359
    activityFacility.delegate = self;
360
            
361
    refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 
362
                                                     target:self 
363
                                                   selector:@selector(forceRefresh:) 
364
                                                   userInfo:self 
365
                                                    repeats:YES] retain];
366
    @synchronized(self) {
367
        browserActive = YES;
368
    }
369
}
370

    
371
- (BOOL)operationsPending {
372
    return ([moveNetworkQueue operationCount] || 
373
            [copyNetworkQueue operationCount] || 
374
            [deleteNetworkQueue operationCount] || 
375
            [uploadNetworkQueue operationCount] || 
376
            [downloadNetworkQueue operationCount] || 
377
            [moveQueue operationCount] || 
378
            [copyQueue operationCount] || 
379
            [deleteQueue operationCount] || 
380
            [uploadQueue operationCount] || 
381
            [downloadQueue operationCount] || 
382
            [moveCallbackQueue operationCount] || 
383
            [copyCallbackQueue operationCount] || 
384
            [deleteCallbackQueue operationCount] || 
385
            [uploadCallbackQueue operationCount] || 
386
            [downloadCallbackQueue operationCount]);
387
}
388

    
389
- (void)dealloc {
390
    [[NSNotificationCenter defaultCenter] removeObserver:self];
391
    [self resetBrowser];
392
    [moveQueue release];
393
    [copyQueue release];
394
    [deleteQueue release];
395
    [uploadQueue release];
396
    [downloadQueue release];
397
    [moveNetworkQueue release];
398
    [copyNetworkQueue release];
399
    [deleteNetworkQueue release];
400
    [uploadNetworkQueue release];
401
    [downloadNetworkQueue release];
402
    [clipboardParentNode release];
403
    [clipboardNodes release];
404
    [draggedParentNode release];
405
    [draggedNodes release];
406
    [sharedPreviewController release];
407
    [othersSharedNode release];
408
    [mySharedNode release];
409
    [sharedNode release];
410
    [containersNodeChildren release];
411
    [containersNode release];
412
    [accountNode release];
413
    [rootNode release];
414
    [pithos release];
415
    [super dealloc];
416
}
417

    
418
- (void)setPithos:(ASIPithos *)aPithos {
419
    if (aPithos) {
420
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
421
            ![aPithos.authToken isEqualToString:pithos.authToken] || 
422
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
423
            ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
424
            [self resetBrowser];
425
            [pithos release];
426
            pithos = [aPithos retain];
427
            [self startBrowser];
428
        } else {
429
            [self startBrowser];
430
        }
431
    }
432
}
433

    
434

    
435
#pragma mark -
436
#pragma mark Observers
437

    
438
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
439
    PithosNode *node = (PithosNode *)[notification object];
440
    if (node == accountNode)
441
        return;
442
    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
443
    NSInteger lastColumn = [browser lastColumn];
444
    for (NSInteger column = lastColumn; column >= 0; column--) {
445
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
446
            [browser reloadColumn:column];
447
            return;
448
        }
449
    }
450
}
451

    
452
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
453
    BOOL containerPithosFound = NO;
454
    BOOL containerTrashFound = NO;
455
    NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
456
    for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
457
        if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
458
            [removedContainersNodeChildren addIndex:i];
459
    }
460
    [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
461
    for (PithosContainerNode *containerNode in accountNode.children) {
462
        if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
463
            if (![containersNodeChildren containsObject:containerNode])
464
                [containersNodeChildren insertObject:containerNode atIndex:0];
465
            containerPithosFound = YES;
466
        } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
467
            NSUInteger insertIndex = 1;
468
            if (!containerPithosFound)
469
                insertIndex = 0;
470
            if (![containersNodeChildren containsObject:containerNode])
471
                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
472
            containerTrashFound = YES;
473
        } else if (![containersNodeChildren containsObject:containerNode]) {
474
            [containersNodeChildren addObject:containerNode];
475
        }
476
    }
477
    BOOL refreshAccountNode = NO;
478
    if (!containerPithosFound) {
479
        // Create pithos node
480
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
481
                                                                                                            containerName:@"pithos"];
482
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
483
        [networkQueue go];
484
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
485
        if ([containerRequest error]) {
486
            dispatch_async(dispatch_get_main_queue(), ^{
487
                [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
488
            });
489
        } else {
490
            refreshAccountNode = YES;
491
        }
492
    }
493
    if (!containerTrashFound) {
494
        // Create trash node
495
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
496
                                                                                                            containerName:@"trash"];
497
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
498
        [networkQueue go];
499
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
500
        if ([containerRequest error]) {
501
            dispatch_async(dispatch_get_main_queue(), ^{
502
                [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
503
            });
504
        } else {
505
            refreshAccountNode = YES;
506
        }
507
    }
508
    
509
    if (refreshAccountNode)
510
        [accountNode refresh];
511
    
512
    [outlineView reloadData];
513
    
514
    // Expand the folder outline view
515
    [outlineView expandItem:nil expandChildren:YES];
516
    
517
    if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
518
        rootNode = [containersNodeChildren objectAtIndex:0];
519
        [browser loadColumnZero];
520
    }
521
    
522
    if (notification)
523
        [self refresh:nil];
524
}
525

    
526
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
527
    [self refresh:nil];
528
}
529

    
530
#pragma mark -
531
#pragma mark Actions
532

    
533
- (IBAction)forceRefresh:(id)sender {
534
    if (sender)
535
        [accountNode forceRefresh];
536
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
537
        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
538
        node.forcedRefresh = YES;
539
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
540
    }
541
    [browser validateVisibleColumns];
542
}
543

    
544
- (IBAction)refresh:(id)sender {
545
    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
546
        [self forceRefresh:sender];
547
    } else {
548
        if (sender)
549
            [accountNode refresh];
550
        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
551
            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
552
        }
553
        [browser validateVisibleColumns];
554
    }
555
}
556

    
557
#pragma mark -
558
#pragma mark NSBrowserDelegate
559

    
560
- (id)rootItemForBrowser:(NSBrowser *)browser {
561
    return rootNode;    
562
}
563

    
564
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
565
    PithosNode *node = (PithosNode *)item;
566
    return node.children.count;
567
}
568

    
569
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
570
    PithosNode *node = (PithosNode *)item;
571
    return [node.children objectAtIndex:index];
572
}
573

    
574
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
575
    PithosNode *node = (PithosNode *)item;
576
    return node.isLeafItem;
577
}
578

    
579
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
580
    PithosNode *node = (PithosNode *)item;
581
    return node;
582
}
583

    
584
- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
585
    if (sharedPreviewController == nil)
586
        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
587
    return sharedPreviewController;
588
}
589

    
590
//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
591
//    if (!forUserResize) {
592
//        id item = [browser parentForItemsInColumn:columnIndex]; 
593
//        if ([self browser:browser isLeafItem:item]) {
594
//            suggestedWidth = 200; 
595
//        }
596
//    }
597
//    return suggestedWidth;
598
//}
599

    
600
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
601
    return NO;
602
}
603

    
604
#pragma mark Editing
605

    
606
- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
607
    PithosNode *node = (PithosNode *)item;
608
    if (node.shared || node.sharingAccount || 
609
        ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
610
        return NO;
611
    return YES;
612
}
613

    
614
- (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
615
    PithosNode *node = (PithosNode *)item;
616
    NSString *newName = (NSString *)object;
617
    NSUInteger newNameLength = [newName length];
618
    NSRange firstSlashRange = [newName rangeOfString:@"/"];
619
    if ((newNameLength == 0) || 
620
        ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
621
        ([newName isEqualToString:node.displayName])) {
622
        return;
623
    }
624
    if (([node class] == [PithosObjectNode class]) || 
625
        (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
626
        // Operation: Rename (move) an object or subdir/ node
627
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
628
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
629
            if (operation.isCancelled) {
630
                [pool drain];
631
                return;
632
            }
633
            NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
634
            if ([newName hasSuffix:@"/"])
635
                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
636
            NSError *error = nil;
637
            BOOL isDirectory;
638
            if ([PithosUtilities objectExistsAtPithos:pithos 
639
                                        containerName:node.pithosContainer.name 
640
                                           objectName:destinationObjectName 
641
                                                error:&error 
642
                                          isDirectory:&isDirectory 
643
                                       sharingAccount:nil]) {
644
                dispatch_async(dispatch_get_main_queue(), ^{
645
                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
646
                    [alert setMessageText:@"Name Taken"];
647
                    [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
648
                    [alert addButtonWithTitle:@"OK"];
649
                    [alert runModal];
650
                });
651
                [pool drain];
652
                return;
653
            } else if (error) {
654
                [pool drain];
655
                return;
656
            }
657
            if (operation.isCancelled) {
658
                [pool drain];
659
                return;
660
            }
661
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
662
                                                                                   containerName:node.pithosContainer.name 
663
                                                                                      objectName:node.pithosObject.name 
664
                                                                        destinationContainerName:node.pithosContainer.name 
665
                                                                           destinationObjectName:destinationObjectName 
666
                                                                                   checkIfExists:NO];
667
            if (!operation.isCancelled && objectRequest) {
668
                objectRequest.delegate = self;
669
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
670
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
671
                NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
672
                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
673
                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
674
                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
675
                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
676
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
677
                                                                           message:messagePrefix];
678
                [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
679
                 [NSDictionary dictionaryWithObjectsAndKeys:
680
                  [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
681
                  [NSNumber numberWithBool:YES], @"refresh", 
682
                  activity, @"activity", 
683
                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
684
                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
685
                  [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
686
                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
687
                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
688
                  NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
689
                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
690
                  moveNetworkQueue, @"networkQueue", 
691
                  @"move", @"operationType", 
692
                  nil]];
693
                [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
694
            }
695
            [pool drain];
696
        }];
697
        [moveQueue addOperation:operation];
698
    } else if ([node class] == [PithosSubdirNode class]) {
699
        if (firstSlashRange.length == 1)
700
            return;
701
        // Operation: Rename (move) a subdir node and its descendants
702
        // The resulting ASIPithosObjectRequests are chained through dependencies
703
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
704
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
705
            if (operation.isCancelled) {
706
                [pool drain];
707
                return;
708
            }
709
            NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
710
            NSError *error = nil;
711
            BOOL isDirectory;
712
            if ([PithosUtilities objectExistsAtPithos:pithos 
713
                                        containerName:node.pithosContainer.name 
714
                                           objectName:destinationObjectName 
715
                                                error:&error 
716
                                          isDirectory:&isDirectory 
717
                                       sharingAccount:nil]) {
718
                dispatch_async(dispatch_get_main_queue(), ^{
719
                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
720
                    [alert setMessageText:@"Name Taken"];
721
                    [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
722
                    [alert addButtonWithTitle:@"OK"];
723
                    [alert runModal];
724
                });
725
                [pool drain];
726
                return;
727
            } else if (error) {
728
                [pool drain];
729
                return;
730
            }
731
            if (operation.isCancelled) {
732
                [pool drain];
733
                return;
734
            }
735
            if (node.pithosObject.subdir)
736
                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
737
            NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
738
                                                                               containerName:node.pithosContainer.name 
739
                                                                                  objectName:node.pithosObject.name 
740
                                                                    destinationContainerName:node.pithosContainer.name 
741
                                                                       destinationObjectName:destinationObjectName 
742
                                                                               checkIfExists:NO];
743
            if (!operation.isCancelled && objectRequests) {
744
                ASIPithosObjectRequest *previousObjectRequest = nil;
745
                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
746
                    if (operation.isCancelled) {
747
                        [pool drain];
748
                        return;
749
                    }
750
                    objectRequest.delegate = self;
751
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
752
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
753
                    NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
754
                                               [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
755
                                               [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
756
                                               [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
757
                                               [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
758
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
759
                                                                               message:messagePrefix];
760
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
761
                     [NSDictionary dictionaryWithObjectsAndKeys:
762
                      [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
763
                      [NSNumber numberWithBool:YES], @"refresh", 
764
                      activity, @"activity", 
765
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
766
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
767
                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
768
                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
769
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
770
                      NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
771
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
772
                      moveNetworkQueue, @"networkQueue", 
773
                      @"move", @"operationType", 
774
                      nil]];
775
                    if (previousObjectRequest)
776
                        [objectRequest addDependency:previousObjectRequest];
777
                    previousObjectRequest = objectRequest;
778
                    [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
779
                }
780
            }
781
            [pool drain];
782
        }];
783
        [moveQueue addOperation:operation];
784
    }
785
}
786

    
787
#pragma mark Drag and Drop source
788

    
789
- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
790
      withEvent:(NSEvent *)event {
791
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
792
    __block BOOL result = YES;
793
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
794
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
795
        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
796
            result = NO;
797
            *stop = YES;
798
        }
799
    }];
800
    return result;
801
}
802

    
803
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
804
   toPasteboard:(NSPasteboard *)pasteboard {
805
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
806
    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
807
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
808
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
809
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
810
        [propertyList addObject:[node.pithosObject.name pathExtension]];
811
        [nodes addObject:node];
812
    }];
813

    
814
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
815
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
816
    self.draggedNodes = nodes;
817
    self.draggedParentNode = [browser parentForItemsInColumn:column];
818
    return YES;
819
}
820

    
821
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
822
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
823
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
824
    for (PithosNode *node in draggedNodes) {        
825
        // If the node is a subdir ask if the whole tree should be downloaded
826
        if ([node class] == [PithosSubdirNode class]) {
827
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
828
            [alert setMessageText:@"Download directory"];
829
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
830
            [alert addButtonWithTitle:@"OK"];
831
            [alert addButtonWithTitle:@"Cancel"];
832
            NSInteger choice = [alert runModal];
833
            if (choice == NSAlertFirstButtonReturn) {
834
                // Operation: Download a subdir node and its descendants
835
                // The resulting ASIPithosObjectRequests are chained through dependencies
836
                __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
837
                    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
838
                    if (operation.isCancelled) {
839
                        [pool drain];
840
                        return;
841
                    }
842
                    NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos 
843
                                                                                       containerName:node.pithosContainer.name 
844
                                                                                          objectName:node.pithosObject.name 
845
                                                                                         toDirectory:[dropDestination path] 
846
                                                                                       checkIfExists:YES 
847
                                                                                      sharingAccount:node.sharingAccount];
848
                    if (!operation.isCancelled && objectRequests) {
849
                        ASIPithosObjectRequest *previousObjectRequest = nil;
850
                        for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
851
                            if (operation.isCancelled) {
852
                                [pool drain];
853
                                return;
854
                            }
855
                            [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
856
                            objectRequest.delegate = self;
857
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
858
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
859
                            NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
860
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
861
                                                                                       message:[messagePrefix stringByAppendingString:@" (0%)"]
862
                                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
863
                                                                                  currentBytes:0];
864
                            dispatch_async(dispatch_get_main_queue(), ^{
865
                                [activityFacility updateActivity:activity withMessage:activity.message];  
866
                            });
867
                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
868
                             [NSDictionary dictionaryWithObjectsAndKeys:
869
                              activity, @"activity", 
870
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
871
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
872
                              [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
873
                              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
874
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
875
                              NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
876
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
877
                              downloadNetworkQueue, @"networkQueue", 
878
                              @"download", @"operationType", 
879
                              nil]];
880
                            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
881
                                [activityFacility updateActivity:activity 
882
                                                     withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
883
                                                      totalBytes:activity.totalBytes 
884
                                                    currentBytes:(activity.currentBytes + size)];
885
                            }];
886
                            if (previousObjectRequest)
887
                                [objectRequest addDependency:previousObjectRequest];
888
                            previousObjectRequest = objectRequest;
889
                            [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
890
                        }
891
                    }
892
                    [pool drain];
893
                }];
894
                [downloadQueue addOperation:operation];
895
            }
896
        } else {
897
            // Operation: Download an object node
898
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
899
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
900
                if (operation.isCancelled) {
901
                    [pool drain];
902
                    return;
903
                }
904
                __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos 
905
                                                                                               containerName:node.pithosContainer.name 
906
                                                                                                  objectName:node.pithosObject.name 
907
                                                                                                 toDirectory:[dropDestination path] 
908
                                                                                               checkIfExists:YES 
909
                                                                                              sharingAccount:node.sharingAccount];
910
                if (!operation.isCancelled && objectRequest) {
911
                    [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
912
                    objectRequest.delegate = self;
913
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
914
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
915
                    NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
916
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
917
                                                                               message:[messagePrefix stringByAppendingString:@" (0%)"]
918
                                                                            totalBytes:node.pithosObject.bytes 
919
                                                                          currentBytes:0];
920
                    dispatch_async(dispatch_get_main_queue(), ^{
921
                        [activityFacility updateActivity:activity withMessage:activity.message];  
922
                    });
923
                    [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
924
                     [NSDictionary dictionaryWithObjectsAndKeys:
925
                      activity, @"activity", 
926
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
927
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
928
                      [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
929
                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
930
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
931
                      NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
932
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
933
                      downloadNetworkQueue, @"networkQueue", 
934
                      @"download", @"operationType", 
935
                      nil]];
936
                    [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
937
                        [activityFacility updateActivity:activity 
938
                                             withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
939
                                              totalBytes:activity.totalBytes 
940
                                            currentBytes:(activity.currentBytes + size)];
941
                    }];
942
                    [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
943
                    [pool drain];
944
                }
945
            }];
946
            [downloadQueue addOperation:operation];
947
        }
948
    }
949
    return names;
950
}
951

    
952
#pragma mark Drag and Drop destination
953

    
954
- (NSDragOperation)browser:aBrowser 
955
              validateDrop:(id<NSDraggingInfo>)info 
956
               proposedRow:(NSInteger *)row 
957
                    column:(NSInteger *)column 
958
             dropOperation:(NSBrowserDropOperation *)dropOperation {
959
    NSDragOperation result = NSDragOperationNone;
960
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
961
        // For a drop above, the drop is redirected to the parent item
962
        if (*dropOperation == NSBrowserDropAbove)
963
            *row = -1;
964
        // Only allow dropping in folders
965
        if (*column != -1) {
966
            PithosNode *dropNode;
967
            if (*row != -1) {
968
                // Check if the node is not a folder and if so redirect to the parent item
969
                dropNode = [browser itemAtRow:*row inColumn:*column];
970
                if ([dropNode class] == [PithosObjectNode class])
971
                    *row = -1;
972
            }
973
            if (*row == -1)
974
                dropNode = [browser parentForItemsInColumn:*column];
975
            
976
            if (!dropNode.shared && 
977
                (!dropNode.sharingAccount || 
978
                 ([dropNode class] == [PithosSubdirNode class]) || 
979
                 ([dropNode class] == [PithosContainerNode class]))) {
980
                *dropOperation = NSBrowserDropOn;
981
                result = NSDragOperationCopy;
982
            }
983
        }
984
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
985
        // For a drop above, the drop is redirected to the parent item
986
        if (*dropOperation == NSBrowserDropAbove) 
987
            *row = -1;
988
        // Only allow dropping in folders
989
        if (*column != -1) {
990
            PithosNode *dropNode;
991
            if (*row != -1) {
992
                // Check if the node is not a folder and if so redirect to the parent item
993
                dropNode = [browser itemAtRow:*row inColumn:*column];
994
                if ([dropNode class] == [PithosObjectNode class])
995
                    *row = -1;
996
            }
997
            if (*row == -1)
998
                dropNode = [browser parentForItemsInColumn:*column];
999
            
1000
            if (!dropNode.shared && !dropNode.sharingAccount) {
1001
                if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1002
                    // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1003
                    if ((([dropNode class] == [PithosContainerNode class]) || 
1004
                         dropNode.pithosObject.subdir || 
1005
                         ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
1006
                        ![dropNode isEqualTo:draggedParentNode]) { 
1007
    //                    ![dropNode isEqualTo:draggedParentNode] && 
1008
    //                    ![draggedNodes containsObject:dropNode]) {                
1009
                        result = NSDragOperationMove;
1010
                    }
1011
                } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1012
                    // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1013
                    if (([dropNode class] == [PithosContainerNode class]) || 
1014
                        dropNode.pithosObject.subdir || 
1015
                        ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
1016
                        result = NSDragOperationCopy;
1017
                    }
1018
                }
1019
            }
1020
        }
1021
    }
1022
    return result;
1023
}
1024

    
1025
- (BOOL)browser:(NSBrowser *)aBrowser 
1026
     acceptDrop:(id<NSDraggingInfo>)info 
1027
          atRow:(NSInteger)row 
1028
         column:(NSInteger)column 
1029
  dropOperation:(NSBrowserDropOperation)dropOperation {
1030
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1031
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1032
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1033
        if ((column != -1) && (filenames != nil)) {
1034
            PithosNode *node;
1035
            if (row != -1)
1036
                node = [browser itemAtRow:row inColumn:column];
1037
            else
1038
                node = [browser parentForItemsInColumn:column];
1039
            NSLog(@"drag in node: %@", node.url);
1040
            return [self uploadFiles:filenames toNode:node];
1041
        }
1042
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1043
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1044
        if ((column != -1) && (draggedNodes != nil)) {
1045
            PithosNode *node;
1046
            if (row != -1)
1047
                node = [browser itemAtRow:row inColumn:column];
1048
            else
1049
                node = [browser parentForItemsInColumn:column];
1050
            NSLog(@"drag local node: %@", node.url);
1051
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
1052
                return [self moveNodes:draggedNodes toNode:node];
1053
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1054
                return [self copyNodes:draggedNodes toNode:node];
1055
        }
1056
    }
1057
    return NO;
1058
}
1059

    
1060
#pragma mark -
1061
#pragma mark Drag and Drop methods
1062

    
1063
- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1064
    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1065
        return NO;
1066
    NSFileManager *fileManager = [NSFileManager defaultManager];
1067
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1068
    NSString *objectNamePrefix;
1069
    if ([destinationNode class] == [PithosSubdirNode class])
1070
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1071
    else
1072
        objectNamePrefix = [NSString string];
1073
    if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1074
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
1075
                                                                                                      containerName:containerName];
1076
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1077
        [networkQueue go];
1078
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1079
        if ([containerRequest error]) {
1080
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1081
            return NO;
1082
        } else if (containerRequest.responseStatusCode != 204) {
1083
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1084
            return NO;
1085
        }
1086
        destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1087
        destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1088
    }    
1089
    NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1090
    NSString *blockHash = destinationNode.pithosContainer.blockHash;
1091
    
1092
    for (NSString *filePath in filenames) {
1093
        BOOL isDirectory;
1094
        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1095
            if (!isDirectory) {
1096
                // Upload file
1097
                NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1098
                // Operation: Upload a local file
1099
                __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1100
                    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1101
                    if (operation.isCancelled) {
1102
                        [pool drain];
1103
                        return;
1104
                    }
1105
                    NSError *error = nil;
1106
                    NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1107
                    if (contentType == nil)
1108
                        contentType = @"application/octet-stream";
1109
                    if (error)
1110
                        NSLog(@"contentType detection error: %@", error);
1111
                    NSArray *hashes = nil;
1112
                    if (operation.isCancelled) {
1113
                        [pool drain];
1114
                        return;
1115
                    }
1116
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1117
                                                                                                containerName:containerName 
1118
                                                                                                   objectName:objectName 
1119
                                                                                                  contentType:contentType 
1120
                                                                                                    blockSize:blockSize 
1121
                                                                                                    blockHash:blockHash 
1122
                                                                                                      forFile:filePath 
1123
                                                                                                checkIfExists:YES 
1124
                                                                                                       hashes:&hashes 
1125
                                                                                               sharingAccount:destinationNode.sharingAccount];
1126
                    if (!operation.isCancelled && objectRequest) {
1127
                        objectRequest.delegate = self;
1128
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1129
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1130
                        NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1131
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1132
                                                                                   message:[messagePrefix stringByAppendingString:@" (0%)"]
1133
                                                                                totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1134
                                                                              currentBytes:0];
1135
                        [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1136
                         [NSDictionary dictionaryWithObjectsAndKeys:
1137
                          containerName, @"containerName", 
1138
                          objectName, @"objectName", 
1139
                          contentType, @"contentType", 
1140
                          [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
1141
                          blockHash, @"blockHash", 
1142
                          filePath, @"filePath", 
1143
                          hashes, @"hashes", 
1144
                          [NSArray arrayWithObject:destinationNode], @"refreshNodes", 
1145
                          [NSNumber numberWithBool:YES], @"refresh", 
1146
                          [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1147
                          activity, @"activity", 
1148
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1149
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1150
                          [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1151
                          [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1152
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1153
                          NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1154
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1155
                          uploadNetworkQueue, @"networkQueue", 
1156
                          @"upload", @"operationType", 
1157
                          nil]];
1158
                        if (destinationNode.sharingAccount)
1159
                            [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1160
                        [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1161
                    }
1162
                    [pool drain];
1163
                }];
1164
                [uploadQueue addOperation:operation];
1165
            } else {
1166
                // Upload directory, confirm first
1167
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1168
                [alert setMessageText:@"Upload directory"];
1169
                [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1170
                [alert addButtonWithTitle:@"OK"];
1171
                [alert addButtonWithTitle:@"Cancel"];
1172
                NSInteger choice = [alert runModal];
1173
                if (choice == NSAlertFirstButtonReturn) {
1174
                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1175
                    // Operation: Upload a local directory and its descendants
1176
                    // The resulting ASIPithosObjectRequests are chained through dependencies
1177
                    __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1178
                        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1179
                        if (operation.isCancelled) {
1180
                            [pool drain];
1181
                            return;
1182
                        }
1183
                        NSMutableArray *objectNames = nil;
1184
                        NSMutableArray *contentTypes = nil;
1185
                        NSMutableArray *filePaths = nil;
1186
                        NSMutableArray *hashesArrays = nil;
1187
                        NSMutableArray *directoryObjectRequests = nil;
1188
                        NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos 
1189
                                                                                       containerName:containerName 
1190
                                                                                          objectName:objectName 
1191
                                                                                           blockSize:blockSize 
1192
                                                                                           blockHash:blockHash 
1193
                                                                                        forDirectory:filePath 
1194
                                                                                       checkIfExists:YES 
1195
                                                                                         objectNames:&objectNames
1196
                                                                                        contentTypes:&contentTypes
1197
                                                                                           filePaths:&filePaths
1198
                                                                                        hashesArrays:&hashesArrays 
1199
                                                                             directoryObjectRequests:&directoryObjectRequests 
1200
                                                                                      sharingAccount:destinationNode.sharingAccount];
1201
                        if (operation.isCancelled) {
1202
                            [pool drain];
1203
                            return;
1204
                        }
1205
                        ASIPithosObjectRequest *previousObjectRequest = nil;
1206
                        for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1207
                            if (operation.isCancelled) {
1208
                                [pool drain];
1209
                                return;
1210
                            }
1211
                            objectRequest.delegate = self;
1212
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1213
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1214
                            NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1215
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1216
                                                                                       message:messagePrefix];
1217
                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1218
                             [NSDictionary dictionaryWithObjectsAndKeys:
1219
                              [NSNumber numberWithBool:YES], @"refresh", 
1220
                              activity, @"activity", 
1221
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1222
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1223
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1224
                              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1225
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1226
                              NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1227
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1228
                              uploadNetworkQueue, @"networkQueue", 
1229
                              @"upload", @"queue", 
1230
                              nil]];
1231
                            if (previousObjectRequest)
1232
                                [objectRequest addDependency:previousObjectRequest];
1233
                            previousObjectRequest = objectRequest;
1234
                            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1235
                        }
1236
                        if (!operation.isCancelled && objectRequests) {
1237
                            for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1238
                                if (operation.isCancelled) {
1239
                                    [pool drain];
1240
                                    return;
1241
                                }
1242
                                ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1243
                                objectRequest.delegate = self;
1244
                                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1245
                                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1246
                                NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1247
                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1248
                                                                                           message:[messagePrefix stringByAppendingString:@" (0%)"]
1249
                                                                                        totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1250
                                                                                      currentBytes:0];
1251
                                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1252
                                 [NSDictionary dictionaryWithObjectsAndKeys:
1253
                                  containerName, @"containerName", 
1254
                                  [objectNames objectAtIndex:i], @"objectName", 
1255
                                  [contentTypes objectAtIndex:i], @"contentType", 
1256
                                  [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
1257
                                  blockHash, @"blockHash", 
1258
                                  [filePaths objectAtIndex:i], @"filePath", 
1259
                                  [hashesArrays objectAtIndex:i], @"hashes", 
1260
                                  [NSNumber numberWithBool:YES], @"refresh", 
1261
                                  [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1262
                                  activity, @"activity", 
1263
                                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1264
                                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1265
                                  [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1266
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1267
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
1268
                                  NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1269
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1270
                                  uploadNetworkQueue, @"networkQueue", 
1271
                                  @"upload", @"queue", 
1272
                                  nil]];
1273
                                if (destinationNode.sharingAccount)
1274
                                    [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1275
                                if (previousObjectRequest)
1276
                                    [objectRequest addDependency:previousObjectRequest];
1277
                                previousObjectRequest = objectRequest;
1278
                                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1279
                            }
1280
                        }
1281
                        [pool drain];
1282
                    }];
1283
                    [uploadQueue addOperation:operation];
1284
                }
1285
            }
1286
        }
1287
    }
1288
    return YES;
1289
}
1290

    
1291
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1292
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1293
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1294
        return NO;
1295
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1296
    NSString *objectNamePrefix;
1297
    if ([destinationNode class] == [PithosSubdirNode class])
1298
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1299
    else
1300
        objectNamePrefix = [NSString string];
1301

    
1302
    for (PithosNode *node in nodes) {
1303
        if (([node class] == [PithosObjectNode class]) || 
1304
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1305
            // Operation: Move an object or subdir/ node
1306
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1307
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1308
                if (operation.isCancelled) {
1309
                    [pool drain];
1310
                    return;
1311
                }
1312
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1313
                if ([node.pithosObject.name hasSuffix:@"/"])
1314
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1315
                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1316
                                                                                       containerName:node.pithosContainer.name 
1317
                                                                                          objectName:node.pithosObject.name 
1318
                                                                            destinationContainerName:containerName 
1319
                                                                               destinationObjectName:destinationObjectName 
1320
                                                                                       checkIfExists:YES];
1321
                if (!operation.isCancelled && objectRequest) {
1322
                    objectRequest.delegate = self;
1323
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1324
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1325
                    NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1326
                                               [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1327
                                               [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1328
                                               [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1329
                                               [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1330
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1331
                                                                               message:messagePrefix];
1332
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1333
                     [NSDictionary dictionaryWithObjectsAndKeys:
1334
                      [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1335
                      activity, @"activity", 
1336
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1337
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1338
                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1339
                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1340
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1341
                      NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1342
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1343
                      moveNetworkQueue, @"networkQueue", 
1344
                      @"move", @"operationType", 
1345
                      nil]];
1346
                    [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1347
                }
1348
                [pool drain];
1349
            }];
1350
            [moveQueue addOperation:operation];
1351
        } else if ([node class] == [PithosSubdirNode class]) {
1352
            // Operation: Move a subdir node and its descendants
1353
            // The resulting ASIPithosObjectRequests are chained through dependencies
1354
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1355
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1356
                if (operation.isCancelled) {
1357
                    [pool drain];
1358
                    return;
1359
                }
1360
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1361
                if (node.pithosObject.subdir)
1362
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1363
                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
1364
                                                                                   containerName:node.pithosContainer.name 
1365
                                                                                      objectName:node.pithosObject.name 
1366
                                                                        destinationContainerName:containerName 
1367
                                                                           destinationObjectName:destinationObjectName 
1368
                                                                                   checkIfExists:YES];
1369
                if (!operation.isCancelled && objectRequests) {
1370
                    ASIPithosObjectRequest *previousObjectRequest = nil;
1371
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1372
                        if (operation.isCancelled) {
1373
                            [pool drain];
1374
                            return;
1375
                        }
1376
                        objectRequest.delegate = self;
1377
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1378
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1379
                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1380
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1381
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1382
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1383
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1384
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1385
                                                                                   message:messagePrefix];
1386
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1387
                         [NSDictionary dictionaryWithObjectsAndKeys:
1388
                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1389
                          [NSNumber numberWithBool:YES], @"refresh", 
1390
                          activity, @"activity", 
1391
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1392
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1393
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1394
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1395
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1396
                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1397
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1398
                          moveNetworkQueue, @"networkQueue", 
1399
                          @"move", @"operationType", 
1400
                          nil]];
1401
                        if (previousObjectRequest)
1402
                            [objectRequest addDependency:previousObjectRequest];
1403
                        previousObjectRequest = objectRequest;
1404
                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1405
                    }
1406
                }
1407
                [pool drain];
1408
            }];
1409
            [moveQueue addOperation:operation];
1410
        }
1411
    }
1412
    return YES;
1413
}
1414

    
1415
- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
1416
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1417
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1418
        return NO;
1419
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1420
    NSString *objectNamePrefix;
1421
    if ([destinationNode class] == [PithosSubdirNode class])
1422
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1423
    else
1424
        objectNamePrefix = [NSString string];
1425
    
1426
    for (PithosNode *node in nodes) {
1427
        if (([node class] == [PithosObjectNode class]) || 
1428
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1429
            // Operation: Copy an object or subdir/ node
1430
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1431
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1432
                if (operation.isCancelled) {
1433
                    [pool drain];
1434
                    return;
1435
                }
1436
                NSString *destinationObjectName;
1437
                if (![destinationNode isEqualTo:node.parent]) {
1438
                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1439
                    if ([node.pithosObject.name hasSuffix:@"/"])
1440
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1441
                } else {
1442
                    destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
1443
                                                                       containerName:containerName 
1444
                                                                          objectName:node.pithosObject.name];
1445
                }
1446
                if (operation.isCancelled) {
1447
                    [pool drain];
1448
                    return;
1449
                }
1450
                ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos 
1451
                                                                                       containerName:node.pithosContainer.name 
1452
                                                                                          objectName:node.pithosObject.name 
1453
                                                                            destinationContainerName:containerName 
1454
                                                                               destinationObjectName:destinationObjectName 
1455
                                                                                       checkIfExists:YES 
1456
                                                                                      sharingAccount:node.sharingAccount];
1457
                if (!operation.isCancelled && objectRequest) {
1458
                    objectRequest.delegate = self;
1459
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1460
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1461
                    NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1462
                                               [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1463
                                               [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1464
                                               [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1465
                                               [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1466
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1467
                                                                               message:messagePrefix];
1468
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1469
                     [NSDictionary dictionaryWithObjectsAndKeys:
1470
                      [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1471
                      activity, @"activity", 
1472
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1473
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1474
                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1475
                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1476
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1477
                      NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1478
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1479
                      copyNetworkQueue, @"networkQueue", 
1480
                      @"copy", @"operationType", 
1481
                      nil]];
1482
                    [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1483
                }
1484
                [pool drain];
1485
            }];
1486
            [copyQueue addOperation:operation];
1487
        } else if ([node class] == [PithosSubdirNode class]) {
1488
            // Operation: Copy a subdir node and its descendants
1489
            // The resulting ASIPithosObjectRequests are chained through dependencies
1490
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1491
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1492
                if (operation.isCancelled) {
1493
                    [pool drain];
1494
                    return;
1495
                }
1496
                NSString *destinationObjectName;
1497
                if (![destinationNode isEqualTo:node.parent]) {
1498
                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1499
                    if (node.pithosObject.subdir)
1500
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1501
                } else {
1502
                    destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
1503
                                                                       containerName:containerName 
1504
                                                                          subdirName:node.pithosObject.name];
1505
                }
1506
                if (operation.isCancelled) {
1507
                    [pool drain];
1508
                    return;
1509
                }
1510
                NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos 
1511
                                                                                   containerName:node.pithosContainer.name 
1512
                                                                                      objectName:node.pithosObject.name 
1513
                                                                        destinationContainerName:containerName 
1514
                                                                           destinationObjectName:destinationObjectName 
1515
                                                                                   checkIfExists:YES 
1516
                                                                                  sharingAccount:node.sharingAccount];
1517
                if (!operation.isCancelled && objectRequests) {
1518
                    ASIPithosObjectRequest *previousObjectRequest = nil;
1519
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1520
                        if (operation.isCancelled) {
1521
                            [pool drain];
1522
                            return;
1523
                        }
1524
                        objectRequest.delegate = self;
1525
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1526
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1527
                        NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1528
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1529
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1530
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1531
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1532
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1533
                                                                                   message:messagePrefix];
1534
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1535
                         [NSDictionary dictionaryWithObjectsAndKeys:
1536
                          [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1537
                          activity, @"activity", 
1538
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1539
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1540
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1541
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1542
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1543
                          NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1544
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1545
                          copyNetworkQueue, @"networkQueue", 
1546
                          @"copy", @"operationType", 
1547
                          nil]];
1548
                        if (previousObjectRequest)
1549
                            [objectRequest addDependency:previousObjectRequest];
1550
                        previousObjectRequest = objectRequest;
1551
                        [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1552
                    }
1553
                }
1554
                [pool drain];
1555
            }];
1556
            [copyQueue addOperation:operation];
1557
        }
1558
    }
1559
    return YES;
1560
}
1561

    
1562
#pragma mark -
1563
#pragma mark ASIHTTPRequestDelegate
1564

    
1565
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1566
    NSOperationQueue *callbackQueue;
1567
    NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1568
    if ([operationType isEqualToString:@"move"])
1569
        callbackQueue = moveCallbackQueue;
1570
    else if ([operationType isEqualToString:@"copy"])
1571
        callbackQueue = copyCallbackQueue;
1572
    else if ([operationType isEqualToString:@"delete"])
1573
        callbackQueue = deleteCallbackQueue;
1574
    else if ([operationType isEqualToString:@"upload"])
1575
        callbackQueue = uploadCallbackQueue;
1576
    else if ([operationType isEqualToString:@"download"])
1577
        callbackQueue = downloadCallbackQueue;
1578
    else
1579
        dispatch_async(dispatch_get_main_queue(), ^{
1580
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1581
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1582
        });
1583
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1584
    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1585
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1586
                                                                               object:request] autorelease];
1587
    operation.completionBlock = ^{
1588
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1589
        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1590
            dispatch_async(dispatch_get_main_queue(), ^{
1591
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1592
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1593
            });
1594
        }
1595
        [pool drain];
1596
    };
1597
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1598
    [callbackQueue addOperation:operation];
1599
}
1600

    
1601
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1602
    if (request.isCancelled) {
1603
        // Request has been cancelled 
1604
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1605
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1606
                   withObject:request];
1607
    } else {
1608
        NSOperationQueue *callbackQueue;
1609
        NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1610
        if ([operationType isEqualToString:@"move"])
1611
            callbackQueue = moveCallbackQueue;
1612
        else if ([operationType isEqualToString:@"copy"])
1613
            callbackQueue = copyCallbackQueue;
1614
        else if ([operationType isEqualToString:@"delete"])
1615
            callbackQueue = deleteCallbackQueue;
1616
        else if ([operationType isEqualToString:@"upload"])
1617
            callbackQueue = uploadCallbackQueue;
1618
        else if ([operationType isEqualToString:@"download"])
1619
            callbackQueue = downloadCallbackQueue;
1620
        else
1621
            dispatch_async(dispatch_get_main_queue(), ^{
1622
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1623
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1624
            });
1625
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1626
        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1627
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1628
                                                                                   object:request] autorelease];
1629
        operation.completionBlock = ^{
1630
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1631
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1632
                dispatch_async(dispatch_get_main_queue(), ^{
1633
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1634
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1635
                });
1636
            }
1637
            [pool drain];
1638
        };
1639
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1640
        [callbackQueue addOperation:operation];
1641
    }
1642
}
1643

    
1644
- (void)requestFailed:(ASIPithosRequest *)request {
1645
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1646
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1647
    NSLog(@"Request failed: %@", request.url);
1648
    if (operation.isCancelled) {
1649
        [pool drain];
1650
        return;        
1651
    }
1652
    if (request.isCancelled) {
1653
        dispatch_async(dispatch_get_main_queue(), ^{
1654
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1655
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1656
        });
1657
        [pool drain];
1658
        return;
1659
    }
1660
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1661
    if (retries > 0) {
1662
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1663
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1664
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1665
        [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1666
    } else {
1667
        dispatch_async(dispatch_get_main_queue(), ^{
1668
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1669
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1670
            if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1671
                [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1672
            else
1673
                [PithosUtilities httpRequestErrorAlertWithRequest:request];
1674
        });
1675
    }
1676
    [pool drain];
1677
}
1678

    
1679
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1680
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1681
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1682
    NSLog(@"Download finished: %@", objectRequest.url);
1683
    if (operation.isCancelled) {
1684
        [self requestFailed:objectRequest];
1685
    } else if (objectRequest.responseStatusCode == 200) {
1686
        NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1687
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1688
        NSUInteger totalBytes = activity.totalBytes;
1689
        
1690
        // XXX change contentLength to objectContentLength if it is fixed in the server
1691
        if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1692
            NSLog(@"Downloaded  0 bytes");
1693
            NSFileManager *fileManager = [NSFileManager defaultManager];
1694
            if (![fileManager fileExistsAtPath:filePath]) {
1695
                if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1696
                    dispatch_async(dispatch_get_main_queue(), ^{
1697
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1698
                        [alert setMessageText:@"Create File Error"];
1699
                        [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1700
                        [alert addButtonWithTitle:@"OK"];
1701
                        [alert runModal];
1702
                    });
1703
                }
1704
            }
1705
        }
1706
        
1707
        NSUInteger currentBytes = [objectRequest objectContentLength];
1708
        if (currentBytes == 0)
1709
            currentBytes = totalBytes;
1710
        dispatch_async(dispatch_get_main_queue(), ^{
1711
            [activityFacility endActivity:activity 
1712
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1713
                               totalBytes:totalBytes 
1714
                             currentBytes:currentBytes];
1715
        });
1716
    } else {
1717
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1718
        [self requestFailed:objectRequest];
1719
    }
1720
    [pool drain];
1721
}
1722

    
1723
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1724
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1725
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1726
    NSLog(@"Upload directory object finished: %@", objectRequest.url);
1727
    if (operation.isCancelled) {
1728
        [self requestFailed:objectRequest];
1729
    } else if (objectRequest.responseStatusCode == 201) {
1730
        NSLog(@"Directory object created: %@", objectRequest.url);
1731
        dispatch_async(dispatch_get_main_queue(), ^{
1732
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1733
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1734
        });
1735
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1736
            [node forceRefresh];
1737
        }
1738
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1739
            [node refresh];
1740
        }
1741
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1742
            [self forceRefresh:self];
1743
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1744
            [self refresh:self];
1745
    } else {
1746
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1747
        [self requestFailed:objectRequest];
1748
    }
1749
    [pool drain];
1750
}
1751

    
1752
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1753
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1754
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1755
    NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1756
    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1757
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1758
    NSUInteger totalBytes = activity.totalBytes;
1759
    NSUInteger currentBytes = activity.currentBytes;
1760
    if (operation.isCancelled) {
1761
        [self requestFailed:objectRequest];
1762
    } else if (objectRequest.responseStatusCode == 201) {
1763
        NSLog(@"Object created: %@", objectRequest.url);
1764
        dispatch_async(dispatch_get_main_queue(), ^{
1765
            [activityFacility endActivity:activity 
1766
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1767
                               totalBytes:totalBytes 
1768
                             currentBytes:totalBytes];
1769
        });
1770
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1771
            [node forceRefresh];
1772
        }
1773
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1774
            [node refresh];
1775
        }
1776
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1777
            [self forceRefresh:self];
1778
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1779
            [self refresh:self];        
1780
    } else if (objectRequest.responseStatusCode == 409) {
1781
        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1782
        if (iteration == 0) {
1783
            NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1784
            dispatch_async(dispatch_get_main_queue(), ^{
1785
                [activityFacility endActivity:activity 
1786
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]]; 
1787
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1788
                [alert setMessageText:@"Upload Timeout"];
1789
                [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
1790
                                           [objectRequest.userInfo objectForKey:@"objectName"]]];
1791
                [alert addButtonWithTitle:@"OK"];
1792
                [alert runModal];
1793
            });
1794
            [pool drain];
1795
            return;
1796
        }
1797
        NSLog(@"object is missing hashes: %@", objectRequest.url);
1798
        NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1799
                                                          withMissingHashes:[objectRequest hashes]];
1800
        NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1801
        if (totalBytes >= [missingBlocks count]*blockSize)
1802
            currentBytes = totalBytes - [missingBlocks count]*blockSize;
1803
        dispatch_async(dispatch_get_main_queue(), ^{
1804
            [activityFacility updateActivity:activity 
1805
                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)] 
1806
                                  totalBytes:totalBytes 
1807
                                currentBytes:currentBytes];
1808
        });
1809
        NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1810
        __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1811
                                                                                                         containerName:[objectRequest.userInfo objectForKey:@"containerName"] 
1812
                                                                                                             blockSize:blockSize 
1813
                                                                                                               forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1814
                                                                                                     missingBlockIndex:missingBlockIndex 
1815
                                                                                                        sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1816
        newContainerRequest.delegate = self;
1817
        newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1818
        newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1819
        newContainerRequest.userInfo = objectRequest.userInfo;
1820
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1821
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1822
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1823
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1824
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1825
        [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1826
            [activityFacility updateActivity:activity 
1827
                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1828
                                  totalBytes:activity.totalBytes 
1829
                                currentBytes:(activity.currentBytes + size)];
1830
        }];
1831
        [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1832
    } else {
1833
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1834
        [self requestFailed:objectRequest];
1835
    }
1836
    [pool drain];
1837
}
1838

    
1839
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1840
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1841
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1842
    NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1843
    NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1844
    NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1845
    PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1846
    if (operation.isCancelled) {
1847
        [self requestFailed:containerRequest];
1848
    } else if (containerRequest.responseStatusCode == 202) {
1849
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1850
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1851
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1852
        if (missingBlockIndex == NSNotFound) {
1853
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1854
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1855
                                                                                           containerName:[containerRequest.userInfo objectForKey:@"containerName"] 
1856
                                                                                              objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
1857
                                                                                             contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
1858
                                                                                               blockSize:blockSize 
1859
                                                                                               blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1860
                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1861
                                                                                           checkIfExists:NO 
1862
                                                                                                  hashes:&hashes 
1863
                                                                                          sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1864
            newObjectRequest.delegate = self;
1865
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1866
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1867
            newObjectRequest.userInfo = containerRequest.userInfo;
1868
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1869
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1870
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1871
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1872
            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1873
        } else {
1874
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1875
                                                                                                             containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1876
                                                                                                                 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1877
                                                                                                                   forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1878
                                                                                                         missingBlockIndex:missingBlockIndex 
1879
                                                                                                            sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1880
            newContainerRequest.delegate = self;
1881
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1882
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1883
            newContainerRequest.userInfo = containerRequest.userInfo;
1884
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1885
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1886
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1887
                [activityFacility updateActivity:activity 
1888
                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1889
                                      totalBytes:activity.totalBytes 
1890
                                    currentBytes:(activity.currentBytes + size)];
1891
            }];
1892
            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1893
        }
1894
    } else {
1895
        [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1896
        [self requestFailed:containerRequest];
1897
    }
1898
    [pool drain];
1899
}
1900

    
1901
- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1902
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1903
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1904
    NSLog(@"Move object finished: %@", objectRequest.url);
1905
    if (operation.isCancelled) {
1906
        [self requestFailed:objectRequest];
1907
    } else if (objectRequest.responseStatusCode == 201) {
1908
        dispatch_async(dispatch_get_main_queue(), ^{
1909
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1910
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1911
        });
1912
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1913
            [node forceRefresh];
1914
        }
1915
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1916
            [node refresh];
1917
        }
1918
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1919
            [self forceRefresh:self];
1920
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1921
            [self refresh:self];
1922
    } else {
1923
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1924
        [self requestFailed:objectRequest];
1925
    }
1926
    [pool drain];
1927
}
1928

    
1929
- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1930
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1931
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1932
    NSLog(@"Copy object finished: %@", objectRequest.url);
1933
    if (operation.isCancelled) {
1934
        [self requestFailed:objectRequest];
1935
    } else if (objectRequest.responseStatusCode == 201) {
1936
        dispatch_async(dispatch_get_main_queue(), ^{
1937
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1938
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1939
        });
1940
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1941
            [node forceRefresh];
1942
        }
1943
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1944
            [node refresh];
1945
        }
1946
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1947
            [self forceRefresh:self];
1948
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1949
            [self refresh:self];
1950
    } else {
1951
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1952
        [self requestFailed:objectRequest];
1953
    }
1954
    [pool drain];
1955
}
1956

    
1957
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1958
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1959
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1960
    NSLog(@"Delete object finished: %@", objectRequest.url);
1961
    if (operation.isCancelled) {
1962
        [self requestFailed:objectRequest];
1963
    } else if (objectRequest.responseStatusCode == 204) {
1964
        dispatch_async(dispatch_get_main_queue(), ^{
1965
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1966
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1967
        });
1968
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1969
            [node forceRefresh];
1970
        }
1971
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1972
            [node refresh];
1973
        }
1974
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1975
            [self forceRefresh:self];
1976
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1977
            [self refresh:self];
1978
    } else {
1979
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1980
        [self requestFailed:objectRequest];
1981
    }
1982
    [pool drain];
1983
}
1984

    
1985
#pragma mark -
1986
#pragma mark NSSplitViewDelegate
1987

    
1988
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1989
    if (splitView == verticalSplitView)
1990
        return 120;
1991
    else
1992
        return ([horizontalSplitView bounds].size.height - 108);
1993
}
1994

    
1995
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1996
    if (splitView == verticalSplitView)
1997
        return 220;
1998
    else
1999
        return ([horizontalSplitView bounds].size.height - 108);
2000
}
2001

    
2002
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2003
    if (splitView == verticalSplitView) {
2004
        if (proposedPosition < 120)
2005
            return 120;
2006
        else if (proposedPosition > 220)
2007
            return 220;
2008
        else
2009
            return proposedPosition;
2010
    } else {
2011
        return ([horizontalSplitView bounds].size.height - 108);
2012
    }
2013
}
2014

    
2015
#pragma mark -
2016
#pragma mark NSOutlineViewDataSource
2017

    
2018
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2019
    if (item == nil)
2020
        return 2;
2021
    if (item == containersNode)
2022
        return containersNodeChildren.count;
2023
    if (item == sharedNode)
2024
        return 2;
2025
    return 0;
2026
}
2027

    
2028
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2029
    if (item == nil)
2030
        return (!index ? containersNode : sharedNode);
2031
    if (item == sharedNode)
2032
        return (!index ? mySharedNode : othersSharedNode);
2033
    return [containersNodeChildren objectAtIndex:index];
2034
}
2035

    
2036
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2037
    if ((item == containersNode) || (item == sharedNode))
2038
        return YES;
2039
    return NO;
2040
}
2041

    
2042
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2043
    PithosNode *node = (PithosNode *)item;
2044
    return node;    
2045
}
2046

    
2047
#pragma mark Drag and Drop destination
2048

    
2049
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2050
                  validateDrop:(id<NSDraggingInfo>)info 
2051
                  proposedItem:(id)item 
2052
            proposedChildIndex:(NSInteger)index {
2053
    NSDragOperation result = NSDragOperationNone;
2054
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2055
        return result;
2056
    PithosNode *dropNode = (PithosNode *)item;
2057
    if ([dropNode class] != [PithosContainerNode class])
2058
        return result;
2059
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2060
        result = NSDragOperationCopy;
2061
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2062
        if ([info draggingSourceOperationMask] & NSDragOperationMove) {
2063
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2064
            if (![dropNode isEqualTo:draggedParentNode])
2065
                result = NSDragOperationMove;
2066
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2067
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2068
            result = NSDragOperationCopy;
2069
        }
2070
    }
2071
   return result;
2072
}
2073

    
2074
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2075
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2076
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2077
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2078
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2079
            PithosNode *node = (PithosNode *)item;
2080
            NSLog(@"drag in node: %@", node.url);
2081
            return [self uploadFiles:filenames toNode:node];
2082
        }
2083
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2084
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2085
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2086
            PithosNode *node = (PithosNode *)item;
2087
            NSLog(@"drag local node: %@", node.url);
2088
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
2089
                return [self moveNodes:draggedNodes toNode:node];
2090
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2091
                return [self copyNodes:draggedNodes toNode:node];
2092
        }
2093
    }
2094
    return NO;
2095
}
2096

    
2097
#pragma mark -
2098
#pragma mark NSOutlineViewDelegate
2099

    
2100
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2101
    if ((item == containersNode) || (item == sharedNode))
2102
        return NO;
2103
    return YES;
2104
}
2105

    
2106
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2107
    if ((item == containersNode) || (item == sharedNode))
2108
        return YES;
2109
    return NO;
2110
}
2111

    
2112
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2113
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2114
    if (node) {
2115
        rootNode = node;
2116
        [browser loadColumnZero];
2117
        [self refresh:nil];
2118
    }
2119
}
2120

    
2121
#pragma mark -
2122
#pragma mark NSMenuDelegate
2123

    
2124
- (void)menuNeedsUpdate:(NSMenu *)menu {
2125
    [menu removeAllItems];
2126
    NSMenuItem *menuItem;
2127
    NSString *menuItemTitle;
2128
    BOOL nodeContextMenu = NO;
2129
    PithosNode *menuNode = nil;
2130
    NSMutableArray *menuNodes;
2131
    if (menu == browserMenu) {
2132
        NSInteger column = [browser clickedColumn];
2133
        NSInteger row = [browser clickedRow];
2134
        if ((column == -1) || (row == -1)) {
2135
            // General context menu
2136
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2137
            if ([menuNodesIndexPaths count] == 0) {
2138
                menuNode = [browser parentForItemsInColumn:0];
2139
            } else if (([menuNodesIndexPaths count] != 1) || 
2140
                       ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2141
                menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2142
            } else {
2143
                menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2144
            }
2145
        } else {
2146
            // Node context menu
2147
            NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2148
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2149
            menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2150
            if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2151
                for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2152
                    [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2153
                }
2154
            } else {
2155
                [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2156
            }
2157
            nodeContextMenu = YES;
2158
        }
2159
    } else if (menu == outlineViewMenu) {
2160
        NSInteger row = [outlineView clickedRow];
2161
        if (row == -1)
2162
            row = [outlineView selectedRow];
2163
        if (row == -1)
2164
            return;
2165
        menuNode = [outlineView itemAtRow:row];
2166
    }
2167

    
2168
    if (!nodeContextMenu) {
2169
        // General context menu
2170
        if (([menuNode class] == [PithosAccountNode class]) || 
2171
            ([menuNode class] == [PithosSharingAccountsNode class]) ||
2172
            ([menuNode class] == [PithosEmptyNode class]))
2173
            return;
2174
        BOOL shared = menuNode.shared;
2175
        BOOL sharingAccount = (menuNode.sharingAccount != nil);
2176
        // New Folder
2177
        if (!shared && !sharingAccount) {
2178
            menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2179
            [menuItem setRepresentedObject:menuNode];
2180
            [menu addItem:menuItem];
2181
            [menu addItem:[NSMenuItem separatorItem]];
2182
        }
2183
        // Refresh
2184
        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2185
        [menu addItem:menuItem];
2186
        [menu addItem:[NSMenuItem separatorItem]];
2187
        // Get Info
2188
        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2189
        [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2190
        [menu addItem:menuItem];
2191
        // Paste
2192
        if (!shared && !sharingAccount) {
2193
            if (clipboardNodes) {
2194
                NSUInteger clipboardNodesCount = [clipboardNodes count];
2195
                if (clipboardNodesCount == 0) {
2196
                    self.clipboardNodes = nil;
2197
                } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2198
                    if (clipboardNodesCount == 1)
2199
                        menuItemTitle = [NSString stringWithString:@"Paste Item"];
2200
                    else
2201
                        menuItemTitle = [NSString stringWithString:@"Paste Items"];
2202
                    [menu addItem:[NSMenuItem separatorItem]];
2203
                    menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2204
                    [menuItem setRepresentedObject:menuNode];
2205
                    [menu addItem:menuItem];
2206
                }
2207
            }
2208
        }
2209
    } else {
2210
        // Node context menu
2211
        NSUInteger menuNodesCount = [menuNodes count];
2212
        PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
2213
        BOOL shared = firstMenuNode.shared;
2214
        BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
2215
        // Move to Trash (pithos container only)
2216
        // Delete
2217
        if (!shared && !sharingAccount) {
2218
            if ([rootNode class] == [PithosContainerNode class]) {
2219
                if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2220
                    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
2221
                    [menuItem setRepresentedObject:menuNodes];
2222
                    [menu addItem:menuItem];
2223
                }
2224
                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2225
                [menuItem setRepresentedObject:menuNodes];
2226
                [menu addItem:menuItem];
2227
                [menu addItem:[NSMenuItem separatorItem]];
2228
            }
2229
        }
2230
        // Refresh
2231
        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2232
        [menu addItem:menuItem];
2233
        [menu addItem:[NSMenuItem separatorItem]];        
2234
        // Get Info
2235
        if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2236
            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2237
            [menuItem setRepresentedObject:menuNodes];
2238
            [menu addItem:menuItem];
2239
            
2240
            if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2241
                [menu addItem:[NSMenuItem separatorItem]];
2242
        }
2243
        // Cut
2244
        if (!shared && !sharingAccount) {
2245
            if (menuNodesCount == 1)
2246
                menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2247
            else 
2248
                menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2249
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2250
            [menuItem setRepresentedObject:menuNodes];
2251
            [menu addItem:menuItem];
2252
        }
2253
        // Copy
2254
        if ((!shared && !sharingAccount) || 
2255
            (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2256
            if (menuNodesCount == 1)
2257
                menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2258
            else 
2259
                menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2260
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2261
            [menuItem setRepresentedObject:menuNodes];
2262
            [menu addItem:menuItem];
2263
        }
2264
        // Paste
2265
        if (!shared && !sharingAccount) {
2266
            if (menuNodesCount == 1) {
2267
                PithosNode *menuNode = [menuNodes objectAtIndex:0];
2268
                if (([menuNode class] == [PithosSubdirNode class]) && 
2269
                    (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
2270
                    if (clipboardNodes) {
2271
                        NSUInteger clipboardNodesCount = [clipboardNodes count];
2272
                        if (clipboardNodesCount == 0) {
2273
                            self.clipboardNodes = nil;
2274
                        } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2275
                            if (clipboardNodesCount == 1)
2276
                                menuItemTitle = [NSString stringWithString:@"Paste Item"];
2277
                            else
2278
                                menuItemTitle = [NSString stringWithString:@"Paste Items"];
2279
                            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2280
                            [menuItem setRepresentedObject:menuNode];
2281
                            [menu addItem:menuItem];
2282
                        }
2283
                    }
2284
                }
2285
            }
2286
        }
2287
    }
2288
}
2289

    
2290
#pragma mark -
2291
#pragma mark Menu Actions
2292

    
2293
- (void)menuNewFolder:(NSMenuItem *)sender {
2294
    PithosNode *node = (PithosNode *)[sender representedObject];
2295
    if ([node class] == [PithosContainerNode class]) {
2296
        // Operation: Create (upload) a new root application/directory object
2297
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2298
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2299
            if (operation.isCancelled) {
2300
                [pool drain];
2301
                return;
2302
            }
2303
            NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2304
                                                                  containerName:node.pithosContainer.name 
2305
                                                                     subdirName:@"untitled folder"];
2306
            NSString *fileName = [safeObjectName lastPathComponent];
2307
            if (operation.isCancelled) {
2308
                [pool drain];
2309
                return;
2310
            }
2311
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
2312
                                                                                               containerName:node.pithosContainer.name 
2313
                                                                                                  objectName:safeObjectName 
2314
                                                                                                        eTag:nil 
2315
                                                                                                 contentType:@"application/directory" 
2316
                                                                                             contentEncoding:nil 
2317
                                                                                          contentDisposition:nil 
2318
                                                                                                    manifest:nil 
2319
                                                                                                     sharing:nil 
2320
                                                                                                    isPublic:ASIPithosObjectRequestPublicIgnore 
2321
                                                                                                    metadata:nil 
2322
                                                                                                        data:[NSData data]];
2323
            objectRequest.delegate = self;
2324
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2325
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2326
            NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2327
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2328
                                                                       message:messagePrefix];
2329
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2330
                                      fileName, @"fileName", 
2331
                                      [NSArray arrayWithObject:node], @"refreshNodes", 
2332
                                      [NSNumber numberWithBool:YES], @"refresh", 
2333
                                      activity, @"activity", 
2334
                                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2335
                                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2336
                                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2337
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2338
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
2339
                                      NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2340
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2341
                                      uploadNetworkQueue, @"networkQueue", 
2342
                                      @"upload", @"operationType", 
2343
                                      nil];
2344
            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2345
            [pool drain];
2346
        }];
2347
        [uploadQueue addOperation:operation];
2348
    } else if (([node class] == [PithosSubdirNode class]) && 
2349
               (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2350
        // Operation: Create (upload) a new aplication/directory object
2351
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2352
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2353
            if (operation.isCancelled) {
2354
                [pool drain];
2355
                return;
2356
            }
2357
            NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2358
                                                                  containerName:node.pithosContainer.name 
2359
                                                                     subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2360
            NSString *fileName = [safeObjectName lastPathComponent];
2361
            if (operation.isCancelled) {
2362
                [pool drain];
2363
                return;
2364
            }
2365
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
2366
                                                                                               containerName:node.pithosContainer.name 
2367
                                                                                                  objectName:safeObjectName 
2368
                                                                                                        eTag:nil 
2369
                                                                                                 contentType:@"application/directory" 
2370
                                                                                             contentEncoding:nil 
2371
                                                                                          contentDisposition:nil 
2372
                                                                                                    manifest:nil 
2373
                                                                                                     sharing:nil 
2374
                                                                                                    isPublic:ASIPithosObjectRequestPublicIgnore 
2375
                                                                                                    metadata:nil 
2376
                                                                                                        data:[NSData data]];
2377
            objectRequest.delegate = self;
2378
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2379
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2380
            NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2381
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2382
                                                                       message:messagePrefix];
2383
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2384
                                      fileName, @"fileName", 
2385
                                      [NSArray arrayWithObject:node], @"refreshNodes", 
2386
                                      [NSNumber numberWithBool:YES], @"refresh", 
2387
                                      activity, @"activity", 
2388
                                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2389
                                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2390
                                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2391
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2392
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
2393
                                      NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2394
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2395
                                      uploadNetworkQueue, @"networkQueue", 
2396
                                      @"upload", @"operationType", 
2397
                                      nil];
2398
            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2399
            [pool drain];
2400
        }];
2401
        [uploadQueue addOperation:operation];
2402
    }
2403
}
2404

    
2405
- (void)menuGetInfo:(NSMenuItem *)sender {
2406
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2407
        [node showPithosNodeInfo:sender];
2408
    }
2409
}
2410

    
2411
- (void)menuDelete:(NSMenuItem *)sender {
2412
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2413
        if (([node class] == [PithosObjectNode class]) || 
2414
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2415
            // Operation: Delete an object or subdir/ node
2416
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2417
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2418
                if (operation.isCancelled) {
2419
                    [pool drain];
2420
                    return;
2421
                }
2422
                NSString *fileName = [node.pithosObject.name lastPathComponent];
2423
                if ([node.pithosObject.name hasSuffix:@"/"])
2424
                    fileName = [fileName stringByAppendingString:@"/"];
2425
                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
2426
                                                                                                containerName:node.pithosContainer.name 
2427
                                                                                                   objectName:node.pithosObject.name];
2428
                if (operation.isCancelled) {
2429
                    [pool drain];
2430
                    return;
2431
                }
2432
                objectRequest.delegate = self;
2433
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2434
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2435
                NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2436
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2437
                                                                           message:messagePrefix];
2438
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2439
                                          fileName, @"fileName", 
2440
                                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2441
                                          activity, @"activity", 
2442
                                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2443
                                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2444
                                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2445
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2446
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
2447
                                          NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2448
                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2449
                                          deleteNetworkQueue, @"networkQueue", 
2450
                                          @"delete", @"operationType", 
2451
                                          nil];
2452
                [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2453
                [pool drain];
2454
            }];
2455
            [deleteQueue addOperation:operation];
2456
        } else if ([node class] == [PithosSubdirNode class]) {
2457
            // Operation: Delete a subdir node and its descendants
2458
            // The resulting ASIPithosObjectRequests are chained through dependencies
2459
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2460
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2461
                if (operation.isCancelled) {
2462
                    [pool drain];
2463
                    return;
2464
                }
2465
                NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos 
2466
                                                                                     containerName:node.pithosContainer.name 
2467
                                                                                        objectName:node.pithosObject.name];
2468
                if (!operation.isCancelled && objectRequests) {
2469
                    ASIPithosObjectRequest *previousObjectRequest = nil;
2470
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2471
                        if (operation.isCancelled) {
2472
                            [pool drain];
2473
                            return;
2474
                        }
2475
                        objectRequest.delegate = self;
2476
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2477
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2478
                        NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2479
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2480
                                                                                   message:messagePrefix];
2481
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2482
                         [NSDictionary dictionaryWithObjectsAndKeys:
2483
                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2484
                          activity, @"activity", 
2485
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2486
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2487
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2488
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2489
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
2490
                          NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2491
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2492
                          deleteNetworkQueue, @"networkQueue", 
2493
                          @"delete", @"operationType", 
2494
                          nil]];
2495
                        if (previousObjectRequest)
2496
                            [objectRequest addDependency:previousObjectRequest];
2497
                        previousObjectRequest = objectRequest;
2498
                        [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2499
                    }
2500
                }
2501
                [pool drain];
2502
            }];
2503
            [deleteQueue addOperation:operation];
2504
        }
2505
    }
2506
}
2507

    
2508
- (void)menuMoveToTrash:(NSMenuItem *)sender {
2509
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2510
        if (([node class] == [PithosObjectNode class]) || 
2511
            (([node class] == [PithosSubdirNode class]) && 
2512
             !node.pithosObject.subdir &&
2513
             [node.pithosObject.name hasSuffix:@"/"])) {
2514
            // Operation: Move to trash an object or subdir/ node
2515
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2516
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2517
                if (operation.isCancelled) {
2518
                    [pool drain];
2519
                    return;
2520
                }
2521
                NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
2522
                                                                      containerName:@"trash" 
2523
                                                                         objectName:node.pithosObject.name];
2524
                if (!operation.isCancelled && safeObjectName) {
2525
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
2526
                                                                                           containerName:node.pithosContainer.name 
2527
                                                                                              objectName:node.pithosObject.name 
2528
                                                                                destinationContainerName:@"trash" 
2529
                                                                                   destinationObjectName:safeObjectName 
2530
                                                                                           checkIfExists:NO];
2531
                    if (!operation.isCancelled && objectRequest) {
2532
                        objectRequest.delegate = self;
2533
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2534
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2535
                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2536
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2537
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2538
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2539
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2540
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2541
                                                                                   message:messagePrefix];
2542
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2543
                         [NSDictionary dictionaryWithObjectsAndKeys:
2544
                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2545
                          activity, @"activity", 
2546
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2547
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2548
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2549
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2550
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
2551
                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2552
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2553
                          moveNetworkQueue, @"networkQueue", 
2554
                          @"move", @"operationType", 
2555
                          nil]];
2556
                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2557
                    }
2558
                }
2559
                [pool drain];
2560
            }];
2561
            [moveQueue addOperation:operation];
2562
        } else if ([node class] == [PithosSubdirNode class]) {
2563
            // Operation: Move to trash a subdir node and its descendants
2564
            // The resulting ASIPithosObjectRequests are chained through dependencies
2565
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2566
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2567
                if (operation.isCancelled) {
2568
                    [pool drain];
2569
                    return;
2570
                }
2571
                NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2572
                                                                      containerName:@"trash" 
2573
                                                                         subdirName:node.pithosObject.name];
2574
                if (!operation.isCancelled && safeObjectName) {
2575
                    NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
2576
                                                                                       containerName:node.pithosContainer.name 
2577
                                                                                          objectName:node.pithosObject.name 
2578
                                                                            destinationContainerName:@"trash" 
2579
                                                                               destinationObjectName:safeObjectName 
2580
                                                                                       checkIfExists:NO];
2581
                    if (!operation.isCancelled && objectRequests) {
2582
                        ASIPithosObjectRequest *previousObjectRequest = nil;
2583
                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2584
                            if (operation.isCancelled) {
2585
                                [pool drain];
2586
                                return;
2587
                            }
2588
                            objectRequest.delegate = self;
2589
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2590
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2591
                            NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2592
                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2593
                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2594
                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2595
                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2596
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2597
                                                                                       message:messagePrefix];
2598
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2599
                             [NSDictionary dictionaryWithObjectsAndKeys:
2600
                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2601
                              activity, @"activity", 
2602
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2603
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2604
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2605
                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2606
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
2607
                              NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2608
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2609
                              moveNetworkQueue, @"networkQueue", 
2610
                              @"move", @"operationType", 
2611
                              nil]];
2612
                            if (previousObjectRequest)
2613
                                [objectRequest addDependency:previousObjectRequest];
2614
                            previousObjectRequest = objectRequest;
2615
                            [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2616
                        }
2617
                    }
2618
                }
2619
                [pool drain];
2620
            }];
2621
            [moveQueue addOperation:operation];
2622
        }
2623
    }
2624
}
2625

    
2626
- (void)menuCut:(NSMenuItem *)sender {
2627
    self.clipboardNodes = (NSArray *)[sender representedObject];
2628
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2629
    self.clipboardCopy = NO;
2630
}
2631

    
2632
- (void)menuCopy:(NSMenuItem *)sender {
2633
    self.clipboardNodes = (NSArray *)[sender representedObject];
2634
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2635
    self.clipboardCopy = YES;
2636
}
2637

    
2638
- (void)menuPaste:(NSMenuItem *)sender {
2639
    if (!clipboardNodes || ![clipboardNodes count])
2640
        return;
2641
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2642
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2643
    if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2644
        self.clipboardNodes = nil;
2645
        self.clipboardParentNode = nil;
2646
        [self moveNodes:localClipboardNodes toNode:dropNode];
2647
    } else {
2648
        [self copyNodes:localClipboardNodes toNode:dropNode];
2649
    }
2650
}
2651
    
2652
#pragma mark -
2653
#pragma mark PithosActivityFacilityDelegate
2654

    
2655
- (void)activityUpdate:(NSDictionary *)info {
2656
    NSString *message = [info objectForKey:@"message"];
2657
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2658
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2659
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2660
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2661
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2662
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2663
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2664
    if (runningActivitiesCount && totalBytes) {
2665
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2666
        [activityProgressIndicator startAnimation:self];
2667
    } else {
2668
        [activityProgressIndicator setDoubleValue:1.0];
2669
        [activityProgressIndicator stopAnimation:self];
2670
    }
2671
    
2672
    if (!message)
2673
        message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2674
    [activityTextField setStringValue:message];
2675
}
2676

    
2677
@end