Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ 6d9d5dce

History | View | Annotate | Download (163.6 kB)

1
//
2
//  PithosBrowserController.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2013 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 "PithosAccount.h"
58
#import "UsingSizeTransformer.h"
59

    
60
#define REFRESH_TIMER_INTERVAL 5
61

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

    
65
@implementation PithosBrowserCell
66

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

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

    
85
@end
86

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

    
90
@implementation PithosOutlineViewCell
91

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

    
103
@end
104

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

    
111
@implementation PithosBrowserController
112
@synthesize pithosAccountManager, accountNode;
113
@synthesize draggedNodes, draggedParentNode;
114
@synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
115

    
116
#pragma mark -
117
#pragma Object Lifecycle
118

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

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

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

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

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

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

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

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

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

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

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

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

    
383
- (void)setPithosAccountManager:(PithosAccount *)aPithosAccountManager {
384
    if (aPithosAccountManager && (aPithosAccountManager != pithosAccountManager)) {
385
        [self resetBrowser];
386
        if (pithosAccountManager)
387
            [[NSNotificationCenter defaultCenter] removeObserver:self name:@"PithosAccountPithosChanged" object:pithosAccountManager];
388
        pithosAccountManager = aPithosAccountManager;
389
        [[NSNotificationCenter defaultCenter] addObserver:self
390
                                                 selector:@selector(pithosAccountManagerPithosChanged:)
391
                                                     name:@"PithosAccountPithosChanged"
392
                                                   object:pithosAccountManager];
393
        [self startBrowser];
394
    }
395
}
396

    
397
#pragma mark -
398
#pragma mark Observers
399

    
400
-(void)pithosAccountManagerPithosChanged:(NSNotification *)notification {
401
    if (![NSThread isMainThread]) {
402
        [self performSelectorOnMainThread:@selector(pithosAccountManagerPithosChanged:) withObject:notification waitUntilDone:NO];
403
        return;
404
    }
405
    [self resetBrowser];
406
    [self startBrowser];
407
}
408

    
409
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
410
    if (![NSThread isMainThread]) {
411
        [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
412
        return;
413
    }
414
    PithosNode *node = (PithosNode *)[notification object];
415
    if ((node == accountNode) || (node.pithosAccountManager != pithosAccountManager))
416
        return;
417
    DLog(@"pithosNodeChildrenUpdated:%@", node.url);
418
    NSInteger lastColumn = [browser lastColumn];
419
    for (NSInteger column = lastColumn; column >= 0; column--) {
420
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
421
            [browser reloadColumn:column];
422
            return;
423
        }
424
    }
425
}
426

    
427
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
428
    if (![NSThread isMainThread]) {
429
        [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
430
        return;
431
    }
432
    BOOL containerPithosFound = NO;
433
    BOOL containerTrashFound = NO;
434
    NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
435
    for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
436
        if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
437
            [removedContainersNodeChildren addIndex:i];
438
    }
439
    [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
440
    for (PithosContainerNode *containerNode in accountNode.children) {
441
        if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
442
            if (![containersNodeChildren containsObject:containerNode])
443
                [containersNodeChildren insertObject:containerNode atIndex:0];
444
            containerPithosFound = YES;
445
        } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
446
            NSUInteger insertIndex = 1;
447
            if (!containerPithosFound)
448
                insertIndex = 0;
449
            if (![containersNodeChildren containsObject:containerNode])
450
                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
451
            containerTrashFound = YES;
452
        } else if (![containersNodeChildren containsObject:containerNode]) {
453
            [containersNodeChildren addObject:containerNode];
454
        }
455
    }
456
    BOOL refreshAccountNode = NO;
457
    if (!containerPithosFound) {
458
        // Create pithos node
459
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithosAccountManager.pithos
460
                                                                                                            containerName:@"pithos"];
461
        [PithosUtilities startAndWaitForRequest:containerRequest];
462
        if ([containerRequest error]) {
463
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
464
        } else {
465
            refreshAccountNode = YES;
466
        }
467
    }
