Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ d4433f6f

History | View | Annotate | Download (154.7 kB)

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

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

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

    
62
@implementation PithosBrowserCell
63

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

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

    
82
@end
83

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

    
87
@implementation PithosOutlineViewCell
88

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

    
100
@end
101

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

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

    
116
#pragma mark -
117
#pragma Object Lifecycle
118

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

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

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

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

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

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

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

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

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

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

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

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

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

    
419

    
420
#pragma mark -
421
#pragma mark Observers
422

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

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

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

    
515
#pragma mark -
516
#pragma mark Actions
517

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

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

    
542
#pragma mark -
543
#pragma mark NSBrowserDelegate
544

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

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

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

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

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

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

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

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

    
589
#pragma mark Editing
590

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

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

    
772
#pragma mark Drag and Drop source
773

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

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

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

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

    
937
#pragma mark Drag and Drop destination
938

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

    
1010
- (BOOL)browser:(NSBrowser *)aBrowser 
1011
     acceptDrop:(id<NSDraggingInfo>)info 
1012
          atRow:(NSInteger)row 
1013
         column:(NSInteger)column 
1014
  dropOperation:(NSBrowserDropOperation)dropOperation {
1015
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1016
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1017
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1018
        if ((column != -1) && (filenames != nil)) {
1019
            PithosNode *node;
1020
            if (row != -1)
1021
                node = [browser itemAtRow:row inColumn:column];
1022
            else
1023
                node = [browser parentForItemsInColumn:column];
1024
            NSLog(@"drag in node: %@", node.url);
1025
            return [self uploadFiles:filenames toNode:node];
1026
        }
1027
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1028
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1029
        if ((column != -1) && (draggedNodes != nil)) {
1030
            PithosNode *node;
1031
            if (row != -1)
1032
                node = [browser itemAtRow:row inColumn:column];
1033
            else
1034
                node = [browser parentForItemsInColumn:column];
1035
            NSLog(@"drag local node: %@", node.url);
1036
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
1037
                return [self moveNodes:draggedNodes toNode:node];
1038
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1039
                return [self copyNodes:draggedNodes toNode:node];
1040
        }
1041
    }
1042
    return NO;
1043
}
1044

    
1045
#pragma mark -
1046
#pragma mark Drag and Drop methods
1047

    
1048
- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1049
    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1050
        return NO;
1051
    NSFileManager *fileManager = [NSFileManager defaultManager];
1052
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1053
    NSString *objectNamePrefix;
1054
    if ([destinationNode class] == [PithosSubdirNode class])
1055
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1056
    else
1057
        objectNamePrefix = [NSString string];
1058
    if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1059
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
1060
                                                                                                      containerName:containerName];
1061
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1062
        [networkQueue go];
1063
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1064
        if ([containerRequest error]) {
1065
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1066
            return NO;
1067
        } else if (containerRequest.responseStatusCode != 204) {
1068
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1069
            return NO;
1070
        }
1071
        destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1072
        destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1073
    }    
1074
    NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1075
    NSString *blockHash = destinationNode.pithosContainer.blockHash;
1076
    
