Fix bug introduced by previous fix.
[pithos-macos] / pithos-macos / PithosBrowserController.m
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 : NSBrowserCell {}
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
56 @end
57
58 @implementation PithosBrowserCell
59
60 - (id)init {
61     if ((self = [super init])) {
62         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
63     }
64     return self;
65 }
66
67 - (void)setObjectValue:(id)object {
68     if ([object isKindOfClass:[PithosNode class]]) {
69         PithosNode *node = (PithosNode *)object;
70         [self setStringValue:node.displayName];
71         [self setImage:node.icon];
72 //        // All cells are set as leafs because a branchingImage is already set!
73 //        // Maybe this cell is already inside an NSBrowserCell
74 //        [self setLeaf:YES];
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;
102 - (void)getInfo:(NSMenuItem *)sender;
103 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
104 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
105 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
106 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
107 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
108 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
109 @end
110
111 @implementation PithosBrowserController
112 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
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     [browserMenu release];
124     [sharedPreviewController release];
125     [outlineViewDataSourceArray release];
126     [accountNode release];
127     [rootNode release];
128     [super dealloc];
129 }
130
131 - (void)awakeFromNib {
132     [super awakeFromNib];
133     
134     [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
135     [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
136     [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
137     
138     [browser setCellClass:[PithosBrowserCell class]];
139     
140     browserMenu = [[NSMenu alloc] init];
141     [browserMenu setDelegate:self];
142     [browser setMenu:browserMenu];
143 }
144
145 - (void)resetContainers {
146     rootNode = nil;
147     [browser loadColumnZero];
148     self.outlineViewDataSourceArray = nil;
149     
150     // Create the outlineView tree
151     // CONTAINERS
152         NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
153                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
154 //    // CONTAINERS/pithos
155 //      [[containersTreeNode mutableChildNodes] addObject:
156 //     [NSTreeNode treeNodeWithRepresentedObject:
157 //      [[[PithosContainerNode alloc] initWithContainerName:@"pithos" 
158 //                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
159 //        ] autorelease]]];
160 //    // CONTAINERS/trash
161 //      [[containersTreeNode mutableChildNodes] addObject:
162 //     [NSTreeNode treeNodeWithRepresentedObject:
163 //      [[[PithosContainerNode alloc] initWithContainerName:@"trash"
164 //                                                     icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
165 //        ] autorelease]]];
166     // SHARED
167         NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
168                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
169     // SHARED/my shared
170         [[sharedTreeNode mutableChildNodes] addObject:
171      [NSTreeNode treeNodeWithRepresentedObject:
172       [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
173                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
174         ] autorelease]]];
175     // SHARED/others shared
176         [[sharedTreeNode mutableChildNodes] addObject:
177      [NSTreeNode treeNodeWithRepresentedObject:
178       [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
179                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
180         ] autorelease]]];
181     
182     self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
183     
184         // Expand the folder outline view
185     [outlineView expandItem:nil expandChildren:YES];
186         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
187     
188     // Create accountNode and trigger a refresh
189     accountNode = [[PithosAccountNode alloc] init];
190     accountNode.children;
191 }
192
193 - (void)windowDidLoad {
194     [super windowDidLoad];
195     
196     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
197     
198     // Register for updates
199     [[NSNotificationCenter defaultCenter] addObserver:self 
200                                              selector:@selector(pithosNodeChildrenUpdated:) 
201                                                  name:@"PithosContainerNodeChildrenUpdated" 
202                                                object:nil];
203     [[NSNotificationCenter defaultCenter] addObserver:self 
204                                              selector:@selector(pithosNodeChildrenUpdated:) 
205                                                  name:@"PithosSubdirNodeChildrenUpdated" 
206                                                object:nil];
207     [[NSNotificationCenter defaultCenter] addObserver:self 
208                                              selector:@selector(pithosAccountNodeChildrenUpdated:) 
209                                                  name:@"PithosAccountNodeChildrenUpdated" 
210                                                object:nil];
211     [[NSNotificationCenter defaultCenter] addObserver:self 
212                                              selector:@selector(resetContainers) 
213                                                  name:@"PithosAuthenticationCredentialsUpdated" 
214                                                object:nil];
215 }
216
217 #pragma mark -
218 #pragma Observers
219
220 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
221     PithosNode *node = (PithosNode *)[notification object];
222     NSInteger lastColumn = [browser lastColumn];
223     for (NSInteger column = lastColumn; column >= 0; column--) {
224         if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
225             [browser reloadColumn:column];
226             if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
227                 // This reloads the preview column
228                 [browser setLastColumn:column];
229                 [browser addColumn];
230             }
231             return;
232         }
233     }
234 }
235
236 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
237     BOOL containerPithosFound = NO;
238     BOOL containerTrashFound = NO;
239     //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
240     NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
241     for (PithosContainerNode *containerNode in accountNode.children) {
242         if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
243             containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
244             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
245             containerPithosFound = YES;
246         } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
247             containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];            
248             NSUInteger insertIndex = 1;
249             if (!containerPithosFound)
250                 insertIndex = 0;
251             [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
252             containerTrashFound = YES;
253         } else {
254             [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
255         }
256     }
257     BOOL refreshAccountNode = NO;
258     if (!containerPithosFound) {
259         // create pithos
260         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
261         [containerRequest startSynchronous];
262         if ([containerRequest error]) {
263             NSLog(@"error:%@", [containerRequest error]);
264             // XXX do something on error
265         } else {
266             refreshAccountNode = YES;
267         }
268     }
269     if (!containerTrashFound) {
270         // create trash
271         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
272         [containerRequest startSynchronous];
273         if ([containerRequest error]) {
274             NSLog(@"error:%@", [containerRequest error]);
275             // XXX do something on error
276         } else {
277             refreshAccountNode = YES;
278         }
279     }
280     if (refreshAccountNode) {
281         [accountNode invalidateChildren];
282         accountNode.children;
283     } else {
284         [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
285         self.outlineViewDataSourceArray = outlineViewDataSourceArray;
286         
287         // Expand the folder outline view
288         [outlineView expandItem:nil expandChildren:YES];
289         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
290         
291         [self refresh:nil];
292     }
293 }
294
295 #pragma mark -
296 #pragma Actions
297
298 - (IBAction)refresh:(id)sender {
299     for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
300         [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
301     }
302     [browser validateVisibleColumns];
303 }
304
305 #pragma mark -
306 #pragma NSBrowserDelegate
307
308 - (id)rootItemForBrowser:(NSBrowser *)browser {
309     return rootNode;    
310 }
311
312 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
313     PithosNode *node = (PithosNode *)item;
314     return node.children.count;
315 }
316
317 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
318     PithosNode *node = (PithosNode *)item;
319     return [node.children objectAtIndex:index];
320 }
321
322 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
323     PithosNode *node = (PithosNode *)item;
324     return node.isLeafItem;
325 }
326
327 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
328     PithosNode *node = (PithosNode *)item;
329     return node;
330 }
331
332 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
333     if (sharedPreviewController == nil)
334         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
335     return sharedPreviewController;
336 }
337
338 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
339 //    if (!forUserResize) {
340 //        id item = [browser parentForItemsInColumn:columnIndex]; 
341 //        if ([self browser:browser isLeafItem:item]) {
342 //            suggestedWidth = 200; 
343 //        }
344 //    }
345 //    return suggestedWidth;
346 //}
347
348 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
349     return NO;
350 }
351
352 #pragma mark Drag and Drop source
353
354 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column 
355    toPasteboard:(NSPasteboard *)pasteboard {
356     NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
357     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; 
358     for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
359         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
360         [propertyList addObject:[node.pithosObject.name pathExtension]];
361     }
362
363     [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
364     [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
365     
366     return YES;
367 }
368
369 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 
370 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
371     NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
372     NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; 
373     for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
374         PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
375         
376         // If the node is a subdir ask if the whole tree should be downloaded
377         if ([node class] == [PithosSubdirNode class]) {
378             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
379             [alert setMessageText:@"Download directory"];
380             [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
381             [alert addButtonWithTitle:@"OK"];
382             [alert addButtonWithTitle:@"Cancel"];
383             NSInteger choice = [alert runModal];
384             if (choice == NSAlertFirstButtonReturn) {
385                 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
386                                                                                                  objectName:node.pithosObject.name 
387                                                                                                 toDirectory:[dropDestination path] 
388                                                                                               checkIfExists:YES];
389                 if (objectRequests) {
390                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
391                         [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
392                         objectRequest.delegate = self;
393                         objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
394                         objectRequest.didFailSelector = @selector(downloadObjectFailed:);
395                         [objectRequest startAsynchronous];
396                     }
397                 }
398             }
399         } else {
400             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
401                                                                                                  objectName:node.pithosObject.name 
402                                                                                                 toDirectory:[dropDestination path] 
403                                                                                               checkIfExists:YES];
404             if (objectRequest) {
405                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
406                 objectRequest.delegate = self;
407                 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
408                 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
409                 [objectRequest startAsynchronous];
410             }
411         }
412     }
413     return names;
414 }
415
416 #pragma mark Drag and Drop destination
417
418 - (NSDragOperation)browser:aBrowser 
419               validateDrop:(id<NSDraggingInfo>)info 
420                proposedRow:(NSInteger *)row 
421                     column:(NSInteger *)column 
422              dropOperation:(NSBrowserDropOperation *)dropOperation {
423     NSDragOperation result = NSDragOperationNone;
424     // Files from the finder are accepted
425     if ([[[info draggingPasteboard] types] indexOfObject:NSFilenamesPboardType] != -1) {
426         // For a between drop, we let the user drop "on" the parent item
427         if (*dropOperation == NSBrowserDropAbove)
428             *row = -1;
429         // Only allow dropping in folders
430         if (*column != -1) {
431             if (*row != -1) {
432                 PithosNode *node = [browser itemAtRow:*row inColumn:*column];
433                 if ([node class] != [PithosSubdirNode class])
434                     *row = -1;
435             }
436             *dropOperation = NSBrowserDropOn;
437             result = NSDragOperationCopy;
438         }
439     }
440     // XXX else local file promises
441     return result;
442 }
443
444 - (BOOL)browser:(NSBrowser *)aBrowser 
445      acceptDrop:(id<NSDraggingInfo>)info 
446           atRow:(NSInteger)row 
447          column:(NSInteger)column 
448   dropOperation:(NSBrowserDropOperation)dropOperation {
449     NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
450     NSLog(@"drag in filenames: %@", filenames);
451     PithosNode *node = nil;
452     if ((column != -1) && (filenames != nil)) {
453         if (row != -1)
454             node = [browser itemAtRow:row inColumn:column];
455         else
456             node = [browser parentForItemsInColumn:column];
457         NSLog(@"drag in node: %@", node.url);
458         if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
459             return NO;
460         
461         NSFileManager *defaultManager = [NSFileManager defaultManager];
462         NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
463         NSString *objectNamePrefix;
464         if ([node class] == [PithosSubdirNode class])
465             objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
466         else
467             objectNamePrefix = [NSString stringWithString:@""];
468         NSUInteger blockSize = node.pithosContainer.blockSize;
469         NSString *blockHash = node.pithosContainer.blockHash;
470         
471         for (NSString *filePath in filenames) {
472             BOOL isDirectory;
473             if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
474                 if (!isDirectory) {
475                     // Upload file
476                     NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
477                     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
478                     dispatch_async(queue, ^{
479                         NSError *error = nil;
480                         NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
481                         if (contentType == nil)
482                             contentType = @"application/octet-stream";
483                         if (error)
484                             NSLog(@"contentType detection error: %@", error);
485                         NSArray *hashes = nil;
486                         ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
487                                                                                                                   objectName:objectName 
488                                                                                                                  contentType:contentType 
489                                                                                                                    blockSize:blockSize 
490                                                                                                                    blockHash:blockHash 
491                                                                                                                      forFile:filePath 
492                                                                                                                checkIfExists:YES 
493                                                                                                                       hashes:&hashes];
494                         if (objectRequest) {
495                             objectRequest.delegate = self;
496                             objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
497                             objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
498                             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
499                                                       containerName, @"containerName", 
500                                                       objectName, @"objectName", 
501                                                       contentType, @"contentType", 
502                                                       [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
503                                                       blockHash, @"blockHash", 
504                                                       filePath, @"filePath", 
505                                                       hashes, @"hashes", 
506                                                       node, @"node", 
507                                                       [NSNumber numberWithUnsignedInteger:10], @"iteration", 
508                                                       nil];
509                             [objectRequest startAsynchronous];
510                         }
511                     });
512                 } else {
513                     // Upload directory, confirm first
514                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
515                     [alert setMessageText:@"Upload directory"];
516                     [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
517                     [alert addButtonWithTitle:@"OK"];
518                     [alert addButtonWithTitle:@"Cancel"];
519                     NSInteger choice = [alert runModal];
520                     if (choice == NSAlertFirstButtonReturn) {
521                         NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
522                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
523                         dispatch_async(queue, ^{
524                             NSMutableArray *objectNames = nil;
525                             NSMutableArray *contentTypes = nil;
526                             NSMutableArray *filePaths = nil;
527                             NSMutableArray *hashesArrays = nil;
528                             NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
529                                                                                                          objectName:objectName 
530                                                                                                           blockSize:blockSize 
531                                                                                                           blockHash:blockHash 
532                                                                                                        forDirectory:filePath 
533                                                                                                       checkIfExists:YES 
534                                                                                                         objectNames:&objectNames
535                                                                                                        contentTypes:&contentTypes
536                                                                                                           filePaths:&filePaths
537                                                                                                          hashesArrays:&hashesArrays];
538                             if (objectRequests) {
539                                 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
540                                     ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
541                                     // XXX if dir creation requests differentiate
542                                     objectRequest.delegate = self;
543                                     objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
544                                     objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
545                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
546                                                               containerName, @"containerName", 
547                                                               [objectNames objectAtIndex:i], @"objectName", 
548                                                               [contentTypes objectAtIndex:i], @"contentType", 
549                                                               [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
550                                                               blockHash, @"blockHash", 
551                                                               [filePaths objectAtIndex:i], @"filePath", 
552                                                               [hashesArrays objectAtIndex:i], @"hashes", 
553                                                               [NSNull null], @"node", 
554                                                               [NSNumber numberWithUnsignedInteger:10], @"iteration", 
555                                                               nil];
556                                     [objectRequest startAsynchronous];
557                                 }
558                             }
559                         });
560                     }
561                 }
562             }
563             
564         }
565         return YES;
566     }
567
568     return NO;
569 }
570
571 #pragma mark -
572 #pragma mark ASIHTTPRequestDelegate
573
574 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
575     NSLog(@"Download completed: %@", [objectRequest url]);
576     if ([objectRequest bytes] == 0) {
577         NSLog(@"Downloaded  0 bytes");
578         NSFileManager *defaultManager = [NSFileManager defaultManager];
579         NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
580         if (![defaultManager fileExistsAtPath:filePath]) {
581             if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
582                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
583                 [alert setMessageText:@"Create File Error"];
584                 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
585                 [alert addButtonWithTitle:@"OK"];
586                 [alert runModal];
587             }
588         }
589     }
590 }
591
592 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
593     NSLog(@"Download failed");
594     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
595 }
596
597 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
598     NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
599     if (objectRequest.responseStatusCode == 201) {
600         NSLog(@"Object created: %@", [objectRequest url]);
601         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
602         if (node != (id)[NSNull null]) {
603             [node invalidateChildren];
604             node.children;
605         } // XXX else total refresh?
606     } else if (objectRequest.responseStatusCode == 409) {
607         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
608         if (iteration == 0) {
609             NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
610             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
611             [alert setMessageText:@"Upload Timeout"];
612             [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
613                                        [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
614             [alert addButtonWithTitle:@"OK"];
615             [alert runModal];
616             return;
617         }
618         NSLog(@"object is missing hashes: %@", [objectRequest url]);
619         NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
620                                                       withMissingHashesResponse:[objectRequest responseString]];
621         NSUInteger missingBlockIndex = [missingBlocks firstIndex];
622         ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
623                                                                                                       objectName:@".upload" 
624                                                                                                        blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
625                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
626                                                                                                missingBlockIndex:missingBlockIndex];
627         newObjectRequest.delegate = self;
628         newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
629         newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
630         newObjectRequest.userInfo = objectRequest.userInfo;
631         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
632         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
633         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
634         [newObjectRequest startAsynchronous];
635     } else {
636         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
637     }
638 }
639
640 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
641     NSLog(@"Upload using hashmap failed");
642     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
643 }
644
645 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
646     NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
647     if (objectRequest.responseStatusCode == 201) {
648         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
649         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
650         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
651         if (missingBlockIndex == NSNotFound) {
652             NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
653             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
654                                                                                                          objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
655                                                                                                         contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
656                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
657                                                                                                           blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
658                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
659                                                                                                       checkIfExists:NO 
660                                                                                                              hashes:&hashes];
661             newObjectRequest.delegate = self;
662             newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
663             newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
664             newObjectRequest.userInfo = objectRequest.userInfo;
665             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
666             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
667             [newObjectRequest startAsynchronous];
668         } else {
669             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
670                                                                                                           objectName:@".upload" 
671                                                                                                            blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
672                                                                                                              forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
673                                                                                                    missingBlockIndex:missingBlockIndex];
674             newObjectRequest.delegate = self;
675             newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
676             newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
677             newObjectRequest.userInfo = objectRequest.userInfo;
678             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
679             [newObjectRequest startAsynchronous];
680         }
681     } else {
682         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
683     }
684 }
685
686 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
687     NSLog(@"Upload of missing block failed");
688     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
689 }
690
691 #pragma mark -
692 #pragma mark NSSplitViewDelegate
693
694 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
695     return 120;
696 }
697
698 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
699     return 220;
700 }
701
702 #pragma mark -
703 #pragma mark NSOutlineViewDelegate
704
705 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
706     return ([[item representedObject] isLeaf]);
707 }
708
709 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
710         return (![[item representedObject] isLeaf]);
711 }
712
713 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
714     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
715     if (node) {
716         rootNode = node;
717         [browser loadColumnZero];
718     }
719 }
720
721 #pragma mark -
722 #pragma mark NSMenuDelegate
723
724 - (void)menuNeedsUpdate:(NSMenu *)menu {
725     NSInteger column = [browser clickedColumn];
726     NSInteger row = [browser clickedRow];
727     [menu removeAllItems];
728     if ((column == -1) || (row == -1)) {
729         // General context menu has 0
730     } else {
731         // PithosNode menu has 1 items
732         // Get Info
733         NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
734         [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
735         [menu addItem:menuItem];
736     }
737 }
738
739 #pragma mark -
740 #pragma mark Menu Actions
741
742 - (void)getInfo:(NSMenuItem *)sender {
743     [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];
744 }
745
746 @end