Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosBrowserController.m @ 133b1f2d

History | View | Annotate | Download (46.3 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 "PithosEmptyNode.h"
45
#import "ImageAndTextCell.h"
46
#import "FileSystemBrowserCell.h"
47
#import "ASIPithosRequest.h"
48
#import "ASIPithosContainerRequest.h"
49
#import "ASIPithosObjectRequest.h"
50
#import "ASIPithosContainer.h"
51
#import "ASIPithosObject.h"
52
#import "PithosFileUtilities.h"
53

    
54
@interface PithosBrowserCell : FileSystemBrowserCell {}
55
@end
56

    
57
@implementation PithosBrowserCell
58

    
59
- (id)init {
60
    if ((self = [super init])) {
61
        [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
62
    }
63
    return self;
64
}
65

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

    
76
@end
77

    
78
@interface PithosOutlineViewCell : ImageAndTextCell {}
79
@end
80

    
81
@implementation PithosOutlineViewCell
82

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

    
94
@end
95

    
96
@interface PithosBrowserController (Private) {}
97
- (void)resetContainers:(NSNotification *)notification;
98
- (void)getInfo:(NSMenuItem *)sender;
99
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
100
- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
101
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
102
- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
103
- (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
104
- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
105
@end
106

    
107
@implementation PithosBrowserController
108
@synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
109

    
110
#pragma mark -
111
#pragma Object Lifecycle
112

    
113
- (id)init {
114
    return [super initWithWindowNibName:@"PithosBrowserController"];
115
}
116

    
117
- (void)dealloc {
118
    [[NSNotificationCenter defaultCenter] removeObserver:self];
119
    [browserMenu release];
120
    [sharedPreviewController release];
121
    [outlineViewDataSourceArray release];
122
    [accountNode release];
123
    [rootNode release];
124
    [super dealloc];
125
}
126

    
127
- (void)awakeFromNib {
128
    [super awakeFromNib];
129
    
130
    [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
131
    [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
132
    [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
133
    
134
    [browser setCellClass:[PithosBrowserCell class]];
135
    
136
    browserMenu = [[NSMenu alloc] init];
137
    [browserMenu setDelegate:self];
138
    [browser setMenu:browserMenu];
139
}
140

    
141
- (void)resetContainers:(NSNotification *)notification {
142
    rootNode = nil;
143
    [browser loadColumnZero];
144
    self.outlineViewDataSourceArray = nil;
145
    
146
    // Create the outlineView tree
147
    // CONTAINERS
148
	NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
149
                            [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
150

    
151
    // SHARED
152
	NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
153
                                      [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
154
    // SHARED/my shared
155
	[[sharedTreeNode mutableChildNodes] addObject:
156
     [NSTreeNode treeNodeWithRepresentedObject:
157
      [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
158
                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
159
        ] autorelease]]];
160
    // SHARED/others shared
161
	[[sharedTreeNode mutableChildNodes] addObject:
162
     [NSTreeNode treeNodeWithRepresentedObject:
163
      [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
164
                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
165
        ] autorelease]]];
166
    
167
    self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
168
    
169
	// Expand the folder outline view
170
    [outlineView expandItem:nil expandChildren:YES];
171
	[outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
172
    
173
    // Create accountNode and trigger a refresh
174
    accountNode = [[PithosAccountNode alloc] init];
175
    accountNode.children;
176
}
177

    
178
- (void)windowDidLoad {
179
    [super windowDidLoad];
180
    
181
    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
182
    
183
    // Register for updates
184
    [[NSNotificationCenter defaultCenter] addObserver:self 
185
                                             selector:@selector(pithosNodeChildrenUpdated:) 
186
                                                 name:@"PithosContainerNodeChildrenUpdated" 
187
                                               object:nil];
188
    [[NSNotificationCenter defaultCenter] addObserver:self 
189
                                             selector:@selector(pithosNodeChildrenUpdated:) 
190
                                                 name:@"PithosSubdirNodeChildrenUpdated" 
191
                                               object:nil];
192
    [[NSNotificationCenter defaultCenter] addObserver:self 
193
                                             selector:@selector(pithosAccountNodeChildrenUpdated:) 
194
                                                 name:@"PithosAccountNodeChildrenUpdated" 
195
                                               object:nil];
196
    [[NSNotificationCenter defaultCenter] addObserver:self 
197
                                             selector:@selector(resetContainers:) 
198
                                                 name:@"PithosAuthenticationCredentialsUpdated" 
199
                                               object:nil];
200
}
201

    
202
#pragma mark -
203
#pragma Observers
204

    
205
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
206
    PithosNode *node = (PithosNode *)[notification object];
207
    NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
208
    NSInteger lastColumn = [browser lastColumn];
209
    for (NSInteger column = lastColumn; column >= 0; column--) {
210
        if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
211
            [browser reloadColumn:column];
212
            // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
213
            // Furthermore it caused problems on delete, because a non-existing parent was asked for
214
            //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
215
            //    // This reloads the preview column
216
            //    [browser setLastColumn:column];
217
            //    [browser addColumn];
218
            //}
219
            return;
220
        }
221
    }
222
}
223

    
224
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
225
    BOOL containerPithosFound = NO;
226
    BOOL containerTrashFound = NO;
227
    NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
228
    for (PithosContainerNode *containerNode in accountNode.children) {
229
        if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
230
            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
231
            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
232
            containerPithosFound = YES;
233
        } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
234
            containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];            
235
            NSUInteger insertIndex = 1;
236
            if (!containerPithosFound)
237
                insertIndex = 0;
238
            [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
239
            containerTrashFound = YES;
240
        } else {
241
            [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
242
        }
243
    }
244
    BOOL refreshAccountNode = NO;
245
    if (!containerPithosFound) {
246
        // create pithos
247
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
248
        [containerRequest startSynchronous];
249
        if ([containerRequest error]) {
250
            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
251
        } else {
252
            refreshAccountNode = YES;
253
        }
254
    }
255
    if (!containerTrashFound) {
256
        // create trash
257
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
258
        [containerRequest startSynchronous];
259
        if ([containerRequest error]) {
260
            [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
261
        } else {
262
            refreshAccountNode = YES;
263
        }
264
    }
265
    if (refreshAccountNode) {
266
        [accountNode invalidateChildren];
267
        accountNode.children;
268
    } else {
269
        [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
270
        self.outlineViewDataSourceArray = outlineViewDataSourceArray;
271
        
272
        // Expand the folder outline view
273
        [outlineView expandItem:nil expandChildren:YES];
274
        [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
275
        
276
        [self refresh:nil];
277
    }
278
}
279

    
280
#pragma mark -
281
#pragma Actions
282

    
283
- (IBAction)refresh:(id)sender {
284
    for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
285
        [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
286
    }
287
    [browser validateVisibleColumns];
288
}
289

    
290
#pragma mark -
291
#pragma NSBrowserDelegate
292

    
293
- (id)rootItemForBrowser:(NSBrowser *)browser {
294
    return rootNode;    
295
}
296

    
297
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
298
    PithosNode *node = (PithosNode *)item;
299
    return node.children.count;
300
}
301

    
302
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
303
    PithosNode *node = (PithosNode *)item;
304
    return [node.children objectAtIndex:index];
305
}
306

    
307
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
308
    PithosNode *node = (PithosNode *)item;
309
    return node.isLeafItem;
310
}
311

    
312
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
313
    PithosNode *node = (PithosNode *)item;
314
    return node;
315
}
316

    
317
- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
318
    if (sharedPreviewController == nil)
319
        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
320
    return sharedPreviewController;
321
}
322

    
323
//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
324
//    if (!forUserResize) {
325
//        id item = [browser parentForItemsInColumn:columnIndex]; 
326
//        if ([self browser:browser isLeafItem:item]) {
327
//            suggestedWidth = 200; 
328
//        }
329
//    }
330
//    return suggestedWidth;
331
//}
332

    
333
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
334
    return NO;
335
}
336

    
337
#pragma mark Drag and Drop source
338

    
339
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
340
   toPasteboard:(NSPasteboard *)pasteboard {
341
    NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
342
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
343
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
344
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
345
        [propertyList addObject:[node.pithosObject.name pathExtension]];
346
    }];