1077
    for (NSString *filePath in filenames) {
1078
        BOOL isDirectory;
1079
        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1080
            if (!isDirectory) {
1081
                // Upload file
1082
                NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1083
                // Operation: Upload a local file
1084
                __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1085
                    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1086
                    if (operation.isCancelled) {
1087
                        [pool drain];
1088
                        return;
1089
                    }
1090
                    NSError *error = nil;
1091
                    NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1092
                    if (contentType == nil)
1093
                        contentType = @"application/octet-stream";
1094
                    if (error)
1095
                        NSLog(@"contentType detection error: %@", error);
1096
                    NSArray *hashes = nil;
1097
                    if (operation.isCancelled) {
1098
                        [pool drain];
1099
                        return;
1100
                    }
1101
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1102
                                                                                                containerName:containerName 
1103
                                                                                                   objectName:objectName 
1104
                                                                                                  contentType:contentType 
1105
                                                                                                    blockSize:blockSize 
1106
                                                                                                    blockHash:blockHash 
1107
                                                                                                      forFile:filePath 
1108
                                                                                                checkIfExists:YES 
1109
                                                                                                       hashes:&hashes 
1110
                                                                                               sharingAccount:destinationNode.sharingAccount];
1111
                    if (!operation.isCancelled && objectRequest) {
1112
                        objectRequest.delegate = self;
1113
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1114
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1115
                        NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1116
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1117
                                                                                   message:[messagePrefix stringByAppendingString:@" (0%)"]
1118
                                                                                totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1119
                                                                              currentBytes:0];
1120
                        [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1121
                         [NSDictionary dictionaryWithObjectsAndKeys:
1122
                          containerName, @"containerName", 
1123
                          objectName, @"objectName", 
1124
                          contentType, @"contentType", 
1125
                          [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
1126
                          blockHash, @"blockHash", 
1127
                          filePath, @"filePath", 
1128
                          hashes, @"hashes", 
1129
                          [NSArray arrayWithObject:destinationNode], @"refreshNodes", 
1130
                          [NSNumber numberWithBool:YES], @"refresh", 
1131
                          [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1132
                          activity, @"activity", 
1133
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1134
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1135
                          [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage", 
1136
                          [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1137
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1138
                          NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1139
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1140
                          uploadNetworkQueue, @"networkQueue", 
1141
                          @"upload", @"operationType", 
1142
                          nil]];
1143
                        if (destinationNode.sharingAccount)
1144
                            [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1145
                        [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1146
                    }
1147
                    [pool drain];
1148
                }];
1149
                [uploadQueue addOperation:operation];
1150
            } else {
1151
                // Upload directory, confirm first
1152
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1153
                [alert setMessageText:@"Upload directory"];
1154
                [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1155
                [alert addButtonWithTitle:@"OK"];
1156
                [alert addButtonWithTitle:@"Cancel"];
1157
                NSInteger choice = [alert runModal];
1158
                if (choice == NSAlertFirstButtonReturn) {
1159
                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1160
                    // Operation: Upload a local directory and its descendants
1161
                    // The resulting ASIPithosObjectRequests are chained through dependencies
1162
                    __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1163
                        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1164
                        if (operation.isCancelled) {
1165
                            [pool drain];
1166
                            return;
1167
                        }
1168
                        NSMutableArray *objectNames = nil;
1169
                        NSMutableArray *contentTypes = nil;
1170
                        NSMutableArray *filePaths = nil;
1171
                        NSMutableArray *hashesArrays = nil;
1172
                        NSMutableArray *directoryObjectRequests = nil;
1173
                        NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos 
1174
                                                                                       containerName:containerName 
1175
                                                                                          objectName:objectName 
1176
                                                                                           blockSize:blockSize 
1177
                                                                                           blockHash:blockHash 
1178
                                                                                        forDirectory:filePath 
1179
                                                                                       checkIfExists:YES 
1180
                                                                                         objectNames:&objectNames
1181
                                                                                        contentTypes:&contentTypes
1182
                                                                                           filePaths:&filePaths
1183
                                                                                        hashesArrays:&hashesArrays 
1184
                                                                             directoryObjectRequests:&directoryObjectRequests 
1185
                                                                                      sharingAccount:destinationNode.sharingAccount];
1186
                        if (operation.isCancelled) {
1187
                            [pool drain];
1188
                            return;
1189
                        }
1190
                        ASIPithosObjectRequest *previousObjectRequest = nil;
1191
                        for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1192
                            if (operation.isCancelled) {
1193
                                [pool drain];
1194
                                return;
1195
                            }
1196
                            objectRequest.delegate = self;
1197
                            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1198
                            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1199
                            NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1200
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1201
                                                                                       message:messagePrefix];
1202
                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1203
                             [NSDictionary dictionaryWithObjectsAndKeys:
1204
                              [NSNumber numberWithBool:YES], @"refresh", 
1205
                              activity, @"activity", 
1206
                              [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1207
                              [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1208
                              [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1209
                              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1210
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1211
                              NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1212
                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1213
                              uploadNetworkQueue, @"networkQueue", 
1214
                              @"upload", @"queue", 
1215
                              nil]];
1216
                            if (previousObjectRequest)
1217
                                [objectRequest addDependency:previousObjectRequest];
1218
                            previousObjectRequest = objectRequest;
1219
                            [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1220
                        }
1221
                        if (!operation.isCancelled && objectRequests) {
1222
                            for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1223
                                if (operation.isCancelled) {
1224
                                    [pool drain];
1225
                                    return;
1226
                                }
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", @"queue", 
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
                        [pool drain];
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
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1293
                if (operation.isCancelled) {
1294
                    [pool drain];
1295
                    return;
1296
                }
1297
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1298
                if ([node.pithosObject.name hasSuffix:@"/"])
1299
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1300
                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1301
                                                                                       containerName:node.pithosContainer.name 
1302
                                                                                          objectName:node.pithosObject.name 
1303
                                                                            destinationContainerName:containerName 
1304
                                                                               destinationObjectName:destinationObjectName 
1305
                                                                                       checkIfExists:YES];
1306
                if (!operation.isCancelled && objectRequest) {
1307
                    objectRequest.delegate = self;
1308
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1309
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1310
                    NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1311
                                               [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1312
                                               [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1313
                                               [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1314
                                               [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1315
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1316
                                                                               message:messagePrefix];
1317
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1318
                     [NSDictionary dictionaryWithObjectsAndKeys:
1319
                      [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1320
                      activity, @"activity", 
1321
                      [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1322
                      [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1323
                      [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1324
                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1325
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1326
                      NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1327
                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1328
                      moveNetworkQueue, @"networkQueue", 
1329
                      @"move", @"operationType", 
1330
                      nil]];
1331
                    [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1332
                }
1333
                [pool drain];
1334
            }];
1335
            [moveQueue addOperation:operation];
1336
        } else if ([node class] == [PithosSubdirNode class]) {
1337
            // Operation: Move a subdir node and its descendants
1338
            // The resulting ASIPithosObjectRequests are chained through dependencies
1339
            __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1340
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1341
                if (operation.isCancelled) {
1342
                    [pool drain];
1343
                    return;
1344
                }
1345
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1346
                if (node.pithosObject.subdir)
1347
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1348
                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos 
1349
                                                                                   containerName:node.pithosContainer.name 
1350
                                                                                      objectName:node.pithosObject.name 
1351
                                                                        destinationContainerName:containerName 
1352
                                                                           destinationObjectName:destinationObjectName 
1353
                                                                                   checkIfExists:YES];
1354
                if (!operation.isCancelled && objectRequests) {
1355
                    ASIPithosObjectRequest *previousObjectRequest = nil;
1356
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1357
                        if (operation.isCancelled) {
1358
                            [pool drain];
1359
                            return;
1360
                        }
1361
                        objectRequest.delegate = self;
1362
                        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1363
                        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1364
                        NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1365
                                                   [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1366
                                                   [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1367
                                                   [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1368
                                                   [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1369
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1370
                                                                                   message:messagePrefix];
1371
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1372
                         [NSDictionary dictionaryWithObjectsAndKeys:
1373
                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
1374
                          [NSNumber numberWithBool:YES], @"refresh", 
1375
                          activity, @"activity", 
1376
                          [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1377
                          [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1378
                          [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1379
                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1380
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1381
                          NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector", 
1382
                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1383
                          moveNetworkQueue, @"networkQueue", 
1384
                          @"move", @"operationType", 
1385
                          nil]];
1386
                        if (previousObjectRequest)
1387
                            [objectRequest addDependency:previousObjectRequest];
1388
                        previousObjectRequest = objectRequest;
1389
                        [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1390
                    }
1391
                }
1392
                [pool drain];
1393
            }];
1394
            [moveQueue addOperation:operation];
1395
        }
1396
    }
1397
    return YES;
1398
}
1399

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

    
1547
#pragma mark -
1548
#pragma mark ASIHTTPRequestDelegate
1549

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

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

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

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

    
1712
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1713
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1714
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1715
    NSLog(@"Upload directory object finished: %@", objectRequest.url);
1716
    if (operation.isCancelled) {
1717
        [self requestFailed:objectRequest];
1718
    } else if (objectRequest.responseStatusCode == 201) {
1719
        NSLog(@"Directory object created: %@", objectRequest.url);
1720
        dispatch_async(dispatch_get_main_queue(), ^{
1721
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1722
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1723
        });
1724
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1725
            [node forceRefresh];
1726
        }
1727
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1728
            [node refresh];
1729
        }
1730
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1731
            [self forceRefresh:self];
1732
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1733
            [self refresh:self];
1734
    } else {
1735
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1736
        [self requestFailed:objectRequest];
1737
    }
1738
    [pool drain];
1739
}
1740

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

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

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

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

    
1946
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1947
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1948
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1949
    NSLog(@"Delete object finished: %@", objectRequest.url);
1950
    if (operation.isCancelled) {
1951
        [self requestFailed:objectRequest];
1952
    } else if (objectRequest.responseStatusCode == 204) {
1953
        dispatch_async(dispatch_get_main_queue(), ^{
1954
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1955
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1956
        });
1957
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1958
            [node forceRefresh];
1959
        }
1960
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1961
            [node refresh];
1962
        }
1963
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1964
            [self forceRefresh:self];
1965
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1966
            [self refresh:self];
1967
    } else {
1968
        [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1969
        [self requestFailed:objectRequest];
1970
    }
1971
    [pool drain];
1972
}
1973

    
1974
#pragma mark -
1975
#pragma mark NSSplitViewDelegate
1976

    
1977
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1978
    if (splitView == verticalSplitView)
1979
        return 120;
1980
    else
1981
        return ([horizontalSplitView bounds].size.height - 108);
1982
}
1983

    
1984
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1985
    if (splitView == verticalSplitView)
1986
        return 220;
1987
    else
1988
        return ([horizontalSplitView bounds].size.height - 108);
1989
}
1990

    
1991
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1992
    if (splitView == verticalSplitView) {
1993
        if (proposedPosition < 120)
1994
            return 120;
1995
        else if (proposedPosition > 220)
1996
            return 220;
1997
        else
1998
            return proposedPosition;
1999
    } else {
2000
        return ([horizontalSplitView bounds].size.height - 108);
2001
    }
2002
}
2003

    
2004
#pragma mark -
2005
#pragma mark NSOutlineViewDataSource
2006

    
2007
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2008
    if (item == nil)
2009
        return 2;
2010
    if (item == containersNode)
2011
        return containersNodeChildren.count;
2012
    if (item == sharedNode)
2013
        return 2;
2014
    return 0;
2015
}
2016

    
2017
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2018
    if (item == nil)
2019
        return (!index ? containersNode : sharedNode);
2020
    if (item == sharedNode)
2021
        return (!index ? mySharedNode : othersSharedNode);
2022
    return [containersNodeChildren objectAtIndex:index];
2023
}
2024

    
2025
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2026
    if ((item == containersNode) || (item == sharedNode))