468
    if (!containerTrashFound) {
469
        // Create trash node
470
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithosAccountManager.pithos 
471
                                                                                                            containerName:@"trash"];
472
        [PithosUtilities startAndWaitForRequest:containerRequest];
473
        if ([containerRequest error]) {
474
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
475
        } else {
476
            refreshAccountNode = YES;
477
        }
478
    }
479
    
480
    if (refreshAccountNode)
481
        [accountNode refresh];
482
    
483
    [outlineView reloadData];
484
    
485
    // Expand the folder outline view
486
    [outlineView expandItem:nil expandChildren:YES];
487
    
488
    if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
489
        rootNode = [containersNodeChildren objectAtIndex:0];
490
        [browser loadColumnZero];
491
    }
492
    
493
    if (notification)
494
        [self refresh:nil];
495
}
496

    
497
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
498
    [self refresh:nil];
499
}
500

    
501
#pragma mark -
502
#pragma mark Actions
503

    
504
- (IBAction)forceRefresh:(id)sender {
505
    if (![NSThread isMainThread]) {
506
        [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO];
507
        return;
508
    }
509
    if (editingItem)
510
        return;
511
    if (sender)
512
        [accountNode forceRefresh];
513
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
514
        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
515
        node.forcedRefresh = YES;
516
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
517
    }
518
    [browser validateVisibleColumns];
519
}
520

    
521
- (IBAction)refresh:(id)sender {
522
    if (![NSThread isMainThread]) {
523
        [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO];
524
        return;
525
    }
526
    if (editingItem)
527
        return;
528
    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
529
        [self forceRefresh:sender];
530
    } else {
531
        if (sender)
532
            [accountNode refresh];
533
        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
534
            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
535
        }
536
        [browser validateVisibleColumns];
537
    }
538
}
539

    
540
#pragma mark -
541
#pragma mark NSBrowserDelegate
542

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

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

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

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

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

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

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

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

    
587
#pragma mark Editing
588

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

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

    
758
#pragma mark Drag and Drop source
759

    
760
- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
761
      withEvent:(NSEvent *)event {
762
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
763
    __block BOOL result = YES;
764
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
765
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
766
        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
767
            result = NO;
768
            *stop = YES;
769
        }
770
    }];
771
    return result;
772
}
773

    
774
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
775
   toPasteboard:(NSPasteboard *)pasteboard {
776
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
777
    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
778
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
779
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
780
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
781
        [propertyList addObject:[node.pithosObject.name pathExtension]];
782
        [nodes addObject:node];
783
    }];
784

    
785
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
786
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
787
    self.draggedNodes = nodes;
788
    self.draggedParentNode = [browser parentForItemsInColumn:column];
789
    return YES;
790
}
791

    
792
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
793
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
794
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
795
    for (PithosNode *node in draggedNodes) {
796
        [names addObject:node.displayName];
797
        // If the node is a subdir ask if the whole tree should be downloaded
798
        if ([node class] == [PithosSubdirNode class]) {
799
            NSAlert *alert = [[NSAlert alloc] init];
800
            [alert setMessageText:@"Download directory"];
801
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
802
            [alert addButtonWithTitle:@"OK"];
803
            [alert addButtonWithTitle:@"Cancel"];
804
            NSInteger choice = [alert runModal];
805
            if (choice == NSAlertFirstButtonReturn)
806
                [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
807
        } else {
808
            [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
809
        }
810
    }
811
    return names;
812
}
813

    
814
#pragma mark Drag and Drop destination
815

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

    
887
- (BOOL)browser:(NSBrowser *)aBrowser 
888
     acceptDrop:(id<NSDraggingInfo>)info 
889
          atRow:(NSInteger)row 
890
         column:(NSInteger)column 
891
  dropOperation:(NSBrowserDropOperation)dropOperation {
892
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
893
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
894
        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
895
        if ((column != -1) && (filenames != nil)) {
896
            PithosNode *node;
897
            if (row != -1)
898
                node = [browser itemAtRow:row inColumn:column];
899
            else
900
                node = [browser parentForItemsInColumn:column];
901
            DLog(@"drag in node: %@", node.url);
902
            return [self uploadFiles:filenames toNode:node];
903
        }
904
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
905
        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
906
        if ((column != -1) && (draggedNodes != nil)) {
907
            PithosNode *node;
908
            if (row != -1)
909
                node = [browser itemAtRow:row inColumn:column];
910
            else
911
                node = [browser parentForItemsInColumn:column];
912
            DLog(@"drag local node: %@", node.url);
913
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
914
                return [self moveNodes:draggedNodes toNode:node];
915
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
916
                return [self cpyNodes:draggedNodes toNode:node];
917
        }
918
    }
919
    return NO;
920
}
921

    
922
#pragma mark -
923
#pragma mark NSBrowser Actions
924

    
925
- (void)browserDoubleAction:(id)sender {
926
    NSInteger column = [browser clickedColumn];
927
    NSInteger row = [browser clickedRow];
928
    if ((column == -1) || (row == -1))
929
        return;
930
    NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
931
    NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
932
    NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
933
    if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
934
        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
935
            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
936
        }