347

    
348
    [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
349
    [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
350
    
351
    return YES;
352
}
353

    
354
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
355
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
356
    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
357
    NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
358
    [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
359
        PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
360
        
361
        // If the node is a subdir ask if the whole tree should be downloaded
362
        if ([node class] == [PithosSubdirNode class]) {
363
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
364
            [alert setMessageText:@"Download directory"];
365
            [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
366
            [alert addButtonWithTitle:@"OK"];
367
            [alert addButtonWithTitle:@"Cancel"];
368
            NSInteger choice = [alert runModal];
369
            if (choice == NSAlertFirstButtonReturn) {
370
                NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
371
                                                                                                 objectName:node.pithosObject.name 
372
                                                                                                toDirectory:[dropDestination path] 
373
                                                                                              checkIfExists:YES];
374
                if (objectRequests) {
375
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
376
                        [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
377
                        objectRequest.delegate = self;
378
                        objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
379
                        objectRequest.didFailSelector = @selector(downloadObjectFailed:);
380
                        [objectRequest startAsynchronous];
381
                    }
382
                }
383
            }
384
        } else {
385
            ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
386
                                                                                                 objectName:node.pithosObject.name 
387
                                                                                                toDirectory:[dropDestination path] 
388
                                                                                              checkIfExists:YES];
389
            if (objectRequest) {
390
                [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
391
                objectRequest.delegate = self;
392
                objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
393
                objectRequest.didFailSelector = @selector(downloadObjectFailed:);
394
                [objectRequest startAsynchronous];
395
            }
396
        }
397
    }];
