Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ 9ab4b378

History | View | Annotate | Download (125.5 kB)

1
//
2
//  PithosBrowserController.m
3
//  pithos-macos
4
//
5
// Copyright 2011 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 "ASIPithosRequest.h"
49
#import "ASIPithosContainerRequest.h"
50
#import "ASIPithosObjectRequest.h"
51
#import "ASIPithosAccount.h"
52
#import "ASIPithosContainer.h"
53
#import "ASIPithosObject.h"
54
#import "PithosUtilities.h"
55
#import "UsingSizeTransformer.h"
56

    
57
@interface PithosBrowserCell : FileSystemBrowserCell {}
58
@end
59

    
60
@implementation PithosBrowserCell
61

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

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

    
80
@end
81

    
82
@interface PithosOutlineViewCell : ImageAndTextCell {}
83
@end
84

    
85
@implementation PithosOutlineViewCell
86

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

    
98
@end
99

    
100
@interface PithosBrowserController (Private)
101
- (void)resetContainers:(NSNotification *)notification;
102
- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
103
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
104
- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
105
@end
106

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

    
114
#pragma mark -
115
#pragma Object Lifecycle
116

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

    
121
- (void)dealloc {
122
    [[NSNotificationCenter defaultCenter] removeObserver:self];
123
    [refreshTimer invalidate];
124
    [refreshTimer release];
125
    [clipboardParentNode release];
126
    [clipboardNodes release];
127
    [draggedParentNode release];
128
    [draggedNodes release];
129
    [sharedPreviewController release];
130
    [othersSharedNode release];
131
    [mySharedNode release];
132
    [sharedNode release];
133
    [containersNodeChildren release];
134
    [containersNode release];
135
    [accountNode release];
136
    [rootNode release];
137
    [super dealloc];
138
}
139

    
140
- (void)awakeFromNib {
141
    [super awakeFromNib];
142
    
143
    [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
144
    [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
145
    [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
146
    
147
    [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
148
    [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
149
    [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
150
    
151
    [browser setCellClass:[PithosBrowserCell class]];
152
}
153

    
154
- (void)resetContainers:(NSNotification *)notification {
155
    [refreshTimer invalidate];
156
    [refreshTimer release];
157
    
158
    rootNode = nil;
159
    [browser loadColumnZero];
160
    [containersNodeChildren removeAllObjects];
161
    [outlineView reloadData];
162
	// Expand the folder outline view
163
    [outlineView expandItem:nil expandChildren:YES];
164
	[outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
165
    
166
    // Refresh account
167
    [accountNode refresh];
168
    [mySharedNode refresh];
169
    [othersSharedNode refresh];
170
    
171
    [activityFacility reset];
172
    
173
    refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(forceRefresh:) userInfo:self repeats:YES] retain];
174
}
175

    
176
- (void)windowDidLoad {
177
    [super windowDidLoad];
178
    
179
    [activityProgressIndicator setUsesThreadedAnimation:YES];
180
    [activityProgressIndicator setMinValue:0.0];
181
    [activityProgressIndicator setMaxValue:1.0];
182
    activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
183
    activityFacility.delegate = self;
184
    
185
    self.accountNode = [[[PithosAccountNode alloc] init] autorelease];
186
    containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
187
    containersNodeChildren = [[NSMutableArray alloc] init];
188
    sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
189
    mySharedNode = [[PithosAccountNode alloc] init];
190
    mySharedNode.displayName = @"my shared";
191
    mySharedNode.shared = YES;
192
    mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
193
    othersSharedNode = [[PithosSharingAccountsNode alloc] init];
194
    othersSharedNode.displayName = @"others shared";
195
    othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
196
    
197
    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
198
    
199
    // Register for updates
200
    // PithosContainerNode updates browser nodes
201
    [[NSNotificationCenter defaultCenter] addObserver:self 
202
                                             selector:@selector(pithosNodeChildrenUpdated:) 
203
                                                 name:@"PithosContainerNodeChildrenUpdated" 
204
                                               object:nil];
205
    // PithosSubdirNode updates browser nodes
206
    [[NSNotificationCenter defaultCenter] addObserver:self 
207
                                             selector:@selector(pithosNodeChildrenUpdated:) 
208
                                                 name:@"PithosSubdirNodeChildrenUpdated" 
209
                                               object:nil];
210
    // PithosAccountNode accountNode updates outlineView container nodes 
211
    [[NSNotificationCenter defaultCenter] addObserver:self 
212
                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
213
                                                 name:@"PithosAccountNodeChildrenUpdated" 
214
                                               object:accountNode];
215
    // PithosAccountNode other than accountNode updates nodes 
216
    [[NSNotificationCenter defaultCenter] addObserver:self 
217
                                             selector:@selector(pithosNodeChildrenUpdated:) 
218
                                                 name:@"PithosAccountNodeChildrenUpdated" 
219
                                               object:nil];
220
    // PithosSharingAccountsNode othersSharedNode updates browser nodes 
221
    [[NSNotificationCenter defaultCenter] addObserver:self 
222
                                             selector:@selector(pithosNodeChildrenUpdated:) 
223
                                                 name:@"PithosSharingAccountsNodeChildrenUpdated" 
224
                                               object:othersSharedNode];
225
    // Updated authentication credentials reset containers in the outline view 
226
    [[NSNotificationCenter defaultCenter] addObserver:self 
227
                                             selector:@selector(resetContainers:) 
228
                                                 name:@"PithosAuthenticationCredentialsUpdated" 
229
                                               object:nil];
230
    // Request for browser refresh 
231
    [[NSNotificationCenter defaultCenter] addObserver:self 
232
                                             selector:@selector(pithosBrowserRefreshNeeded:) 
233
                                                 name:@"PithosBrowserRefreshNeeeded" 
234
                                               object:nil];    
235
}
236

    
237
#pragma mark -
238
#pragma mark Observers
239

    
240
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
241
    PithosNode *node = (PithosNode *)[notification object];
242
    if (node == accountNode)
243
        return;
244
    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
245
    NSInteger lastColumn = [browser lastColumn];
246
    for (NSInteger column = lastColumn; column >= 0; column--) {
247
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
248
            [browser reloadColumn:column];
249
            return;
250
        }
251
    }
252
}
253

    
254
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
255
    BOOL containerPithosFound = NO;
256
    BOOL containerTrashFound = NO;
257
    NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
258
    for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
259
        if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
260
            [removedContainersNodeChildren addIndex:i];
261
    }
262
    [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
263
    for (PithosContainerNode *containerNode in accountNode.children) {
264
        if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
265
            if (![containersNodeChildren containsObject:containerNode])
266
                [containersNodeChildren insertObject:containerNode atIndex:0];
267
            containerPithosFound = YES;
268
        } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
269
            NSUInteger insertIndex = 1;
270
            if (!containerPithosFound)
271
                insertIndex = 0;
272
            if (![containersNodeChildren containsObject:containerNode])
273
                [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
274
            containerTrashFound = YES;
275
        } else if (![containersNodeChildren containsObject:containerNode]) {
276
            [containersNodeChildren addObject:containerNode];
277
        }
278
    }
279
    BOOL refreshAccountNode = NO;
280
    if (!containerPithosFound) {
281
        // Create pithos node
282
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
283
        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
284
        while (![containerRequest isFinished]) {
285
            sleep(1);
286
        }
287
        if ([containerRequest error]) {
288
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
289
        } else {
290
            refreshAccountNode = YES;
291
        }
292
    }
293
    if (!containerTrashFound) {
294
        // Create trash node
295
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
296
        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
297
        while (![containerRequest isFinished]) {
298
            sleep(1);
299
        }
300
        if ([containerRequest error]) {
301
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
302
        } else {
303
            refreshAccountNode = YES;
304
        }
305
    }
306
    
307
    if (refreshAccountNode)
308
        [accountNode refresh];
309
    
310
    [outlineView reloadData];
311
    
312
    // Expand the folder outline view
313
    [outlineView expandItem:nil expandChildren:YES];
314
    
315
    if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
316
        rootNode = [containersNodeChildren objectAtIndex:0];
317
        [browser loadColumnZero];
318
    }
319
    
320
    if (notification)
321
        [self refresh:nil];
322
}
323

    
324
- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
325
    [self refresh:nil];
