Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ 32ae06f7

History | View | Annotate | Download (155.5 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
    // PithosAccountNode accountNode updates outlineView container nodes 
227
    [[NSNotificationCenter defaultCenter] addObserver:self 
228
                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
229
                                                 name:@"PithosNodeChildrenUpdated" 
230
                                               object:accountNode];
231
    // PithosNode updates browser nodes
232
    [[NSNotificationCenter defaultCenter] addObserver:self 
233
                                             selector:@selector(pithosNodeChildrenUpdated:) 
234
                                                 name:@"PithosNodeChildrenUpdated" 
235
                                               object:nil];
236
    // Request for browser refresh 
237
    [[NSNotificationCenter defaultCenter] addObserver:self 
238
                                             selector:@selector(pithosBrowserRefreshNeeded:) 
239
                                                 name:@"PithosBrowserRefreshNeeeded" 
240
                                               object:nil];
241
}
242

    
243
- (void)resetBrowser {
244
    @synchronized(self) {
245
        if (!browserActive)
246
            return;
247
    }
248

    
249
    [refreshTimer invalidate];
250
    [refreshTimer release];
251
    
252
    [moveNetworkQueue reset];
253
    [copyNetworkQueue reset];
254
    [deleteNetworkQueue reset];
255
    [uploadNetworkQueue reset];
256
    [downloadNetworkQueue reset];
257
    
258
    [moveQueue cancelAllOperations];
259
    [moveQueue setSuspended:YES];
260
    [copyQueue cancelAllOperations];
261
    [copyQueue setSuspended:YES];
262
    [deleteQueue cancelAllOperations];
263
    [deleteQueue setSuspended:YES];
264
    [uploadQueue cancelAllOperations];
265
    [uploadQueue setSuspended:YES];
266
    [downloadQueue cancelAllOperations];
267
    [downloadQueue setSuspended:YES];
268

    
269
    [moveCallbackQueue cancelAllOperations];
270
    [moveCallbackQueue setSuspended:YES];
271
    [copyCallbackQueue cancelAllOperations];
272
    [copyCallbackQueue setSuspended:YES];
273
    [deleteCallbackQueue cancelAllOperations];
274
    [deleteCallbackQueue setSuspended:YES];
275
    [uploadCallbackQueue cancelAllOperations];
276
    [uploadCallbackQueue setSuspended:YES];
277
    [downloadCallbackQueue cancelAllOperations];
278
    [downloadCallbackQueue setSuspended:YES];
279

    
280
    rootNode = nil;
281
    [browser loadColumnZero];
282
    [containersNodeChildren removeAllObjects];
283
    [outlineView reloadData];
284
    // Expand the folder outline view
285
    [outlineView expandItem:nil expandChildren:YES];
286
    [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
287
    
288
    activityFacility.delegate = nil;
289
    [activityProgressIndicator setDoubleValue:1.0];
290
    [activityProgressIndicator stopAnimation:self];
291
    
292
    @synchronized(self) {
293
        browserActive = NO;
294
    }
295
}
296

    
297
- (void)startBrowser {
298
    @synchronized(self) {
299
        if (browserActive)
300
            return;
301
    }
302
    
303
    // In the improbable case of leftover operations
304
    [moveNetworkQueue reset];
305
    [copyNetworkQueue reset];
306
    [deleteNetworkQueue reset];
307
    [uploadNetworkQueue reset];
308
    [downloadNetworkQueue reset];
309
    [moveQueue cancelAllOperations];
310
    [copyQueue cancelAllOperations];
311
    [deleteQueue cancelAllOperations];
312
    [uploadQueue cancelAllOperations];
313
    [downloadQueue cancelAllOperations];
314
    [moveCallbackQueue cancelAllOperations];
315
    [copyCallbackQueue cancelAllOperations];
316
    [deleteCallbackQueue cancelAllOperations];
317
    [uploadCallbackQueue cancelAllOperations];
318
    [downloadCallbackQueue cancelAllOperations];
319

    
320
    [moveNetworkQueue go];
321
    [copyNetworkQueue go];
322
    [deleteNetworkQueue go];
323
    [uploadNetworkQueue go];
324
    [downloadNetworkQueue go];
325
    [moveQueue setSuspended:NO];
326
    [copyQueue setSuspended:NO];
327
    [deleteQueue setSuspended:NO];
328
    [uploadQueue setSuspended:NO];
329
    [downloadQueue setSuspended:NO];
330
    [moveCallbackQueue setSuspended:NO];
331
    [copyCallbackQueue setSuspended:NO];
332
    [deleteCallbackQueue setSuspended:NO];
333
    [uploadCallbackQueue setSuspended:NO];
334
    [downloadCallbackQueue setSuspended:NO];
335

    
336
    accountNode.pithos = pithos;
337
    [accountNode forceRefresh];
338
    mySharedNode.pithos = pithos;
339
    [mySharedNode forceRefresh];
340
    othersSharedNode.pithos = pithos;
341
    [othersSharedNode forceRefresh];
342
            
343
//    [activityFacility reset];
344
    activityFacility.delegate = self;
345
            
346
    refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 
347
                                                     target:self 
348
                                                   selector:@selector(forceRefresh:) 
349
                                                   userInfo:self 
350
                                                    repeats:YES] retain];