937
    } else {
938
        [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
939
    }
940
    NSMenuItem *menuItem = [[NSMenuItem alloc] init];
941
    menuItem.representedObject = menuNodes;
942
    [self menuDownload:menuItem];
943
}
944

    
945
#pragma mark -
946
#pragma mark Drag and Drop methods
947

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

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

    
1279
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1280
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1281
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
1282
        return NO;
1283
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1284
    NSString *objectNamePrefix;
1285
    if ([destinationNode class] == [PithosSubdirNode class])
1286
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1287
    else
1288
        objectNamePrefix = [NSString string];
1289

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

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

    
1534
#pragma mark -
1535
#pragma mark ASIHTTPRequestDelegate
1536

    
1537
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1538
    NSOperationQueue *callbackQueue;
1539
    NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1540
    if ([operationType isEqualToString:@"move"])
1541
        callbackQueue = moveCallbackQueue;
1542
    else if ([operationType isEqualToString:@"copy"])
1543
        callbackQueue = copyCallbackQueue;
1544
    else if ([operationType isEqualToString:@"delete"])
1545
        callbackQueue = deleteCallbackQueue;
1546
    else if ([operationType isEqualToString:@"upload"])
1547
        callbackQueue = uploadCallbackQueue;
1548
    else if ([operationType isEqualToString:@"download"])
1549
        callbackQueue = downloadCallbackQueue;
1550
    else {
1551
        dispatch_async(dispatch_get_main_queue(), ^{
1552
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1553
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1554
        });
1555
        return;
1556
    }
1557
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1558
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
1559
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1560
                                                                               object:request];
1561
    operation.completionBlock = ^{
1562
        @autoreleasepool {
1563
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1564
                dispatch_async(dispatch_get_main_queue(), ^{
1565
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1566
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1567
                });
1568
            }
1569
        }
1570
    };
1571
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1572
    [callbackQueue addOperation:operation];
1573
}
1574

    
1575
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1576
    if (request.isCancelled) {
1577
        // Request has been cancelled 
1578
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1579
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1580
                   withObject:request];
1581
    } else {
1582
        NSOperationQueue *callbackQueue;
1583
        NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1584
        if ([operationType isEqualToString:@"move"])
1585
            callbackQueue = moveCallbackQueue;
1586
        else if ([operationType isEqualToString:@"copy"])
1587
            callbackQueue = copyCallbackQueue;
1588
        else if ([operationType isEqualToString:@"delete"])
1589
            callbackQueue = deleteCallbackQueue;
1590
        else if ([operationType isEqualToString:@"upload"])
1591
            callbackQueue = uploadCallbackQueue;
1592
        else if ([operationType isEqualToString:@"download"])
1593
            callbackQueue = downloadCallbackQueue;
1594
        else {
1595
            dispatch_async(dispatch_get_main_queue(), ^{
1596
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1597
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1598
            });
1599
            return;
1600
        }
1601
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1602
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
1603
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1604
                                                                                   object:request];