326
}
327

    
328
#pragma mark -
329
#pragma mark Actions
330

    
331
- (IBAction)forceRefresh:(id)sender {
332
    if (sender)
333
        [accountNode forceRefresh];
334
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
335
        PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
336
        node.forcedRefresh = YES;
337
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];                      
338
    }
339
    [browser validateVisibleColumns];
340
}
341

    
342
- (IBAction)refresh:(id)sender {
343
    if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
344
        [self forceRefresh:sender];
345
    } else {
346
        if (sender)
347
            [accountNode refresh];
348
        for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
349
            [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
350
        }
351
        [browser validateVisibleColumns];
352
    }
353
}
354

    
355
#pragma mark -
356
#pragma mark NSBrowserDelegate
357

    
358
- (id)rootItemForBrowser:(NSBrowser *)browser {
359
    return rootNode;    
360
}
361

    
362
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
363
    PithosNode *node = (PithosNode *)item;
364
    return node.children.count;
365
}
366

    
367
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
368
    PithosNode *node = (PithosNode *)item;
369
    return [node.children objectAtIndex:index];
370
}
371

    
372
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
373
    PithosNode *node = (PithosNode *)item;
374
    return node.isLeafItem;
375
}
376

    
377
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
378
    PithosNode *node = (PithosNode *)item;
379
    return node;
380
}
381

    
382
- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
383
    if (sharedPreviewController == nil)
384
        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
385
    return sharedPreviewController;
386
}
387

    
388
//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
389
//    if (!forUserResize) {
390
//        id item = [browser parentForItemsInColumn:columnIndex]; 
391
//        if ([self browser:browser isLeafItem:item]) {
392
//            suggestedWidth = 200; 
393
//        }
394
//    }
395
//    return suggestedWidth;
396
//}
397

    
398
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
399
    return NO;
400
}
401

    
402
#pragma mark Editing
403

    
404
- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
405
    PithosNode *node = (PithosNode *)item;
406
    if (node.shared || node.sharingAccount || 
407
        ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
408
        return NO;
409
    return YES;
410
}
411

    
412
- (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
413
    PithosNode *node = (PithosNode *)item;
414
    NSString *newName = (NSString *)object;
415
    NSUInteger newNameLength = [newName length];
416
    NSRange firstSlashRange = [newName rangeOfString:@"/"];
417
    if ((newNameLength == 0) || 
418
        ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || 
419
        ([newName isEqualToString:node.displayName])) {
420
        return;
421
    }
422
    if (([node class] == [PithosObjectNode class]) || 
423
        (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
424
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
425
        dispatch_async(queue, ^{
426
            NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
427
            if ([newName hasSuffix:@"/"])
428
                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
429
            NSError *error = nil;
430
            BOOL isDirectory;
431
            if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name 
432
                                                  objectName:destinationObjectName 
433
                                                       error:&error 
434
                                                 isDirectory:&isDirectory 
435
                                              sharingAccount:nil]) {
436
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
437
                [alert setMessageText:@"Name Taken"];
438
                [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
439
                [alert addButtonWithTitle:@"OK"];
440
                [alert runModal];
441
                return;
442
            } else if (error) {
443
                return;
444
            }
445
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
446
                                                                                             objectName:node.pithosObject.name 
447
                                                                               destinationContainerName:node.pithosContainer.name 
448
                                                                                  destinationObjectName:destinationObjectName 
449
                                                                                          checkIfExists:NO];
450
            if (objectRequest) {
451
                objectRequest.delegate = self;
452
                objectRequest.didFinishSelector = @selector(moveFinished:);
453
                objectRequest.didFailSelector = @selector(moveFailed:);
454
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
455
                                                                           message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
456
                                                                                    [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
457
                                                                                    [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
458
                                                                                    [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
459
                                                                                    [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
460
                [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
461
                 [NSDictionary dictionaryWithObjectsAndKeys:
462
                  [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
463
                  [NSNumber numberWithBool:YES], @"refresh", 
464
                  activity, @"activity", 
465
                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
466
                  nil]];
467
                [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
468
            }
469
        });
470
    } else if ([node class] == [PithosSubdirNode class]) {
471
        if (firstSlashRange.length == 1)
472
            return;
473
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
474
        dispatch_async(queue, ^{
475
            NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
476
            NSError *error = nil;
477
            BOOL isDirectory;
478
            if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name 
479
                                                  objectName:destinationObjectName 
480
                                                       error:&error 
481
                                                 isDirectory:&isDirectory 
482
                                              sharingAccount:nil]) {
483
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
484
                [alert setMessageText:@"Name Taken"];
485
                [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
486
                [alert addButtonWithTitle:@"OK"];
487
                [alert runModal];
488
                return;
489
            } else if (error) {
490
                return;
491
            }
492
            if (node.pithosObject.subdir)
493
                destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
494
            NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
495
                                                                                         objectName:node.pithosObject.name 
496
                                                                           destinationContainerName:node.pithosContainer.name 
497
                                                                              destinationObjectName:destinationObjectName 
498
                                                                                      checkIfExists:NO];
499
            if (objectRequests) {
500
                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
501
                    objectRequest.delegate = self;
502
                    objectRequest.didFinishSelector = @selector(moveFinished:);
503
                    objectRequest.didFailSelector = @selector(moveFailed:);
504
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
505
                                                                               message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
506
                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
507
                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
508
                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
509
                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
510
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
511
                     [NSDictionary dictionaryWithObjectsAndKeys:
512
                      [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
513
                      [NSNumber numberWithBool:YES], @"refresh", 
514
                      activity, @"activity", 
515
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
516
                      nil]];
517
                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
518
                }
519
            }
520
        });
521
    }
522
}
523

    
524
#pragma mark Drag and Drop source
525

    
526
- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
527
      withEvent:(NSEvent *)event {
528
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
529
    __block BOOL result = YES;
530
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
531
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
532
        if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
533
            result = NO;
534
            *stop = YES;
535
        }
536
    }];
537
    return result;
538
}
539

    
540
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
541
   toPasteboard:(NSPasteboard *)pasteboard {
542
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
543
    NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
544
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
545
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
546
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
547
        [propertyList addObject:[node.pithosObject.name pathExtension]];
548
        [nodes addObject:node];
549
    }];
550

    
551
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
552
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
553
    self.draggedNodes = nodes;
554
    self.draggedParentNode = [browser parentForItemsInColumn:column];
555
    return YES;
556
}
557

    
558
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
559
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
560
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
561
    for (PithosNode *node in draggedNodes) {        
562
        // If the node is a subdir ask if the whole tree should be downloaded
563
        if ([node class] == [PithosSubdirNode class]) {
564
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
565
            [alert setMessageText:@"Download directory"];
566
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
567
            [alert addButtonWithTitle:@"OK"];
568
            [alert addButtonWithTitle:@"Cancel"];
569
            NSInteger choice = [alert runModal];
570
            if (choice == NSAlertFirstButtonReturn) {
571
                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
572
                dispatch_async(queue, ^{
573
                    NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
574
                                                                                                     objectName:node.pithosObject.name 
575
                                                                                                    toDirectory:[dropDestination path] 
576
                                                                                                  checkIfExists:YES 
577
                                                                                                 sharingAccount:node.sharingAccount];
578
                    if (objectRequests) {
579
                        for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
580
                            [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
581
                            objectRequest.delegate = self;
582
                            objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
583
                            objectRequest.didFailSelector = @selector(downloadObjectFailed:);
584
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
585
                                                                                       message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo objectForKey:@"fileName"]] 
586
                                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] 
587
                                                                                  currentBytes:0];
588
                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
589
                             [NSDictionary dictionaryWithObjectsAndKeys:
590
                              activity, @"activity", 
591
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
592
                              nil]];
593
                            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
594
                                [activityFacility updateActivity:activity 
595
                                                     withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
596
                                                      totalBytes:activity.totalBytes 
597
                                                    currentBytes:(activity.currentBytes + size)];
598
                            }];