351
    @synchronized(self) {
352
        browserActive = YES;
353
    }
354
}
355

    
356
- (BOOL)operationsPending {
357
    return ([moveNetworkQueue operationCount] || 
358
            [copyNetworkQueue operationCount] || 
359
            [deleteNetworkQueue operationCount] || 
360
            [uploadNetworkQueue operationCount] || 
361
            [downloadNetworkQueue operationCount] || 
362
            [moveQueue operationCount] || 
363
            [copyQueue operationCount] || 
364
            [deleteQueue operationCount] || 
365
            [uploadQueue operationCount] || 
366
            [downloadQueue operationCount] || 
367
            [moveCallbackQueue operationCount] || 
368
            [copyCallbackQueue operationCount] || 
369
            [deleteCallbackQueue operationCount] || 
370
            [uploadCallbackQueue operationCount] || 
371
            [downloadCallbackQueue operationCount]);
372
}
373

    
374
- (void)dealloc {
375
    [[NSNotificationCenter defaultCenter] removeObserver:self];
376
    [self resetBrowser];
377
    [moveQueue release];
378
    [copyQueue release];
379
    [deleteQueue release];
380
    [uploadQueue release];
381
    [downloadQueue release];
382
    [moveNetworkQueue release];
383
    [copyNetworkQueue release];
384
    [deleteNetworkQueue release];
385
    [uploadNetworkQueue release];
386
    [downloadNetworkQueue release];
387
    [clipboardParentNode release];
388
    [clipboardNodes release];
389
    [draggedParentNode release];
390
    [draggedNodes release];
391
    [sharedPreviewController release];
392
    [othersSharedNode release];
393
    [mySharedNode release];
394
    [sharedNode release];
395
    [containersNodeChildren release];
396
    [containersNode release];
397
    [accountNode release];
398
    [rootNode release];
399
    [pithos release];
400
    [super dealloc];
401
}
402

    
403
- (void)setPithos:(ASIPithos *)aPithos {
404
    if (aPithos) {
405
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
406
            ![aPithos.authToken isEqualToString:pithos.authToken] || 
407
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
408
            ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
409
            [self resetBrowser];
410
            [pithos release];
411
            pithos = [aPithos retain];
412
            [self startBrowser];
413
        } else {
414
            [self startBrowser];
415
        }
416
    }
417
}
418

    
419

    
420
#pragma mark -
421
#pragma mark Observers
422

    
423
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
424
    PithosNode *node = (PithosNode *)[notification object];
425
    if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
426
        return;
427
    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
428
    NSInteger lastColumn = [browser lastColumn];
429
    for (NSInteger column = lastColumn; column >= 0; column--) {
430
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
431
            [browser reloadColumn:column];
432
            return;
433
        }
434
    }
435
}
436

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

    
511
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
512
    [self refresh:nil];
513
}
514

    
515
#pragma mark -
516
#pragma mark Actions
517

    
518
- (IBAction)forceRefresh:(id)sender {
519
    if (sender)
520
        [accountNode forceRefresh];
521
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
522
        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
523
        node.forcedRefresh = YES;
524
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
525
    }