1605
        operation.completionBlock = ^{
1606
            @autoreleasepool {
1607
                if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1608
                    dispatch_async(dispatch_get_main_queue(), ^{
1609
                        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1610
                                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1611
                    });
1612
                }
1613
            }
1614
        };
1615
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1616
        [callbackQueue addOperation:operation];
1617
    }
1618
}
1619

    
1620
- (void)requestFailed:(ASIPithosRequest *)request {
1621
    @autoreleasepool {
1622
        NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1623
        DLog(@"Request failed: %@", request.url);
1624
        if (operation.isCancelled)
1625
            return;
1626
        if (request.isCancelled) {
1627
            dispatch_async(dispatch_get_main_queue(), ^{
1628
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1629
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1630
            });
1631
            return;
1632
        }
1633
        NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1634
        if (retries > 0) {
1635
            ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities retryWithUpdatedURLRequest:request
1636
                                                                                   andPithosAccountManager:pithosAccountManager];;
1637
            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1638
            [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1639
            [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1640
        } else {
1641
            dispatch_async(dispatch_get_main_queue(), ^{
1642
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1643
                                  withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1644
            });
1645
            if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1646
                [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1647
            else
1648
                [PithosUtilities httpRequestErrorAlertWithRequest:request];
1649
        }
1650
    }
1651
}
1652

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

    
1703
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1704
    @autoreleasepool {
1705
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1706
        DLog(@"Upload directory object finished: %@", objectRequest.url);
1707
        if (operation.isCancelled) {
1708
            [self requestFailed:objectRequest];
1709
        } else if (objectRequest.responseStatusCode == 201) {
1710
            DLog(@"Directory object created: %@", objectRequest.url);
1711
            dispatch_async(dispatch_get_main_queue(), ^{
1712
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1713
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1714
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1715
                    [node forceRefresh];
1716
                }
1717
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1718
                    [node refresh];
1719
                }
1720
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1721
                    [self forceRefresh:self];
1722
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1723
                    [self refresh:self];
1724
            });
1725
        } else {
1726
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1727
            [self requestFailed:objectRequest];
1728
        }
1729
    }
1730
}
1731

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

    
1818
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1819
    @autoreleasepool {
1820
        NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1821
        DLog(@"Upload of missing block finished: %@", containerRequest.url);
1822
        NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1823
        NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1824
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1825
        if (operation.isCancelled) {
1826
            [self requestFailed:containerRequest];
1827
        } else if (containerRequest.responseStatusCode == 202) {
1828
            NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1829
            NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1830
            missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1831
            if (missingBlockIndex == NSNotFound) {
1832
                NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1833
                ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithosAccountManager.pithos 
1834
                                                                                               containerName:[containerRequest.userInfo objectForKey:@"containerName"] 
1835
                                                                                                  objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
1836
                                                                                                 contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
1837
                                                                                                   blockSize:blockSize 
1838
                                                                                                   blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1839
                                                                                                     forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1840
                                                                                               checkIfExists:NO 
1841
                                                                                                      hashes:&hashes 
1842
                                                                                              sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1843
                newObjectRequest.delegate = self;
1844
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1845
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1846
                newObjectRequest.userInfo = containerRequest.userInfo;
1847
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1848
                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1849
                [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1850
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1851
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1852
            } else {
1853
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithosAccountManager.pithos 
1854
                                                                                                                 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1855
                                                                                                                     blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1856
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1857
                                                                                                             missingBlockIndex:missingBlockIndex 
1858
                                                                                                                sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1859
                newContainerRequest.delegate = self;
1860
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1861
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1862
                newContainerRequest.userInfo = containerRequest.userInfo;
1863
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1864
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1865
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1866
                    [activityFacility updateActivity:activity 
1867
                                         withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)] 
1868
                                          totalBytes:activity.totalBytes 
1869
                                        currentBytes:(activity.currentBytes + size)];
1870
                }];
1871
                [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1872
            }
1873
        } else {
1874
            [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1875
            [self requestFailed:containerRequest];
1876
        }
1877
    }