599
                            [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
600
                        }
601
                    }
602
                });
603
            }
604
        } else {
605
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
606
                                                                                                     objectName:node.pithosObject.name 
607
                                                                                                    toDirectory:[dropDestination path] 
608
                                                                                                  checkIfExists:YES 
609
                                                                                                 sharingAccount:node.sharingAccount];
610
            if (objectRequest) {
611
                [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
612
                objectRequest.delegate = self;
613
                objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
614
                objectRequest.didFailSelector = @selector(downloadObjectFailed:);
615
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
616
                                                                           message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
617
                                                                        totalBytes:node.pithosObject.bytes 
618
                                                                      currentBytes:0];
619
                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
620
                 [NSDictionary dictionaryWithObjectsAndKeys:
621
                  activity, @"activity", 
622
                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
623
                  nil]];
624
                [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
625
                    [activityFacility updateActivity:activity 
626
                                         withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
627
                                          totalBytes:activity.totalBytes 
628
                                        currentBytes:(activity.currentBytes + size)];
629
                }];
630
                [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
631
            }
632
        }
633
    }
634
    return names;
635
}
636

    
637
#pragma mark Drag and Drop destination
638

    
639
- (NSDragOperation)browser:aBrowser 
640
              validateDrop:(id<NSDraggingInfo>)info 
641
               proposedRow:(NSInteger *)row 
642
                    column:(NSInteger *)column 
643
             dropOperation:(NSBrowserDropOperation *)dropOperation {
644
    NSDragOperation result = NSDragOperationNone;
645
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
646
        // For a drop above, the drop is redirected to the parent item
647
        if (*dropOperation == NSBrowserDropAbove)
648
            *row = -1;
649
        // Only allow dropping in folders
650
        if (*column != -1) {
651
            PithosNode *dropNode;
652
            if (*row != -1) {
653
                // Check if the node is not a folder and if so redirect to the parent item
654
                dropNode = [browser itemAtRow:*row inColumn:*column];
655
                if ([dropNode class] == [PithosObjectNode class])
656
                    *row = -1;
657
            }
658
            if (*row == -1)
659
                dropNode = [browser parentForItemsInColumn:*column];
660
            
661
            if (!dropNode.shared && 
662
                (!dropNode.sharingAccount || 
663
                 ([dropNode class] == [PithosSubdirNode class]) || 
664
                 ([dropNode class] == [PithosContainerNode class]))) {
665
                *dropOperation = NSBrowserDropOn;
666
                result = NSDragOperationCopy;
667
            }
668
        }
669
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
670
        // For a drop above, the drop is redirected to the parent item
671
        if (*dropOperation == NSBrowserDropAbove) 
672
            *row = -1;
673
        // Only allow dropping in folders
674
        if (*column != -1) {
675
            PithosNode *dropNode;
676
            if (*row != -1) {
677
                // Check if the node is not a folder and if so redirect to the parent item
678
                dropNode = [browser itemAtRow:*row inColumn:*column];
679
                if ([dropNode class] == [PithosObjectNode class])
680
                    *row = -1;
681
            }
682
            if (*row == -1)
683
                dropNode = [browser parentForItemsInColumn:*column];
684
            
685
            if (!dropNode.shared && !dropNode.sharingAccount) {
686
                if ([info draggingSourceOperationMask] & NSDragOperationMove) {
687
                    // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
688
                    if ((([dropNode class] == [PithosContainerNode class]) || 
689
                         dropNode.pithosObject.subdir || 
690
                         ![dropNode.pithosObject.name hasSuffix:@"/"]) && 
691
                        ![dropNode isEqualTo:draggedParentNode]) { 
692
    //                    ![dropNode isEqualTo:draggedParentNode] && 
693
    //                    ![draggedNodes containsObject:dropNode]) {                
694
                        result = NSDragOperationMove;
695
                    }
696
                } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
697
                    // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
698
                    if (([dropNode class] == [PithosContainerNode class]) || 
699
                        dropNode.pithosObject.subdir || 
700
                        ![dropNode.pithosObject.name hasSuffix:@"/"]) { 
701
                        result = NSDragOperationCopy;
702
                    }
703
                }
704
            }
705
        }
706
    }
707
    return result;
708
}
709

    
710
- (BOOL)browser:(NSBrowser *)aBrowser 
711
     acceptDrop:(id<NSDraggingInfo>)info 
712
          atRow:(NSInteger)row 
713
         column:(NSInteger)column 
714
  dropOperation:(NSBrowserDropOperation)dropOperation {
715
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
716
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
717
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
718
        if ((column != -1) && (filenames != nil)) {
719
            PithosNode *node;
720
            if (row != -1)
721
                node = [browser itemAtRow:row inColumn:column];
722
            else
723
                node = [browser parentForItemsInColumn:column];
724
            NSLog(@"drag in node: %@", node.url);
725
            return [self uploadFiles:filenames toNode:node];
726
        }
727
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
728
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
729
        if ((column != -1) && (draggedNodes != nil)) {
730
            PithosNode *node;
731
            if (row != -1)
732
                node = [browser itemAtRow:row inColumn:column];
733
            else
734
                node = [browser parentForItemsInColumn:column];
735
            NSLog(@"drag local node: %@", node.url);
736
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
737
                return [self moveNodes:draggedNodes toNode:node];
738
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
739
                return [self copyNodes:draggedNodes toNode:node];
740
        }
741
    }
742
    return NO;
743
}
744

    
745
#pragma mark -
746
#pragma mark Drag and Drop methods
747

    
748
- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
749
    if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
750
        return NO;
751
    NSFileManager *fileManager = [NSFileManager defaultManager];
752
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
753
    NSString *objectNamePrefix;
754
    if ([destinationNode class] == [PithosSubdirNode class])
755
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
756
    else
757
        objectNamePrefix = [NSString string];
758
    if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
