Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ cb6abe72

History | View | Annotate | Download (162.2 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
#define REFRESH_TIMER_INTERVAL 5
60

    
61
@interface PithosBrowserCell : FileSystemBrowserCell {}
62
@end
63

    
64
@implementation PithosBrowserCell
65

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

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

    
84
@end
85

    
86
@interface PithosOutlineViewCell : ImageAndTextCell {}
87
@end
88

    
89
@implementation PithosOutlineViewCell
90

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

    
102
@end
103

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

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

    
118
#pragma mark -
119
#pragma Object Lifecycle
120

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

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

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

    
246
- (void)resetBrowser {
247
    @synchronized(self) {
248
        if (!browserActive)
249
            return;
250
    }
251

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

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

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

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

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

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

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

    
376
- (void)dealloc {
377
    [[NSNotificationCenter defaultCenter] removeObserver:self];
378
    [self resetBrowser];
379
}
380

    
381
- (void)setPithos:(ASIPithos *)aPithos {
382
    if (aPithos) {
383
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
384
            ![aPithos.authToken isEqualToString:pithos.authToken] || 
385
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
386
            ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
387
            [self resetBrowser];
388
            pithos = aPithos;
389
            [self startBrowser];
390
        } else {
391
            [self startBrowser];
392
        }
393
    }
394
}
395

    
396

    
397
#pragma mark -
398
#pragma mark Observers
399

    
400
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
401
    PithosNode *node = (PithosNode *)[notification object];
402
    if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
403
        return;
404
    DLog(@"pithosNodeChildrenUpdated:%@", node.url);
405
    NSInteger lastColumn = [browser lastColumn];
406
    for (NSInteger column = lastColumn; column >= 0; column--) {
407
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
408
            [browser reloadColumn:column];
409
            return;
410
        }
411
    }
412
}
413

    
414
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
415
    BOOL containerPithosFound = NO;
416
    BOOL containerTrashFound = NO;
417
    NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
418
    for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
419
        if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
420
            [removedContainersNodeChildren addIndex:i];
421
    }
422
    [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
423
    for (PithosContainerNode *containerNode in accountNode.children) {
424
        if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
425
            if (![containersNodeChildren containsObject:containerNode])
426
                [containersNodeChildren insertObject:containerNode atIndex:0];
427
            containerPithosFound = YES;
428
        } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
429
            NSUInteger insertIndex = 1;
430
            if (!containerPithosFound)
431
                insertIndex = 0;
432
            if (![containersNodeChildren containsObject:containerNode])
433
                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
434
            containerTrashFound = YES;
435
        } else if (![containersNodeChildren containsObject:containerNode]) {
436
            [containersNodeChildren addObject:containerNode];
437
        }
438
    }
439
    BOOL refreshAccountNode = NO;
440
    if (!containerPithosFound) {
441
        // Create pithos node
442
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
443
                                                                                                            containerName:@"pithos"];
444
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
445
        [networkQueue go];
446
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
447
        if ([containerRequest error]) {
448
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
449
        } else {
450
            refreshAccountNode = YES;
451
        }
452
    }
453
    if (!containerTrashFound) {
454
        // Create trash node
455
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos 
456
                                                                                                            containerName:@"trash"];
457
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
458
        [networkQueue go];
459
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
460
        if ([containerRequest error]) {
461
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
462
        } else {
463
            refreshAccountNode = YES;
464
        }
465
    }
466
    
467
    if (refreshAccountNode)
468
        [accountNode refresh];
469
    
470
    [outlineView reloadData];
471
    
472
    // Expand the folder outline view
473
    [outlineView expandItem:nil expandChildren:YES];
474
    
475
    if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
476
        rootNode = [containersNodeChildren objectAtIndex:0];
477
        [browser loadColumnZero];
478
    }
479
    
480
    if (notification)
481
        [self refresh:nil];
482
}
483

    
484
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
485
    [self refresh:nil];
486
}
487

    
488
#pragma mark -
489
#pragma mark Actions
490

    
491
- (IBAction)forceRefresh:(id)sender {
492
    if (editingItem)
493
        return;
494
    if (sender)
495
        [accountNode forceRefresh];
496
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
497
        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
498
        node.forcedRefresh = YES;
499
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
500
    }
501
    [browser validateVisibleColumns];
502
}
503

    
504
- (IBAction)refresh:(id)sender {
505
    if (editingItem)
506
        return;
507
    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
508
        [self forceRefresh:sender];
509
    } else {
510
        if (sender)
511
            [accountNode refresh];
512
        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
513
            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
514
        }
515
        [browser validateVisibleColumns];
516
    }
517
}
518

    
519
#pragma mark -
520
#pragma mark NSBrowserDelegate
521

    
522
- (id)rootItemForBrowser:(NSBrowser *)browser {
523
    return rootNode;    
524
}
525

    
526
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
527
    PithosNode *node = (PithosNode *)item;
528
    return node.children.count;
529
}
530

    
531
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
532
    PithosNode *node = (PithosNode *)item;
533
    return [node.children objectAtIndex:index];
534
}
535

    
536
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
537
    PithosNode *node = (PithosNode *)item;
538
    return node.isLeafItem;
539
}
540

    
541
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
542
    PithosNode *node = (PithosNode *)item;
543
    return node;
544
}
545

    
546
- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
547
    if (sharedPreviewController == nil)
548
        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
549
    return sharedPreviewController;
550
}
551

    
552
//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
553
//    if (!forUserResize) {
554
//        id item = [browser parentForItemsInColumn:columnIndex]; 
555
//        if ([self browser:browser isLeafItem:item]) {
556
//            suggestedWidth = 200; 
557
//        }
558
//    }
559
//    return suggestedWidth;
560
//}
561

    
562
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
563
    return NO;
564
}
565

    
566
#pragma mark Editing
567

    
568
- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
569
    PithosNode *node = (PithosNode *)item;
570
    if (node.shared || node.sharingAccount || 
571
        ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
572
        return NO;
573
    editingItem = YES;
574
    return YES;
575
}
576

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

    
737
#pragma mark Drag and Drop source
738

    
739
- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
740
      withEvent:(NSEvent *)event {
741
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
742
    __block BOOL result = YES;
743
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
744
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
745
        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
746
            result = NO;
747
            *stop = YES;
748
        }
749
    }];
750
    return result;
751
}
752

    
753
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
754
   toPasteboard:(NSPasteboard *)pasteboard {
755
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
756
    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
757
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
758
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
759
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
760
        [propertyList addObject:[node.pithosObject.name pathExtension]];
761
        [nodes addObject:node];
762
    }];
763

    
764
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
765
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
766
    self.draggedNodes = nodes;
767
    self.draggedParentNode = [browser parentForItemsInColumn:column];
768
    return YES;