526
    [browser validateVisibleColumns];
527
}
528

    
529
- (IBAction)refresh:(id)sender {
530
    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
531
        [self forceRefresh:sender];
532
    } else {
533
        if (sender)
534
            [accountNode refresh];
535
        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
536
            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
537
        }
538
        [browser validateVisibleColumns];
539
    }
540
}
541

    
542
#pragma mark -
543
#pragma mark NSBrowserDelegate
544

    
545
- (id)rootItemForBrowser:(NSBrowser *)browser {
546
    return rootNode;    
547
}
548

    
549
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
550
    PithosNode *node = (PithosNode *)item;
551
    return node.children.count;
552
}
553

    
554
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
555
    PithosNode *node = (PithosNode *)item;
556
    return [node.children objectAtIndex:index];
557
}
558

    
559
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
560
    PithosNode *node = (PithosNode *)item;
561
    return node.isLeafItem;
562
}
563

    
564
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
565
    PithosNode *node = (PithosNode *)item;
566
    return node;
567
}
568

    
569
- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
570
    if (sharedPreviewController == nil)
571
        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
572
    return sharedPreviewController;
573
}
574

    
575
//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
576
//    if (!forUserResize) {
577
//        id item = [browser parentForItemsInColumn:columnIndex]; 
578
//        if ([self browser:browser isLeafItem:item]) {
579
//            suggestedWidth = 200; 
580
//        }
581
//    }
582
//    return suggestedWidth;
583
//}
584

    
585
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
586
    return NO;
587
}
588

    
589
#pragma mark Editing
590

    
591
- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
592
    PithosNode *node = (PithosNode *)item;
593
    if (node.shared || node.sharingAccount || 
594
        ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
595
        return NO;
596
    return YES;
597
}
598

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

    
772
#pragma mark Drag and Drop source
773

    
774
- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
775
      withEvent:(NSEvent *)event {
776
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
777
    __block BOOL result = YES;
778
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
779
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
780
        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
781
            result = NO;
782
            *stop = YES;
783
        }
784
    }];
785
    return result;
786
}
787

    
788
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
789
   toPasteboard:(NSPasteboard *)pasteboard {
790
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
791
    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
792
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
793
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
794
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
795
        [propertyList addObject:[node.pithosObject.name pathExtension]];
796
        [nodes addObject:node];
797
    }];
798

    
799
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
800
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
801
    self.draggedNodes = nodes;
802
    self.draggedParentNode = [browser parentForItemsInColumn:column];
803
    return YES;