759
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithContainerName:containerName];
760
        [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
761
        while (![containerRequest isFinished]) {
762
            sleep(1);
763
        }
764
        if ([containerRequest error]) {
765
            [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
766
            return NO;
767
        } else if (containerRequest.responseStatusCode != 204) {
768
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
769
            return NO;
770
        }
771
        destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
772
        destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
773
    }    
774
    NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
775
    NSString *blockHash = destinationNode.pithosContainer.blockHash;
776
    
777
    for (NSString *filePath in filenames) {
778
        BOOL isDirectory;
779
        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
780
            if (!isDirectory) {
781
                // Upload file
782
                NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
783
                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
784
                dispatch_async(queue, ^{
785
                    NSError *error = nil;
786
                    NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
787
                    if (contentType == nil)
788
                        contentType = @"application/octet-stream";
789
                    if (error)
790
                        NSLog(@"contentType detection error: %@", error);
791
                    NSArray *hashes = nil;
792
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
793
                                                                                                          objectName:objectName 
794
                                                                                                         contentType:contentType 
795
                                                                                                           blockSize:blockSize 
796
                                                                                                           blockHash:blockHash 
797
                                                                                                             forFile:filePath 
798
                                                                                                       checkIfExists:YES 
799
                                                                                                              hashes:&hashes 
800
                                                                                                      sharingAccount:destinationNode.sharingAccount];
801
                    if (objectRequest) {
802
                        objectRequest.delegate = self;
803
                        objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
804
                        objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
805
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
806
                                                                                   message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
807
                                                                                totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
808
                                                                              currentBytes:0];
809
                        [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
810
                         [NSDictionary dictionaryWithObjectsAndKeys:
811
                          containerName, @"containerName", 
812
                          objectName, @"objectName", 
813
                          contentType, @"contentType", 
814
                          [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
815
                          blockHash, @"blockHash", 
816
                          filePath, @"filePath", 
817
                          hashes, @"hashes", 
818
                          [NSArray arrayWithObject:destinationNode], @"refreshNodes", 
819
                          [NSNumber numberWithBool:YES], @"refresh", 
820
                          [NSNumber numberWithUnsignedInteger:10], @"iteration", 
821
                          activity, @"activity", 
822
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
823
                          nil]];
824
                        if (destinationNode.sharingAccount)
825
                            [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
826
                        [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
827
                    }
828
                });
829
            } else {
830
                // Upload directory, confirm first
831
                NSAlert *alert = [[[NSAlert alloc] init] autorelease];
832
                [alert setMessageText:@"Upload directory"];
833
                [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
834
                [alert addButtonWithTitle:@"OK"];
835
                [alert addButtonWithTitle:@"Cancel"];
836
                NSInteger choice = [alert runModal];
837
                if (choice == NSAlertFirstButtonReturn) {
838
                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
839
                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
840
                    dispatch_async(queue, ^{
841
                        NSMutableArray *objectNames = nil;
842
                        NSMutableArray *contentTypes = nil;
843
                        NSMutableArray *filePaths = nil;
844
                        NSMutableArray *hashesArrays = nil;
845
                        NSMutableArray *directoryObjectRequests = nil;
846
                        NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithContainerName:containerName 
847
                                                                                                     objectName:objectName 
848
                                                                                                      blockSize:blockSize 
849
                                                                                                      blockHash:blockHash 
850
                                                                                                   forDirectory:filePath 
851
                                                                                                  checkIfExists:YES 
852
                                                                                                    objectNames:&objectNames
853
                                                                                                   contentTypes:&contentTypes
854
                                                                                                      filePaths:&filePaths
855
                                                                                                   hashesArrays:&hashesArrays 
856
                                                                                        directoryObjectRequests:&directoryObjectRequests 
857
                                                                                                 sharingAccount:destinationNode.sharingAccount];
858
                        for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
859
                            objectRequest.delegate = self;
860
                            objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
861
                            objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
862
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
863
                                                                                       message:[NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo valueForKey:@"fileName"]]];
864
                            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
865
                             [NSDictionary dictionaryWithObjectsAndKeys:
866
                              [NSNumber numberWithBool:YES], @"refresh", 
867
                              activity, @"activity", 
868
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
869
                              nil]];
870
                            [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
871
                        }
872
                        if (objectRequests) {
873
                            for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
874
                                ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
875
                                objectRequest.delegate = self;
876
                                objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
877
                                objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
878
                                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
879
                                                                                           message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
880
                                                                                        totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
881
                                                                                      currentBytes:0];
882
                                [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
883
                                 [NSDictionary dictionaryWithObjectsAndKeys:
884
                                  containerName, @"containerName", 
885
                                  [objectNames objectAtIndex:i], @"objectName", 
886
                                  [contentTypes objectAtIndex:i], @"contentType", 
887
                                  [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
888
                                  blockHash, @"blockHash", 
889
                                  [filePaths objectAtIndex:i], @"filePath", 
890
                                  [hashesArrays objectAtIndex:i], @"hashes", 
891
                                  [NSNumber numberWithBool:YES], @"refresh", 
892
                                  [NSNumber numberWithUnsignedInteger:10], @"iteration", 
893
                                  activity, @"activity", 
894
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
895
                                  nil]];
896
                                if (destinationNode.sharingAccount)
897
                                    [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
898
                                [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
899
                            }
900
                        }
901
                    });
902
                }
903
            }
904
        }
905
    }
906
    return YES;
907
}
908

    
909
- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
910
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
911
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
912
        return NO;
913
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
914
    NSString *objectNamePrefix;
915
    if ([destinationNode class] == [PithosSubdirNode class])
916
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
917
    else
918
        objectNamePrefix = [NSString string];
919

    
920
    for (PithosNode *node in nodes) {
921
        if (([node class] == [PithosObjectNode class]) || 
922
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
923
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
924
            dispatch_async(queue, ^{
925
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
926
                if ([node.pithosObject.name hasSuffix:@"/"])
927
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
928
                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
929
                                                                                                     objectName:node.pithosObject.name 
930
                                                                                       destinationContainerName:containerName 
931
                                                                                          destinationObjectName:destinationObjectName 
932
                                                                                                  checkIfExists:YES];
933
                if (objectRequest) {
934
                    objectRequest.delegate = self;
935
                    objectRequest.didFinishSelector = @selector(moveFinished:);
936
                    objectRequest.didFailSelector = @selector(moveFailed:);
937
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
938
                                                                               message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
939
                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
940
                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
941
                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
942
                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
943
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
944
                     [NSDictionary dictionaryWithObjectsAndKeys:
945
                      [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
946
                      activity, @"activity", 
947
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
948
                      nil]];
949
                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
950
                }
951
            });
952
        } else if ([node class] == [PithosSubdirNode class]) {
953
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
954
            dispatch_async(queue, ^{
955
                NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
956
                if (node.pithosObject.subdir)
957
                    destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
958
                NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
959
                                                                                                 objectName:node.pithosObject.name 
960
                                                                                   destinationContainerName:containerName 
961
                                                                                      destinationObjectName:destinationObjectName 
962
                                                                                              checkIfExists:YES];
963
                if (objectRequests) {
964
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
965
                        objectRequest.delegate = self;
966
                        objectRequest.didFinishSelector = @selector(moveFinished:);
967
                        objectRequest.didFailSelector = @selector(moveFailed:);
968
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
969
                                                                                   message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
970
                                                                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
971
                                                                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
972
                                                                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
973
                                                                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
974
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
975
                         [NSDictionary dictionaryWithObjectsAndKeys:
976
                          [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes", 
977
                          [NSNumber numberWithBool:YES], @"refresh", 
978
                          activity, @"activity", 
979
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
980
                          nil]];
981
                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
982
                    }
983
                }
984
            });
985
        }
986
    }
987
    return YES;
