- //
+//
// PithosBrowserController.m
// pithos-macos
//
// 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 {}
@end
@interface PithosBrowserController (Private) {}
-- (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken;
- (void)resetContainers;
+- (void)getInfo:(NSMenuItem *)sender;
@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
- (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 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 {
-// [userDefaultsController setAppliesImmediately:NO];
+ [super windowDidLoad];
[[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
selector:@selector(pithosNodeChildrenUpdated:)
name:@"PithosSubdirNodeChildrenUpdated"
object:nil];
-
- [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(pithosAccountNodeChildrenUpdated:)
+ name:@"PithosAccountNodeChildrenUpdated"
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(resetContainers)
+ name:@"PithosAuthenticationCredentialsUpdated"
+ object:nil];
}
#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]) {
- [ASIPithosRequest setStorageURL:[storageURLPrefix stringByAppendingString:[authUser stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
- [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];
}
}
-- (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 -
// return suggestedWidth;
//}
+- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
+ return NO;
+}
+
+#pragma mark Drag and Drop
+
+//- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
+// withEvent:(NSEvent *)event {
+// // XXX allow only objects? or when a subdir is dragged download the whole tree?
+// 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 (node.pithosObject.subdir)
+// return NO;
+// }
+// return YES;
+//}
+
+- (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.pithosObject.subdir) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [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"]];
+ // XXX set delegates and queue
+ [objectRequest setCompletionBlock:^{
+ NSLog(@"dl completed: %@", [objectRequest url]);
+ }];
+ [objectRequest setFailedBlock:^{
+ NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
+ }];
+ [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"]];
+ // XXX set delegates and queue
+ [objectRequest setCompletionBlock:^{
+ NSLog(@"dl completed: %@", [objectRequest url]);
+ }];
+ [objectRequest setFailedBlock:^{
+ NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
+ }];
+ [objectRequest startAsynchronous];
+ }
+ }
+ }
+ return names;
+}
+
#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 -
}
}
+#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