Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ baaf1397

History | View | Annotate | Download (162.6 kB)

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

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

    
59
#define REFRESH_TIMER_INTERVAL 5
60

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

    
64
@implementation PithosBrowserCell
65

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

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

    
84
@end
85

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

    
89
@implementation PithosOutlineViewCell
90

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

    
102
@end
103

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

    
110
@implementation PithosBrowserController
111
@synthesize pithos;
112
@synthesize 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 andPithos:pithos];
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 andPithos:pithos];
217
    mySharedNode.displayName = @"shared by me";
218
    mySharedNode.shared = YES;
219
    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
220
    othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
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.pithos = pithos;
341
    accountNode.pithosAccountManager = pithosAccountManager;
342
    [accountNode forceRefresh];
343
    mySharedNode.pithos = pithos;
344
    mySharedNode.pithosAccountManager = pithosAccountManager;
345
    [mySharedNode forceRefresh];
346
    othersSharedNode.pithos = pithos;
347
    othersSharedNode.pithosAccountManager = pithosAccountManager;
348
    [othersSharedNode forceRefresh];
349
            
350
//    [activityFacility reset];
351
    activityFacility.delegate = self;
352
            
353
    refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL 
354
                                                     target:self 
355
                                                   selector:@selector(forceRefresh:) 
356
                                                   userInfo:self 
357
                                                    repeats:YES];
358
    @synchronized(self) {
359
        browserActive = YES;
360
    }
361
}
362

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

    
381
- (void)dealloc {
382
    [[NSNotificationCenter defaultCenter] removeObserver:self];
383
    [self resetBrowser];
384
}
385

    
386
- (void)setPithos:(ASIPithos *)aPithos {
387
    if (aPithos) {
388
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
389
            ![aPithos.authToken isEqualToString:pithos.authToken] ||
390
            (aPithos.ignoreSSLErrors != pithos.ignoreSSLErrors) ||
391
            ![aPithos.storageURLPrefix isEqual:pithos.storageURLPrefix] ||
392
            ![aPithos.publicURLPrefix isEqual:pithos.publicURLPrefix]) {
393
            [self resetBrowser];
394
            pithos = aPithos;
395
            [self startBrowser];
396
        } else {
397
            [self startBrowser];
398
        }
399
    }
400
}
401

    
402

    
403
#pragma mark -
404
#pragma mark Observers
405

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

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

    
494
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
495
    [self refresh:nil];
496
}
497

    
498
#pragma mark -
499
#pragma mark Actions
500

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

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

    
537
#pragma mark -
538
#pragma mark NSBrowserDelegate
539

    
540
- (id)rootItemForBrowser:(NSBrowser *)browser {
541
    return rootNode;    
542
}
543

    
544
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
545
    PithosNode *node = (PithosNode *)item;
546
    return node.children.count;
547
}
548

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

    
554
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
555
    PithosNode *node = (PithosNode *)item;
556
    return node.isLeafItem;
557
}
558

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

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

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

    
580
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
581
    return NO;
582
}
583

    
584
#pragma mark Editing
585

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

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

    
755
#pragma mark Drag and Drop source
756

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

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

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

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

    
811
#pragma mark Drag and Drop destination
812

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

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

    
919
#pragma mark -
920
#pragma mark NSBrowser Actions
921

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

    
942
#pragma mark -
943
#pragma mark Drag and Drop methods
944

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

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

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

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

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

    
1531
#pragma mark -
1532
#pragma mark ASIHTTPRequestDelegate
1533

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

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

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

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

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

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

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

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

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

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

    
1960
#pragma mark -
1961
#pragma mark NSSplitViewDelegate
1962

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

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

    
1977
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
1978
    if (((splitView == verticalSplitView) && (view == leftView)) ||
1979
        ((splitView == horizontalSplitView) && (view == leftBottomView))) {
1980
        return NO;
1981
    }
1982
    return YES;
1983
}
1984

    
1985
#pragma mark -
1986
#pragma mark NSOutlineViewDataSource
1987

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

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

    
2010
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2011
    if ((item == containersNode) || (item == sharedNode))
2012
        return YES;
2013
    return NO;
2014
}
2015

    
2016
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2017
    PithosNode *node = (PithosNode *)item;
2018
    return node;    
2019
}
2020

    
2021
#pragma mark Drag and Drop destination
2022

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

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

    
2073
#pragma mark -
2074
#pragma mark NSOutlineViewDelegate
2075

    
2076
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2077
    if ((item == containersNode) || (item == sharedNode))
2078
        return NO;
2079
    return YES;
2080
}
2081

    
2082
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2083
    if ((item == containersNode) || (item == sharedNode))
2084
        return YES;
2085
    return NO;
2086
}
2087

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

    
2097
#pragma mark -
2098
#pragma mark NSMenuDelegate
2099

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

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

    
2279
#pragma mark -
2280
#pragma mark NSMenuValidation
2281

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

    
2323
        menuItem.representedObject = menuNode;
2324
    }
2325
    return YES;
2326
}
2327

    
2328
- (void)cut:(NSMenuItem *)sender {
2329
    [self menuCut:sender];
2330
}
2331

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

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

    
2340
- (void)delete:(NSMenuItem *)sender {
2341
    if (sender.tag == 0)
2342
        [self menuMoveToTrash:sender];
2343
    else
2344
        [self menuDelete:sender];
2345
}
2346

    
2347
#pragma mark -
2348
#pragma mark Menu Actions
2349

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

    
2454
- (void)menuGetInfo:(NSMenuItem *)sender {
2455
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2456
        [node showPithosNodeInfo:sender];
2457
    }
2458
}
2459

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

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

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

    
2693
- (void)menuCut:(NSMenuItem *)sender {
2694
    self.clipboardNodes = (NSArray *)[sender representedObject];
2695
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2696
    self.clipboardCopy = NO;
2697
}
2698

    
2699
- (void)menuCopy:(NSMenuItem *)sender {
2700
    self.clipboardNodes = (NSArray *)[sender representedObject];
2701
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2702
    self.clipboardCopy = YES;
2703
}
2704

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

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

    
2744
@end