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;
104 @implementation PithosBrowserController
105 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
108 #pragma Object Lifecycle
111 return [super initWithWindowNibName:@"PithosBrowserController"];
115 [[NSNotificationCenter defaultCenter] removeObserver:self];
116 [sharedPreviewController release];
117 [outlineViewDataSourceArray release];
118 [accountNode release];
122 [outlineView release];
126 - (void)awakeFromNib {
127 [super awakeFromNib];
129 [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
130 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
132 [browser setCellClass:[PithosBrowserCell class]];
135 - (void)resetContainers {
137 [browser loadColumnZero];
138 self.outlineViewDataSourceArray = nil;
140 // Create the outlineView tree
142 NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
143 [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
144 // // CONTAINERS/pithos
145 // [[containersTreeNode mutableChildNodes] addObject:
146 // [NSTreeNode treeNodeWithRepresentedObject:
147 // [[[PithosContainerNode alloc] initWithContainerName:@"pithos"
148 // icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
150 // // CONTAINERS/trash
151 // [[containersTreeNode mutableChildNodes] addObject:
152 // [NSTreeNode treeNodeWithRepresentedObject:
153 // [[[PithosContainerNode alloc] initWithContainerName:@"trash"
154 // icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
157 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
158 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
160 [[sharedTreeNode mutableChildNodes] addObject:
161 [NSTreeNode treeNodeWithRepresentedObject:
162 [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
163 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
165 // SHARED/others shared
166 [[sharedTreeNode mutableChildNodes] addObject:
167 [NSTreeNode treeNodeWithRepresentedObject:
168 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
169 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
172 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
174 // Expand the folder outline view
175 [outlineView expandItem:nil expandChildren:YES];
176 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
178 // Create accountNode and trigger a refresh
179 accountNode = [[PithosAccountNode alloc] init];
180 accountNode.children;
183 - (void)windowDidLoad {
184 [super windowDidLoad];
186 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
188 // Register for updates
189 [[NSNotificationCenter defaultCenter] addObserver:self
190 selector:@selector(pithosNodeChildrenUpdated:)
191 name:@"PithosContainerNodeChildrenUpdated"
193 [[NSNotificationCenter defaultCenter] addObserver:self
194 selector:@selector(pithosNodeChildrenUpdated:)
195 name:@"PithosSubdirNodeChildrenUpdated"
197 [[NSNotificationCenter defaultCenter] addObserver:self
198 selector:@selector(pithosAccountNodeChildrenUpdated:)
199 name:@"PithosAccountNodeChildrenUpdated"
201 [[NSNotificationCenter defaultCenter] addObserver:self
202 selector:@selector(resetContainers)
203 name:@"PithosAuthenticationCredentialsUpdated"
210 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
211 PithosNode *node = (PithosNode *)[notification object];
212 NSInteger lastColumn = [browser lastColumn];
213 for (NSInteger column = lastColumn; column >= 0; column--) {
214 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
215 [browser reloadColumn:column];
216 if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
217 // This reloads the preview column
218 [browser setLastColumn:column];
226 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
227 BOOL containerPithosFound = NO;
228 BOOL containerTrashFound = NO;
229 //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
230 NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
231 for (PithosContainerNode *containerNode in accountNode.children) {
232 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
233 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
234 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
235 containerPithosFound = YES;
236 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
237 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
238 NSUInteger insertIndex = 1;
239 if (!containerPithosFound)
241 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
242 containerTrashFound = YES;
244 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
247 BOOL refreshAccountNode = NO;
248 if (!containerPithosFound) {
250 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
251 [containerRequest startSynchronous];
252 if ([containerRequest error]) {
253 NSLog(@"error:%@", [containerRequest error]);
254 // XXX do something on error
256 refreshAccountNode = YES;
259 if (!containerTrashFound) {
261 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
262 [containerRequest startSynchronous];
263 if ([containerRequest error]) {
264 NSLog(@"error:%@", [containerRequest error]);
265 // XXX do something on error
267 refreshAccountNode = YES;
270 if (refreshAccountNode) {
271 [accountNode invalidateChildren];
272 accountNode.children;
274 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
275 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
277 // Expand the folder outline view
278 [outlineView expandItem:nil expandChildren:YES];
279 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
286 - (IBAction)refresh:(id)sender {
287 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
288 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
290 [browser validateVisibleColumns];
294 #pragma NSBrowserDelegate
296 - (id)rootItemForBrowser:(NSBrowser *)browser {
300 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
301 PithosNode *node = (PithosNode *)item;
302 return node.children.count;
305 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
306 PithosNode *node = (PithosNode *)item;
307 return [node.children objectAtIndex:index];
310 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
311 PithosNode *node = (PithosNode *)item;
312 return node.isLeafItem;
315 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
316 PithosNode *node = (PithosNode *)item;
320 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
321 if (sharedPreviewController == nil)
322 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
323 return sharedPreviewController;
326 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
327 // if (!forUserResize) {
328 // id item = [browser parentForItemsInColumn:columnIndex];
329 // if ([self browser:browser isLeafItem:item]) {
330 // suggestedWidth = 200;
333 // return suggestedWidth;
336 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
340 #pragma mark Drag and Drop
342 //- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
343 // withEvent:(NSEvent *)event {
344 // // XXX allow only objects? or when a subdir is dragged download the whole tree?
345 // NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
346 // for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
347 // PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
348 // if (node.pithosObject.subdir)
354 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
355 toPasteboard:(NSPasteboard *)pasteboard {
356 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
357 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
358 for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
359 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
360 [propertyList addObject:[node.pithosObject.name pathExtension]];
363 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
364 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
369 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
370 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
371 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
372 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
373 for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
374 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
377 // If the node is a subdir ask if the whole tree should be downloaded
378 if (node.pithosObject.subdir) {
379 NSAlert *alert = [[NSAlert alloc] init];
380 [alert setMessageText:@"Download directory"];
381 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
382 [alert addButtonWithTitle:@"OK"];
383 [alert addButtonWithTitle:@"Cancel"];
384 NSInteger choice = [alert runModal];
385 if (choice == NSAlertFirstButtonReturn) {
386 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
387 objectName:node.pithosObject.name
388 toDirectory:[dropDestination path]
390 if (objectRequests) {
391 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
392 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
393 // XXX set delegates and queue
394 [objectRequest setCompletionBlock:^{
395 NSLog(@"dl completed: %@", [objectRequest url]);
397 [objectRequest setFailedBlock:^{
398 NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
400 [objectRequest startAsynchronous];
405 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
406 objectName:node.pithosObject.name
407 toDirectory:[dropDestination path]
410 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
411 // XXX set delegates and queue
412 [objectRequest setCompletionBlock:^{
413 NSLog(@"dl completed: %@", [objectRequest url]);
415 [objectRequest setFailedBlock:^{
416 NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
418 [objectRequest startAsynchronous];
426 #pragma mark NSSplitViewDelegate
428 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
432 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
437 #pragma mark NSOutlineViewDelegate
439 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
440 return ([[item representedObject] isLeaf]);
443 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
444 return (![[item representedObject] isLeaf]);
447 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
448 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
451 [browser loadColumnZero];