1878
}
1879

    
1880
- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1881
    @autoreleasepool {
1882
        NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1883
        DLog(@"Move object finished: %@", objectRequest.url);
1884
        if (operation.isCancelled) {
1885
            [self requestFailed:objectRequest];
1886
        } else if (objectRequest.responseStatusCode == 201) {
1887
            dispatch_async(dispatch_get_main_queue(), ^{
1888
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1889
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1890
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1891
                    [node forceRefresh];
1892
                }
1893
                for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1894
                    [node refresh];
1895
                }
1896
                if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1897
                    [self forceRefresh:self];
1898
                else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1899
                    [self refresh:self];
1900
            });
1901
        } else {
1902
            [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1903
            [self requestFailed:objectRequest];
1904
        }
1905
    }
1906
}
1907

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

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

    
1964
#pragma mark -
1965
#pragma mark NSSplitViewDelegate
1966

    
1967
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1968
    if (splitView == verticalSplitView)
1969
        return 140;
1970
    else
1971
        return ([horizontalSplitView bounds].size.height - 142);
1972
}
1973

    
1974
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1975
    if (splitView == verticalSplitView)
1976
        return 220;
1977
    else
1978
        return ([horizontalSplitView bounds].size.height - 108);
1979
}
1980

    
1981
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
1982
    if (((splitView == verticalSplitView) && (view == leftView)) ||
1983
        ((splitView == horizontalSplitView) && (view == leftBottomView))) {
1984
        return NO;
1985
    }
1986
    return YES;
1987
}
1988

    
1989
#pragma mark -
1990
#pragma mark NSOutlineViewDataSource
1991

    
1992
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1993
    if (!browserInitialized)
1994
        return 0;
1995
    if (item == nil)
1996
        return 2;
1997
    if (item == containersNode)
1998
        return containersNodeChildren.count;
1999
    if (item == sharedNode)
2000
        return 2;
2001
    return 0;
2002
}
2003

    
2004
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2005
    if (!browserInitialized)
2006
        return nil;
2007
    if (item == nil)
2008
        return (!index ? containersNode : sharedNode);
2009
    if (item == sharedNode)
2010
        return (!index ? mySharedNode : othersSharedNode);
2011
    return [containersNodeChildren objectAtIndex:index];
2012
}
2013

    
2014
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2015
    if ((item == containersNode) || (item == sharedNode))
2016
        return YES;
2017
    return NO;
2018
}
2019

    
2020
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2021
    PithosNode *node = (PithosNode *)item;
2022
    return node;    
2023
}
2024

    
2025
#pragma mark Drag and Drop destination
2026

    
2027
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2028
                  validateDrop:(id<NSDraggingInfo>)info 
2029
                  proposedItem:(id)item 
2030
            proposedChildIndex:(NSInteger)index {
2031
    NSDragOperation result = NSDragOperationNone;
2032
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2033
        return result;
2034
    PithosNode *dropNode = (PithosNode *)item;
2035
    if ([dropNode class] != [PithosContainerNode class])
2036
        return result;
2037
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2038
        result = NSDragOperationCopy;
2039
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2040
        if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2041
            ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2042
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2043
            if (![dropNode isEqualTo:draggedParentNode])
2044
                result = NSDragOperationMove;
2045
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2046
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2047
            result = NSDragOperationCopy;
2048
        }
2049
    }
2050
   return result;
2051
}
2052

    
2053
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2054
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2055
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2056
        DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2057
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2058
            PithosNode *node = (PithosNode *)item;
2059
            DLog(@"drag in node: %@", node.url);
2060
            return [self uploadFiles:filenames toNode:node];
2061
        }
2062
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2063
        DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2064
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2065
            PithosNode *node = (PithosNode *)item;
2066
            DLog(@"drag local node: %@", node.url);
2067
            if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] && 
2068
                ([info draggingSourceOperationMask] & NSDragOperationMove))
2069
                return [self moveNodes:draggedNodes toNode:node];
2070
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2071
                return [self cpyNodes:draggedNodes toNode:node];
2072
        }