988
}
989

    
990
- (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {    
991
    if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
992
        (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"])) 
993
        return NO;
994
    NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
995
    NSString *objectNamePrefix;
996
    if ([destinationNode class] == [PithosSubdirNode class])
997
        objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
998
    else
999
        objectNamePrefix = [NSString string];
1000
    
1001
    for (PithosNode *node in nodes) {
1002
        if (([node class] == [PithosObjectNode class]) || 
1003
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1004
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1005
            dispatch_async(queue, ^{
1006
                NSString *destinationObjectName;
1007
                if (![destinationNode isEqualTo:node.parent]) {
1008
                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1009
                    if ([node.pithosObject.name hasSuffix:@"/"])
1010
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1011
                } else {
1012
                    destinationObjectName = [PithosUtilities safeObjectNameForContainerName:containerName 
1013
                                                                                 objectName:node.pithosObject.name];
1014
                }
1015
                ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithContainerName:node.pithosContainer.name 
1016
                                                                                                     objectName:node.pithosObject.name 
1017
                                                                                       destinationContainerName:containerName 
1018
                                                                                          destinationObjectName:destinationObjectName 
1019
                                                                                                  checkIfExists:YES 
1020
                                                                                                 sharingAccount:node.sharingAccount];
1021
                if (objectRequest) {
1022
                    objectRequest.delegate = self;
1023
                    objectRequest.didFinishSelector = @selector(copyFinished:);
1024
                    objectRequest.didFailSelector = @selector(copyFailed:);
1025
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1026
                                                                               message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1027
                                                                                        [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1028
                                                                                        [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1029
                                                                                        [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1030
                                                                                        [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1031
                    [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1032
                     [NSDictionary dictionaryWithObjectsAndKeys:
1033
                      [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1034
                      activity, @"activity", 
1035
                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1036
                      nil]];
1037
                    [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1038
                }
1039
            });
1040
        } else if ([node class] == [PithosSubdirNode class]) {
1041
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1042
            dispatch_async(queue, ^{
1043
                NSString *destinationObjectName;
1044
                if (![destinationNode isEqualTo:node.parent]) {
1045
                    destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1046
                    if (node.pithosObject.subdir)
1047
                        destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1048
                } else {
1049
                    destinationObjectName = [PithosUtilities safeSubdirNameForContainerName:containerName 
1050
                                                                                     subdirName:node.pithosObject.name];
1051
                }
1052
                NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1053
                                                                                                 objectName:node.pithosObject.name 
1054
                                                                                   destinationContainerName:containerName 
1055
                                                                                      destinationObjectName:destinationObjectName 
1056
                                                                                              checkIfExists:YES 
1057
                                                                                             sharingAccount:node.sharingAccount];
1058
                if (objectRequests) {
1059
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1060
                        objectRequest.delegate = self;
1061
                        objectRequest.didFinishSelector = @selector(copyFinished:);
1062
                        objectRequest.didFailSelector = @selector(copyFailed:);
1063
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy 
1064
                                                                                   message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'", 
1065
                                                                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1066
                                                                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1067
                                                                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1068
                                                                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1069
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1070
                         [NSDictionary dictionaryWithObjectsAndKeys:
1071
                          [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes", 
1072
                          activity, @"activity", 
1073
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1074
                          nil]];
1075
                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1076
                    }
1077
                }
1078
            });
1079
        }
1080
    }
1081
    return YES;
1082
}
1083

    
1084
#pragma mark -
1085
#pragma mark ASIHTTPRequestDelegate
1086

    
1087
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1088
    NSLog(@"Download finished: %@", objectRequest.url);
1089
    if (objectRequest.responseStatusCode == 200) {
1090
        NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1091
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1092
        NSUInteger totalBytes = activity.totalBytes;
1093
        NSUInteger currentBytes = activity.currentBytes;
1094
        
1095
        // XXX change contentLength to objectContentLength if it is fixed in the server
1096
        if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1097
            NSLog(@"Downloaded  0 bytes");
1098
            NSFileManager *fileManager = [NSFileManager defaultManager];
1099
            if (![fileManager fileExistsAtPath:filePath]) {
1100
                if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1101
                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1102
                    [alert setMessageText:@"Create File Error"];
1103
                    [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1104
                    [alert addButtonWithTitle:@"OK"];
1105
                    [alert runModal];
1106
                }
1107
            }
1108
        }
1109
        
1110
        currentBytes = [objectRequest objectContentLength];
1111
        if (currentBytes == 0)
1112
            currentBytes = totalBytes;
1113
        [activityFacility endActivity:activity 
1114
                          withMessage:[NSString stringWithFormat:@"Downloading '%@' (100%%)", 
1115
                                       [objectRequest.userInfo objectForKey:@"fileName"]] 
1116
                           totalBytes:totalBytes 
1117
                         currentBytes:currentBytes];
1118
    } else {
1119
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1120
        if (retries > 0) {
1121
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1122
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1123
            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1124
        } else {
1125
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1126
                              withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)", 
1127
                                           [objectRequest.userInfo objectForKey:@"fileName"]]];
1128
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1129
        }
1130
    }
1131
}
1132

    
1133
- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1134
    NSLog(@"Download failed: %@", objectRequest.url);
1135
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1136
    if (retries > 0) {
1137
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1138
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1139
        [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1140
    } else {
1141
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1142
                          withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)", 
1143
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1144
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1145
    }
1146
}
1147

    
1148
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1149
    NSLog(@"Upload directory object finished: %@", objectRequest.url);
1150
    if (objectRequest.responseStatusCode == 201) {
1151
        NSLog(@"Directory object created: %@", objectRequest.url);
1152
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1153
                          withMessage:[NSString stringWithFormat:@"Creating directory '%@' (finished)", 
1154
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1155
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1156
            [node forceRefresh];
1157
        }
1158
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1159
            [node refresh];
1160
        }
1161
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1162
            [self forceRefresh:self];
1163
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1164
            [self refresh:self];
1165
    } else {
1166
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1167
        if (retries > 0) {
1168
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1169
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1170
            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1171
        } else {
1172
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1173
                              withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)", 
1174
                                           [objectRequest.userInfo objectForKey:@"fileName"]]];
1175
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1176
        }
1177
    }
1178
}
1179

    
1180
- (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1181
    NSLog(@"Upload directory object failed: %@", objectRequest.url);
1182
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1183
    if (retries > 0) {
1184
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1185
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1186
        [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1187
    } else {
1188
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1189
                          withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)", 
1190
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1191
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1192
    }
1193
}
1194

    
1195
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1196
    NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1197
    NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1198
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1199
    NSUInteger totalBytes = activity.totalBytes;
1200
    NSUInteger currentBytes = activity.currentBytes;
1201
    if (objectRequest.responseStatusCode == 201) {
1202
        NSLog(@"Object created: %@", objectRequest.url);
1203
        [activityFacility endActivity:activity 
1204
                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (100%%)", fileName] 
1205
                           totalBytes:totalBytes 
1206
                         currentBytes:totalBytes];
1207
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1208
            [node forceRefresh];
1209
        }
1210
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1211
            [node refresh];
1212
        }
1213
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1214
            [self forceRefresh:self];
1215
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1216
            [self refresh:self];        
1217
    } else if (objectRequest.responseStatusCode == 409) {
1218
        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1219
        if (iteration == 0) {
1220
            NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1221
            [activityFacility endActivity:activity 
1222
                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]]; 
1223
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1224
            [alert setMessageText:@"Upload Timeout"];
1225
            [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
1226
                                       [objectRequest.userInfo objectForKey:@"objectName"]]];
1227
            [alert addButtonWithTitle:@"OK"];
1228
            [alert runModal];
1229
            return;
1230
        }
1231
        NSLog(@"object is missing hashes: %@", objectRequest.url);
1232
        NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1233
                                                  withMissingHashesResponse:[objectRequest responseString]];
1234
        NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1235
        if (totalBytes >= [missingBlocks count]*blockSize)
1236
            currentBytes = totalBytes - [missingBlocks count]*blockSize;
1237
        [activityFacility updateActivity:activity 
1238
                             withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1239
                              totalBytes:totalBytes 
1240
                            currentBytes:currentBytes];
1241
        NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1242
        __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
1243
                                                                                                                    blockSize:blockSize 
1244
                                                                                                                      forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1245
                                                                                                            missingBlockIndex:missingBlockIndex 
1246
                                                                                                               sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1247
        newContainerRequest.delegate = self;
1248
        newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1249
        newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1250
        newContainerRequest.userInfo = objectRequest.userInfo;
1251
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1252
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1253
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1254
        [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1255
        [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1256
            [activityFacility updateActivity:activity 
1257
                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1258
                                  totalBytes:activity.totalBytes 
1259
                                currentBytes:(activity.currentBytes + size)];
1260
        }];
1261
        [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1262
    } else {
1263
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1264
        if (retries > 0) {
1265
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1266
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1267
            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1268
        } else {
1269
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1270
                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1271
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1272
        }
1273
    }
1274
}
1275

    
1276
- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1277
    NSLog(@"Upload using hashmap failed: %@", objectRequest.url);