769
}
770

    
771
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
772
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
773
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
774
    for (PithosNode *node in draggedNodes) {
775
        [names addObject:node.displayName];
776
        // If the node is a subdir ask if the whole tree should be downloaded
777
        if ([node class] == [PithosSubdirNode class]) {
778
            NSAlert *alert = [[NSAlert alloc] init];
779
            [alert setMessageText:@"Download directory"];
780
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
781
            [alert addButtonWithTitle:@"OK"];
782
            [alert addButtonWithTitle:@"Cancel"];
783
            NSInteger choice = [alert runModal];
784
            if (choice == NSAlertFirstButtonReturn)
785
                [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
786
        } else {
787
            [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
788
        }
789
    }
790
    return names;
791
}
792

    
793
#pragma mark Drag and Drop destination
794

    
795
- (NSDragOperation)browser:aBrowser 
796
              validateDrop:(id<NSDraggingInfo>)info 
797
               proposedRow:(NSInteger *)row 
798
                    column:(NSInteger *)column 
799
             dropOperation:(NSBrowserDropOperation *)dropOperation {
800
    NSDragOperation result = NSDragOperationNone;
801
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
802
        // For a drop above, the drop is redirected to the parent item
803
        if (*dropOperation == NSBrowserDropAbove)
804
            *row = -1;
805
        // Only allow dropping in folders
806
        if (*column != -1) {
807
            PithosNode *dropNode;
808
            if (*row != -1) {
809
                // Check if the node is not a folder and if so redirect to the parent item
810
                dropNode = [browser itemAtRow:*row inColumn:*column];
811
                if ([dropNode class] == [PithosObjectNode class])
812
                    *row = -1;
813
            }
814
            if (*row == -1)
815
                dropNode = [browser parentForItemsInColumn:*column];
816
            
817
            if (!dropNode.shared && 
818
                (!dropNode.sharingAccount || 
819
                 ([dropNode class] == [PithosSubdirNode class]) || 
820
                 ([dropNode class] == [PithosContainerNode class]))) {
821
                *dropOperation = NSBrowserDropOn;
822
                result = NSDragOperationCopy;
823
            }
824
        }
825
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
826
        // For a drop above, the drop is redirected to the parent item
827
        if (*dropOperation == NSBrowserDropAbove) 
828
            *row = -1;
829
        // Only allow dropping in folders
830
        if (*column != -1) {
831
            PithosNode *dropNode;
832
            if (*row != -1) {
833
                // Check if the node is not a folder and if so redirect to the parent item
834
                dropNode = [browser itemAtRow:*row inColumn:*column];
835
                if ([dropNode class] == [PithosObjectNode class])
836
                    *row = -1;
837
            }
838
            if (*row == -1)
839
                dropNode = [browser parentForItemsInColumn:*column];
840
            
841
            if (!dropNode.shared && !dropNode.sharingAccount) {
842
                if ([info draggingSourceOperationMask] & NSDragOperationMove) {
843
                    // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
844
                    if ((([dropNode class] == [PithosContainerNode class]) || 
845
                         dropNode.pithosObject.subdir || 
846
                         ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
847
                        ![dropNode isEqualTo:draggedParentNode]) { 
848
    //                    ![dropNode isEqualTo:draggedParentNode] && 
849
    //                    ![draggedNodes containsObject:dropNode]) {                
850
                        result = NSDragOperationMove;
851
                    }
852
                } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
853
                    // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
854
                    if (([dropNode class] == [PithosContainerNode class]) || 
855
                        dropNode.pithosObject.subdir || 
856
                        ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
857
                        result = NSDragOperationCopy;
858
                    }
859
                }
860
            }
861
        }
862
    }
863
    return result;
864
}
865

    
866
- (BOOL)browser:(NSBrowser *)aBrowser 
867
     acceptDrop:(id<NSDraggingInfo>)info 
868
          atRow:(NSInteger)row 
869
         column:(NSInteger)column 
870
  dropOperation:(NSBrowserDropOperation)dropOperation {
871
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
872
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
873
        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
874
        if ((column != -1) && (filenames != nil)) {
875
            PithosNode *node;
876
            if (row != -1)
877
                node = [browser itemAtRow:row inColumn:column];
878
            else
879
                node = [browser parentForItemsInColumn:column];
880
            DLog(@"drag in node: %@", node.url);
881
            return [self uploadFiles:filenames toNode:node];
882
        }
883
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
884
        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
885
        if ((column != -1) && (draggedNodes != nil)) {
886
            PithosNode *node;
887
            if (row != -1)
888
                node = [browser itemAtRow:row inColumn:column];
889
            else
890
                node = [browser parentForItemsInColumn:column];
891
            DLog(@"drag local node: %@", node.url);
892
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
893
                return [self moveNodes:draggedNodes toNode:node];
894
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
895
                return [self cpyNodes:draggedNodes toNode:node];
896
        }
897
    }
898
    return NO;
899
}
900

    
901
#pragma mark -
902
#pragma mark NSBrowser Actions
903

    
904
- (void)browserDoubleAction:(id)sender {
905
    NSInteger column = [browser clickedColumn];
906
    NSInteger row = [browser clickedRow];
907
    if ((column == -1) || (row == -1))
908
        return;
909
    NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
910
    NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
911
    NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
912
    if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
913
        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
914
            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
915
        }
916
    } else {
917
        [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
918
    }
919
    NSMenuItem *menuItem = [[NSMenuItem alloc] init];
920
    menuItem.representedObject = menuNodes;
921
    [self menuDownload:menuItem];
922
}
923

    
924
#pragma mark -
925
#pragma mark Drag and Drop methods
926

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

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

    
1260
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1261
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1262
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1263
        return NO;
1264
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1265
    NSString *objectNamePrefix;
1266
    if ([destinationNode class] == [PithosSubdirNode class])
1267
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1268
    else
1269
        objectNamePrefix = [NSString string];
1270

    
1271
    for (PithosNode *node in nodes) {
1272
        if (([node class] == [PithosObjectNode class]) || 
1273
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1274
            // Operation: Move an object or subdir/ node
1275
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1276
                @autoreleasepool {
1277
                    if (operation.isCancelled)
1278
                        return;
1279
                    NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1280
                    if ([node.pithosObject.name hasSuffix:@"/"])
1281
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1282
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1283
                                                                                           containerName:node.pithosContainer.name 
1284
                                                                                              objectName:node.pithosObject.name 
1285
                                                                                destinationContainerName:containerName 
1286
                                                                                   destinationObjectName:destinationObjectName 
1287
                                                                                           checkIfExists:YES];
1288
                    if (!operation.isCancelled && objectRequest) {
1289
                        objectRequest.delegate = self;
1290
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1291
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1292
                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1293
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1294
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1295
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1296
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1297
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1298
                                                                                   message:messagePrefix];
1299
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1300
                         [NSDictionary dictionaryWithObjectsAndKeys:
1301
                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1302
                          activity, @"activity", 
1303
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1304
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1305
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1306
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1307
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1308
                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1309
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1310
                          moveNetworkQueue, @"networkQueue", 
1311
                          @"move", @"operationType", 
1312
                          nil]];
1313
                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1314
                    }
1315
                }
1316
            }];
1317
            [moveQueue addOperation:operation];
