Minor changes.
[pithos-macos] / pithos-macos / PithosBrowserController.m
1     //
2 //  PithosBrowserController.m
3 //  pithos-macos
4 //
5 // Copyright 2011 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
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.
19 // 
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.
32 // 
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.
37
38 #import "PithosBrowserController.h"
39 #import "ASIPithosRequest.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosEmptyNode.h"
43 #import "ImageAndTextCell.h"
44 #import "FileSystemBrowserCell.h"
45
46 //@interface PithosBrowserCell : NSBrowserCell {}
47 @interface PithosBrowserCell : FileSystemBrowserCell {}
48 @end
49
50 @implementation PithosBrowserCell
51
52 - (id)init {
53     if ((self = [super init])) {
54         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
55     }
56     return self;
57 }
58
59 - (void)setObjectValue:(id)object {
60     if ([object isKindOfClass:[PithosNode class]]) {
61         PithosNode *node = (PithosNode *)object;
62         [self setStringValue:node.displayName];
63         [self setImage:node.icon];
64 //        // All cells are set as leafs because a branchingImage is already set!
65 //        // Maybe this cell is already inside an NSBrowserCell
66 //        [self setLeaf:YES];
67     } else {
68         [super setObjectValue:object];
69     }
70 }
71
72 @end
73
74 @interface PithosOutlineViewCell : ImageAndTextCell {}
75 @end
76
77 @implementation PithosOutlineViewCell
78
79 - (void)setObjectValue:(id)object {
80     if ([object isKindOfClass:[PithosNode class]]) {
81         PithosNode *node = (PithosNode *)object;
82         [self setStringValue:node.displayName];
83         [self setImage:node.icon];
84         [self setEditable:NO];
85     } else {
86         [super setObjectValue:object];
87     }
88 }
89
90 @end
91
92 @interface PithosBrowserController (Private) {}
93 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken;
94 - (void)resetContainers;
95 @end
96
97 @implementation PithosBrowserController
98 @synthesize userDefaultsController, outlineViewDataSourceArray, splitView, outlineView, browser;
99 @synthesize authenticationPanel, authenticationUserTextField, authenticationTokenTextField, authenticationRenewCheckBox, 
100             authenticationCancelPushButton, authenticationManualPushButton, authenticationLoginPushButton;
101
102 #pragma mark -
103 #pragma Object Lifecycle
104
105 - (id)init {
106     return [super initWithWindowNibName:@"PithosBrowserController"];
107 }
108
109 - (void)dealloc {
110     [[NSNotificationCenter defaultCenter] removeObserver:self];
111     [sharedPreviewController release];
112     [outlineViewDataSourceArray release];
113     [rootNode release];
114     [authenticationLoginPushButton release];
115     [authenticationManualPushButton release];
116     [authenticationCancelPushButton release];
117     [authenticationRenewCheckBox release];
118     [authenticationTokenTextField release];
119     [authenticationUserTextField release];
120     [authenticationPanel release];
121     [browser release];
122     [splitView release];
123     [outlineView release];
124     [userDefaultsController release];
125     [super dealloc];
126 }
127
128 - (void)awakeFromNib {
129     [browser setCellClass:[PithosBrowserCell class]];
130 }
131
132 - (void)resetContainers {
133     rootNode = nil;
134     [browser loadColumnZero];
135     self.outlineViewDataSourceArray = nil;
136     
137     // Create the outlineView tree
138     // CONTAINERS
139         NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
140                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
141     // CONTAINERS/pithos
142         [[containersTreeNode mutableChildNodes] addObject:
143      [NSTreeNode treeNodeWithRepresentedObject:
144       [[[PithosContainerNode alloc] initWithContainerName:@"pithos" 
145                                                      icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
146         ] autorelease]]];
147     // CONTAINERS/trash
148         [[containersTreeNode mutableChildNodes] addObject:
149      [NSTreeNode treeNodeWithRepresentedObject:
150       [[[PithosContainerNode alloc] initWithContainerName:@"trash"
151                                                      icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
152         ] autorelease]]];
153     // SHARED
154         NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
155                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
156     // SHARED/my shared
157         [[sharedTreeNode mutableChildNodes] addObject:
158      [NSTreeNode treeNodeWithRepresentedObject:
159       [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
160                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
161         ] autorelease]]];
162     // SHARED/others shared
163         [[sharedTreeNode mutableChildNodes] addObject:
164      [NSTreeNode treeNodeWithRepresentedObject:
165       [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
166                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
167         ] autorelease]]];
168     
169     self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
170     
171         // Expand the folder outline view
172     [outlineView expandItem:nil expandChildren:YES];
173         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
174 }
175
176 - (void)windowDidLoad {
177 //    [userDefaultsController setAppliesImmediately:NO];
178     
179     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
180     
181     // Register for updates
182     [[NSNotificationCenter defaultCenter] addObserver:self 
183                                              selector:@selector(pithosNodeChildrenUpdated:) 
184                                                  name:@"PithosContainerNodeChildrenUpdated" 
185                                                object:nil];
186     [[NSNotificationCenter defaultCenter] addObserver:self 
187                                              selector:@selector(pithosNodeChildrenUpdated:) 
188                                                  name:@"PithosSubdirNodeChildrenUpdated" 
189                                                object:nil];
190     
191     [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
192 }
193
194 #pragma mark -
195 #pragma Observers
196
197 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
198     if ([[browser parentForItemsInColumn:[browser lastColumn]] isEqualTo:[notification object]]) 
199         [browser reloadColumn:[browser lastColumn]];
200 }
201
202 #pragma mark -
203 #pragma Actions
204
205 - (IBAction)refresh:(id)sender {
206     [[browser parentForItemsInColumn:[browser lastColumn]] invalidateChildren];
207     [browser reloadColumn:[browser lastColumn]];
208 }
209
210 #pragma mark -
211 #pragma Authentication
212
213 - (void)authenticateFromURLWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken {
214     if ([authUser length] && [authToken length]) {
215         [authenticationUserTextField setStringValue:authUser];
216         [authenticationTokenTextField setStringValue:authToken];
217         [userDefaultsController save:self];
218         [self authenticateWithAuthUser:authUser authToken:authToken];
219     }
220     // XXX else maybe an error message?
221 }
222
223 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken {
224     // XXX hardcoded for now
225     NSString *storageURLPrefix = @"https://pithos.dev.grnet.gr/v1/";
226     
227     NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
228     if ([authUser length] && [authToken length]) {
229         [ASIPithosRequest setStorageURL:[storageURLPrefix stringByAppendingString:[authUser stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
230         [ASIPithosRequest setAuthToken:authToken];
231         [self resetContainers];
232     } else {
233         [self authenticationSelect:nil];
234     }
235 }
236
237 - (IBAction)authenticationSelect:(id)sender {
238         [self.window makeFirstResponder:nil];
239         [NSApp beginSheet:authenticationPanel
240            modalForWindow:self.window
241         modalDelegate:self
242            didEndSelector:NULL
243                   contextInfo:nil];
244 }
245
246 - (IBAction)authenticationLogin:(id)sender {
247         [NSApp endSheet:authenticationPanel];
248         [authenticationPanel orderOut:self];
249     // XXX hardcoded for now
250     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
251     NSString *loginURL = [NSString stringWithFormat:@"https://pithos.dev.grnet.gr/login?next=pithos://%@_%d",
252                           [processInfo processName], [processInfo processIdentifier]];
253     if ([authenticationRenewCheckBox state] == NSOnState)
254         loginURL = [loginURL stringByAppendingString:@"&renew="];
255     NSLog(@"loginURL: %@", loginURL);
256     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
257     // XXX Should we wait for results or do something else?
258     // XXX check the case where this happens for the first time
259     // XXX maybe don't remove the Panel, and let the handler do it
260 }
261
262 - (IBAction)authenticationCancel:(id)sender {
263         [NSApp endSheet:authenticationPanel];
264         [authenticationPanel orderOut:self];
265     [userDefaultsController revert:sender];
266 }
267
268 - (IBAction)authenticationManual:(id)sender {
269         [NSApp endSheet:authenticationPanel];
270         [authenticationPanel orderOut:self];
271     [userDefaultsController save:sender];
272     // Because of delayed saves of the userDefaultsController, we use the TextField values directly, instead of 
273     //NSString *authUser = [[userDefaultsController values] valueForKey:@"authUser"];
274     //NSString *authToken = [[userDefaultsController values] valueForKey:@"authToken"];    
275     [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
276 }
277
278 #pragma mark -
279 #pragma NSBrowserDelegate
280
281 - (id)rootItemForBrowser:(NSBrowser *)browser {
282     return rootNode;    
283 }
284
285 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
286     PithosNode *node = (PithosNode *)item;
287     return node.children.count;
288 }
289
290 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
291     PithosNode *node = (PithosNode *)item;
292     return [node.children objectAtIndex:index];
293 }
294
295 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
296     PithosNode *node = (PithosNode *)item;
297     return node.isLeafItem;
298 }
299
300 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
301     PithosNode *node = (PithosNode *)item;
302     return node;
303 }
304
305 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
306     if (sharedPreviewController == nil)
307         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
308     return sharedPreviewController;
309 }
310
311 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
312 //    if (!forUserResize) {
313 //        id item = [browser parentForItemsInColumn:columnIndex]; 
314 //        if ([self browser:browser isLeafItem:item]) {
315 //            suggestedWidth = 200; 
316 //        }
317 //    }
318 //    return suggestedWidth;
319 //}
320
321 #pragma mark -
322 #pragma NSSplitViewDelegate
323
324 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
325     return 100;
326 }
327
328 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
329     return 260;
330 }
331
332 #pragma mark -
333 #pragma mark NSOutlineViewDelegate
334
335 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
336     return ([[item representedObject] isLeaf]);
337 }
338
339 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
340         return (![[item representedObject] isLeaf]);
341 }
342
343 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
344     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
345     if (node) {
346         rootNode = node;
347         [browser loadColumnZero];
348     }
349 }
350
351 @end