2027
        return YES;
2028
    return NO;
2029
}
2030

    
2031
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2032
    PithosNode *node = (PithosNode *)item;
2033
    return node;    
2034
}
2035

    
2036
#pragma mark Drag and Drop destination
2037

    
2038
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
2039
                  validateDrop:(id<NSDraggingInfo>)info 
2040
                  proposedItem:(id)item 
2041
            proposedChildIndex:(NSInteger)index {
2042
    NSDragOperation result = NSDragOperationNone;
2043
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2044
        return result;
2045
    PithosNode *dropNode = (PithosNode *)item;
2046
    if ([dropNode class] != [PithosContainerNode class])
2047
        return result;
2048
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2049
        result = NSDragOperationCopy;
2050
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2051
        if ([info draggingSourceOperationMask] & NSDragOperationMove) {
2052
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2053
            if (![dropNode isEqualTo:draggedParentNode])
2054
                result = NSDragOperationMove;
2055
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2056
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2057
            result = NSDragOperationCopy;
2058
        }
2059
    }
2060
   return result;
2061
}
2062

    
2063
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2064
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2065
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2066
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2067
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2068
            PithosNode *node = (PithosNode *)item;
2069
            NSLog(@"drag in node: %@", node.url);
2070
            return [self uploadFiles:filenames toNode:node];