1318
        } else if ([node class] == [PithosSubdirNode class]) {
1319
            // Operation: Move a subdir node and its descendants
1320
            // The resulting ASIPithosObjectRequests are chained through dependencies
1321
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1322
                @autoreleasepool {
1323
                    if (operation.isCancelled)
1324
                        return;
1325
                    NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1326
                    if (node.pithosObject.subdir)
1327
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1328
                    NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
1329
                                                                                       containerName:node.pithosContainer.name 
1330
                                                                                          objectName:node.pithosObject.name 
1331
                                                                            destinationContainerName:containerName 
1332
                                                                               destinationObjectName:destinationObjectName 
1333
                                                                                       checkIfExists:YES];
1334
                    if (!operation.isCancelled && objectRequests) {
1335
                        ASIPithosObjectRequest *previousObjectRequest = nil;
1336
                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1337
                            if (operation.isCancelled)
1338
                                return;
1339
                            objectRequest.delegate = self;
1340
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1341
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1342
                            NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1343
                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1344
                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1345
                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1346
                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1347
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1348
                                                                                       message:messagePrefix];
1349
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1350
                             [NSDictionary dictionaryWithObjectsAndKeys:
1351
                              [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1352
                              [NSNumber numberWithBool:YES], @"refresh", 
1353
                              activity, @"activity", 
1354
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1355
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1356
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1357
                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1358
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1359
                              NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1360
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1361
                              moveNetworkQueue, @"networkQueue", 
1362
                              @"move", @"operationType", 
1363
                              nil]];
1364
                            if (previousObjectRequest)
1365
                                [objectRequest addDependency:previousObjectRequest];
1366
                            previousObjectRequest = objectRequest;
1367
                            [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1368
                        }
1369
                    }
1370
                }
1371
            }];
1372
            [moveQueue addOperation:operation];
1373
        }
1374
    }
1375
    return YES;
1376
}
1377

    
1378
- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
1379
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1380
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1381
        return NO;
1382
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1383
    NSString *objectNamePrefix;
1384
    if ([destinationNode class] == [PithosSubdirNode class])
1385
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1386
    else
1387
        objectNamePrefix = [NSString string];
1388
    
1389
    for (PithosNode *node in nodes) {
1390
        if (([node class] == [PithosObjectNode class]) || 
1391
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1392
            // Operation: Copy an object or subdir/ node
1393
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1394
                @autoreleasepool {
1395
                    if (operation.isCancelled)
1396
                        return;
1397
                    NSString *destinationObjectName;
1398
                    if (![destinationNode isEqualTo:node.parent]) {
1399
                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1400
                        if ([node.pithosObject.name hasSuffix:@"/"])
1401
                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1402
                    } else {
1403
                        destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos 
1404
                                                                           containerName:containerName 
1405
                                                                              objectName:node.pithosObject.name];
1406
                    }
1407
                    if (operation.isCancelled)
1408
                        return;
1409
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
1410
                                                                                           containerName:node.pithosContainer.name 
1411
                                                                                              objectName:node.pithosObject.name 
1412
                                                                                destinationContainerName:containerName 
1413
                                                                                   destinationObjectName:destinationObjectName 
1414
                                                                                           checkIfExists:YES 
1415
                                                                                          sharingAccount:node.sharingAccount];
1416
                    if (!operation.isCancelled && objectRequest) {
1417
                        objectRequest.delegate = self;
1418
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1419
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1420
                        NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1421
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1422
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1423
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1424
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1425
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1426
                                                                                   message:messagePrefix];
1427
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1428
                         [NSDictionary dictionaryWithObjectsAndKeys:
1429
                          [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1430
                          activity, @"activity", 
1431
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1432
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1433
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1434
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1435
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1436
                          NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1437
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1438
                          copyNetworkQueue, @"networkQueue", 
1439
                          @"copy", @"operationType", 
1440
                          nil]];
1441
                        [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1442
                    }
1443
                }
1444
            }];
1445
            [copyQueue addOperation:operation];
1446
        } else if ([node class] == [PithosSubdirNode class]) {
1447
            // Operation: Copy a subdir node and its descendants
1448
            // The resulting ASIPithosObjectRequests are chained through dependencies
1449
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1450
                @autoreleasepool {
1451
                    if (operation.isCancelled)
1452
                        return;
1453
                    NSString *destinationObjectName;
1454
                    if (![destinationNode isEqualTo:node.parent]) {
1455
                        destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1456
                        if (node.pithosObject.subdir)
1457
                            destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1458
                    } else {
1459
                        destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
1460
                                                                           containerName:containerName 
1461
                                                                              subdirName:node.pithosObject.name];
1462
                    }
1463
                    if (operation.isCancelled)
1464
                        return;
1465
                    NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
1466
                                                                                       containerName:node.pithosContainer.name 
1467
                                                                                          objectName:node.pithosObject.name 
1468
                                                                            destinationContainerName:containerName 
1469
                                                                               destinationObjectName:destinationObjectName 
1470
                                                                                       checkIfExists:YES 
1471
                                                                                      sharingAccount:node.sharingAccount];
1472
                    if (!operation.isCancelled && objectRequests) {
1473
                        ASIPithosObjectRequest *previousObjectRequest = nil;
1474
                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1475
                            if (operation.isCancelled)
1476
                                return;
1477
                            objectRequest.delegate = self;
1478
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1479
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1480
                            NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1481
                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1482
                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1483
                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1484
                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1485
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1486
                                                                                       message:messagePrefix];
1487
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1488
                             [NSDictionary dictionaryWithObjectsAndKeys:
1489
                              [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1490
                              activity, @"activity", 
1491
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1492
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1493
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1494
                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1495
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1496
                              NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector", 
1497
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1498
                              copyNetworkQueue, @"networkQueue", 
1499
                              @"copy", @"operationType", 
1500
                              nil]];
1501
                            if (previousObjectRequest)
1502
                                [objectRequest addDependency:previousObjectRequest];
1503
                            previousObjectRequest = objectRequest;
1504
                            [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1505
                        }
1506
                    }
1507
                }
1508
            }];
1509
            [copyQueue addOperation:operation];
1510
        }
1511
    }
1512
    return YES;
1513
}
1514

    
1515
#pragma mark -
1516
#pragma mark ASIHTTPRequestDelegate
1517

    
1518
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1519
    NSOperationQueue *callbackQueue;
1520
    NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1521
    if ([operationType isEqualToString:@"move"])
1522
        callbackQueue = moveCallbackQueue;
1523
    else if ([operationType isEqualToString:@"copy"])
1524
        callbackQueue = copyCallbackQueue;
1525
    else if ([operationType isEqualToString:@"delete"])
1526
        callbackQueue = deleteCallbackQueue;
1527
    else if ([operationType isEqualToString:@"upload"])
1528
        callbackQueue = uploadCallbackQueue;
1529
    else if ([operationType isEqualToString:@"download"])
1530
        callbackQueue = downloadCallbackQueue;
1531
    else {
1532
        dispatch_async(dispatch_get_main_queue(), ^{
1533
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1534
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1535
        });
1536
        return;
1537
    }
1538
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1539
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
1540
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1541
                                                                               object:request];
1542
    operation.completionBlock = ^{
1543
        @autoreleasepool {
1544
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1545
                dispatch_async(dispatch_get_main_queue(), ^{
1546
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1547
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1548
                });
1549
            }
1550
        }
1551
    };
1552
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1553
    [callbackQueue addOperation:operation];
1554
}
1555

    
1556
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1557
    if (request.isCancelled) {
1558
        // Request has been cancelled 
1559
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1560
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1561
                   withObject:request];
1562
    } else {
1563
        NSOperationQueue *callbackQueue;
1564
        NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1565
        if ([operationType isEqualToString:@"move"])
1566
            callbackQueue = moveCallbackQueue;
1567
        else if ([operationType isEqualToString:@"copy"])
1568
            callbackQueue = copyCallbackQueue;
1569
        else if ([operationType isEqualToString:@"delete"])
1570
            callbackQueue = deleteCallbackQueue;
1571
        else if ([operationType isEqualToString:@"upload"])
1572
            callbackQueue = uploadCallbackQueue;
1573
        else if ([operationType isEqualToString:@"download"])
1574
            callbackQueue = downloadCallbackQueue;
1575
        else {
1576
            dispatch_async(dispatch_get_main_queue(), ^{
1577
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1578
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1579
            });
1580
            return;
1581
        }
1582
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1583
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
1584
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1585
                                                                                   object:request];
