Refactored PithosNodeInfoController.
[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 : 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                 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
371                 dispatch_async(queue, ^{
372                     NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name 
373                                                                                                      objectName:node.pithosObject.name 
374                                                                                                     toDirectory:[dropDestination path] 
375                                                                                                   checkIfExists:YES];
376                     if (objectRequests) {
377                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
378                             [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
379                             objectRequest.delegate = self;
380                             objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
381                             objectRequest.didFailSelector = @selector(downloadObjectFailed:);
382                             [objectRequest startAsynchronous];
383                         }
384                     }
385                 });
386             }
387         } else {
388             ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name 
389                                                                                                  objectName:node.pithosObject.name 
390                                                                                                 toDirectory:[dropDestination path] 
391                                                                                               checkIfExists:YES];
392             if (objectRequest) {
393                 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
394                 objectRequest.delegate = self;
395                 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
396                 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
397                 [objectRequest startAsynchronous];
398             }
399         }
400     }];
401     return names;
402 }
403
404 #pragma mark Drag and Drop destination
405
406 - (NSDragOperation)browser:aBrowser 
407               validateDrop:(id<NSDraggingInfo>)info 
408                proposedRow:(NSInteger *)row 
409                     column:(NSInteger *)column 
410              dropOperation:(NSBrowserDropOperation *)dropOperation {
411     NSDragOperation result = NSDragOperationNone;
412     // Files from the finder are accepted
413     if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
414         // For a between drop, we let the user drop "on" the parent item
415         if (*dropOperation == NSBrowserDropAbove)
416             *row = -1;
417         // Only allow dropping in folders
418         if (*column != -1) {
419             if (*row != -1) {
420                 PithosNode *node = [browser itemAtRow:*row inColumn:*column];
421                 if ([node class] != [PithosSubdirNode class])
422                     *row = -1;
423             }
424             *dropOperation = NSBrowserDropOn;
425             result = NSDragOperationCopy;
426         }
427     }
428     // XXX else local file promises
429     return result;
430 }
431
432 - (BOOL)browser:(NSBrowser *)aBrowser 
433      acceptDrop:(id<NSDraggingInfo>)info 
434           atRow:(NSInteger)row 
435          column:(NSInteger)column 
436   dropOperation:(NSBrowserDropOperation)dropOperation {
437     NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
438     NSLog(@"drag in filenames: %@", filenames);
439     PithosNode *node = nil;
440     if ((column != -1) && (filenames != nil)) {
441         if (row != -1)
442             node = [browser itemAtRow:row inColumn:column];
443         else
444             node = [browser parentForItemsInColumn:column];
445         NSLog(@"drag in node: %@", node.url);
446         if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
447             return NO;
448         
449         NSFileManager *defaultManager = [NSFileManager defaultManager];
450         NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
451         NSString *objectNamePrefix;
452         if ([node class] == [PithosSubdirNode class])
453             objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
454         else
455             objectNamePrefix = [NSString stringWithString:@""];
456         NSUInteger blockSize = node.pithosContainer.blockSize;
457         NSString *blockHash = node.pithosContainer.blockHash;
458         
459         for (NSString *filePath in filenames) {
460             BOOL isDirectory;
461             if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
462                 if (!isDirectory) {
463                     // Upload file
464                     NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
465                     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
466                     dispatch_async(queue, ^{
467                         NSError *error = nil;
468                         NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
469                         if (contentType == nil)
470                             contentType = @"application/octet-stream";
471                         if (error)
472                             NSLog(@"contentType detection error: %@", error);
473                         NSArray *hashes = nil;
474                         ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName 
475                                                                                                                   objectName:objectName 
476                                                                                                                  contentType:contentType 
477                                                                                                                    blockSize:blockSize 
478                                                                                                                    blockHash:blockHash 
479                                                                                                                      forFile:filePath 
480                                                                                                                checkIfExists:YES 
481                                                                                                                       hashes:&hashes];
482                         if (objectRequest) {
483                             objectRequest.delegate = self;
484                             objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
485                             objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
486                             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
487                                                       containerName, @"containerName", 
488                                                       objectName, @"objectName", 
489                                                       contentType, @"contentType", 
490                                                       [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
491                                                       blockHash, @"blockHash", 
492                                                       filePath, @"filePath", 
493                                                       hashes, @"hashes", 
494                                                       node, @"node", 
495                                                       [NSNumber numberWithUnsignedInteger:10], @"iteration", 
496                                                       nil];
497                             [objectRequest startAsynchronous];
498                         }
499                     });
500                 } else {
501                     // Upload directory, confirm first
502                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
503                     [alert setMessageText:@"Upload directory"];
504                     [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
505                     [alert addButtonWithTitle:@"OK"];
506                     [alert addButtonWithTitle:@"Cancel"];
507                     NSInteger choice = [alert runModal];
508                     if (choice == NSAlertFirstButtonReturn) {
509                         NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
510                         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
511                         dispatch_async(queue, ^{
512                             NSMutableArray *objectNames = nil;
513                             NSMutableArray *contentTypes = nil;
514                             NSMutableArray *filePaths = nil;
515                             NSMutableArray *hashesArrays = nil;
516                             NSMutableArray *directoryObjectRequests = nil;
517                             NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName 
518                                                                                                          objectName:objectName 
519                                                                                                           blockSize:blockSize 
520                                                                                                           blockHash:blockHash 
521                                                                                                        forDirectory:filePath 
522                                                                                                       checkIfExists:YES 
523                                                                                                         objectNames:&objectNames
524                                                                                                        contentTypes:&contentTypes
525                                                                                                           filePaths:&filePaths
526                                                                                                          hashesArrays:&hashesArrays 
527                                                                                             directoryObjectRequests:&directoryObjectRequests];
528                             for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
529                                 objectRequest.delegate = self;
530                                 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
531                                 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
532                                 [objectRequest startAsynchronous];
533                             }
534                             if (objectRequests) {
535                                 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
536                                     ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
537                                     objectRequest.delegate = self;
538                                     objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
539                                     objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
540                                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
541                                                               containerName, @"containerName", 
542                                                               [objectNames objectAtIndex:i], @"objectName", 
543                                                               [contentTypes objectAtIndex:i], @"contentType", 
544                                                               [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", 
545                                                               blockHash, @"blockHash", 
546                                                               [filePaths objectAtIndex:i], @"filePath", 
547                                                               [hashesArrays objectAtIndex:i], @"hashes", 
548                                                               [NSNull null], @"node", 
549                                                               [NSNumber numberWithUnsignedInteger:10], @"iteration", 
550                                                               nil];
551                                     [objectRequest startAsynchronous];
552                                 }
553                             }
554                         });
555                     }
556                 }
557             }
558             
559         }
560         return YES;
561     }
562
563     return NO;
564 }
565
566 #pragma mark -
567 #pragma mark ASIHTTPRequestDelegate
568
569 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
570     NSLog(@"Download completed: %@", [objectRequest url]);
571     if (objectRequest.responseStatusCode == 200) {
572         if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
573             NSLog(@"Downloaded  0 bytes");
574             NSFileManager *defaultManager = [NSFileManager defaultManager];
575             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
576             if (![defaultManager fileExistsAtPath:filePath]) {
577                 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
578                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
579                     [alert setMessageText:@"Create File Error"];
580                     [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
581                     [alert addButtonWithTitle:@"OK"];
582                     [alert runModal];
583                 }
584             }
585         }
586     } else {
587         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
588     }
589 }
590
591 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
592     NSLog(@"Download failed");
593     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
594 }
595
596 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
597     NSLog(@"Upload directory object completed: %@", [objectRequest url]);
598     if (objectRequest.responseStatusCode == 201) {
599         NSLog(@"Directory object created: %@", [objectRequest url]);
600         [self refresh:nil];
601     } else {
602         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
603     }
604 }
605
606 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
607     NSLog(@"Upload directory object failed");
608     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
609 }
610
611 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
612     NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
613     if (objectRequest.responseStatusCode == 201) {
614         NSLog(@"Object created: %@", [objectRequest url]);
615         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
616         if (node != (id)[NSNull null]) {
617             [node.parent invalidateChildren];
618             node.parent.children;
619         } else {
620             [self refresh:nil];
621         }
622     } else if (objectRequest.responseStatusCode == 409) {
623         NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
624         if (iteration == 0) {
625             NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
626             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
627             [alert setMessageText:@"Upload Timeout"];
628             [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", 
629                                        [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
630             [alert addButtonWithTitle:@"OK"];
631             [alert runModal];
632             return;
633         }
634         NSLog(@"object is missing hashes: %@", [objectRequest url]);
635         NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
636                                                       withMissingHashesResponse:[objectRequest responseString]];
637         NSUInteger missingBlockIndex = [missingBlocks firstIndex];
638         ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
639                                                                                                       objectName:@".upload" 
640                                                                                                        blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
641                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
642                                                                                                missingBlockIndex:missingBlockIndex];
643         newObjectRequest.delegate = self;
644         newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
645         newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
646         newObjectRequest.userInfo = objectRequest.userInfo;
647         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
648         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
649         [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
650         [newObjectRequest startAsynchronous];
651     } else {
652         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
653     }
654 }
655
656 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
657     NSLog(@"Upload using hashmap failed");
658     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
659 }
660
661 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
662     NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
663     if (objectRequest.responseStatusCode == 201) {
664         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
665         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
666         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
667         if (missingBlockIndex == NSNotFound) {
668             NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
669             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] 
670                                                                                                          objectName:[objectRequest.userInfo objectForKey:@"objectName"] 
671                                                                                                         contentType:[objectRequest.userInfo objectForKey:@"contentType"] 
672                                                                                                           blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] 
673                                                                                                           blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
674                                                                                                             forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
675                                                                                                       checkIfExists:NO 
676                                                                                                              hashes:&hashes];
677             newObjectRequest.delegate = self;
678             newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
679             newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
680             newObjectRequest.userInfo = objectRequest.userInfo;
681             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
682             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
683             [newObjectRequest startAsynchronous];
684         } else {
685             ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
686                                                                                                           objectName:@".upload" 
687                                                                                                            blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
688                                                                                                              forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
689                                                                                                    missingBlockIndex:missingBlockIndex];
690             newObjectRequest.delegate = self;
691             newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
692             newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
693             newObjectRequest.userInfo = objectRequest.userInfo;
694             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
695             [newObjectRequest startAsynchronous];
696         }
697     } else {
698         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
699     }
700 }
701
702 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
703     NSLog(@"Upload of missing block failed");
704     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
705 }
706
707 #pragma mark -
708 #pragma mark NSSplitViewDelegate
709
710 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
711     return 120;
712 }
713
714 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
715     return 220;
716 }
717
718 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
719     if (proposedPosition < 120)
720         return 120;
721     else if (proposedPosition > 220)
722         return 220;
723     else
724         return proposedPosition;
725 }
726
727 #pragma mark -
728 #pragma mark NSOutlineViewDelegate
729
730 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
731     return ([[item representedObject] isLeaf]);
732 }
733
734 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
735         return (![[item representedObject] isLeaf]);
736 }
737
738 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
739     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
740     if (node) {
741         rootNode = node;
742         [browser loadColumnZero];
743         [self refresh:nil];                                  
744     }
745 }
746
747 #pragma mark -
748 #pragma mark NSMenuDelegate
749
750 - (void)menuNeedsUpdate:(NSMenu *)menu {
751     NSInteger column = [browser clickedColumn];
752     NSInteger row = [browser clickedRow];
753     [menu removeAllItems];
754     NSMenuItem *menuItem;
755     if ((column == -1) || (row == -1)) {
756         // General context menu
757         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
758         PithosNode *menuNode;
759         if ([menuNodesIndexPaths count] == 0) {
760             menuNode = [browser parentForItemsInColumn:0];
761         } else if (([menuNodesIndexPaths count] != 1) || 
762             ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
763             menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
764         } else {
765             menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
766         }
767         NSArray *menuNodes = [NSArray arrayWithObject:menuNode];
768         // New Folder
769         menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease];
770         [menuItem setRepresentedObject:menuNodes];
771         [menu addItem:menuItem];
772         [menu addItem:[NSMenuItem separatorItem]];
773         // Get Info
774         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
775         [menuItem setRepresentedObject:menuNodes];
776         [menu addItem:menuItem];
777     } else {
778         // Node context menu
779         NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
780         NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
781         NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
782         if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
783             for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
784                 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
785             }
786         } else {
787             [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
788         }
789         // Move to Trash (pithos container only)
790         // Delete
791         if ([rootNode class] == [PithosContainerNode class]) {
792             if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
793                 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
794                 [menuItem setRepresentedObject:menuNodes];
795                 [menu addItem:menuItem];
796             }
797             menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
798             [menuItem setRepresentedObject:menuNodes];
799             [menu addItem:menuItem];
800             [menu addItem:[NSMenuItem separatorItem]];
801         }
802         // Get Info
803         menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
804         [menuItem setRepresentedObject:menuNodes];
805         [menu addItem:menuItem];
806     }
807 }
808
809 #pragma mark -
810 #pragma mark Menu Actions
811
812 - (void)newFolder:(NSMenuItem *)sender {
813     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
814         if ([node class] == [PithosContainerNode class]) {
815             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
816             dispatch_async(queue, ^{
817                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
818                                                                                     subdirName:@"untitled folder"];
819                 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
820                                                                                                              objectName:safeObjectName 
821                                                                                                                eTag:nil 
822                                                                                                         contentType:@"application/directory" 
823                                                                                                     contentEncoding:nil 
824                                                                                                  contentDisposition:nil 
825                                                                                                            manifest:nil 
826                                                                                                             sharing:nil 
827                                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
828                                                                                                            metadata:nil 
829                                                                                                                data:[NSData data]];
830                 objectRequest.delegate = self;
831                 objectRequest.didFinishSelector = @selector(newFolderFinished:);
832                 objectRequest.didFailSelector = @selector(newFolderFailed:);
833                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
834                                           node, @"node", 
835                                           nil];
836                 [objectRequest startAsynchronous];
837             });
838         } else if (([node class] == [PithosSubdirNode class]) && 
839                    (node.pithosObject.subdir || 
840                     ![node.pithosObject.name hasSuffix:@"/"])) {
841             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
842             dispatch_async(queue, ^{
843                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name 
844                                                                                    subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
845                 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name 
846                                                                                                             objectName:safeObjectName 
847                                                                                                                   eTag:nil 
848                                                                                                            contentType:@"application/directory" 
849                                                                                                        contentEncoding:nil 
850                                                                                                     contentDisposition:nil 
851                                                                                                               manifest:nil 
852                                                                                                                sharing:nil 
853                                                                                                               isPublic:ASIPithosObjectRequestPublicIgnore 
854                                                                                                               metadata:nil 
855                                                                                                                   data:[NSData data]];
856                 objectRequest.delegate = self;
857                 objectRequest.didFinishSelector = @selector(newFolderFinished:);
858                 objectRequest.didFailSelector = @selector(newFolderFailed:);
859                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
860                                          node, @"node", 
861                                          nil];
862                 [objectRequest startAsynchronous];
863             });
864         }
865     }
866 }
867
868 - (void)getInfo:(NSMenuItem *)sender {
869     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
870         [node showPithosNodeInfo:sender];
871     }
872 }
873
874 - (void)deleteObject:(NSMenuItem *)sender {
875     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
876         if (([node class] == [PithosObjectNode class]) || 
877             (([node class] == [PithosSubdirNode class]) && 
878              !node.pithosObject.subdir &&
879              [node.pithosObject.name hasSuffix:@"/"])) {
880             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
881                                                                                                       objectName:node.pithosObject.name];
882             objectRequest.delegate = self;
883             objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
884             objectRequest.didFailSelector = @selector(deleteObjectFailed:);
885             [objectRequest startAsynchronous];
886         } else if ([node class] == [PithosSubdirNode class]) {
887             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
888             dispatch_async(queue, ^{
889                 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
890                                                                                                    objectName:node.pithosObject.name];
891                 if (objectRequests) {
892                     for (ASIPithosObjectRequest *objectRequest in objectRequests) {
893                         objectRequest.delegate = self;
894                         objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
895                         objectRequest.didFailSelector = @selector(deleteObjectFailed:);
896                         [objectRequest startAsynchronous];
897                     }
898                 }
899             });
900         }
901     }
902 }
903
904 - (void)moveToTrash:(NSMenuItem *)sender {
905     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
906         if (([node class] == [PithosObjectNode class]) || 
907             (([node class] == [PithosSubdirNode class]) && 
908              !node.pithosObject.subdir &&
909              [node.pithosObject.name hasSuffix:@"/"])) {
910             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
911             dispatch_async(queue, ^{
912                 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" 
913                                                                                     objectName:node.pithosObject.name];
914                 if (safeObjectName) {
915                     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:node.pithosContainer.name 
916                                                                                                                 objectName:node.pithosObject.name 
917                                                                                                                contentType:nil 
918                                                                                                            contentEncoding:nil 
919                                                                                                         contentDisposition:nil 
920                                                                                                                   manifest:nil 
921                                                                                                                    sharing:nil 
922                                                                                                                   isPublic:ASIPithosObjectRequestPublicIgnore 
923                                                                                                                   metadata:nil 
924                                                                                                   destinationContainerName:@"trash" 
925                                                                                                      destinationObjectName:safeObjectName];
926                     objectRequest.delegate = self;
927                     objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
928                     objectRequest.didFailSelector = @selector(moveToTrashFailed:);
929                     [objectRequest startAsynchronous];
930                 }
931             });
932         } else if ([node class] == [PithosSubdirNode class]) {
933             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
934             dispatch_async(queue, ^{
935                 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" 
936                                                                                     subdirName:node.pithosObject.name];
937                 if (safeObjectName) {
938                     NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name 
939                                                                                                      objectName:node.pithosObject.name 
940                                                                                        destinationContainerName:@"trash" 
941                                                                                           destinationObjectName:safeObjectName];
942                     if (objectRequests) {
943                         for (ASIPithosObjectRequest *objectRequest in objectRequests) {
944                             objectRequest.delegate = self;
945                             objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
946                             objectRequest.didFailSelector = @selector(moveToTrashFailed:);
947                             [objectRequest startAsynchronous];
948                         }
949                     }
950                 }
951             });
952         }
953     }
954 }
955
956 #pragma mark -
957 #pragma mark Menu Actions ASIHTTPRequestDelegate
958
959 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
960     if (objectRequest.responseStatusCode == 201) {
961         NSLog(@"New application/directory object created: %@", [objectRequest url]);
962         PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
963         [node invalidateChildren];
964         node.children;
965     } else {
966         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
967     }
968 }
969
970 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
971     NSLog(@"Creation of new application/directory object failed");
972     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
973 }
974
975 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
976     if (objectRequest.responseStatusCode == 204) {
977         NSLog(@"Object deleted: %@", [objectRequest url]);
978         [self refresh:nil];
979     } else {
980         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
981     }
982 }
983
984 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
985     NSLog(@"Delete of object failed");
986     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
987 }
988
989 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
990     if (objectRequest.responseStatusCode == 201) {
991         NSLog(@"Object moved: %@", [objectRequest url]);
992         [self refresh:nil];
993     } else {
994         [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
995     }
996 }
997
998 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
999     NSLog(@"Move of object failed");
1000     [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1001 }
1002
1003
1004 @end