804
}
805

    
806
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
807
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
808
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
809
    for (PithosNode *node in draggedNodes) {
810
        [names addObject:node.displayName];
811
        // If the node is a subdir ask if the whole tree should be downloaded
812
        if ([node class] == [PithosSubdirNode class]) {
813
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
814
            [alert setMessageText:@"Download directory"];
815
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
816
            [alert addButtonWithTitle:@"OK"];
817
            [alert addButtonWithTitle:@"Cancel"];
818
            NSInteger choice = [alert runModal];
819
            if (choice == NSAlertFirstButtonReturn)
820
                [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
821
        } else {
822
            [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
823
        }
824
    }
825
    return names;
826
}
827

    
828
#pragma mark Drag and Drop destination
829

    
830
- (NSDragOperation)browser:aBrowser 
831
              validateDrop:(id<NSDraggingInfo>)info 
832
               proposedRow:(NSInteger *)row 
833
                    column:(NSInteger *)column 
834
             dropOperation:(NSBrowserDropOperation *)dropOperation {
835
    NSDragOperation result = NSDragOperationNone;
836
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
837
        // For a drop above, the drop is redirected to the parent item
838
        if (*dropOperation == NSBrowserDropAbove)
839
            *row = -1;
840
        // Only allow dropping in folders
841
        if (*column != -1) {
842
            PithosNode *dropNode;
843
            if (*row != -1) {
844
                // Check if the node is not a folder and if so redirect to the parent item
845
                dropNode = [browser itemAtRow:*row inColumn:*column];
846
                if ([dropNode class] == [PithosObjectNode class])
847
                    *row = -1;
848
            }
849
            if (*row == -1)
850
                dropNode = [browser parentForItemsInColumn:*column];
851
            
852
            if (!dropNode.shared && 
853
                (!dropNode.sharingAccount || 
854
                 ([dropNode class] == [PithosSubdirNode class]) || 
855
                 ([dropNode class] == [PithosContainerNode class]))) {
856
                *dropOperation = NSBrowserDropOn;
857
                result = NSDragOperationCopy;
858
            }
859
        }
860
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
861
        // For a drop above, the drop is redirected to the parent item
862
        if (*dropOperation == NSBrowserDropAbove) 
863
            *row = -1;
864
        // Only allow dropping in folders
865
        if (*column != -1) {
866
            PithosNode *dropNode;
867
            if (*row != -1) {
868
                // Check if the node is not a folder and if so redirect to the parent item
869
                dropNode = [browser itemAtRow:*row inColumn:*column];
870
                if ([dropNode class] == [PithosObjectNode class])
871
                    *row = -1;
872
            }
873
            if (*row == -1)
874
                dropNode = [browser parentForItemsInColumn:*column];
875
            
876
            if (!dropNode.shared && !dropNode.sharingAccount) {
877
                if ([info draggingSourceOperationMask] & NSDragOperationMove) {
878
                    // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
879
                    if ((([dropNode class] == [PithosContainerNode class]) || 
880
                         dropNode.pithosObject.subdir || 
881
                         ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
882
                        ![dropNode isEqualTo:draggedParentNode]) { 
883
    //                    ![dropNode isEqualTo:draggedParentNode] && 
884
    //                    ![draggedNodes containsObject:dropNode]) {                
885
                        result = NSDragOperationMove;
886
                    }
887
                } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
888
                    // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
889
                    if (([dropNode class] == [PithosContainerNode class]) || 
890
                        dropNode.pithosObject.subdir || 
891
                        ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
892
                        result = NSDragOperationCopy;
893
                    }
894
                }
895
            }
896
        }
897
    }
898
    return result;
899
}
900

    
901
- (BOOL)browser:(NSBrowser *)aBrowser 
902
     acceptDrop:(id<NSDraggingInfo>)info 
903
          atRow:(NSInteger)row 
904
         column:(NSInteger)column 
905
  dropOperation:(NSBrowserDropOperation)dropOperation {
906
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
907
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
908
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
909
        if ((column != -1) && (filenames != nil)) {
910
            PithosNode *node;
911
            if (row != -1)
912
                node = [browser itemAtRow:row inColumn:column];
913
            else
914
                node = [browser parentForItemsInColumn:column];
915
            NSLog(@"drag in node: %@", node.url);
916
            return [self uploadFiles:filenames toNode:node];
917
        }
918
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
919
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
920
        if ((column != -1) && (draggedNodes != nil)) {
921
            PithosNode *node;
922
            if (row != -1)
923
                node = [browser itemAtRow:row inColumn:column];
924
            else
925
                node = [browser parentForItemsInColumn:column];
926
            NSLog(@"drag local node: %@", node.url);
927
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
928
                return [self moveNodes:draggedNodes toNode:node];
929
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
930
                return [self copyNodes:draggedNodes toNode:node];
931
        }
932
    }
933
    return NO;
934
}
935

    
936
#pragma mark -
937
#pragma mark Drag and Drop methods
938

    
939
- (void)downloadNode:(PithosNode *)node 
940
         toDirectory:(NSString *)dirPath 
941
     withNewFileName:(NSString *)newFileName 
