X-Git-Url: https://code.grnet.gr/git/pithos-macos/blobdiff_plain/9f3a92de6d51dba59ba6079d612274bca2fa951a..d7c03491c4cbedea26402107da3097617db0f36e:/pithos-macos/PithosBrowserController.m diff --git a/pithos-macos/PithosBrowserController.m b/pithos-macos/PithosBrowserController.m index f5d2738..64916ba 100644 --- a/pithos-macos/PithosBrowserController.m +++ b/pithos-macos/PithosBrowserController.m @@ -1,4 +1,4 @@ - // +// // PithosBrowserController.m // pithos-macos // @@ -36,12 +36,20 @@ // or implied, of GRNET S.A. #import "PithosBrowserController.h" -#import "ASIPithosRequest.h" +#import "PithosNode.h" #import "PithosAccountNode.h" #import "PithosContainerNode.h" +#import "PithosSubdirNode.h" +#import "PithosObjectNode.h" #import "PithosEmptyNode.h" #import "ImageAndTextCell.h" #import "FileSystemBrowserCell.h" +#import "ASIPithosRequest.h" +#import "ASIPithosContainerRequest.h" +#import "ASIPithosObjectRequest.h" +#import "ASIPithosContainer.h" +#import "ASIPithosObject.h" +#import "PithosFileUtilities.h" //@interface PithosBrowserCell : NSBrowserCell {} @interface PithosBrowserCell : FileSystemBrowserCell {} @@ -90,14 +98,18 @@ @end @interface PithosBrowserController (Private) {} -- (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken; - (void)resetContainers; +- (void)getInfo:(NSMenuItem *)sender; +- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest; +- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest; +- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest; +- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest; +- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest; +- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest; @end @implementation PithosBrowserController -@synthesize userDefaultsController, outlineViewDataSourceArray, splitView, outlineView, browser; -@synthesize authenticationPanel, authenticationUserTextField, authenticationTokenTextField, authenticationRenewCheckBox, - authenticationCancelPushButton, authenticationManualPushButton, authenticationLoginPushButton; +@synthesize outlineViewDataSourceArray, splitView, outlineView, browser; #pragma mark - #pragma Object Lifecycle @@ -108,25 +120,26 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; + [browserMenu release]; [sharedPreviewController release]; [outlineViewDataSourceArray release]; + [accountNode release]; [rootNode release]; - [authenticationLoginPushButton release]; - [authenticationManualPushButton release]; - [authenticationCancelPushButton release]; - [authenticationRenewCheckBox release]; - [authenticationTokenTextField release]; - [authenticationUserTextField release]; - [authenticationPanel release]; - [browser release]; - [splitView release]; - [outlineView release]; - [userDefaultsController release]; [super dealloc]; } - (void)awakeFromNib { + [super awakeFromNib]; + + [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; + [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES]; + [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; + [browser setCellClass:[PithosBrowserCell class]]; + + browserMenu = [[NSMenu alloc] init]; + [browserMenu setDelegate:self]; + [browser setMenu:browserMenu]; } - (void)resetContainers { @@ -135,119 +148,158 @@ self.outlineViewDataSourceArray = nil; // Create the outlineView tree - NSTreeNode *treeNode = [NSTreeNode treeNodeWithRepresentedObject: + // CONTAINERS + NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject: [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]]; - [[treeNode mutableChildNodes] addObject: +// // CONTAINERS/pithos +// [[containersTreeNode mutableChildNodes] addObject: +// [NSTreeNode treeNodeWithRepresentedObject: +// [[[PithosContainerNode alloc] initWithContainerName:@"pithos" +// icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] +// ] autorelease]]]; +// // CONTAINERS/trash +// [[containersTreeNode mutableChildNodes] addObject: +// [NSTreeNode treeNodeWithRepresentedObject: +// [[[PithosContainerNode alloc] initWithContainerName:@"trash" +// icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] +// ] autorelease]]]; + // SHARED + NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject: + [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]]; + // SHARED/my shared + [[sharedTreeNode mutableChildNodes] addObject: [NSTreeNode treeNodeWithRepresentedObject: - [[[PithosContainerNode alloc] initWithContainerName:@"pithos"] autorelease]]]; - [[treeNode mutableChildNodes] addObject: + [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" + icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)] + ] autorelease]]]; + // SHARED/others shared + [[sharedTreeNode mutableChildNodes] addObject: [NSTreeNode treeNodeWithRepresentedObject: - [[[PithosContainerNode alloc] initWithContainerName:@"trash"] autorelease]]]; + [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared" + icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)] + ] autorelease]]]; - self.outlineViewDataSourceArray = [NSMutableArray arrayWithObject:treeNode]; + self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil]; // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; + + // Create accountNode and trigger a refresh + accountNode = [[PithosAccountNode alloc] init]; + accountNode.children; } - (void)windowDidLoad { -// [userDefaultsController setAppliesImmediately:NO]; + [super windowDidLoad]; [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]]; // Register for updates [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosNodeChildrenUpdated:) - name:@"PithosNodeChildrenUpdated" + name:@"PithosContainerNodeChildrenUpdated" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(pithosNodeChildrenUpdated:) + name:@"PithosSubdirNodeChildrenUpdated" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(pithosAccountNodeChildrenUpdated:) + name:@"PithosAccountNodeChildrenUpdated" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(resetContainers) + name:@"PithosAuthenticationCredentialsUpdated" object:nil]; - - [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]]; } #pragma mark - #pragma Observers - (void)pithosNodeChildrenUpdated:(NSNotification *)notification { - if ([[browser parentForItemsInColumn:[browser lastColumn]] isEqualTo:[notification object]]) - [browser reloadColumn:[browser lastColumn]]; -} - -#pragma mark - -#pragma Actions - -- (IBAction)refresh:(id)sender { - [[browser parentForItemsInColumn:[browser lastColumn]] invalidateChildren]; - [browser reloadColumn:[browser lastColumn]]; -} - -#pragma mark - -#pragma Authentication - -- (void)authenticateFromURLWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken { - if ([authUser length] && [authToken length]) { - [authenticationUserTextField setStringValue:authUser]; - [authenticationTokenTextField setStringValue:authToken]; - [userDefaultsController save:self]; - [self authenticateWithAuthUser:authUser authToken:authToken]; + PithosNode *node = (PithosNode *)[notification object]; + NSInteger lastColumn = [browser lastColumn]; + for (NSInteger column = lastColumn; column >= 0; column--) { + if ([[browser parentForItemsInColumn:column] isEqualTo:node]) { + [browser reloadColumn:column]; + if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) { + // This reloads the preview column + [browser setLastColumn:column]; + [browser addColumn]; + } + return; + } } - // XXX else maybe an error message? } -- (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken { - // XXX hardcoded for now - NSString *storageURLPrefix = @"https://pithos.dev.grnet.gr/v1/"; - - NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken); - if ([authUser length] && [authToken length]) { - //if (authUser && ([authUser length] > 0) && authToken && ([authToken length] > 0)) { - [ASIPithosRequest setStorageURL:[storageURLPrefix stringByAppendingString:authUser]]; - [ASIPithosRequest setAuthToken:authToken]; - [self resetContainers]; +- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification { + BOOL containerPithosFound = NO; + BOOL containerTrashFound = NO; + //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes]; + NSMutableArray *containersTreeNodeChildren = [NSMutableArray array]; + for (PithosContainerNode *containerNode in accountNode.children) { + if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) { + containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]; + [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0]; + containerPithosFound = YES; + } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) { + containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]; + NSUInteger insertIndex = 1; + if (!containerPithosFound) + insertIndex = 0; + [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex]; + containerTrashFound = YES; + } else { + [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]]; + } + } + BOOL refreshAccountNode = NO; + if (!containerPithosFound) { + // create pithos + ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"]; + [containerRequest startSynchronous]; + if ([containerRequest error]) { + NSLog(@"error:%@", [containerRequest error]); + // XXX do something on error + } else { + refreshAccountNode = YES; + } + } + if (!containerTrashFound) { + // create trash + ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"]; + [containerRequest startSynchronous]; + if ([containerRequest error]) { + NSLog(@"error:%@", [containerRequest error]); + // XXX do something on error + } else { + refreshAccountNode = YES; + } + } + if (refreshAccountNode) { + [accountNode invalidateChildren]; + accountNode.children; } else { - [self authenticationSelect:nil]; + [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren]; + self.outlineViewDataSourceArray = outlineViewDataSourceArray; + + // Expand the folder outline view + [outlineView expandItem:nil expandChildren:YES]; + [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; + + [self refresh:nil]; } } -- (IBAction)authenticationSelect:(id)sender { - [self.window makeFirstResponder:nil]; - [NSApp beginSheet:authenticationPanel - modalForWindow:self.window - modalDelegate:self - didEndSelector:NULL - contextInfo:nil]; -} - -- (IBAction)authenticationLogin:(id)sender { - [NSApp endSheet:authenticationPanel]; - [authenticationPanel orderOut:self]; - // XXX hardcoded for now - NSProcessInfo *processInfo = [NSProcessInfo processInfo]; - NSString *loginURL = [NSString stringWithFormat:@"https://pithos.dev.grnet.gr/login?next=pithos://%@_%d", - [processInfo processName], [processInfo processIdentifier]]; - if ([authenticationRenewCheckBox state] == NSOnState) - loginURL = [loginURL stringByAppendingString:@"&renew="]; - NSLog(@"loginURL: %@", loginURL); - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]]; - // XXX Should we wait for results or do something else? - // XXX check the case where this happens for the first time - // XXX maybe don't remove the Panel, and let the handler do it -} - -- (IBAction)authenticationCancel:(id)sender { - [NSApp endSheet:authenticationPanel]; - [authenticationPanel orderOut:self]; - [userDefaultsController revert:sender]; -} +#pragma mark - +#pragma Actions -- (IBAction)authenticationManual:(id)sender { - [NSApp endSheet:authenticationPanel]; - [authenticationPanel orderOut:self]; - [userDefaultsController save:sender]; - // Because of delayed saves of the userDefaultsController, we use the TextField values directly, instead of - //NSString *authUser = [[userDefaultsController values] valueForKey:@"authUser"]; - //NSString *authToken = [[userDefaultsController values] valueForKey:@"authToken"]; - [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]]; +- (IBAction)refresh:(id)sender { + for (NSInteger column = [browser lastColumn]; column >= 0; column--) { + [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren]; + } + [browser validateVisibleColumns]; } #pragma mark - @@ -283,16 +335,325 @@ return sharedPreviewController; } +//- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth { +// if (!forUserResize) { +// id item = [browser parentForItemsInColumn:columnIndex]; +// if ([self browser:browser isLeafItem:item]) { +// suggestedWidth = 200; +// } +// } +// return suggestedWidth; +//} + +- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column { + return NO; +} + +#pragma mark Drag and Drop source + +- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column + toPasteboard:(NSPasteboard *)pasteboard { + NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; + NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; + for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) { + PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]]; + [propertyList addObject:[node.pithosObject.name pathExtension]]; + } + + [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self]; + [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType]; + + return YES; +} + +- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination +forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { + NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; + NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; + for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) { + PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]]; + + // If the node is a subdir ask if the whole tree should be downloaded + if ([node class] == [PithosSubdirNode class]) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"Download directory"]; + [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]]; + [alert addButtonWithTitle:@"OK"]; + [alert addButtonWithTitle:@"Cancel"]; + NSInteger choice = [alert runModal]; + if (choice == NSAlertFirstButtonReturn) { + NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name + objectName:node.pithosObject.name + toDirectory:[dropDestination path] + checkIfExists:YES]; + if (objectRequests) { + for (ASIPithosObjectRequest *objectRequest in objectRequests) { + [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]]; + objectRequest.delegate = self; + objectRequest.didFinishSelector = @selector(downloadObjectFinished:); + objectRequest.didFailSelector = @selector(downloadObjectFailed:); + [objectRequest startAsynchronous]; + } + } + } + } else { + ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name + objectName:node.pithosObject.name + toDirectory:[dropDestination path] + checkIfExists:YES]; + if (objectRequest) { + [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]]; + objectRequest.delegate = self; + objectRequest.didFinishSelector = @selector(downloadObjectFinished:); + objectRequest.didFailSelector = @selector(downloadObjectFailed:); + [objectRequest startAsynchronous]; + } + } + } + return names; +} + +#pragma mark Drag and Drop destination + +- (NSDragOperation)browser:aBrowser + validateDrop:(id)info + proposedRow:(NSInteger *)row + column:(NSInteger *)column + dropOperation:(NSBrowserDropOperation *)dropOperation { + NSDragOperation result = NSDragOperationNone; + // Files from the finder are accepted + if ([[[info draggingPasteboard] types] indexOfObject:NSFilenamesPboardType] != -1) { + // For a between drop, we let the user drop "on" the parent item + if (*dropOperation == NSBrowserDropAbove) + *row = -1; + // Only allow dropping in folders + if (*column != -1) { + if (*row != -1) { + PithosNode *node = [browser itemAtRow:*row inColumn:*column]; + if ([node class] != [PithosSubdirNode class]) + *row = -1; + *dropOperation = NSBrowserDropOn; + result = NSDragOperationCopy; + } + } + } + // XXX else local file promises + return result; +} + +- (BOOL)browser:(NSBrowser *)aBrowser + acceptDrop:(id)info + atRow:(NSInteger)row + column:(NSInteger)column + dropOperation:(NSBrowserDropOperation)dropOperation { + NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; + NSLog(@"drag in filenames: %@", filenames); + PithosNode *node = nil; + if ((column != -1) && (filenames != nil)) { + if (row != -1) + node = [browser itemAtRow:row inColumn:column]; + else + node = [browser parentForItemsInColumn:column]; + NSLog(@"drag in node: %@", node.url); + if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class])) + return NO; + + NSFileManager *defaultManager = [NSFileManager defaultManager]; + NSString *containerName = [NSString stringWithString:node.pithosContainer.name]; + NSString *objectNamePrefix; + if ([node class] == [PithosSubdirNode class]) + objectNamePrefix = [NSString stringWithString:node.pithosObject.name]; + else + objectNamePrefix = [NSString stringWithString:@""]; + NSUInteger blockSize = node.pithosContainer.blockSize; + NSString *blockHash = node.pithosContainer.blockHash; + + for (NSString *filePath in filenames) { + BOOL isDirectory; + if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { + if (!isDirectory) { + // Upload file + NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]; + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(queue, ^{ + NSError *error = nil; + NSURLResponse *response = nil; + [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath] + cachePolicy:NSURLCacheStorageNotAllowed + timeoutInterval:.1] + returningResponse:&response + error:&error]; + NSString *contentType = [response MIMEType]; + if (contentType == nil) + contentType = @"application/binary"; + if (error) + NSLog(@"contentType detection error: %@", error); + NSArray *hashes = nil; + ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName + objectName:objectName + contentType:contentType + blockSize:blockSize + blockHash:blockHash + forFile:filePath + checkIfExists:YES + hashes:&hashes]; + if (objectRequest) { + // XXX set delegates and queue + objectRequest.delegate = self; + objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:); + objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:); + objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + containerName, @"containerName", + objectName, @"objectName", + contentType, @"contentType", + [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", + blockHash, @"blockHash", + filePath, @"filePath", + hashes, @"hashes", + [NSNumber numberWithBool:YES], @"checkIfExists", + node, @"node", + [NSNumber numberWithUnsignedInteger:0], @"iteration", + nil]; + [objectRequest startAsynchronous]; + } + // XXX else show alert? + }); + } else { + // Upload directory, confirm first + // XXX implement this + } + } + + } + return YES; + } + + return NO; +} + +#pragma mark - +#pragma mark ASIHTTPRequestDelegate + +- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"download completed: %@", [objectRequest url]); + if ([objectRequest bytes] == 0) { + NSLog(@"downloaded 0 bytes"); + NSFileManager *defaultManager = [NSFileManager defaultManager]; + NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"]; + if (![defaultManager fileExistsAtPath:filePath]) { + if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"File Creation Error"]; + [alert setInformativeText:[NSString stringWithFormat:@"Couldn't create zero length file at %@", filePath]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + } + } + } +} + +- (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"download failed: %@, error: %@", [objectRequest url], [objectRequest error]); + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"HTTP Request Error"]; + [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; +} + +- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"upload using hashmap completed: %@", [objectRequest url]); + if (objectRequest.responseStatusCode == 201) { + NSLog(@"object created: %@", [objectRequest url]); + PithosNode *node = [objectRequest.userInfo objectForKey:@"node"]; + [node invalidateChildren]; + node.children; + } else if (objectRequest.responseStatusCode == 409) { + NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] + 1; + if (iteration > 10) { + NSLog(@"upload iteration limit reached: %@", [objectRequest url]); + // XXX show alert + return; + } + NSLog(@"object is missing hashes: %@", [objectRequest url]); + ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] + objectName:@".upload" + contentType:[objectRequest.userInfo objectForKey:@"contentType"] + blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] + forFile:[objectRequest.userInfo objectForKey:@"filePath"] + hashes:[objectRequest.userInfo objectForKey:@"hashes"] + missingHashesResponse:[objectRequest responseString] + checkIfExists:[[objectRequest.userInfo objectForKey:@"checkIfExists"] boolValue]]; + newObjectRequest.shouldAttemptPersistentConnection = NO; + newObjectRequest.delegate = self; + newObjectRequest.didFinishSelector = @selector(uploadMissingHashesFinished:); + newObjectRequest.didFailSelector = @selector(uploadMissingHashesFailed:); + newObjectRequest.userInfo = objectRequest.userInfo; + [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithBool:NO] forKey:@"checkIfExists"]; + [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"]; + [newObjectRequest startAsynchronous]; + } else { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"Unexpected Response Status"]; + [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + } +} + +- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"upload failed: %@, error: %@", [objectRequest url], [objectRequest error]); + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"HTTP Request Error"]; + [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; +} + +- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"upload of missing hashes completed: %@", [objectRequest url]); + if ((objectRequest.responseStatusCode == 201) || (objectRequest.responseStatusCode == 204)) { + NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"]; + ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] + objectName:[objectRequest.userInfo objectForKey:@"objectName"] + contentType:[objectRequest.userInfo objectForKey:@"contentType"] + blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] + blockHash:[objectRequest.userInfo objectForKey:@"blockHash"] + forFile:[objectRequest.userInfo objectForKey:@"filePath"] + checkIfExists:NO + hashes:&hashes]; + newObjectRequest.delegate = self; + newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:); + newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:); + newObjectRequest.userInfo = objectRequest.userInfo; + [newObjectRequest startAsynchronous]; + } else { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"Unexpected Response Status"]; + [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + } +} + +- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest { + NSLog(@"upload of missing hashes failed: %@, error: %@", [objectRequest url], [objectRequest error]); + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:@"HTTP Request Error"]; + [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; +} #pragma mark - -#pragma NSSplitViewDelegate +#pragma mark NSSplitViewDelegate - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { - return 100; + return 120; } - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { - return 260; + return 220; } #pragma mark - @@ -314,4 +675,29 @@ } } +#pragma mark - +#pragma mark NSMenuDelegate + +- (void)menuNeedsUpdate:(NSMenu *)menu { + NSInteger column = [browser clickedColumn]; + NSInteger row = [browser clickedRow]; + [menu removeAllItems]; + if ((column == -1) || (row == -1)) { + // General context menu has 0 + } else { + // PithosNode menu has 1 items + // Get Info + NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""]; + [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]]; + [menu addItem:menuItem]; + } +} + +#pragma mark - +#pragma mark Menu Actions + +- (void)getInfo:(NSMenuItem *)sender { + [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender]; +} + @end \ No newline at end of file