1278
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1279
    if (retries > 0) {
1280
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1281
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1282
        [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1283
    } else {
1284
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1285
                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", 
1286
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1287
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1288
    }
1289
}
1290

    
1291
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1292
    NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1293
    NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1294
    NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1295
    PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1296
    if (containerRequest.responseStatusCode == 202) {
1297
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1298
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1299
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1300
        if (missingBlockIndex == NSNotFound) {
1301
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1302
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"] 
1303
                                                                                                     objectName:[containerRequest.userInfo objectForKey:@"objectName"] 
1304
                                                                                                    contentType:[containerRequest.userInfo objectForKey:@"contentType"] 
1305
                                                                                                      blockSize:blockSize 
1306
                                                                                                      blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1307
                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1308
                                                                                                  checkIfExists:NO 
1309
                                                                                                         hashes:&hashes 
1310
                                                                                                 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1311
            newObjectRequest.delegate = self;
1312
            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1313
            newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1314
            newObjectRequest.userInfo = containerRequest.userInfo;
1315
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1316
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1317
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1318
            [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1319
        } else {
1320
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1321
                                                                                                                        blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1322
                                                                                                                          forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1323
                                                                                                                missingBlockIndex:missingBlockIndex 
1324
                                                                                                                   sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1325
            newContainerRequest.delegate = self;
1326
            newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1327
            newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1328
            newContainerRequest.userInfo = containerRequest.userInfo;
1329
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1330
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1331
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1332
                [activityFacility updateActivity:activity 
1333
                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1334
                                      totalBytes:activity.totalBytes 
1335
                                    currentBytes:(activity.currentBytes + size)];
1336
            }];
1337
            [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1338
        }
1339
    } else {
1340
        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1341
        if (retries > 0) {
1342
            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1343
            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1344
            [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1345
        } else {
1346
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1347
                              withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1348
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1349
        }
1350
    }
1351
}
1352

    
1353
- (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
1354
    NSLog(@"Upload of missing block failed: %@", containerRequest.url);
1355
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1356
    if (retries > 0) {
1357
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1358
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1359
        [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1360
    } else {
1361
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1362
                          withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", 
1363
                                       [containerRequest.userInfo objectForKey:@"fileName"]]];
1364
        [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1365
    }
1366
}
1367

    
1368
- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1369
    NSLog(@"Move object finished: %@", objectRequest.url);
1370
    if (objectRequest.responseStatusCode == 201) {
1371
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1372
                          withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (finished)", 
1373
                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1374
                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1375
                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1376
                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1377
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1378
            [node forceRefresh];
1379
        }
1380
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1381
            [node refresh];
1382
        }
1383
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1384
            [self forceRefresh:self];
1385
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1386
            [self refresh:self];
1387
    } else {
1388
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1389
        if (retries > 0) {
1390
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1391
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1392
            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1393
        } else {
1394
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1395
                              withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)", 
1396
                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1397
                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1398
                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1399
                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1400
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1401
        }
1402
    }
1403
}
1404

    
1405
- (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1406
    NSLog(@"Move object failed: %@", objectRequest.url);
1407
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1408
    if (retries > 0) {
1409
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1410
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1411
        [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1412
    } else {
1413
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1414
                          withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)", 
1415
                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1416
                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1417
                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1418
                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1419
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1420
    }
1421
}
1422

    
1423
- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1424
    NSLog(@"Copy object finished: %@", objectRequest.url);
1425
    if (objectRequest.responseStatusCode == 201) {
1426
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1427
                          withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (finished)", 
1428
                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1429
                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1430
                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1431
                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1432
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1433
            [node forceRefresh];
1434
        }
1435
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1436
            [node refresh];
1437
        }
1438
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1439
            [self forceRefresh:self];
1440
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1441
            [self refresh:self];
1442
    } else {
1443
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1444
        if (retries > 0) {
1445
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1446
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1447
            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1448
        } else {
1449
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1450
                              withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)", 
1451
                                           [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1452
                                           [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1453
                                           [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1454
                                           [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1455
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1456
        }
1457
    }
1458
}
1459

    
1460
- (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1461
    NSLog(@"Copy object failed: %@", objectRequest.url);
1462
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1463
    if (retries > 0) {
1464
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1465
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1466
        [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1467
    } else {
1468
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1469
                          withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)", 
1470
                                       [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1471
                                       [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1472
                                       [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1473
                                       [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1474
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1475
    }
1476
}
1477

    
1478
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1479
    NSLog(@"Delete object finished: %@", objectRequest.url);
1480
    if (objectRequest.responseStatusCode == 204) {
1481
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1482
                          withMessage:[NSString stringWithFormat:@"Deleting '%@' (finished)", 
1483
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1484
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1485
            [node forceRefresh];
1486
        }
1487
        for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1488
            [node refresh];
1489
        }
1490
        if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1491
            [self forceRefresh:self];
1492
        else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1493
            [self refresh:self];
1494
    } else {
1495
        NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1496
        if (retries > 0) {
1497
            ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1498
            [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1499
            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1500
        } else {
1501
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1502
                              withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)", 
1503
                                           [objectRequest.userInfo objectForKey:@"fileName"]]];
1504
            [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1505
        }
1506
    }
1507
}
1508

    
1509
- (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1510
    NSLog(@"Delete object failed: %@", objectRequest.url);
1511
    NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1512
    if (retries > 0) {
1513
        ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1514
        [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1515
        [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1516
    } else {
1517
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1518
                          withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)", 
1519
                                       [objectRequest.userInfo objectForKey:@"fileName"]]];
1520
        [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1521
    }
1522
}
1523

    
1524
#pragma mark -
1525
#pragma mark NSSplitViewDelegate
1526

    
1527
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1528
    if (splitView == verticalSplitView)
1529
        return 120;
1530
    else
1531
        return ([horizontalSplitView bounds].size.height - 108);
1532
}
1533

    
1534
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1535
    if (splitView == verticalSplitView)
1536
        return 220;
1537
    else
1538
        return ([horizontalSplitView bounds].size.height - 108);
1539
}
1540

    
1541
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1542
    if (splitView == verticalSplitView) {
1543
        if (proposedPosition < 120)
1544
            return 120;
1545
        else if (proposedPosition > 220)
1546
            return 220;
1547
        else
1548
            return proposedPosition;
1549
    } else {
1550
        return ([horizontalSplitView bounds].size.height - 108);
1551
    }
1552
}
1553

    
1554
#pragma mark -
1555
#pragma mark NSOutlineViewDataSource
1556

    
1557
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1558
    if (item == nil)
1559
        return 2;
1560
    if (item == containersNode)
1561
        return containersNodeChildren.count;
1562
    if (item == sharedNode)
1563
        return 2;
1564
    return 0;
1565
}
1566

    
1567
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1568
    if (item == nil)
1569
        return (!index ? containersNode : sharedNode);
1570
    if (item == sharedNode)
1571
        return (!index ? mySharedNode : othersSharedNode);
1572
    return [containersNodeChildren objectAtIndex:index];
1573
}
1574

    
1575
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1576
    if ((item == containersNode) || (item == sharedNode))
1577
        return YES;
1578
    return NO;
1579
}
1580

    
1581
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1582
    PithosNode *node = (PithosNode *)item;
1583
    return node;    
1584
}
1585

    
1586
#pragma mark Drag and Drop destination
1587

    
1588
- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView 
1589
                  validateDrop:(id<NSDraggingInfo>)info 
1590
                  proposedItem:(id)item 
1591
            proposedChildIndex:(NSInteger)index {
1592
    NSDragOperation result = NSDragOperationNone;
1593
    if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
1594
        return result;
1595
    PithosNode *dropNode = (PithosNode *)item;
1596
    if ([dropNode class] != [PithosContainerNode class])
1597
        return result;
1598
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1599
        result = NSDragOperationCopy;
1600
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1601
        if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1602
            // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1603
            if (![dropNode isEqualTo:draggedParentNode])
1604
                result = NSDragOperationMove;
1605
        } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1606
            // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1607
            result = NSDragOperationCopy;
1608
        }
1609
    }
1610
   return result;
1611
}
1612

    
1613
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
1614
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1615
        NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1616
        NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1617
        if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
1618
            PithosNode *node = (PithosNode *)item;
1619
            NSLog(@"drag in node: %@", node.url);
1620
            return [self uploadFiles:filenames toNode:node];
1621
        }
1622
    } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1623
        NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1624
        if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
