2 // PithosBrowserController.m
5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
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"
54 //@interface PithosBrowserCell : NSBrowserCell {}
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
58 @implementation PithosBrowserCell
61 if ((self = [super init])) {
62 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
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];
76 [super setObjectValue:object];
82 @interface PithosOutlineViewCell : ImageAndTextCell {}
85 @implementation PithosOutlineViewCell
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];
94 [super setObjectValue:object];
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;
111 @implementation PithosBrowserController
112 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
115 #pragma Object Lifecycle
118 return [super initWithWindowNibName:@"PithosBrowserController"];
122 [[NSNotificationCenter defaultCenter] removeObserver:self];
123 [browserMenu release];
124 [sharedPreviewController release];
125 [outlineViewDataSourceArray release];
126 [accountNode release];
131 - (void)awakeFromNib {
132 [super awakeFromNib];
134 [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
135 [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
136 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
138 [browser setCellClass:[PithosBrowserCell class]];
140 browserMenu = [[NSMenu alloc] init];
141 [browserMenu setDelegate:self];
142 [browser setMenu:browserMenu];
145 - (void)resetContainers {
147 [browser loadColumnZero];
148 self.outlineViewDataSourceArray = nil;
150 // Create the outlineView tree
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)]
160 // // CONTAINERS/trash
161 // [[containersTreeNode mutableChildNodes] addObject:
162 // [NSTreeNode treeNodeWithRepresentedObject:
163 // [[[PithosContainerNode alloc] initWithContainerName:@"trash"
164 // icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
167 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
168 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
170 [[sharedTreeNode mutableChildNodes] addObject:
171 [NSTreeNode treeNodeWithRepresentedObject:
172 [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
173 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
175 // SHARED/others shared
176 [[sharedTreeNode mutableChildNodes] addObject:
177 [NSTreeNode treeNodeWithRepresentedObject:
178 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
179 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
182 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
184 // Expand the folder outline view
185 [outlineView expandItem:nil expandChildren:YES];
186 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
188 // Create accountNode and trigger a refresh
189 accountNode = [[PithosAccountNode alloc] init];
190 accountNode.children;
193 - (void)windowDidLoad {
194 [super windowDidLoad];
196 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
198 // Register for updates
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(pithosNodeChildrenUpdated:)
201 name:@"PithosContainerNodeChildrenUpdated"
203 [[NSNotificationCenter defaultCenter] addObserver:self
204 selector:@selector(pithosNodeChildrenUpdated:)
205 name:@"PithosSubdirNodeChildrenUpdated"
207 [[NSNotificationCenter defaultCenter] addObserver:self
208 selector:@selector(pithosAccountNodeChildrenUpdated:)
209 name:@"PithosAccountNodeChildrenUpdated"
211 [[NSNotificationCenter defaultCenter] addObserver:self
212 selector:@selector(resetContainers)
213 name:@"PithosAuthenticationCredentialsUpdated"
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];
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)
251 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
252 containerTrashFound = YES;
254 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
257 BOOL refreshAccountNode = NO;
258 if (!containerPithosFound) {
260 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
261 [containerRequest startSynchronous];
262 if ([containerRequest error]) {
263 NSLog(@"error:%@", [containerRequest error]);
264 // XXX do something on error
266 refreshAccountNode = YES;
269 if (!containerTrashFound) {
271 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
272 [containerRequest startSynchronous];
273 if ([containerRequest error]) {
274 NSLog(@"error:%@", [containerRequest error]);
275 // XXX do something on error
277 refreshAccountNode = YES;
280 if (refreshAccountNode) {
281 [accountNode invalidateChildren];
282 accountNode.children;
284 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
285 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
287 // Expand the folder outline view
288 [outlineView expandItem:nil expandChildren:YES];
289 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
298 - (IBAction)refresh:(id)sender {
299 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
300 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
302 [browser validateVisibleColumns];
306 #pragma NSBrowserDelegate
308 - (id)rootItemForBrowser:(NSBrowser *)browser {
312 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
313 PithosNode *node = (PithosNode *)item;
314 return node.children.count;
317 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
318 PithosNode *node = (PithosNode *)item;
319 return [node.children objectAtIndex:index];
322 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
323 PithosNode *node = (PithosNode *)item;
324 return node.isLeafItem;
327 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
328 PithosNode *node = (PithosNode *)item;
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;
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;
345 // return suggestedWidth;
348 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
352 #pragma mark Drag and Drop source
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]];
363 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
364 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
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]];
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]
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];
400 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
401 objectName:node.pithosObject.name
402 toDirectory:[dropDestination path]
405 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
406 objectRequest.delegate = self;
407 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
408 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
409 [objectRequest startAsynchronous];
416 #pragma mark Drag and Drop destination
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)
429 // Only allow dropping in folders
432 PithosNode *node = [browser itemAtRow:*row inColumn:*column];
433 if ([node class] != [PithosSubdirNode class])
436 *dropOperation = NSBrowserDropOn;
437 result = NSDragOperationCopy;
440 // XXX else local file promises
444 - (BOOL)browser:(NSBrowser *)aBrowser
445 acceptDrop:(id<NSDraggingInfo>)info
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)) {
454 node = [browser itemAtRow:row inColumn:column];
456 node = [browser parentForItemsInColumn:column];
457 NSLog(@"drag in node: %@", node.url);
458 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
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];
467 objectNamePrefix = [NSString stringWithString:@""];
468 NSUInteger blockSize = node.pithosContainer.blockSize;
469 NSString *blockHash = node.pithosContainer.blockHash;
471 for (NSString *filePath in filenames) {
473 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
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";
484 NSLog(@"contentType detection error: %@", error);
485 NSArray *hashes = nil;
486 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
487 objectName:objectName
488 contentType:contentType
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",
507 [NSNumber numberWithUnsignedInteger:10], @"iteration",
509 [objectRequest startAsynchronous];
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
532 forDirectory:filePath
534 objectNames:&objectNames
535 contentTypes:&contentTypes
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",
556 [objectRequest startAsynchronous];
572 #pragma mark ASIHTTPRequestDelegate
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"];
592 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
593 NSLog(@"Download failed");
594 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
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];
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"];
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];
636 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
640 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
641 NSLog(@"Upload using hashmap failed");
642 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
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"]
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];
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];
682 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
686 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
687 NSLog(@"Upload of missing block failed");
688 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
692 #pragma mark NSSplitViewDelegate
694 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
698 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
703 #pragma mark NSOutlineViewDelegate
705 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
706 return ([[item representedObject] isLeaf]);
709 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
710 return (![[item representedObject] isLeaf]);
713 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
714 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
717 [browser loadColumnZero];
722 #pragma mark NSMenuDelegate
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
731 // PithosNode menu has 1 items
733 NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
734 [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
735 [menu addItem:menuItem];
740 #pragma mark Menu Actions
742 - (void)getInfo:(NSMenuItem *)sender {
743 [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];