// 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 {}
@interface PithosBrowserController (Private) {}
- (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
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ [browserMenu release];
[sharedPreviewController release];
[outlineViewDataSourceArray release];
+ [accountNode release];
[rootNode release];
- [browser release];
- [splitView release];
- [outlineView 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 {
// CONTAINERS
NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
[[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
- // 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]]];
+// // 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]];
// 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 {
+ [super windowDidLoad];
+
[[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
// Register for updates
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];
}
}
+- (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 {
+ [[[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];
+ }
+}
+
#pragma mark -
#pragma Actions
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<NSDraggingInfo>)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<NSDraggingInfo>)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 NSSplitViewDelegate
+#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 mark NSSplitViewDelegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
return 120;
}
}
+#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