1586
        operation.completionBlock = ^{
1587
            @autoreleasepool {
1588
                if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1589
                    dispatch_async(dispatch_get_main_queue(), ^{
1590
                        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1591
                                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1592
                    });
1593
                }
1594
            }
1595
        };
1596
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1597
        [callbackQueue addOperation:operation];
1598
    }
1599
}
1600

    
1601
- (void)requestFailed:(ASIPithosRequest *)request {
1602
    @autoreleasepool {
1603
        NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1604
        DLog(@"Request failed: %@", request.url);
1605
        if (operation.isCancelled)
1606
            return;
1607
        if (request.isCancelled) {
1608
            dispatch_async(dispatch_get_main_queue(), ^{
1609
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1610
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1611
            });
1612
            return;
1613
        }
1614
        NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1615
        if (retries > 0) {
1616
            ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1617
            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1618
            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1619
            [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1620
        } else {
1621
            dispatch_async(dispatch_get_main_queue(), ^{
1622
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1623
                                  withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1624
            });
1625
            if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1626
                [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1627
            else
1628
                [PithosUtilities httpRequestErrorAlertWithRequest:request];
1629
        }
1630
    }
1631
}
1632

    
1633
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1634
    @autoreleasepool {
1635
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1636
        DLog(@"Download finished: %@", objectRequest.url);
1637
        if (operation.isCancelled) {
1638
            [self requestFailed:objectRequest];
1639
        } else if (objectRequest.responseStatusCode == 200) {
1640
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1641
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1642
            NSUInteger totalBytes = activity.totalBytes;
1643
            
1644
            // XXX change contentLength to objectContentLength if it is fixed in the server
1645
            if ([objectRequest contentLength] == 0) {
1646
                // The check above was:
1647
                // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1648
                // I checked for directory content types in order not to create a file in place of a directory,
1649
                // but this callback method is not called in the case of a directory download.
1650
                // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1651
                // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1652
                DLog(@"Downloaded  0 bytes");
1653
                NSFileManager *fileManager = [NSFileManager defaultManager];
1654
                if (![fileManager fileExistsAtPath:filePath]) {
1655
                    if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1656
                        dispatch_async(dispatch_get_main_queue(), ^{
1657
                            NSAlert *alert = [[NSAlert alloc] init];
1658
                            [alert setMessageText:@"Create File Error"];
1659
                            [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1660
                            [alert addButtonWithTitle:@"OK"];
1661
                            [alert runModal];
1662
                        });
1663
                    }
1664
                }
1665
            }
1666
            
1667
            NSUInteger currentBytes = [objectRequest objectContentLength];
1668
            if (currentBytes == 0)
1669
                currentBytes = totalBytes;
1670
            dispatch_async(dispatch_get_main_queue(), ^{
1671
                [activityFacility endActivity:activity 
1672
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1673
                                   totalBytes:totalBytes 
1674
                                 currentBytes:currentBytes];
1675
            });
1676
        } else {
1677
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1678
            [self requestFailed:objectRequest];
1679
        }
1680
    }
1681
}
1682

    
1683
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1684
    @autoreleasepool {
1685
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1686
        DLog(@"Upload directory object finished: %@", objectRequest.url);
1687
        if (operation.isCancelled) {
1688
            [self requestFailed:objectRequest];
1689
        } else if (objectRequest.responseStatusCode == 201) {
1690
            DLog(@"Directory object created: %@", objectRequest.url);
1691
            dispatch_async(dispatch_get_main_queue(), ^{
1692
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1693
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1694
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1695
                    [node forceRefresh];
1696
                }
1697
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1698
                    [node refresh];
1699
                }
1700
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1701
                    [self forceRefresh:self];
1702
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1703
                    [self refresh:self];
1704
            });
1705
        } else {
1706
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1707
            [self requestFailed:objectRequest];
1708
        }
1709
    }
1710
}
1711

    
1712
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1713
    @autoreleasepool {
1714
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1715
        DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1716
        NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1717
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1718
        NSUInteger totalBytes = activity.totalBytes;
1719
        NSUInteger currentBytes = activity.currentBytes;
1720
        if (operation.isCancelled) {
1721
            [self requestFailed:objectRequest];
1722
        } else if (objectRequest.responseStatusCode == 201) {
1723
            DLog(@"Object created: %@", objectRequest.url);
1724
            dispatch_async(dispatch_get_main_queue(), ^{
1725
                [activityFacility endActivity:activity 
1726
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1727
                                   totalBytes:totalBytes 
1728
                                 currentBytes:totalBytes];
1729
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1730
                    [node forceRefresh];
1731
                }
1732
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1733
                    [node refresh];
1734
                }
1735
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1736
                    [self forceRefresh:self];
1737
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1738
                    [self refresh:self];        
1739
            });
1740
        } else if (objectRequest.responseStatusCode == 409) {
1741
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1742
            if (iteration == 0) {
1743
                DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1744
                dispatch_async(dispatch_get_main_queue(), ^{
1745
                    [activityFacility endActivity:activity 
1746
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]]; 
1747
                    NSAlert *alert = [[NSAlert alloc] init];
1748
                    [alert setMessageText:@"Upload Timeout"];
1749
                    [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
1750
                                               [objectRequest.userInfo objectForKey:@"objectName"]]];
1751
                    [alert addButtonWithTitle:@"OK"];
1752
                    [alert runModal];
1753
                });
1754
                return;
1755
            }
1756
            DLog(@"object is missing hashes: %@", objectRequest.url);
1757
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1758
                                                              withMissingHashes:[objectRequest hashes]];
1759
            NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1760
            if (totalBytes >= [missingBlocks count]*blockSize)
1761
                currentBytes = totalBytes - [missingBlocks count]*blockSize;
1762
            dispatch_async(dispatch_get_main_queue(), ^{
1763
                [activityFacility updateActivity:activity 
1764
                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)] 
1765
                                      totalBytes:totalBytes 
1766
                                    currentBytes:currentBytes];
1767
            });
1768
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1769
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1770
                                                                                                             containerName:[objectRequest.userInfo objectForKey:@"containerName"] 
1771
                                                                                                                 blockSize:blockSize 
1772
                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1773
                                                                                                         missingBlockIndex:missingBlockIndex 
1774
                                                                                                            sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1775
            newContainerRequest.delegate = self;
1776
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1777
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1778
            newContainerRequest.userInfo = objectRequest.userInfo;
1779
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1780
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1781
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1782
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1783
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1784
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1785
                [activityFacility updateActivity:activity 
1786
                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1787
                                      totalBytes:activity.totalBytes 
1788
                                    currentBytes:(activity.currentBytes + size)];
1789
            }];
1790
            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1791
        } else {
1792
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1793
            [self requestFailed:objectRequest];
1794
        }
1795
    }