398
    return names;
399
}
400

    
401
#pragma mark Drag and Drop destination
402

    
403
- (NSDragOperation)browser:aBrowser 
404
              validateDrop:(id<NSDraggingInfo>)info 
405
               proposedRow:(NSInteger *)row 
406
                    column:(NSInteger *)column 
407
             dropOperation:(NSBrowserDropOperation *)dropOperation {
408
    NSDragOperation result = NSDragOperationNone;
409
    // Files from the finder are accepted
410
    if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
411
        // For a between drop, we let the user drop "on" the parent item
412
        if (*dropOperation == NSBrowserDropAbove)
413
            *row = -1;
414
        // Only allow dropping in folders
415
        if (*column != -1) {
416
            if (*row != -1) {
417
                PithosNode *node = [browser itemAtRow:*row inColumn:*column];
418
                if ([node class] != [PithosSubdirNode class])
419
                    *row = -1;
420
            }
421
            *dropOperation = NSBrowserDropOn;
422
            result = NSDragOperationCopy;
423
        }
424
    }
425
    // XXX else local file promises
426
    return result;
427
}
428

    
429
- (BOOL)browser:(NSBrowser *)aBrowser 
430
     acceptDrop:(id<NSDraggingInfo>)info 
431
          atRow:(NSInteger)row 
432
         column:(NSInteger)column 
