2 // PithosBrowserController.m
5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosEmptyNode.h"
45 #import "ImageAndTextCell.h"
46 #import "FileSystemBrowserCell.h"
47 #import "ASIPithosRequest.h"
48 #import "ASIPithosContainerRequest.h"
49 #import "ASIPithosObjectRequest.h"
50 #import "ASIPithosContainer.h"
51 #import "ASIPithosObject.h"
52 #import "PithosFileUtilities.h"
54 //@interface PithosBrowserCell : NSBrowserCell {}
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
58 @implementation PithosBrowserCell
61 if ((self = [super init])) {
62 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
67 - (void)setObjectValue:(id)object {
68 if ([object isKindOfClass:[PithosNode class]]) {
69 PithosNode *node = (PithosNode *)object;
70 [self setStringValue:node.displayName];
71 [self setImage:node.icon];
72 // // All cells are set as leafs because a branchingImage is already set!
73 // // Maybe this cell is already inside an NSBrowserCell
74 // [self setLeaf:YES];
76 [super setObjectValue:object];
82 @interface PithosOutlineViewCell : ImageAndTextCell {}
85 @implementation PithosOutlineViewCell
87 - (void)setObjectValue:(id)object {
88 if ([object isKindOfClass:[PithosNode class]]) {
89 PithosNode *node = (PithosNode *)object;
90 [self setStringValue:node.displayName];
91 [self setImage:node.icon];
92 [self setEditable:NO];
94 [super setObjectValue:object];
100 @interface PithosBrowserController (Private) {}
101 - (void)resetContainers;
102 - (void)getInfo:(NSMenuItem *)sender;
105 @implementation PithosBrowserController
106 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
109 #pragma Object Lifecycle
112 return [super initWithWindowNibName:@"PithosBrowserController"];
116 [[NSNotificationCenter defaultCenter] removeObserver:self];
117 [browserMenu release];
118 [sharedPreviewController release];
119 [outlineViewDataSourceArray release];
120 [accountNode release];
125 - (void)awakeFromNib {
126 [super awakeFromNib];
128 [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
129 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
131 [browser setCellClass:[PithosBrowserCell class]];
133 browserMenu = [[NSMenu alloc] init];
134 [browserMenu setDelegate:self];
135 [browser setMenu:browserMenu];
138 - (void)resetContainers {
140 [browser loadColumnZero];
141 self.outlineViewDataSourceArray = nil;
143 // Create the outlineView tree
145 NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
146 [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
147 // // CONTAINERS/pithos
148 // [[containersTreeNode mutableChildNodes] addObject:
149 // [NSTreeNode treeNodeWithRepresentedObject:
150 // [[[PithosContainerNode alloc] initWithContainerName:@"pithos"
151 // icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
153 // // CONTAINERS/trash
154 // [[containersTreeNode mutableChildNodes] addObject:
155 // [NSTreeNode treeNodeWithRepresentedObject:
156 // [[[PithosContainerNode alloc] initWithContainerName:@"trash"
157 // icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
160 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
161 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
163 [[sharedTreeNode mutableChildNodes] addObject:
164 [NSTreeNode treeNodeWithRepresentedObject:
165 [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
166 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
168 // SHARED/others shared
169 [[sharedTreeNode mutableChildNodes] addObject:
170 [NSTreeNode treeNodeWithRepresentedObject:
171 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
172 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
175 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
177 // Expand the folder outline view
178 [outlineView expandItem:nil expandChildren:YES];
179 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
181 // Create accountNode and trigger a refresh
182 accountNode = [[PithosAccountNode alloc] init];
183 accountNode.children;
186 - (void)windowDidLoad {
187 [super windowDidLoad];
189 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
191 // Register for updates
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(pithosNodeChildrenUpdated:)
194 name:@"PithosContainerNodeChildrenUpdated"
196 [[NSNotificationCenter defaultCenter] addObserver:self
197 selector:@selector(pithosNodeChildrenUpdated:)
198 name:@"PithosSubdirNodeChildrenUpdated"
200 [[NSNotificationCenter defaultCenter] addObserver:self
201 selector:@selector(pithosAccountNodeChildrenUpdated:)
202 name:@"PithosAccountNodeChildrenUpdated"
204 [[NSNotificationCenter defaultCenter] addObserver:self
205 selector:@selector(resetContainers)
206 name:@"PithosAuthenticationCredentialsUpdated"
213 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
214 PithosNode *node = (PithosNode *)[notification object];
215 NSInteger lastColumn = [browser lastColumn];
216 for (NSInteger column = lastColumn; column >= 0; column--) {
217 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
218 [browser reloadColumn:column];
219 if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
220 // This reloads the preview column
221 [browser setLastColumn:column];
229 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
230 BOOL containerPithosFound = NO;
231 BOOL containerTrashFound = NO;
232 //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
233 NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
234 for (PithosContainerNode *containerNode in accountNode.children) {
235 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
236 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
237 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
238 containerPithosFound = YES;
239 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
240 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
241 NSUInteger insertIndex = 1;
242 if (!containerPithosFound)
244 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
245 containerTrashFound = YES;
247 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
250 BOOL refreshAccountNode = NO;
251 if (!containerPithosFound) {
253 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
254 [containerRequest startSynchronous];
255 if ([containerRequest error]) {
256 NSLog(@"error:%@", [containerRequest error]);
257 // XXX do something on error
259 refreshAccountNode = YES;
262 if (!containerTrashFound) {
264 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
265 [containerRequest startSynchronous];
266 if ([containerRequest error]) {
267 NSLog(@"error:%@", [containerRequest error]);
268 // XXX do something on error
270 refreshAccountNode = YES;
273 if (refreshAccountNode) {
274 [accountNode invalidateChildren];
275 accountNode.children;
277 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
278 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
280 // Expand the folder outline view
281 [outlineView expandItem:nil expandChildren:YES];
282 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
289 - (IBAction)refresh:(id)sender {
290 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
291 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
293 [browser validateVisibleColumns];
297 #pragma NSBrowserDelegate
299 - (id)rootItemForBrowser:(NSBrowser *)browser {
303 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
304 PithosNode *node = (PithosNode *)item;
305 return node.children.count;
308 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
309 PithosNode *node = (PithosNode *)item;
310 return [node.children objectAtIndex:index];
313 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
314 PithosNode *node = (PithosNode *)item;
315 return node.isLeafItem;
318 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
319 PithosNode *node = (PithosNode *)item;
323 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
324 if (sharedPreviewController == nil)
325 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
326 return sharedPreviewController;
329 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
330 // if (!forUserResize) {
331 // id item = [browser parentForItemsInColumn:columnIndex];
332 // if ([self browser:browser isLeafItem:item]) {
333 // suggestedWidth = 200;
336 // return suggestedWidth;
339 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
343 #pragma mark Drag and Drop
345 //- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
346 // withEvent:(NSEvent *)event {
347 // // XXX allow only objects? or when a subdir is dragged download the whole tree?
348 // NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
349 // for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
350 // PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
351 // if (node.pithosObject.subdir)
357 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
358 toPasteboard:(NSPasteboard *)pasteboard {
359 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
360 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
361 for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
362 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
363 [propertyList addObject:[node.pithosObject.name pathExtension]];
366 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
367 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
372 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
373 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
374 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
375 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
376 for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
377 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
380 // If the node is a subdir ask if the whole tree should be downloaded
381 if (node.pithosObject.subdir) {
382 NSAlert *alert = [[NSAlert alloc] init];
383 [alert setMessageText:@"Download directory"];
384 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
385 [alert addButtonWithTitle:@"OK"];
386 [alert addButtonWithTitle:@"Cancel"];
387 NSInteger choice = [alert runModal];
388 if (choice == NSAlertFirstButtonReturn) {
389 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
390 objectName:node.pithosObject.name
391 toDirectory:[dropDestination path]
393 if (objectRequests) {
394 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
395 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
396 // XXX set delegates and queue
397 [objectRequest setCompletionBlock:^{
398 NSLog(@"dl completed: %@", [objectRequest url]);
400 [objectRequest setFailedBlock:^{
401 NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
403 [objectRequest startAsynchronous];
408 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
409 objectName:node.pithosObject.name
410 toDirectory:[dropDestination path]
413 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
414 // XXX set delegates and queue
415 [objectRequest setCompletionBlock:^{
416 NSLog(@"dl completed: %@", [objectRequest url]);
418 [objectRequest setFailedBlock:^{
419 NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
421 [objectRequest startAsynchronous];
429 #pragma mark NSSplitViewDelegate
431 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
435 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
440 #pragma mark NSOutlineViewDelegate
442 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
443 return ([[item representedObject] isLeaf]);
446 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
447 return (![[item representedObject] isLeaf]);
450 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
451 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
454 [browser loadColumnZero];
459 #pragma mark NSMenuDelegate
461 - (void)menuNeedsUpdate:(NSMenu *)menu {
462 NSInteger column = [browser clickedColumn];
463 NSInteger row = [browser clickedRow];
464 [menu removeAllItems];
465 if ((column == -1) || (row == -1)) {
466 // General context menu has 0
468 // PithosNode menu has 1 items
470 NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
471 [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
472 [menu addItem:menuItem];
477 #pragma mark Menu Actions
479 - (void)getInfo:(NSMenuItem *)sender {
480 [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];