1796
}
1797

    
1798
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1799
    @autoreleasepool {
1800
        NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1801
        DLog(@"Upload of missing block finished: %@", containerRequest.url);
1802
        NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1803
        NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1804
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1805
        if (operation.isCancelled) {
1806
            [self requestFailed:containerRequest];
1807
        } else if (containerRequest.responseStatusCode == 202) {
1808
            NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1809
            NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1810
            missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1811
            if (missingBlockIndex == NSNotFound) {
1812
                NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1813
                ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1814
                                                                                               containerName:[containerRequest.userInfo objectForKey:@"containerName"] 
1815
                                                                                                  objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
1816
                                                                                                 contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
1817
                                                                                                   blockSize:blockSize 
1818
                                                                                                   blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1819
                                                                                                     forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1820
                                                                                               checkIfExists:NO 
1821
                                                                                                      hashes:&hashes 
1822
                                                                                              sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1823
                newObjectRequest.delegate = self;
1824
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1825
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1826
                newObjectRequest.userInfo = containerRequest.userInfo;
1827
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1828
                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1829
                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1830
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1831
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1832
            } else {
1833
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1834
                                                                                                                 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1835
                                                                                                                     blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1836
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1837
                                                                                                             missingBlockIndex:missingBlockIndex 
1838
                                                                                                                sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1839
                newContainerRequest.delegate = self;
1840
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1841
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1842
                newContainerRequest.userInfo = containerRequest.userInfo;
1843
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1844
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1845
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1846
                    [activityFacility updateActivity:activity 
1847
                                         withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1848
                                          totalBytes:activity.totalBytes 
1849
                                        currentBytes:(activity.currentBytes + size)];
1850
                }];
1851
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1852
            }
1853
        } else {
1854
            [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1855
            [self requestFailed:containerRequest];
1856
        }
1857
    }
1858
}
1859

    
1860
- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1861
    @autoreleasepool {
1862
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1863
        DLog(@"Move object finished: %@", objectRequest.url);
1864
        if (operation.isCancelled) {
1865
            [self requestFailed:objectRequest];
1866
        } else if (objectRequest.responseStatusCode == 201) {
1867
            dispatch_async(dispatch_get_main_queue(), ^{
1868
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1869
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1870
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1871
                    [node forceRefresh];
1872
                }
1873
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1874
                    [node refresh];
1875
                }
1876
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1877
                    [self forceRefresh:self];
1878
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1879
                    [self refresh:self];
1880
            });
1881
        } else {
1882
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1883
            [self requestFailed:objectRequest];
1884
        }
1885
    }
1886
}
1887

    
1888
- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1889
    @autoreleasepool {
1890
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1891
        DLog(@"Copy object finished: %@", objectRequest.url);
1892
        if (operation.isCancelled) {
1893
            [self requestFailed:objectRequest];
1894
        } else if (objectRequest.responseStatusCode == 201) {
1895
            dispatch_async(dispatch_get_main_queue(), ^{
1896
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1897
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1898
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1899
                    [node forceRefresh];
1900
                }
1901
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1902
                    [node refresh];
1903
                }
1904
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1905
                    [self forceRefresh:self];
1906
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1907
                    [self refresh:self];
1908
            });
1909
        } else {
1910
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1911
            [self requestFailed:objectRequest];
1912
        }
1913
    }
1914
}
1915

    
1916
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1917
    @autoreleasepool {
1918
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1919
        DLog(@"Delete object finished: %@", objectRequest.url);
1920
        if (operation.isCancelled) {
1921
            [self requestFailed:objectRequest];
1922
        } else if (objectRequest.responseStatusCode == 204) {
1923
            dispatch_async(dispatch_get_main_queue(), ^{
1924
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1925
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1926
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1927
                    [node forceRefresh];
1928
                }
1929
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1930
                    [node refresh];
1931
                }
1932
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1933
                    [self forceRefresh:self];
1934
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1935
                    [self refresh:self];
1936
            });
1937
        } else {
1938
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1939
            [self requestFailed:objectRequest];
1940
        }
1941
    }
1942
}
1943

    
1944
#pragma mark -
1945
#pragma mark NSSplitViewDelegate
1946

    
1947
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1948
    if (splitView == verticalSplitView)
1949
        return 120;
1950
    else
1951
        return ([horizontalSplitView bounds].size.height - 108);
1952
}
1953

    
1954
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1955
    if (splitView == verticalSplitView)
1956
        return 220;
1957
    else
1958
        return ([horizontalSplitView bounds].size.height - 108);
1959
}
1960

    
1961
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1962
    if (splitView == verticalSplitView) {
1963
        if (proposedPosition < 120)
1964
            return 120;
1965
        else if (proposedPosition > 220)
1966
            return 220;
1967
        else
1968
            return proposedPosition;
1969
    } else {
1970
        return ([horizontalSplitView bounds].size.height - 108);
1971
    }
1972
}
1973

    
1974
#pragma mark -
1975
#pragma mark NSOutlineViewDataSource
1976

    
1977
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1978
    if (!browserInitialized)
1979
        return 0;
1980
    if (item == nil)
1981
        return 2;
1982
    if (item == containersNode)
1983
        return containersNodeChildren.count;
1984
    if (item == sharedNode)
1985
        return 2;
1986
    return 0;
1987
}
1988

    
1989
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1990
    if (!browserInitialized)
1991
        return nil;
1992
    if (item == nil)
1993
        return (!index ? containersNode : sharedNode);
1994
    if (item == sharedNode)
1995
        return (!index ? mySharedNode : othersSharedNode);
1996
    return [containersNodeChildren objectAtIndex:index];
1997
}
1998

    
1999
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2000
    if ((item == containersNode) || (item == sharedNode))
2001
        return YES;
2002
    return NO;
2003
}
2004

    
2005
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2006
    PithosNode *node = (PithosNode *)item;
2007
    return node;    
2008
}
2009

    
2010
#pragma mark Drag and Drop destination
2011

    
2012
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2013
                  validateDrop:(id<NSDraggingInfo>)info 
2014
                  proposedItem:(id)item 
2015
            proposedChildIndex:(NSInteger)index {
2016
    NSDragOperation result = NSDragOperationNone;
2017
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2018
        return result;
2019
    PithosNode *dropNode = (PithosNode *)item;
2020
    if ([dropNode class] != [PithosContainerNode class])
2021
        return result;
2022
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2023
        result = NSDragOperationCopy;
2024
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2025
        if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2026
            ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2027
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2028
            if (![dropNode isEqualTo:draggedParentNode])
2029
                result = NSDragOperationMove;
2030
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2031
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2032
            result = NSDragOperationCopy;
2033
        }
2034
    }
2035
   return result;
2036
}
2037

    
2038
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2039
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2040
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2041
        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2042
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2043
            PithosNode *node = (PithosNode *)item;
2044
            DLog(@"drag in node: %@", node.url);
2045
            return [self uploadFiles:filenames toNode:node];
2046
        }
2047
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2048
        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2049
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2050
            PithosNode *node = (PithosNode *)item;
2051
            DLog(@"drag local node: %@", node.url);
2052
            if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2053
                ([info draggingSourceOperationMask] & NSDragOperationMove))
2054
                return [self moveNodes:draggedNodes toNode:node];
2055
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2056
                return [self cpyNodes:draggedNodes toNode:node];
2057
        }
2058
    }
2059
    return NO;
2060
}
2061

    
2062
#pragma mark -
2063
#pragma mark NSOutlineViewDelegate
2064

    
2065
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2066
    if ((item == containersNode) || (item == sharedNode))
2067
        return NO;
2068
    return YES;
2069
}
2070

    
2071
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2072
    if ((item == containersNode) || (item == sharedNode))
2073
        return YES;
2074
    return NO;