433
  dropOperation:(NSBrowserDropOperation)dropOperation {
434
    NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
435
    NSLog(@"drag in filenames: %@", filenames);
436
    PithosNode *node = nil;
437
    if ((column != -1) && (filenames != nil)) {
438
        if (row != -1)
439
            node = [browser itemAtRow:row inColumn:column];
440
        else
441
            node = [browser parentForItemsInColumn:column];
442
        NSLog(@"drag in node: %@", node.url);
443
        if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
444
            return NO;
445
        
446
        NSFileManager *defaultManager = [NSFileManager defaultManager];
447
        NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
448
        NSString *objectNamePrefix;
449
        if ([node class] == [PithosSubdirNode class])
450
            objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
451
        else
452
            objectNamePrefix = [NSString stringWithString:@""];
453
        NSUInteger blockSize = node.pithosContainer.blockSize;
454
        NSString *blockHash = node.pithosContainer.blockHash;
455
        
456
        for (NSString *filePath in filenames) {
457
            BOOL isDirectory;
458
            if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
459
                if (!isDirectory) {
460
                    // Upload file
461
                    NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
462
                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
463
                    dispatch_async(queue, ^{
464
                        NSError *error = nil;
465
                        NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
466
                        if (contentType == nil)
467
                            contentType = @"application/octet-stream";
468
                        if (error)
469
                            NSLog(@"contentType detection error: %@", error);
470
                        NSArray *hashes = nil;
471
                        ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
472
                                                                                                                  objectName:objectName 
473
                                                                                                                 contentType:contentType 
474
                                                                                                                   blockSize:blockSize 
475
                                                                                                                   blockHash:blockHash 
476
                                                                                                                     forFile:filePath 
477
                                                                                                               checkIfExists:YES 
478
                                                                                                                      hashes:&hashes];
479
                        if (objectRequest) {
480
                            objectRequest.delegate = self;
481
                            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
482
                            objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
483
                            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
484
                                                      containerName, @"containerName", 
485
                                                      objectName, @"objectName", 
486
                                                      contentType, @"contentType", 
487
                                                      [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
488
                                                      blockHash, @"blockHash", 
489
                                                      filePath, @"filePath", 
490
                                                      hashes, @"hashes", 
491
                                                      node, @"node", 
492
                                                      [NSNumber numberWithUnsignedInteger:10], @"iteration", 
493
                                                      nil];
494
                            [objectRequest startAsynchronous];
495
                        }
496
                    });
497
                } else {
498
                    // Upload directory, confirm first
499
                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
500
                    [alert setMessageText:@"Upload directory"];
501
                    [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
502
                    [alert addButtonWithTitle:@"OK"];
503
                    [alert addButtonWithTitle:@"Cancel"];
504
                    NSInteger choice = [alert runModal];
505
                    if (choice == NSAlertFirstButtonReturn) {
506
                        NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
507
                        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
508
                        dispatch_async(queue, ^{
509
                            NSMutableArray *objectNames = nil;
510
                            NSMutableArray *contentTypes = nil;
511
                            NSMutableArray *filePaths = nil;
512
                            NSMutableArray *hashesArrays = nil;
513
                            NSMutableArray *directoryObjectRequests = nil;
514
                            NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
515
                                                                                                         objectName:objectName 
516
                                                                                                          blockSize:blockSize 
517
                                                                                                          blockHash:blockHash 
518
                                                                                                       forDirectory:filePath 
519
                                                                                                      checkIfExists:YES 
520
                                                                                                        objectNames:&objectNames
521
                                                                                                       contentTypes:&contentTypes
522
                                                                                                          filePaths:&filePaths
523
                                                                                                         hashesArrays:&hashesArrays 
524
                                                                                            directoryObjectRequests:&directoryObjectRequests];
525
                            for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
526
                                objectRequest.delegate = self;
527
                                objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
528
                                objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
529
                                [objectRequest startAsynchronous];
530
                            }
531
                            if (objectRequests) {
532
                                for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
533
                                    ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
534
                                    objectRequest.delegate = self;
535
                                    objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
536
                                    objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
537
                                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
538
                                                              containerName, @"containerName", 
539
                                                              [objectNames objectAtIndex:i], @"objectName", 
540
                                                              [contentTypes objectAtIndex:i], @"contentType", 
541
                                                              [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
542
                                                              blockHash, @"blockHash", 
543
                                                              [filePaths objectAtIndex:i], @"filePath", 
544
                                                              [hashesArrays objectAtIndex:i], @"hashes", 
545
                                                              [NSNull null], @"node", 
546
                                                              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
547
                                                              nil];
548
                                    [objectRequest startAsynchronous];
549
                                }
550
                            }
551
                        });
552
                    }
553
                }
554
            }
555
            
556
        }
557
        return YES;
558
    }
559

    
560
    return NO;
561
}
562

    
563
#pragma mark -
564
#pragma mark ASIHTTPRequestDelegate
565

    
566
- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
567
    NSLog(@"Download completed: %@", [objectRequest url]);
568
    if (objectRequest.responseStatusCode == 200) {
569
        if ([objectRequest contentLength] == 0) {
570
            NSLog(@"Downloaded  0 bytes");
571
            NSFileManager *defaultManager = [NSFileManager defaultManager];
572
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
573
            if (![defaultManager fileExistsAtPath:filePath]) {
574
                if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
575
                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
576
                    [alert setMessageText:@"Create File Error"];
577
                    [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
578
                    [alert addButtonWithTitle:@"OK"];
579
                    [alert runModal];
580
                }
581
            }
582
        }
583
    } else {
584
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
585
    }
586
}
587

    
588
- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
589
    NSLog(@"Download failed");
590
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
591
}
592

    
593
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
594
    NSLog(@"Upload directory object completed: %@", [objectRequest url]);
595
    if (objectRequest.responseStatusCode == 201) {
596
        NSLog(@"Directory object created: %@", [objectRequest url]);
597
        [self refresh:nil];
598
    } else {
599
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
600
    }
601
}
602

    
603
- (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
604
    NSLog(@"Upload directory object failed");
605
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
606
}
607

    
608
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
609
    NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