2071
        }
2072
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2073
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2074
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2075
            PithosNode *node = (PithosNode *)item;
2076
            NSLog(@"drag local node: %@", node.url);
2077
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
2078
                return [self moveNodes:draggedNodes toNode:node];
2079
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2080
                return [self copyNodes:draggedNodes toNode:node];
2081
        }
2082
    }
2083
    return NO;
2084
}
2085

    
2086
#pragma mark -
2087
#pragma mark NSOutlineViewDelegate
2088

    
2089
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2090
    if ((item == containersNode) || (item == sharedNode))
2091
        return NO;
2092
    return YES;
2093
}
2094

    
2095
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2096
    if ((item == containersNode) || (item == sharedNode))
2097
        return YES;
2098
    return NO;
2099
}
2100

    
2101
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2102
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2103
    if (node) {
2104
        rootNode = node;
2105
        [browser loadColumnZero];
2106
        [self refresh:nil];
2107
    }
2108
}
2109

    
2110
#pragma mark -
2111
#pragma mark NSMenuDelegate
2112

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

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

    
2279
#pragma mark -
2280
#pragma mark Menu Actions
2281

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

    
2394
- (void)menuGetInfo:(NSMenuItem *)sender {
2395
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2396
        [node showPithosNodeInfo:sender];
2397
    }