942
             version:(NSString *)version {
943
    if ([node class] == [PithosSubdirNode class]) {
944
        // XXX newFilename and version are ignored in the case of a subdir node for now
945
        // Operation: Download a subdir node and its descendants
946
        // The resulting ASIPithosObjectRequests are chained through dependencies
947
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
948
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
949
            if (operation.isCancelled) {
950
                [pool drain];
951
                return;
952
            }
953
            NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos 
954
                                                                               containerName:node.pithosContainer.name 
955
                                                                                  objectName:node.pithosObject.name 
956
                                                                                 toDirectory:dirPath 
957
                                                                               checkIfExists:YES 
958
                                                                              sharingAccount:node.sharingAccount];
959
            if (!operation.isCancelled && objectRequests) {
960
                ASIPithosObjectRequest *previousObjectRequest = nil;
961
                for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
962
                    if (operation.isCancelled) {
963
                        [pool drain];
964
                        return;
965
                    }
966
                    objectRequest.delegate = self;
967
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
968
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
969
                    NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
970
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
971
                                                                               message:[messagePrefix stringByAppendingString:@" (0%)"]
972
                                                                            totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
973
                                                                          currentBytes:0];
974
                    dispatch_async(dispatch_get_main_queue(), ^{
975
                        [activityFacility updateActivity:activity withMessage:activity.message];  
976
                    });
977
                    [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
978
                     [NSDictionary dictionaryWithObjectsAndKeys:
979
                      activity, @"activity", 
980
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
981
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
982
                      [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
983
                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
984
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
985
                      NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
986
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
987
                      downloadNetworkQueue, @"networkQueue", 
988
                      @"download", @"operationType", 
989
                      nil]];
990
                    [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
991
                        [activityFacility updateActivity:activity 
992
                                             withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
993
                                              totalBytes:activity.totalBytes 
994
                                            currentBytes:(activity.currentBytes + size)];
995
                    }];
996
                    if (previousObjectRequest)
997
                        [objectRequest addDependency:previousObjectRequest];
998
                    previousObjectRequest = objectRequest;
999
                    [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1000
                }
1001
            }
1002
            [pool drain];
1003
        }];
1004
        [downloadQueue addOperation:operation];
1005
    } else if ([node class] == [PithosObjectNode class]) {
1006
        // Operation: Download an object node
1007
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1008
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1009
            if (operation.isCancelled) {
1010
                [pool drain];
1011
                return;
1012
            }
1013
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos 
1014
                                                                                           containerName:node.pithosContainer.name 
1015
                                                                                              objectName:node.pithosObject.name 
1016
                                                                                                 version:version 
1017
                                                                                            toDirectory:dirPath 
1018
                                                                                         withNewFileName:newFileName 
1019
                                                                                           checkIfExists:(version ? NO : YES) 
1020
                                                                                          sharingAccount:node.sharingAccount];
1021
            if (!operation.isCancelled && objectRequest) {
1022
                objectRequest.delegate = self;
1023
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1024
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1025
                NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1026
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
1027
                                                                           message:[messagePrefix stringByAppendingString:@" (0%)"]
1028
                                                                        totalBytes:node.pithosObject.bytes 
1029
                                                                      currentBytes:0];
1030
                dispatch_async(dispatch_get_main_queue(), ^{
1031
                    [activityFacility updateActivity:activity withMessage:activity.message];  
1032
                });
1033
                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1034
                 [NSDictionary dictionaryWithObjectsAndKeys:
1035
                  activity, @"activity", 
1036
                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1037
                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1038
                  [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1039
                  [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1040
                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
1041
                  NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector", 
1042
                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1043
                  downloadNetworkQueue, @"networkQueue", 
1044
                  @"download", @"operationType", 
1045
                  nil]];
1046
                [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1047
                    [activityFacility updateActivity:activity 
1048
                                         withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1049
                                          totalBytes:activity.totalBytes 
1050
                                        currentBytes:(activity.currentBytes + size)];
1051
                }];
1052
                [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1053
                [pool drain];
1054
            }
1055
        }];
1056
        [downloadQueue addOperation:operation];
1057
    }
1058
}
1059

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

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

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

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

    
1559
#pragma mark -
1560
#pragma mark ASIHTTPRequestDelegate
1561

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

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

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

    
1680
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1681
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1682
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1683
    NSLog(@"Download finished: %@", objectRequest.url);