2073
    }
2074
    return NO;
2075
}
2076

    
2077
#pragma mark -
2078
#pragma mark NSOutlineViewDelegate
2079

    
2080
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2081
    if ((item == containersNode) || (item == sharedNode))
2082
        return NO;
2083
    return YES;
2084
}
2085

    
2086
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2087
    if ((item == containersNode) || (item == sharedNode))
2088
        return YES;
2089
    return NO;
2090
}
2091

    
2092
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2093
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2094
    if (node) {
2095
        rootNode = node;
2096
        [browser loadColumnZero];
2097
        [self refresh:nil];
2098
    }
2099
}
2100

    
2101
#pragma mark -
2102
#pragma mark NSMenuDelegate
2103

    
2104
- (void)menuNeedsUpdate:(NSMenu *)menu {
2105
    [menu removeAllItems];
2106
    NSMenuItem *menuItem;
2107
    NSString *menuItemTitle;
2108
    BOOL nodeContextMenu = NO;
2109
    PithosNode *menuNode = nil;
2110
    NSMutableArray *menuNodes;
2111
    if (menu == browserMenu) {
2112
        NSInteger column = [browser clickedColumn];
2113
        NSInteger row = [browser clickedRow];
2114
        if ((column == -1) || (row == -1)) {
2115
            if (column == -1) {
2116
                // General context menu
2117
                NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2118
                if ([menuNodesIndexPaths count] == 0) {
2119
                    menuNode = [browser parentForItemsInColumn:0];
2120
                } else if (([menuNodesIndexPaths count] != 1) || 
2121
                           ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2122
                    menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2123
                } else {
2124
                    menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2125
                }
2126
            } else {
2127
                menuNode = [browser parentForItemsInColumn:column];
2128
                if ([menuNode class] == [PithosObjectNode class]) {
2129
                    // Node context menu
2130
                    menuNodes = [NSMutableArray arrayWithObject:menuNode];
2131
                    nodeContextMenu = YES;
2132
                }
2133
                // else 
2134
                // General context menu
2135
            }
2136
        } else {
2137
            // Node context menu
2138
            NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2139
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2140
            menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2141
            if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2142
                for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2143
                    [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2144
                }
2145
            } else {
2146
                [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2147
            }
2148
            nodeContextMenu = YES;
2149
        }
2150
    } else if (menu == outlineViewMenu) {
2151
        NSInteger row = [outlineView clickedRow];
2152
        if (row == -1)
2153
            row = [outlineView selectedRow];
2154
        if (row == -1)
2155
            return;
2156
        menuNode = [outlineView itemAtRow:row];
2157
    }
2158

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

    
2283
#pragma mark -
2284
#pragma mark NSMenuValidation
2285

    
2286
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2287
    if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2288
        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2289
        if ([menuNodesIndexPaths count] == 0)
2290
            return NO;
2291
        
2292
        PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2293
        if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) || 
2294
            ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) && 
