// // PithosBrowserController.m // pithos-macos // // 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 "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 @implementation PithosBrowserCell - (id)init { if ((self = [super init])) { [self setLineBreakMode:NSLineBreakByTruncatingMiddle]; } return self; } - (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)resetContainers; @end @implementation PithosBrowserController @synthesize outlineViewDataSourceArray, splitView, outlineView, browser; #pragma mark - #pragma Object Lifecycle - (id)init { return [super initWithWindowNibName:@"PithosBrowserController"]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [sharedPreviewController release]; [outlineViewDataSourceArray release]; [accountNode release]; [rootNode release]; [browser release]; [splitView release]; [outlineView release]; [super dealloc]; } - (void)awakeFromNib { [super awakeFromNib]; [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES]; [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; [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]]]; self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil]; // 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 { [super windowDidLoad]; [[[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]; [[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 { 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; } } } - (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 { [[[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]; } } #pragma mark - #pragma Actions - (IBAction)refresh:(id)sender { for (NSInteger column = [browser lastColumn]; column >= 0; column--) { [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren]; } [browser validateVisibleColumns]; } #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; //} - (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 mark NSSplitViewDelegate - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { return 120; } - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { return 220; } #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