610
    if (objectRequest.responseStatusCode == 201) {
611
        NSLog(@"Object created: %@", [objectRequest url]);
612
        PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
613
        if (node != (id)[NSNull null]) {
614
            [node.parent invalidateChildren];
615
            node.parent.children;
616
        } else {
617
            [self refresh:nil];
618
        }
619
    } else if (objectRequest.responseStatusCode == 409) {
620
        NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
621
        if (iteration == 0) {
622
            NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
623
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
624
            [alert setMessageText:@"Upload Timeout"];
625
            [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
626
                                       [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
627
            [alert addButtonWithTitle:@"OK"];
628
            [alert runModal];
629
            return;
630
        }
631
        NSLog(@"object is missing hashes: %@", [objectRequest url]);
632
        NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
633
                                                      withMissingHashesResponse:[objectRequest responseString]];
634
        NSUInteger missingBlockIndex = [missingBlocks firstIndex];
635
        ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
636
                                                                                                      objectName:@".upload" 
637
                                                                                                       blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
638
                                                                                                         forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
639
                                                                                               missingBlockIndex:missingBlockIndex];
640
        newObjectRequest.delegate = self;
641
        newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
642
        newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
643
        newObjectRequest.userInfo = objectRequest.userInfo;
644
        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
645
        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
646
        [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
647
        [newObjectRequest startAsynchronous];
648
    } else {
649
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
650
    }
651
}
652

    
653
- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
654
    NSLog(@"Upload using hashmap failed");
655
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
656
}
657

    
658
- (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
659
    NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
660
    if (objectRequest.responseStatusCode == 201) {
661
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
662
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
663
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
664
        if (missingBlockIndex == NSNotFound) {
665
            NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
666
            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
667
                                                                                                         objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
668
                                                                                                        contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
669
                                                                                                          blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
670
                                                                                                          blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
671
                                                                                                            forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
672
                                                                                                      checkIfExists:NO 
673
                                                                                                             hashes:&hashes];
674
            newObjectRequest.delegate = self;
675
            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
676
            newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
677
            newObjectRequest.userInfo = objectRequest.userInfo;
678
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
679
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
680
            [newObjectRequest startAsynchronous];
681
        } else {
682
            ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
683
                                                                                                          objectName:@".upload" 
684
                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
685
                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
686
                                                                                                   missingBlockIndex:missingBlockIndex];
687
            newObjectRequest.delegate = self;
688
            newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
689
            newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
690
            newObjectRequest.userInfo = objectRequest.userInfo;
691
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
692
            [newObjectRequest startAsynchronous];
693
        }
694
    } else {
695
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
696
    }
697
}
698

    
699
- (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
700
    NSLog(@"Upload of missing block failed");
701
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
702
}
703

    
704
#pragma mark -
705
#pragma mark NSSplitViewDelegate
706

    
707
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
708
    return 120;
709
}
710

    
711
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
712
    return 220;
713
}
714

    
715
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
716
    if (proposedPosition < 120)
717
        return 120;
718
    else if (proposedPosition > 220)
719
        return 220;
720
    else
721
        return proposedPosition;
722
}
723

    
724
#pragma mark -
725
#pragma mark NSOutlineViewDelegate
726

    
727
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
728
    return ([[item representedObject] isLeaf]);
729
}
730

    
731
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
732
	return (![[item representedObject] isLeaf]);
733
}
734

    
735
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
736
    PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
737
    if (node) {
738
        rootNode = node;
739
        [browser loadColumnZero];
740
        [self refresh:nil];                                  
741
    }
742
}
743

    
744
#pragma mark -
745
#pragma mark NSMenuDelegate
746

    
747
- (void)menuNeedsUpdate:(NSMenu *)menu {
748
    NSInteger column = [browser clickedColumn];
749
    NSInteger row = [browser clickedRow];
750
    [menu removeAllItems];
751
    NSMenuItem *menuItem;
752
    if ((column == -1) || (row == -1)) {
753
        // General context menu
754
    } else {
755
        NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
756
        NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
757
        if (![menuNodesIndexPaths containsObject:clickedNodeIndexPath])
758
            menuNodesIndexPaths = [NSArray arrayWithObject:clickedNodeIndexPath];
759
        
760
        // Move to Trash (pithos container only)
761
        // Delete
762
        if ([rootNode class] == [PithosContainerNode class]) {
763
            if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
764
                menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
765
                [menuItem setRepresentedObject:menuNodesIndexPaths];
766
                [menu addItem:menuItem];
767
            }
768
            menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
769
            [menuItem setRepresentedObject:menuNodesIndexPaths];
770
            [menu addItem:menuItem];
771
            [menu addItem:[NSMenuItem separatorItem]];
772
        }
773
        // Get Info
774
        menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
775
        [menuItem setRepresentedObject:menuNodesIndexPaths];
776
        [menu addItem:menuItem];
777
    }