2075
}
2076

    
2077
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2078
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2079
    if (node) {
2080
        rootNode = node;
2081
        [browser loadColumnZero];
2082
        [self refresh:nil];
2083
    }
2084
}
2085

    
2086
#pragma mark -
2087
#pragma mark NSMenuDelegate
2088

    
2089
- (void)menuNeedsUpdate:(NSMenu *)menu {
2090
    [menu removeAllItems];
2091
    NSMenuItem *menuItem;
2092
    NSString *menuItemTitle;
2093
    BOOL nodeContextMenu = NO;
2094
    PithosNode *menuNode = nil;
2095
    NSMutableArray *menuNodes;
2096
    if (menu == browserMenu) {
2097
        NSInteger column = [browser clickedColumn];
2098
        NSInteger row = [browser clickedRow];
2099
        if ((column == -1) || (row == -1)) {
2100
            if (column == -1) {
2101
                // General context menu
2102
                NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2103
                if ([menuNodesIndexPaths count] == 0) {
2104
                    menuNode = [browser parentForItemsInColumn:0];
2105
                } else if (([menuNodesIndexPaths count] != 1) || 
2106
                           ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2107
                    menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2108
                } else {
2109
                    menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2110
                }
2111
            } else {
2112
                menuNode = [browser parentForItemsInColumn:column];
2113
                if ([menuNode class] == [PithosObjectNode class]) {
2114
                    // Node context menu
2115
                    menuNodes = [NSMutableArray arrayWithObject:menuNode];
2116
                    nodeContextMenu = YES;
2117
                }
2118
                // else 
2119
                // General context menu
2120
            }
2121
        } else {
2122
            // Node context menu
2123
            NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2124
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2125
            menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2126
            if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2127
                for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2128
                    [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2129
                }
2130
            } else {
2131
                [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2132
            }
2133
            nodeContextMenu = YES;
2134
        }
2135
    } else if (menu == outlineViewMenu) {
2136
        NSInteger row = [outlineView clickedRow];
2137
        if (row == -1)
2138
            row = [outlineView selectedRow];
2139
        if (row == -1)
2140
            return;
2141
        menuNode = [outlineView itemAtRow:row];
2142
    }
2143

    
2144
    if (!nodeContextMenu) {
2145
        // General context menu
2146
        if (([menuNode class] == [PithosAccountNode class]) || 
2147
            ([menuNode class] == [PithosSharingAccountsNode class]) ||
2148
            ([menuNode class] == [PithosEmptyNode class]))
2149
            return;
2150
        // New Folder
2151
        if (!menuNode.shared && !menuNode.sharingAccount) {
2152
            menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
2153
            [menuItem setRepresentedObject:menuNode];
2154
            [menu addItem:menuItem];
2155
            [menu addItem:[NSMenuItem separatorItem]];
2156
        }
2157
        // Refresh
2158
        menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2159
        [menu addItem:menuItem];
2160
        [menu addItem:[NSMenuItem separatorItem]];
2161
        // Get Info
2162
        menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
2163
                                               action:@selector(menuGetInfo:) 
2164
                                        keyEquivalent:@""];
2165
        [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2166
        [menu addItem:menuItem];
2167
        // Paste
2168
        if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount && 
2169
            (([menuNode class] == [PithosContainerNode class]) || 
2170
             (([menuNode class] == [PithosSubdirNode class]) && 
2171
              (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2172
            NSUInteger clipboardNodesCount = [clipboardNodes count];
2173
            if (clipboardNodesCount == 0) {
2174
                self.clipboardNodes = nil;
2175
            } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2176
                if (clipboardNodesCount == 1)
2177
                    menuItemTitle = @"Paste Item";
2178
                else
2179
                    menuItemTitle = @"Paste Items";
2180
                [menu addItem:[NSMenuItem separatorItem]];
2181
                menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2182
                [menuItem setRepresentedObject:menuNode];
2183
                [menu addItem:menuItem];
2184
            }
2185
        }
2186
    } else {
2187
        // Node context menu
2188
        NSUInteger menuNodesCount = [menuNodes count];
2189
        PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2190
        // Download
2191
        if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2192
            menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
2193
            [menuItem setRepresentedObject:menuNodes];
2194
            [menu addItem:menuItem];
2195
            [menu addItem:[NSMenuItem separatorItem]];
2196
        }
2197
        // Move to Trash (pithos container only)
2198
        // Delete
2199
        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2200
            if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2201
                menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash" 
2202
                                                       action:@selector(menuMoveToTrash:) 
2203
                                                keyEquivalent:@""];
2204
                [menuItem setRepresentedObject:menuNodes];
2205
                [menu addItem:menuItem];
2206
            }
2207
            menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
2208
            [menuItem setRepresentedObject:menuNodes];
2209
            [menu addItem:menuItem];
2210
            [menu addItem:[NSMenuItem separatorItem]];
2211
        }
2212
        // Refresh
2213
        menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2214
        [menu addItem:menuItem];
2215
        // Get Info
2216
        if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2217
            [menu addItem:[NSMenuItem separatorItem]];
2218
            menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing") 
2219
                                                   action:@selector(menuGetInfo:) 
2220
                                            keyEquivalent:@""];
2221
            [menuItem setRepresentedObject:menuNodes];
2222
            [menu addItem:menuItem];
2223
            
2224
            if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2225
                [menu addItem:[NSMenuItem separatorItem]];
2226
        }
2227
        // Cut
2228
        if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2229
            if (menuNodesCount == 1)
2230
                menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2231
            else 
2232
                menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2233
            menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
2234
            [menuItem setRepresentedObject:menuNodes];
2235
            [menu addItem:menuItem];
2236
        }
2237
        // Copy
2238
        if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || 
2239
            (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2240
            if (menuNodesCount == 1)
2241
                menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2242
            else 
2243
                menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2244
            menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
2245
            [menuItem setRepresentedObject:menuNodes];
2246
            [menu addItem:menuItem];
2247
        }
2248
        // Paste
2249
        if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) && 
2250
            ([firstMenuNode class] == [PithosSubdirNode class]) && 
2251
            (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2252
            NSUInteger clipboardNodesCount = [clipboardNodes count];
2253
            if (clipboardNodesCount == 0) {
2254
                self.clipboardNodes = nil;
2255
            } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2256
                if (clipboardNodesCount == 1)
2257
                    menuItemTitle = @"Paste Item";
2258
                else
2259
                    menuItemTitle = @"Paste Items";
2260
                menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2261
                [menuItem setRepresentedObject:firstMenuNode];
2262
                [menu addItem:menuItem];
2263
            }
2264
        }
2265
    }
2266
}
2267

    
2268
#pragma mark -
2269
#pragma mark NSMenuValidation
2270

    
2271
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2272
    if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2273
        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2274
        if ([menuNodesIndexPaths count] == 0)
2275
            return NO;
2276
        
2277
        PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2278
        if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) || 
2279
            ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) && 