2398
}
2399

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

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

    
2615
- (void)menuCut:(NSMenuItem *)sender {
2616
    self.clipboardNodes = (NSArray *)[sender representedObject];
2617
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2618
    self.clipboardCopy = NO;
2619
}
2620

    
2621
- (void)menuCopy:(NSMenuItem *)sender {
2622
    self.clipboardNodes = (NSArray *)[sender representedObject];
2623
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2624
    self.clipboardCopy = YES;
2625
}
2626

    
2627
- (void)menuPaste:(NSMenuItem *)sender {
2628
    if (!clipboardNodes || ![clipboardNodes count])
2629
        return;
2630
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2631
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2632
    if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2633
        self.clipboardNodes = nil;
2634
        self.clipboardParentNode = nil;
2635
        [self moveNodes:localClipboardNodes toNode:dropNode];
2636
    } else {
2637
        [self copyNodes:localClipboardNodes toNode:dropNode];
2638
    }
2639
}
2640
    
2641
#pragma mark -
2642
#pragma mark PithosActivityFacilityDelegate
2643

    
2644
- (void)activityUpdate:(NSDictionary *)info {
2645
    NSString *message = [info objectForKey:@"message"];
2646
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2647
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2648
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2649
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2650
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2651
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2652
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2653
    if (runningActivitiesCount && totalBytes) {
2654
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2655
        [activityProgressIndicator startAnimation:self];
2656
    } else {
2657
        [activityProgressIndicator setDoubleValue:1.0];
2658
        [activityProgressIndicator stopAnimation:self];
2659
    }
2660
    
2661
    if (!message)
2662
        message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2663
    [activityTextField setStringValue:message];
2664
}
2665

    
2666
@end