2295
             (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2296
            ((menuItem.action == @selector(delete:)) && 
2297
             (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) || 
2298
              ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2299
            return NO;
2300
        
2301
        NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2302
        for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2303
            [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2304
        }
2305
        menuItem.representedObject = menuNodes;
2306
    } else if (menuItem.action == @selector(paste:)) {
2307
        if (!clipboardNodes || ![clipboardNodes count])
2308
            return NO;
2309
        
2310
        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2311
        PithosNode *menuNode;
2312
        if ([menuNodesIndexPaths count] == 0)
2313
            menuNode = [browser parentForItemsInColumn:0];
2314
        else if (([menuNodesIndexPaths count] != 1) || 
2315
                 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2316
            menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2317
        else
2318
            menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2319
        
2320
        if (menuNode.shared || menuNode.sharingAccount || 
2321
            (([menuNode class] != [PithosContainerNode class]) && 
2322
             (([menuNode class] != [PithosSubdirNode class]) || 
2323
              (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) || 
2324
            (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2325
            return NO;
2326

    
2327
        menuItem.representedObject = menuNode;
2328
    }
2329
    return YES;
2330
}
2331

    
2332
- (void)cut:(NSMenuItem *)sender {
2333
    [self menuCut:sender];
2334
}
2335

    
2336
- (void)copy:(NSMenuItem *)sender {
2337
    [self menuCopy:sender];
2338
}
2339

    
2340
- (void)paste:(NSMenuItem *)sender {
2341
    [self menuPaste:sender];
2342
}
2343

    
2344
- (void)delete:(NSMenuItem *)sender {
2345
    if (sender.tag == 0)
2346
        [self menuMoveToTrash:sender];
2347
    else
2348
        [self menuDelete:sender];
2349
}
2350

    
2351
#pragma mark -
2352
#pragma mark Menu Actions
2353

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

    
2458
- (void)menuGetInfo:(NSMenuItem *)sender {
2459
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2460
        [node showPithosNodeInfo:sender];
2461
    }
2462
}
2463

    
2464
- (void)menuDownload:(NSMenuItem *)sender {
2465
    NSArray *nodes = (NSArray *)[sender representedObject];
2466
    PithosNode *firstNode = [nodes objectAtIndex:0];
2467
    if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2468
        NSSavePanel *save = [NSSavePanel savePanel];
2469
        save.nameFieldStringValue = firstNode.displayName;
2470
        NSInteger result = [save runModal];
2471
        if (result == NSOKButton) {
2472
            NSString *destinationPath = save.URL.path;
2473
            NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2474
            NSString *newFileName = [destinationPath lastPathComponent];
2475
            if ([destinationPath hasSuffix:@"/"])
2476
                newFileName = [newFileName stringByAppendingString:@"/"];
2477
            if ([firstNode.displayName isEqualToString:newFileName])
2478
                newFileName = nil;
2479
            [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2480
        }
2481
    } else {
2482
        NSOpenPanel *open = [NSOpenPanel openPanel];
2483
        open.canChooseFiles = NO;
2484
        open.canChooseDirectories = YES;
2485
        open.canCreateDirectories = YES;
2486
        NSInteger result = [open runModal];
2487
        if (result == NSOKButton) {
2488
            NSString *directoryPath = open.URL.path;
2489
            for (PithosNode *node in nodes) {
2490
                [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2491
            }
2492
        }
2493
    }
2494
}
2495

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

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

    
2697
- (void)menuCut:(NSMenuItem *)sender {
2698
    self.clipboardNodes = (NSArray *)[sender representedObject];
2699
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2700
    self.clipboardCopy = NO;
2701
}
2702

    
2703
- (void)menuCopy:(NSMenuItem *)sender {
2704
    self.clipboardNodes = (NSArray *)[sender representedObject];
2705
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2706
    self.clipboardCopy = YES;
2707
}
2708

    
2709
- (void)menuPaste:(NSMenuItem *)sender {
2710
    if (!clipboardNodes || ![clipboardNodes count])
2711
        return;
2712
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2713
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2714
    if (clipboardCopy) {
2715
        [self cpyNodes:localClipboardNodes toNode:dropNode];
2716
    } else if (![dropNode isEqualTo:clipboardParentNode]) {
2717
        self.clipboardNodes = nil;
2718
        self.clipboardParentNode = nil;
2719
        [self moveNodes:localClipboardNodes toNode:dropNode];
2720
    }
2721
}
2722
    
2723
#pragma mark -
2724
#pragma mark PithosActivityFacilityDelegate
2725

    
2726
- (void)activityUpdate:(NSDictionary *)info {
2727
    NSString *message = [info objectForKey:@"message"];
2728
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2729
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2730
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2731
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2732
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2733
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2734
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2735
    if (runningActivitiesCount && totalBytes) {
2736
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2737
        [activityProgressIndicator startAnimation:self];
2738
    } else {
2739
        [activityProgressIndicator setDoubleValue:1.0];
2740
        [activityProgressIndicator stopAnimation:self];
2741
    }
2742
    
2743
    if (!message)
2744
        message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2745
    [activityTextField setStringValue:message];
2746
}
2747

    
2748
@end