2280
             (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2281
            ((menuItem.action == @selector(delete:)) && 
2282
             (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) || 
2283
              ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2284
            return NO;
2285
        
2286
        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2287
        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2288
            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2289
        }
2290
        menuItem.representedObject = menuNodes;
2291
    } else if (menuItem.action == @selector(paste:)) {
2292
        if (!clipboardNodes || ![clipboardNodes count])
2293
            return NO;
2294
        
2295
        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2296
        PithosNode *menuNode;
2297
        if ([menuNodesIndexPaths count] == 0)
2298
            menuNode = [browser parentForItemsInColumn:0];
2299
        else if (([menuNodesIndexPaths count] != 1) || 
2300
                 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2301
            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2302
        else
2303
            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2304
        
2305
        if (menuNode.shared || menuNode.sharingAccount || 
2306
            (([menuNode class] != [PithosContainerNode class]) && 
2307
             (([menuNode class] != [PithosSubdirNode class]) || 
2308
              (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) || 
2309
            (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2310
            return NO;
2311

    
2312
        menuItem.representedObject = menuNode;
2313
    }
2314
    return YES;
2315
}
2316

    
2317
- (void)cut:(NSMenuItem *)sender {
2318
    [self menuCut:sender];
2319
}
2320

    
2321
- (void)copy:(NSMenuItem *)sender {
2322
    [self menuCopy:sender];
2323
}
2324

    
2325
- (void)paste:(NSMenuItem *)sender {
2326
    [self menuPaste:sender];
2327
}
2328

    
2329
- (void)delete:(NSMenuItem *)sender {
2330
    if (sender.tag == 0)
2331
        [self menuMoveToTrash:sender];
2332
    else
2333
        [self menuDelete:sender];
2334
}
2335

    
2336
#pragma mark -
2337
#pragma mark Menu Actions
2338

    
2339
- (void)menuNewFolder:(NSMenuItem *)sender {
2340
    PithosNode *node = (PithosNode *)[sender representedObject];
2341
    if ([node class] == [PithosContainerNode class]) {
2342
        // Operation: Create (upload) a new root application/directory object
2343
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2344
            @autoreleasepool {
2345
                if (operation.isCancelled)
2346
                    return;
2347
                NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2348
                                                                      containerName:node.pithosContainer.name 
2349
                                                                         subdirName:@"untitled folder"];
2350
                NSString *fileName = [safeObjectName lastPathComponent];
2351
                if (operation.isCancelled)
2352
                    return;
2353
                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2354
                                                                                                   containerName:node.pithosContainer.name 
2355
                                                                                                      objectName:safeObjectName 
2356
                                                                                                            eTag:nil 
2357
                                                                                                     contentType:@"application/directory" 
2358
                                                                                                 contentEncoding:nil 
2359
                                                                                              contentDisposition:nil 
2360
                                                                                                        manifest:nil 
2361
                                                                                                         sharing:nil 
2362
                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
2363
                                                                                                        metadata:nil 
2364
                                                                                                            data:[NSData data]];
2365
                objectRequest.delegate = self;
2366
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2367
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2368
                NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2369
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2370
                                                                           message:messagePrefix];
2371
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2372
                                          fileName, @"fileName", 
2373
                                          [NSArray arrayWithObject:node], @"refreshNodes", 
2374
                                          [NSNumber numberWithBool:YES], @"refresh", 
2375
                                          activity, @"activity", 
2376
                                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2377
                                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2378
                                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2379
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2380
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
2381
                                          NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2382
                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2383
                                          uploadNetworkQueue, @"networkQueue", 
2384
                                          @"upload", @"operationType", 
2385
                                          nil];
2386
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2387
            }
2388
        }];
2389
        [uploadQueue addOperation:operation];
2390
    } else if (([node class] == [PithosSubdirNode class]) && 
2391
               (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2392
        // Operation: Create (upload) a new aplication/directory object
2393
        __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2394
            @autoreleasepool {
2395
                if (operation.isCancelled)
2396
                    return;
2397
                NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2398
                                                                      containerName:node.pithosContainer.name 
2399
                                                                         subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2400
                NSString *fileName = [safeObjectName lastPathComponent];
2401
                if (operation.isCancelled)
2402
                    return;
2403
                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
2404
                                                                                                   containerName:node.pithosContainer.name 
2405
                                                                                                      objectName:safeObjectName 
2406
                                                                                                            eTag:nil
2407
                                                                                                     contentType:@"application/directory" 
2408
                                                                                                 contentEncoding:nil 
2409
                                                                                              contentDisposition:nil 
2410
                                                                                                        manifest:nil 
2411
                                                                                                         sharing:nil 
2412
                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
2413
                                                                                                        metadata:nil 
2414
                                                                                                            data:[NSData data]];
2415
                objectRequest.delegate = self;
2416
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2417
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2418
                NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2419
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
2420
                                                                           message:messagePrefix];
2421
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2422
                                          fileName, @"fileName", 
2423
                                          [NSArray arrayWithObject:node], @"refreshNodes", 
2424
                                          [NSNumber numberWithBool:YES], @"refresh", 
2425
                                          activity, @"activity", 
2426
                                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2427
                                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2428
                                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2429
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2430
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
2431
                                          NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
2432
                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2433
                                          uploadNetworkQueue, @"networkQueue", 
2434
                                          @"upload", @"operationType", 
2435
                                          nil];
2436
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2437
            }
2438
        }];
2439
        [uploadQueue addOperation:operation];
2440
    }
2441
}
2442

    
2443
- (void)menuGetInfo:(NSMenuItem *)sender {
2444
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2445
        [node showPithosNodeInfo:sender];
2446
    }
2447
}
2448

    
2449
- (void)menuDownload:(NSMenuItem *)sender {
2450
    NSArray *nodes = (NSArray *)[sender representedObject];
2451
    PithosNode *firstNode = [nodes objectAtIndex:0];
2452
    if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2453
        NSSavePanel *save = [NSSavePanel savePanel];
2454
        save.nameFieldStringValue = firstNode.displayName;
2455
        NSInteger result = [save runModal];
2456
        if (result == NSOKButton) {
2457
            NSString *destinationPath = save.URL.path;
2458
            NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2459
            NSString *newFileName = [destinationPath lastPathComponent];
2460
            if ([destinationPath hasSuffix:@"/"])
2461
                newFileName = [newFileName stringByAppendingString:@"/"];
2462
            if ([firstNode.displayName isEqualToString:newFileName])
2463
                newFileName = nil;
2464
            [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2465
        }
2466
    } else {
2467
        NSOpenPanel *open = [NSOpenPanel openPanel];
2468
        open.canChooseFiles = NO;
2469
        open.canChooseDirectories = YES;
2470
        open.canCreateDirectories = YES;
2471
        NSInteger result = [open runModal];
2472
        if (result == NSOKButton) {
2473
            NSString *directoryPath = open.URL.path;
2474
            for (PithosNode *node in nodes) {
2475
                [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2476
            }
2477
        }
2478
    }
2479
}
2480

    
2481
- (void)menuDelete:(NSMenuItem *)sender {
2482
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2483
        if (([node class] == [PithosObjectNode class]) || 
2484
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2485
            // Operation: Delete an object or subdir/ node
2486
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2487
                @autoreleasepool {
2488
                    if (operation.isCancelled)
2489
                        return;
2490
                    NSString *fileName = [node.pithosObject.name lastPathComponent];
2491
                    if ([node.pithosObject.name hasSuffix:@"/"])
2492
                        fileName = [fileName stringByAppendingString:@"/"];
2493
                    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
2494
                                                                                                    containerName:node.pithosContainer.name 
2495
                                                                                                       objectName:node.pithosObject.name];
2496
                    if (operation.isCancelled)
2497
                        return;
2498
                    objectRequest.delegate = self;
2499
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2500
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2501
                    NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2502
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2503
                                                                               message:messagePrefix];
2504
                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2505
                                              fileName, @"fileName", 
2506
                                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2507
                                              activity, @"activity", 
2508
                                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2509
                                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2510
                                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2511
                                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2512
                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
2513
                                              NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2514
                                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2515
                                              deleteNetworkQueue, @"networkQueue", 
2516
                                              @"delete", @"operationType", 
2517
                                              nil];
2518
                    [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2519
                }
2520
            }];