1625
            PithosNode *node = (PithosNode *)item;
1626
            NSLog(@"drag local node: %@", node.url);
1627
            if ([info draggingSourceOperationMask] & NSDragOperationMove)
1628
                return [self moveNodes:draggedNodes toNode:node];
1629
            else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1630
                return [self copyNodes:draggedNodes toNode:node];
1631
        }
1632
    }
1633
    return NO;
1634
}
1635

    
1636
#pragma mark -
1637
#pragma mark NSOutlineViewDelegate
1638

    
1639
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1640
    if ((item == containersNode) || (item == sharedNode))
1641
        return NO;
1642
    return YES;
1643
}
1644

    
1645
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1646
    if ((item == containersNode) || (item == sharedNode))
1647
        return YES;
1648
    return NO;
1649
}
1650

    
1651
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1652
    PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1653
    if (node) {
1654
        rootNode = node;
1655
        [browser loadColumnZero];
1656
        [self refresh:nil];
1657
    }
1658
}
1659

    
1660
#pragma mark -
1661
#pragma mark NSMenuDelegate
1662

    
1663
- (void)menuNeedsUpdate:(NSMenu *)menu {
1664
    [menu removeAllItems];
1665
    NSMenuItem *menuItem;
1666
    NSString *menuItemTitle;
1667
    BOOL nodeContextMenu = NO;
1668
    PithosNode *menuNode;
1669
    NSMutableArray *menuNodes;
1670
    if (menu == browserMenu) {
1671
        NSInteger column = [browser clickedColumn];
1672
        NSInteger row = [browser clickedRow];
1673
        if ((column == -1) || (row == -1)) {
1674
            // General context menu
1675
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1676
            if ([menuNodesIndexPaths count] == 0) {
1677
                menuNode = [browser parentForItemsInColumn:0];
1678
            } else if (([menuNodesIndexPaths count] != 1) || 
1679
                       ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1680
                menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1681
            } else {
1682
                menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1683
            }
1684
        } else {
1685
            // Node context menu
1686
            NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1687
            NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1688
            menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1689
            if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1690
                for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1691
                    [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1692
                }
1693
            } else {
1694
                [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1695
            }
1696
            nodeContextMenu = YES;
1697
        }
1698
    } else if (menu == outlineViewMenu) {
1699
        NSInteger row = [outlineView clickedRow];
1700
        if (row == -1)
1701
            row = [outlineView selectedRow];
1702
        if (row == -1)
1703
            return;
1704
        menuNode = [outlineView itemAtRow:row];
1705
    }
1706

    
1707
    if (!nodeContextMenu) {
1708
        // General context menu
1709
        if (([menuNode class] == [PithosAccountNode class]) || 
1710
            ([menuNode class] == [PithosSharingAccountsNode class]) ||
1711
            ([menuNode class] == [PithosEmptyNode class]))
1712
            return;
1713
        BOOL shared = menuNode.shared;
1714
        BOOL sharingAccount = (menuNode.sharingAccount != nil);
1715
        // New Folder
1716
        if (!shared && !sharingAccount) {
1717
            menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1718
            [menuItem setRepresentedObject:menuNode];
1719
            [menu addItem:menuItem];
1720
            [menu addItem:[NSMenuItem separatorItem]];
1721
        }
1722
        // Get Info
1723
        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1724
        [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1725
        [menu addItem:menuItem];
1726
        // Paste
1727
        if (!shared && !sharingAccount) {
1728
            if (clipboardNodes) {
1729
                NSUInteger clipboardNodesCount = [clipboardNodes count];
1730
                if (clipboardNodesCount == 0) {
1731
                    self.clipboardNodes = nil;
1732
                } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1733
                    if (clipboardNodesCount == 1)
1734
                        menuItemTitle = [NSString stringWithString:@"Paste Item"];
1735
                    else
1736
                        menuItemTitle = [NSString stringWithString:@"Paste Items"];
1737
                    [menu addItem:[NSMenuItem separatorItem]];
1738
                    menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1739
                    [menuItem setRepresentedObject:menuNode];
1740
                    [menu addItem:menuItem];
1741
                }
1742
            }
1743
        }
1744
    } else {
1745
        // Node context menu
1746
        NSUInteger menuNodesCount = [menuNodes count];
1747
        PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1748
        BOOL shared = firstMenuNode.shared;
1749
        BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1750
        // Move to Trash (pithos container only)
1751
        // Delete
1752
        if (!shared && !sharingAccount) {
1753
            if ([rootNode class] == [PithosContainerNode class]) {
1754
                if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1755
                    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1756
                    [menuItem setRepresentedObject:menuNodes];
1757
                    [menu addItem:menuItem];
1758
                }
1759
                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1760
                [menuItem setRepresentedObject:menuNodes];
1761
                [menu addItem:menuItem];
1762
                [menu addItem:[NSMenuItem separatorItem]];
1763
            }
1764
        }
1765
        // Get Info
1766
        if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1767
            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1768
            [menuItem setRepresentedObject:menuNodes];
1769
            [menu addItem:menuItem];
1770
            
1771
            if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1772
                [menu addItem:[NSMenuItem separatorItem]];
1773
        }
1774
        // Cut
1775
        if (!shared && !sharingAccount) {
1776
            if (menuNodesCount == 1)
1777
                menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1778
            else 
1779
                menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1780
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1781
            [menuItem setRepresentedObject:menuNodes];
1782
            [menu addItem:menuItem];
1783
        }
1784
        // Copy
1785
        if ((!shared && !sharingAccount) || 
1786
            (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1787
            if (menuNodesCount == 1)
1788
                menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1789
            else 
1790
                menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1791
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1792
            [menuItem setRepresentedObject:menuNodes];
1793
            [menu addItem:menuItem];
1794
        }
1795
        // Paste
1796
        if (!shared && !sharingAccount) {
1797
            if (menuNodesCount == 1) {
1798
                PithosNode *menuNode = [menuNodes objectAtIndex:0];
1799
                if (([menuNode class] == [PithosSubdirNode class]) && 
1800
                    (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1801
                    if (clipboardNodes) {
1802
                        NSUInteger clipboardNodesCount = [clipboardNodes count];
1803
                        if (clipboardNodesCount == 0) {
1804
                            self.clipboardNodes = nil;
1805
                        } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1806
                            if (clipboardNodesCount == 1)
1807
                                menuItemTitle = [NSString stringWithString:@"Paste Item"];
1808
                            else
1809
                                menuItemTitle = [NSString stringWithString:@"Paste Items"];
1810
                            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1811
                            [menuItem setRepresentedObject:menuNode];
1812
                            [menu addItem:menuItem];
1813
                        }
1814
                    }
1815
                }
1816
            }
1817
        }
1818
    }
1819
}
1820

    
1821
#pragma mark -
1822
#pragma mark Menu Actions
1823

    
1824
- (void)menuNewFolder:(NSMenuItem *)sender {
1825
    PithosNode *node = (PithosNode *)[sender representedObject];
1826
    if ([node class] == [PithosContainerNode class]) {
1827
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1828
        dispatch_async(queue, ^{
1829
            NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1830
                                                                                subdirName:@"untitled folder"];
1831
            NSString *fileName = [safeObjectName lastPathComponent];
1832
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1833
                                                                                                         objectName:safeObjectName 
1834
                                                                                                           eTag:nil 
1835
                                                                                                    contentType:@"application/directory" 
1836
                                                                                                contentEncoding:nil 
1837
                                                                                             contentDisposition:nil 
1838
                                                                                                       manifest:nil 
1839
                                                                                                        sharing:nil 
1840
                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
1841
                                                                                                       metadata:nil 
1842
                                                                                                           data:[NSData data]];
1843
            objectRequest.delegate = self;
1844
            objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1845
            objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1846
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1847
                                                                       message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1848
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1849
                                      fileName, @"fileName", 
1850
                                      [NSArray arrayWithObject:node], @"refreshNodes", 
1851
                                      [NSNumber numberWithBool:YES], @"refresh", 
1852
                                      activity, @"activity", 
1853
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1854
                                      nil];
1855
            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1856
        });