778
}
779

    
780
#pragma mark -
781
#pragma mark Menu Actions
782

    
783
- (void)getInfo:(NSMenuItem *)sender {
784
    for (NSIndexPath *nodeIndexPath in ((NSArray *)[sender representedObject])) {
785
        PithosNode *node = [browser itemAtIndexPath:nodeIndexPath];
786
        [node showPithosNodeInfo:sender];
787
    }
788
}
789

    
790
- (void)deleteObject:(NSMenuItem *)sender {
791
    for (NSIndexPath *nodeIndexPath in ((NSArray *)[sender representedObject])) {
792
        PithosNode *node = [browser itemAtIndexPath:nodeIndexPath];
793
        if (([node class] == [PithosObjectNode class]) || 
794
            (([node class] == [PithosSubdirNode class]) && 
795
             !node.pithosObject.subdir &&
796
             [node.pithosObject.name hasSuffix:@"/"])) {
797
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
798
                                                                                                      objectName:node.pithosObject.name];
799
            objectRequest.delegate = self;
800
            objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
801
            objectRequest.didFailSelector = @selector(deleteObjectFailed:);
802
            [objectRequest startAsynchronous];
803
        } else if ([node class] == [PithosSubdirNode class]) {
804
            NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
805
                                                                                               objectName:node.pithosObject.name];
806
            if (objectRequests) {
807
                for (ASIPithosObjectRequest *objectRequest in objectRequests) {
808
                    objectRequest.delegate = self;
809
                    objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
810
                    objectRequest.didFailSelector = @selector(deleteObjectFailed:);
811
                    [objectRequest startAsynchronous];
812
                }
813
            }
814
            
815
        }
816
    }
817
}
818

    
819
- (void)moveToTrash:(NSMenuItem *)sender {
820
    for (NSIndexPath *nodeIndexPath in ((NSArray *)[sender representedObject])) {
821
        PithosNode *node = [browser itemAtIndexPath:nodeIndexPath];
822
        if (([node class] == [PithosObjectNode class]) || 
823
            (([node class] == [PithosSubdirNode class]) && 
824
             !node.pithosObject.subdir &&
825
             [node.pithosObject.name hasSuffix:@"/"])) {
826
            NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
827
                                                                                objectName:node.pithosObject.name];
828
            if (safeObjectName) {
829
                ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:node.pithosContainer.name 
830
                                                                                                            objectName:node.pithosObject.name 
831
                                                                                                           contentType:nil 
832
                                                                                                       contentEncoding:nil 
833
                                                                                                    contentDisposition:nil 
834
                                                                                                              manifest:nil 
835
                                                                                                               sharing:nil 
836
                                                                                                              isPublic:ASIPithosObjectRequestPublicIgnore 
837
                                                                                                              metadata:nil 
838
                                                                                              destinationContainerName:@"trash" 
839
                                                                                                 destinationObjectName:safeObjectName];
840
                objectRequest.delegate = self;
841
                objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
842
                objectRequest.didFailSelector = @selector(moveToTrashFailed:);
843
                [objectRequest startAsynchronous];
844
            }
845
        } else if ([node class] == [PithosSubdirNode class]) {
846
            NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
847
                                                                                subdirName:node.pithosObject.name];
848
            if (safeObjectName) {
849
                NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
850
                                                                                                 objectName:node.pithosObject.name 
851
                                                                                   destinationContainerName:@"trash" 
852
                                                                                      destinationObjectName:safeObjectName];
853
                if (objectRequests) {
854
                    for (ASIPithosObjectRequest *objectRequest in objectRequests) {
855
                        objectRequest.delegate = self;
856
                        objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
857
                        objectRequest.didFailSelector = @selector(moveToTrashFailed:);
858
                        [objectRequest startAsynchronous];
859
                    }
860
                }
861
            }        
862
        }
863
    }
864
}
865

    
866
#pragma mark -
867
#pragma mark Menu Actions ASIHTTPRequestDelegate
868

    
869
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
870
    if (objectRequest.responseStatusCode == 204) {
871
        NSLog(@"Object deleted: %@", [objectRequest url]);
872
        [self refresh:nil];
873
    } else {
874
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
875
    }
876
}
877

    
878
- (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
879
    NSLog(@"Delete of object failed");
880
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
881
}
882

    
883
- (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
884
    if (objectRequest.responseStatusCode == 201) {
885
        NSLog(@"Object moved: %@", [objectRequest url]);
886
        [self refresh:nil];
887
    } else {
888
        [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
889
    }
890
}
891

    
892
- (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
893
    NSLog(@"Move of object failed");
894
    [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
895
}
896

    
897

    
898
@end