2521
            [deleteQueue addOperation:operation];
2522
        } else if ([node class] == [PithosSubdirNode class]) {
2523
            // Operation: Delete a subdir node and its descendants
2524
            // The resulting ASIPithosObjectRequests are chained through dependencies
2525
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2526
                @autoreleasepool {
2527
                    if (operation.isCancelled)
2528
                        return;
2529
                    NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2530
                                                                                         containerName:node.pithosContainer.name 
2531
                                                                                            objectName:node.pithosObject.name];
2532
                    if (!operation.isCancelled && objectRequests) {
2533
                        ASIPithosObjectRequest *previousObjectRequest = nil;
2534
                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2535
                            if (operation.isCancelled)
2536
                                return;
2537
                            objectRequest.delegate = self;
2538
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2539
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2540
                            NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2541
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
2542
                                                                                       message:messagePrefix];
2543
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2544
                             [NSDictionary dictionaryWithObjectsAndKeys:
2545
                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2546
                              activity, @"activity", 
2547
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2548
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2549
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2550
                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2551
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
2552
                              NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
2553
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2554
                              deleteNetworkQueue, @"networkQueue", 
2555
                              @"delete", @"operationType", 
2556
                              nil]];
2557
                            if (previousObjectRequest)
2558
                                [objectRequest addDependency:previousObjectRequest];
2559
                            previousObjectRequest = objectRequest;
2560
                            [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2561
                        }
2562
                    }
2563
                }
2564
            }];
2565
            [deleteQueue addOperation:operation];
2566
        }
2567
    }
2568
}
2569

    
2570
- (void)menuMoveToTrash:(NSMenuItem *)sender {
2571
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2572
        if (([node class] == [PithosObjectNode class]) || 
2573
            (([node class] == [PithosSubdirNode class]) && 
2574
             !node.pithosObject.subdir &&
2575
             [node.pithosObject.name hasSuffix:@"/"])) {
2576
            // Operation: Move to trash an object or subdir/ node
2577
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2578
                @autoreleasepool {
2579
                    if (operation.isCancelled)
2580
                        return;
2581
                    NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2582
                                                                          containerName:@"trash" 
2583
                                                                             objectName:node.pithosObject.name];
2584
                    if (!operation.isCancelled && safeObjectName) {
2585
                        ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
2586
                                                                                               containerName:node.pithosContainer.name 
2587
                                                                                                  objectName:node.pithosObject.name 
2588
                                                                                    destinationContainerName:@"trash" 
2589
                                                                                       destinationObjectName:safeObjectName 
2590
                                                                                               checkIfExists:NO];
2591
                        if (!operation.isCancelled && objectRequest) {
2592
                            objectRequest.delegate = self;
2593
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2594
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2595
                            NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2596
                                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2597
                                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2598
                                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2599
                                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2600
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2601
                                                                                       message:messagePrefix];
2602
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2603
                             [NSDictionary dictionaryWithObjectsAndKeys:
2604
                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2605
                              activity, @"activity", 
2606
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2607
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2608
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2609
                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2610
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
2611
                              NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2612
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2613
                              moveNetworkQueue, @"networkQueue", 
2614
                              @"move", @"operationType", 
2615
                              nil]];
2616
                            [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2617
                        }
2618
                    }
2619
                }
2620
            }];
2621
            [moveQueue addOperation:operation];
2622
        } else if ([node class] == [PithosSubdirNode class]) {
2623
            // Operation: Move to trash a subdir node and its descendants
2624
            // The resulting ASIPithosObjectRequests are chained through dependencies
2625
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2626
                @autoreleasepool {
2627
                    if (operation.isCancelled)
2628
                        return;
2629
                    NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos 
2630
                                                                          containerName:@"trash" 
2631
                                                                             subdirName:node.pithosObject.name];
2632
                    if (!operation.isCancelled && safeObjectName) {
2633
                        NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
2634
                                                                                           containerName:node.pithosContainer.name 
2635
                                                                                              objectName:node.pithosObject.name 
2636
                                                                                destinationContainerName:@"trash" 
2637
                                                                                   destinationObjectName:safeObjectName 
2638
                                                                                           checkIfExists:NO];
2639
                        if (!operation.isCancelled && objectRequests) {
2640
                            ASIPithosObjectRequest *previousObjectRequest = nil;
2641
                            for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2642
                                if (operation.isCancelled)
2643
                                    return;
2644
                                objectRequest.delegate = self;
2645
                                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2646
                                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2647
                                NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2648
                                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2649
                                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2650
                                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2651
                                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2652
                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
2653
                                                                                           message:messagePrefix];
2654
                                [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2655
                                 [NSDictionary dictionaryWithObjectsAndKeys:
2656
                                  [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2657
                                  activity, @"activity", 
2658
                                  [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
2659
                                  [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
2660
                                  [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
2661
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
2662
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
2663
                                  NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
2664
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
2665
                                  moveNetworkQueue, @"networkQueue", 
2666
                                  @"move", @"operationType", 
2667
                                  nil]];
2668
                                if (previousObjectRequest)
2669
                                    [objectRequest addDependency:previousObjectRequest];
2670
                                previousObjectRequest = objectRequest;
2671
                                [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2672
                            }
2673
                        }
2674
                    }
2675
                }
2676
            }];
2677
            [moveQueue addOperation:operation];
2678
        }
2679
    }
2680
}
2681

    
2682
- (void)menuCut:(NSMenuItem *)sender {
2683
    self.clipboardNodes = (NSArray *)[sender representedObject];
2684
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2685
    self.clipboardCopy = NO;
2686
}
2687

    
2688
- (void)menuCopy:(NSMenuItem *)sender {
2689
    self.clipboardNodes = (NSArray *)[sender representedObject];
2690
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2691
    self.clipboardCopy = YES;
2692
}
2693

    
2694
- (void)menuPaste:(NSMenuItem *)sender {
2695
    if (!clipboardNodes || ![clipboardNodes count])
2696
        return;
2697
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2698
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2699
    if (clipboardCopy) {
2700
        [self cpyNodes:localClipboardNodes toNode:dropNode];
2701
    } else if (![dropNode isEqualTo:clipboardParentNode]) {
2702
        self.clipboardNodes = nil;
2703
        self.clipboardParentNode = nil;
2704
        [self moveNodes:localClipboardNodes toNode:dropNode];
2705
    }
2706
}
2707
    
2708
#pragma mark -
2709
#pragma mark PithosActivityFacilityDelegate
2710

    
2711
- (void)activityUpdate:(NSDictionary *)info {
2712
    NSString *message = [info objectForKey:@"message"];
2713
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2714
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2715
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2716
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2717
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2718
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2719
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2720
    if (runningActivitiesCount && totalBytes) {
2721
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2722
        [activityProgressIndicator startAnimation:self];
2723
    } else {
2724
        [activityProgressIndicator setDoubleValue:1.0];
2725
        [activityProgressIndicator stopAnimation:self];
2726
    }
2727
    
2728
    if (!message)
2729
        message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2730
    [activityTextField setStringValue:message];
2731
}
2732

    
2733
@end