1857
    } else if (([node class] == [PithosSubdirNode class]) && 
1858
               (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
1859
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1860
        dispatch_async(queue, ^{
1861
            NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
1862
                                                                               subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1863
            NSString *fileName = [safeObjectName lastPathComponent];
1864
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
1865
                                                                                                        objectName:safeObjectName 
1866
                                                                                                              eTag:nil 
1867
                                                                                                       contentType:@"application/directory" 
1868
                                                                                                   contentEncoding:nil 
1869
                                                                                                contentDisposition:nil 
1870
                                                                                                          manifest:nil 
1871
                                                                                                           sharing:nil 
1872
                                                                                                          isPublic:ASIPithosObjectRequestPublicIgnore 
1873
                                                                                                          metadata:nil 
1874
                                                                                                              data:[NSData data]];
1875
            objectRequest.delegate = self;
1876
            objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1877
            objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1878
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1879
                                                                       message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1880
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1881
                                      fileName, @"fileName", 
1882
                                      [NSArray arrayWithObject:node], @"refreshNodes", 
1883
                                      [NSNumber numberWithBool:YES], @"refresh", 
1884
                                      activity, @"activity", 
1885
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1886
                                      nil];
1887
            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1888
        });
1889
    }
1890
}
1891

    
1892
- (void)menuGetInfo:(NSMenuItem *)sender {
1893
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1894
        [node showPithosNodeInfo:sender];
1895
    }
1896
}
1897

    
1898
- (void)menuDelete:(NSMenuItem *)sender {
1899
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1900
        if (([node class] == [PithosObjectNode class]) || 
1901
            (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1902
            NSString *fileName = [node.pithosObject.name lastPathComponent];
1903
            if ([node.pithosObject.name hasSuffix:@"/"])
1904
                fileName = [fileName stringByAppendingString:@"/"];
1905
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
1906
                                                                                                      objectName:node.pithosObject.name];
1907
            objectRequest.delegate = self;
1908
            objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1909
            objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1910
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1911
                                                                       message:[NSString stringWithFormat:@"Deleting '%@'", fileName]];
1912
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1913
                                      fileName, @"fileName", 
1914
                                      [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
1915
                                      activity, @"activity", 
1916
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1917
                                      nil];
1918
            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1919
        } else if ([node class] == [PithosSubdirNode class]) {
1920
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1921
            dispatch_async(queue, ^{
1922
                NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1923
                                                                                               objectName:node.pithosObject.name];
1924
                if (objectRequests) {
1925
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1926
                        objectRequest.delegate = self;
1927
                        objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1928
                        objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1929
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1930
                                                                                   message:[NSString stringWithFormat:@"Deleting '%@'", 
1931
                                                                                            [objectRequest.userInfo objectForKey:@"fileName"]]];
1932
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1933
                         [NSDictionary dictionaryWithObjectsAndKeys:
1934
                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
1935
                          activity, @"activity", 
1936
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1937
                          nil]];
1938
                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1939
                    }
1940
                }
1941
            });
1942
        }
1943
    }
1944
}
1945

    
1946
- (void)menuMoveToTrash:(NSMenuItem *)sender {
1947
    for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1948
        if (([node class] == [PithosObjectNode class]) || 
1949
            (([node class] == [PithosSubdirNode class]) && 
1950
             !node.pithosObject.subdir &&
1951
             [node.pithosObject.name hasSuffix:@"/"])) {
1952
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1953
            dispatch_async(queue, ^{
1954
                NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
1955
                                                                                    objectName:node.pithosObject.name];
1956
                if (safeObjectName) {
1957
                    ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name 
1958
                                                                                                         objectName:node.pithosObject.name 
1959
                                                                                           destinationContainerName:@"trash" 
1960
                                                                                              destinationObjectName:safeObjectName 
1961
                                                                                                      checkIfExists:NO];
1962
                    if (objectRequest) {
1963
                        objectRequest.delegate = self;
1964
                        objectRequest.didFinishSelector = @selector(moveFinished:);
1965
                        objectRequest.didFailSelector = @selector(moveFailed:);
1966
                        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1967
                                                                                   message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
1968
                                                                                            [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
1969
                                                                                            [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
1970
                                                                                            [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
1971
                                                                                            [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1972
                        [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1973
                         [NSDictionary dictionaryWithObjectsAndKeys:
1974
                          [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
1975
                          activity, @"activity", 
1976
                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
1977
                          nil]];
1978
                        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1979
                    }
1980
                }
1981
            });
1982
        } else if ([node class] == [PithosSubdirNode class]) {
1983
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1984
            dispatch_async(queue, ^{
1985
                NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:@"trash" 
1986
                                                                                    subdirName:node.pithosObject.name];
1987
                if (safeObjectName) {
1988
                    NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
1989
                                                                                                     objectName:node.pithosObject.name 
1990
                                                                                       destinationContainerName:@"trash" 
1991
                                                                                          destinationObjectName:safeObjectName 
1992
                                                                                                  checkIfExists:NO];
1993
                    if (objectRequests) {
1994
                        for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1995
                            objectRequest.delegate = self;
1996
                            objectRequest.didFinishSelector = @selector(moveFinished:);
1997
                            objectRequest.didFailSelector = @selector(moveFailed:);
1998
                            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove 
1999
                                                                                       message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'", 
2000
                                                                                                [objectRequest.userInfo objectForKey:@"sourceContainerName"], 
2001
                                                                                                [objectRequest.userInfo objectForKey:@"sourceObjectName"], 
2002
                                                                                                [objectRequest.userInfo objectForKey:@"destinationContainerName"], 
2003
                                                                                                [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
2004
                            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2005
                             [NSDictionary dictionaryWithObjectsAndKeys:
2006
                              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
2007
                              activity, @"activity", 
2008
                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
2009
                              nil]];
2010
                            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
2011
                        }
2012
                    }
2013
                }
2014
            });
2015
        }
2016
    }
2017
}
2018

    
2019
- (void)menuCut:(NSMenuItem *)sender {
2020
    self.clipboardNodes = (NSArray *)[sender representedObject];
2021
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2022
    self.clipboardCopy = NO;
2023
}
2024

    
2025
- (void)menuCopy:(NSMenuItem *)sender {
2026
    self.clipboardNodes = (NSArray *)[sender representedObject];
2027
    self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2028
    self.clipboardCopy = YES;
2029
}
2030

    
2031
- (void)menuPaste:(NSMenuItem *)sender {
2032
    if (!clipboardNodes || ![clipboardNodes count])
2033
        return;
2034
    PithosNode *dropNode = (PithosNode *)[sender representedObject];
2035
    NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2036
    if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2037
        self.clipboardNodes = nil;
2038
        self.clipboardParentNode = nil;
2039
        [self moveNodes:localClipboardNodes toNode:dropNode];
2040
    } else {
2041
        [self copyNodes:localClipboardNodes toNode:dropNode];
2042
    }
2043
}
2044
    
2045
#pragma mark -
2046
#pragma mark PithosActivityFacilityDelegate
2047

    
2048
- (void)activityUpdate:(NSDictionary *)info {
2049
    NSString *message = [info objectForKey:@"message"];
2050
    NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2051
//    NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2052
    NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2053
    NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2054
    NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2055
    NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2056
    NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2057
    if (runningActivitiesCount && totalBytes) {
2058
        [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2059
        [activityProgressIndicator startAnimation:self];
2060
    } else {
2061
        [activityProgressIndicator setDoubleValue:1.0];
2062
        [activityProgressIndicator stopAnimation:self];
2063
    }
2064
    
2065
    if (!message)
2066
        message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2067
    [activityTextField setStringValue:message];
2068
}
2069

    
2070
@end