1684
    if (operation.isCancelled) {
1685
        [self requestFailed:objectRequest];
1686
    } else if (objectRequest.responseStatusCode == 200) {
1687
        NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1688
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1689
        NSUInteger totalBytes = activity.totalBytes;
1690
        
1691
        // XXX change contentLength to objectContentLength if it is fixed in the server
1692
        if ([objectRequest contentLength] == 0) {
1693
            // The check above was:
1694
            // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1695
            // I checked for directory content types in order not to create a file in place of a directory,
1696
            // but this callback method is not called in the case of a directory download.
1697
            // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1698
            // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1699
            NSLog(@"Downloaded  0 bytes");
1700
            NSFileManager *fileManager = [NSFileManager defaultManager];
1701
            if (![fileManager fileExistsAtPath:filePath]) {
1702
                if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1703
                    dispatch_async(dispatch_get_main_queue(), ^{
1704
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1705
                        [alert setMessageText:@"Create File Error"];
1706
                        [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1707
                        [alert addButtonWithTitle:@"OK"];
1708
                        [alert runModal];
1709
                    });
1710
                }
1711
            }
1712
        }
1713
        
1714
        NSUInteger currentBytes = [objectRequest objectContentLength];
1715
        if (currentBytes == 0)
1716
            currentBytes = totalBytes;
1717
        dispatch_async(dispatch_get_main_queue(), ^{
1718
            [activityFacility endActivity:activity 
1719
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1720
                               totalBytes:totalBytes 
1721
                             currentBytes:currentBytes];
1722
        });
1723
    } else {
1724
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1725
        [self requestFailed:objectRequest];
1726
    }
1727
    [pool drain];
1728
}
1729

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

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

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

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

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

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

    
1992
#pragma mark -
1993
#pragma mark NSSplitViewDelegate
1994

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

    
2002
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2003
    if (splitView == verticalSplitView)
2004
        return 220;
2005
    else
2006
        return ([horizontalSplitView bounds].size.height - 108);
2007
}
2008

    
2009
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2010
    if (splitView == verticalSplitView) {
2011
        if (proposedPosition < 120)
2012
            return 120;
2013
        else if (proposedPosition > 220)
2014
            return 220;
2015
        else
2016
            return proposedPosition;
2017
    } else {
2018
        return ([horizontalSplitView bounds].size.height - 108);
2019
    }
2020
}
2021

    
2022
#pragma mark -
2023
#pragma mark NSOutlineViewDataSource
2024

    
2025
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2026
    if (!browserInitialized)
2027
        return 0;
2028
    if (item == nil)
2029
        return 2;
2030
    if (item == containersNode)
2031
        return containersNodeChildren.count;
2032
    if (item == sharedNode)
2033
        return 2;
2034
    return 0;
2035
}
2036

    
2037
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2038
    if (!browserInitialized)
2039
        return nil;
2040
    if (item == nil)
2041
        return (!index ? containersNode : sharedNode);
2042
    if (item == sharedNode)
2043
        return (!index ? mySharedNode : othersSharedNode);
2044
    return [containersNodeChildren objectAtIndex:index];
2045
}
2046

    
2047
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2048
    if ((item == containersNode) || (item == sharedNode))
2049
        return YES;
2050
    return NO;
2051
}
2052

    
2053
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2054
    PithosNode *node = (PithosNode *)item;
2055
    return node;    
2056
}
2057

    
2058
#pragma mark Drag and Drop destination
2059

    
2060
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2061
                  validateDrop:(id<NSDraggingInfo>)info 
2062
                  proposedItem:(id)item 
2063
            proposedChildIndex:(NSInteger)index {
2064
    NSDragOperation result = NSDragOperationNone;
2065
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2066
        return result;
2067
    PithosNode *dropNode = (PithosNode *)item;
2068
    if ([dropNode class] != [PithosContainerNode class])
2069
        return result;
2070
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2071
        result = NSDragOperationCopy;
2072
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2073
        if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2074
            ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2075
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2076
            if (![dropNode isEqualTo:draggedParentNode])
2077
                result = NSDragOperationMove;
2078
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2079
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2080
            result = NSDragOperationCopy;
2081
        }
2082
    }
2083
   return result;
2084
}
2085

    
2086
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2087
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2088
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2089
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2090
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2091
            PithosNode *node = (PithosNode *)item;
2092
            NSLog(@"drag in node: %@", node.url);
2093
            return [self uploadFiles:filenames toNode:node];
2094
        }
2095
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2096
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2097
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2098
            PithosNode *node = (PithosNode *)item;
2099
            NSLog(@"drag local node: %@", node.url);
2100
            if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2101
                ([info draggingSourceOperationMask] & NSDragOperationMove))
2102
                return [self moveNodes:draggedNodes toNode:node];
