Minor changes.
[pithos-macos] / pithos-macos / PithosBrowserController.m
index 4363dee..6720a92 100644 (file)
-//
+    //
 //  PithosBrowserController.m
 //  pithos-macos
 //
-//  Created by Miltiadis Vasilakis on 01/08/11.
-//  Copyright 2011 koomasi. All rights reserved.
+// Copyright 2011 GRNET S.A. All rights reserved.
 //
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
 
 #import "PithosBrowserController.h"
+#import "ASIPithosRequest.h"
+#import "PithosAccountNode.h"
+#import "PithosContainerNode.h"
+#import "PithosEmptyNode.h"
+#import "ImageAndTextCell.h"
+#import "FileSystemBrowserCell.h"
 
+//@interface PithosBrowserCell : NSBrowserCell {}
+@interface PithosBrowserCell : FileSystemBrowserCell {}
+@end
 
-@implementation PithosBrowserController
+@implementation PithosBrowserCell
 
-- (id)initWithWindow:(NSWindow *)window
-{
-    self = [super initWithWindow:window];
-    if (self) {
-        // Initialization code here.
+- (id)init {
+    if ((self = [super init])) {
+        [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
     }
-    
     return self;
 }
 
-- (void)dealloc
-{
+- (void)setObjectValue:(id)object {
+    if ([object isKindOfClass:[PithosNode class]]) {
+        PithosNode *node = (PithosNode *)object;
+        [self setStringValue:node.displayName];
+        [self setImage:node.icon];
+//        // All cells are set as leafs because a branchingImage is already set!
+//        // Maybe this cell is already inside an NSBrowserCell
+//        [self setLeaf:YES];
+    } else {
+        [super setObjectValue:object];
+    }
+}
+
+@end
+
+@interface PithosOutlineViewCell : ImageAndTextCell {}
+@end
+
+@implementation PithosOutlineViewCell
+
+- (void)setObjectValue:(id)object {
+    if ([object isKindOfClass:[PithosNode class]]) {
+        PithosNode *node = (PithosNode *)object;
+        [self setStringValue:node.displayName];
+        [self setImage:node.icon];
+        [self setEditable:NO];
+    } else {
+        [super setObjectValue:object];
+    }
+}
+
+@end
+
+@interface PithosBrowserController (Private) {}
+- (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken;
+- (void)resetContainers;
+@end
+
+@implementation PithosBrowserController
+@synthesize userDefaultsController, outlineViewDataSourceArray, splitView, outlineView, browser;
+@synthesize authenticationPanel, authenticationUserTextField, authenticationTokenTextField, authenticationRenewCheckBox, 
+            authenticationCancelPushButton, authenticationManualPushButton, authenticationLoginPushButton;
+
+#pragma mark -
+#pragma Object Lifecycle
+
+- (id)init {
+    return [super initWithWindowNibName:@"PithosBrowserController"];
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [sharedPreviewController release];
+    [outlineViewDataSourceArray 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)windowDidLoad
-{
-    [super windowDidLoad];
+- (void)awakeFromNib {
+    [browser setCellClass:[PithosBrowserCell class]];
+}
+
+- (void)resetContainers {
+    rootNode = nil;
+    [browser loadColumnZero];
+    self.outlineViewDataSourceArray = nil;
+    
+    // Create the outlineView tree
+    // 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]]];
+    // SHARED
+       NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
+                                      [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
+    // SHARED/my shared
+       [[sharedTreeNode mutableChildNodes] addObject:
+     [NSTreeNode treeNodeWithRepresentedObject:
+      [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
+                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
+        ] autorelease]]];
+    // SHARED/others shared
+       [[sharedTreeNode mutableChildNodes] addObject:
+     [NSTreeNode treeNodeWithRepresentedObject:
+      [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
+                                               icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
+        ] autorelease]]];
     
-    // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
+    self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
+    
+       // Expand the folder outline view
+    [outlineView expandItem:nil expandChildren:YES];
+       [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
 }
 
-@end
+- (void)windowDidLoad {
+//    [userDefaultsController setAppliesImmediately:NO];
+    
+    [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
+    
+    // Register for updates
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosNodeChildrenUpdated:) 
+                                                 name:@"PithosContainerNodeChildrenUpdated" 
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self 
+                                             selector:@selector(pithosNodeChildrenUpdated:) 
+                                                 name:@"PithosSubdirNodeChildrenUpdated" 
+                                               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];
+    }
+    // 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];
+    } else {
+        [self authenticationSelect: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];
+}
+
+- (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]];
+}
+
+#pragma mark -
+#pragma NSBrowserDelegate
+
+- (id)rootItemForBrowser:(NSBrowser *)browser {
+    return rootNode;    
+}
+
+- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return node.children.count;
+}
+
+- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return [node.children objectAtIndex:index];
+}
+
+- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return node.isLeafItem;
+}
+
+- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
+    PithosNode *node = (PithosNode *)item;
+    return node;
+}
+
+- (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
+    if (sharedPreviewController == nil)
+        sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
+    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;
+//}
+
+#pragma mark -
+#pragma NSSplitViewDelegate
+
+- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
+    return 100;
+}
+
+- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
+    return 260;
+}
+
+#pragma mark -
+#pragma mark NSOutlineViewDelegate
+
+- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
+    return ([[item representedObject] isLeaf]);
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
+       return (![[item representedObject] isLeaf]);
+}
+
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
+    PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
+    if (node) {
+        rootNode = node;
+        [browser loadColumnZero];
+    }
+}
+
+@end
\ No newline at end of file