2103
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2104
                return [self copyNodes:draggedNodes toNode:node];
2105
        }
2106
    }
2107
    return NO;
2108
}
2109

    
2110
#pragma mark -
2111
#pragma mark NSOutlineViewDelegate
2112

    
2113
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2114
    if ((item == containersNode) || (item == sharedNode))
2115
        return NO;
2116
    return YES;
2117
}
2118

    
2119
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2120
    if ((item == containersNode) || (item == sharedNode))
2121
        return YES;
2122
    return NO;
2123
}
2124

    
2125
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2126
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2127
    if (node) {
2128
        rootNode = node;
2129
        [browser loadColumnZero];
2130
        [self refresh:nil];
2131
    }
2132
}
2133

    
2134
#pragma mark -
2135
#pragma mark NSMenuDelegate
2136

    
2137
- (void)menuNeedsUpdate:(NSMenu *)menu {
2138
    [menu removeAllItems];
2139
    NSMenuItem *menuItem;
2140
    NSString *menuItemTitle;
2141
    BOOL nodeContextMenu = NO;
2142
    PithosNode *menuNode = nil;
2143
    NSMutableArray *menuNodes;
2144
    if (menu == browserMenu) {
2145
        NSInteger column = [browser clickedColumn];
2146
        NSInteger row = [browser clickedRow];
2147
        if ((column == -1) || (row == -1)) {
2148
            // General context menu
2149
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2150
            if ([menuNodesIndexPaths count] == 0) {
2151
                menuNode = [browser parentForItemsInColumn:0];
2152
            } else if (([menuNodesIndexPaths count] != 1) || 
2153
                       ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2154
                menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2155
            } else {
2156
                menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2157
            }
2158
        } else {
2159
            // Node context menu
2160
            NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2161
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2162
            menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2163
            if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2164
                for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2165
                    [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2166
                }
2167
            } else {
2168
                [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2169
            }
2170
            nodeContextMenu = YES;
2171
        }
2172
    } else if (menu == outlineViewMenu) {
2173
        NSInteger row = [outlineView clickedRow];
2174
        if (row == -1)
2175
            row = [outlineView selectedRow];
2176
        if (row == -1)
2177
            return;
2178
        menuNode = [outlineView itemAtRow:row];
2179
    }
2180

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

    
2303
#pragma mark -
2304
#pragma mark Menu Actions
2305

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

    
2418
- (void)menuGetInfo:(NSMenuItem *)sender {
2419
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2420
        [node showPithosNodeInfo:sender];
2421
    }
2422
}
2423

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

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

    
2639
- (void)menuCut:(NSMenuItem *)sender {
2640
    self.clipboardNodes = (NSArray *)[sender representedObject];
2641
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2642
    self.clipboardCopy = NO;
2643
}
2644

    
2645
- (void)menuCopy:(NSMenuItem *)sender {
2646
    self.clipboardNodes = (NSArray *)[sender representedObject];
2647
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2648
    self.clipboardCopy = YES;
2649
}
2650

    
2651
- (void)menuPaste:(NSMenuItem *)sender {
2652
    if (!clipboardNodes || ![clipboardNodes count])
2653
        return;
2654
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2655
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2656
    if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2657
        self.clipboardNodes = nil;
2658
        self.clipboardParentNode = nil;
2659
        [self moveNodes:localClipboardNodes toNode:dropNode];
2660
    } else {
2661
        [self copyNodes:localClipboardNodes toNode:dropNode];
2662
    }
2663
}
2664
    
2665
#pragma mark -
2666
#pragma mark PithosActivityFacilityDelegate
2667

    
2668
- (void)activityUpdate:(NSDictionary *)info {
2669
    NSString *message = [info objectForKey:@"message"];
2670
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2671
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2672
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2673
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2674
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2675
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2676
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2677
    if (runningActivitiesCount && totalBytes) {
2678
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2679
        [activityProgressIndicator startAnimation:self];
2680
    } else {
2681
        [activityProgressIndicator setDoubleValue:1.0];
2682
        [activityProgressIndicator stopAnimation:self];
2683
    }
2684
    
2685
    if (!message)
2686
        message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2687
    [activityTextField setStringValue:message];
2688
}
2689

    
2690
@end