// // PithosPreferencesController.m // pithos-macos // // Copyright 2011-2012 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 "PithosPreferencesController.h" #import "PithosBrowserController.h" #import "PithosAccountNode.h" #import "PithosSharingAccountsNode.h" #import "PithosContainerNode.h" #import "PithosSubdirNode.h" #import "PithosObjectNode.h" #import "PithosEmptyNode.h" #import "PithosAccount.h" #import "pithos_macosAppDelegate.h" #import "ImageAndTextCell.h" @interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {} @end @implementation PithosPreferencesSyncOutlineViewCell - (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 @implementation PithosPreferencesController @synthesize selectedPithosAccount; @synthesize accountsArrayController; @synthesize accountRemoveEnable; @synthesize serverURL, authUser, authToken, manual, loginEnable, loginCancelEnable; @synthesize syncActive, syncSkipHidden, syncDirectoryPath, syncAccountsDictionary, syncApplyEnable, syncCancelEnable, syncAccountsOutlineView, syncAccountsRootFilesNodes; @synthesize groupsDictionaryController, selectedGroupMembersDictionaryController; #pragma mark - #pragma mark Object Lifecycle - (id)init { return [super initWithWindowNibName:@"PithosPreferencesController"]; } - (void)windowDidLoad { [super windowDidLoad]; NSWindow *window = [self window]; [window setHidesOnDeactivate:NO]; [window setExcludedFromWindowsMenu:YES]; // // Select the first tab when the window is loaded for the first time. // [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"]; [[[syncAccountsOutlineView tableColumns] objectAtIndex:1] setDataCell:[[[PithosPreferencesSyncOutlineViewCell alloc] init] autorelease]]; syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"" icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]]; [groupsDictionaryController setInitialKey:@"group"]; [groupsDictionaryController setInitialValue:@"user"]; [selectedGroupMembersDictionaryController setInitialKey:@"user"]; [selectedGroupMembersDictionaryController setInitialValue:@""]; [window setDelegate:self]; self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0]; [accountsArrayController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:NULL]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedPithosAccountNodeChildrenUpdated:) name:@"SelectedPithosAccountNodeChildrenUpdated" object:nil]; } - (BOOL)windowShouldClose:(id)sender { return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated]; } //- (void)windowWillClose:(NSNotification *)notification { //} //- (IBAction)toolbarItemSelected:(id)sender { //} #pragma mark - #pragma Observers - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isEqualTo:accountsArrayController] && [keyPath isEqualToString:@"selection"] && [[accountsArrayController selectedObjects] count]) { self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0]; } } - (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification { [syncAccountsOutlineView reloadData]; // [syncAccountsOutlineView expandItem:nil expandChildren:YES]; } #pragma mark - #pragma Update - (void)updateAccounts { pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate]; self.accountRemoveEnable = (delegate.activated && ([delegate.pithosAccounts count] > 1)); } - (void)updateLogin { self.loginEnable = ([selectedPithosAccount urlIsValid:serverURL] && (!manual || ([authUser length] && [authToken length]))); self.loginCancelEnable = (![selectedPithosAccount.serverURL isEqualToString:serverURL] || (selectedPithosAccount.authUser && ![selectedPithosAccount.authUser isEqualToString:authUser]) || (selectedPithosAccount.authToken && ![selectedPithosAccount.authToken isEqualToString:authToken])); } - (void)updateSync { BOOL isDirectory; self.syncApplyEnable = (selectedPithosAccount.active && ((selectedPithosAccount.syncActive != syncActive) || (selectedPithosAccount.syncSkipHidden != syncSkipHidden) || (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] && (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] || isDirectory)) || ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary])); self.syncCancelEnable = (selectedPithosAccount.active && ((selectedPithosAccount.syncActive != syncActive) || (selectedPithosAccount.syncSkipHidden != syncSkipHidden) || ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] || ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary])); } #pragma mark - #pragma Properties - (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount { if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) { selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil; selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = nil; [selectedPithosAccount release]; selectedPithosAccount = [aSelectedPithosAccount retain]; selectedPithosAccount.accountNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated"; selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated"; [self updateAccounts]; [self loginCancel:self]; [self syncCancel:self]; [self groupsRevert:self]; } } #pragma Login Properties - (void)setServerURL:(NSString *)aServerURL { [serverURL release]; serverURL = [aServerURL copy]; [self updateLogin]; } - (void)setAuthUser:(NSString *)anAuthUser { [authUser release]; authUser = [anAuthUser copy]; [self updateLogin]; } - (void)setAuthToken:(NSString *)anAuthToken { [authToken release]; authToken = [anAuthToken copy]; [self updateLogin]; } - (void)setManual:(BOOL)aManual { manual = aManual; [self updateLogin]; if (!manual) { self.authUser = selectedPithosAccount.authUser; self.authToken = selectedPithosAccount.authToken; } } #pragma Sync Properties - (void)setSyncActive:(BOOL)aSyncActive { syncActive = aSyncActive; [self updateSync]; } - (void)setSyncSkipHidden:(BOOL)aSyncSkipHidden { syncSkipHidden = aSyncSkipHidden; [self updateSync]; [self selectedPithosAccountNodeChildrenUpdated:nil]; } - (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath { [syncDirectoryPath release]; syncDirectoryPath = [aSyncDirectoryPath copy]; [self updateSync]; } - (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary { [syncAccountsDictionary release]; syncAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncAccountsDictionary count]]; for (NSString *accountName in aSyncAccountsDictionary) { NSDictionary *aSyncContainersDictionary = [aSyncAccountsDictionary objectForKey:accountName]; NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary]; for (NSString *containerName in aSyncContainersDictionary) { if (![accountName isEqualToString:@""] || ![[containerName lowercaseString] isEqualToString:@"shared to me"]) [syncContainersDictionary setObject:[NSMutableSet setWithSet:[aSyncContainersDictionary objectForKey:containerName]] forKey:containerName]; } if ([syncContainersDictionary count]) [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName]; } [self updateSync]; } #pragma mark - #pragma Actions - (IBAction)addAccount:(id)sender { [accountsArrayController addObject:[PithosAccount pithosAccount]]; [self updateAccounts]; pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate]; [delegate.pithosAccountsDictionary setObject:selectedPithosAccount forKey:selectedPithosAccount.name]; [delegate savePithosAccounts:self]; } - (IBAction)removeAccount:(id)sender { [self updateAccounts]; if (!accountRemoveEnable) return; PithosAccount *removedPithosAccount = [selectedPithosAccount retain]; pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate]; if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Operations Pending"]; [alert setInformativeText:@"There are pending operations in the browser, do you want to remove the account and cancel them?"]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) { [removedPithosAccount release]; return; } } [accountsArrayController removeObject:selectedPithosAccount]; [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name]; [delegate removedPithosAccount:removedPithosAccount]; [delegate savePithosAccounts:self]; [removedPithosAccount release]; [self updateAccounts]; } #pragma Login Actions - (IBAction)login:(id)sender { self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary]; if (!manual) { [selectedPithosAccount loginWithServerURL:serverURL]; } else { [selectedPithosAccount authenticateWithServerURL:serverURL authUser:authUser authToken:authToken]; self.manual = NO; pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate]; [delegate savePithosAccounts:self]; if (!delegate.activated) { delegate.activated = YES; [delegate showPithosBrowser:self]; } if ([selectedPithosAccount isEqualTo:delegate.currentPithosAccount]) delegate.pithosBrowserController.pithos = selectedPithosAccount.pithos; } } - (IBAction)loginCancel:(id)server { self.serverURL = selectedPithosAccount.serverURL; self.authUser = selectedPithosAccount.authUser; self.authToken = selectedPithosAccount.authToken; self.manual = NO; } #pragma Sync Actions - (IBAction)syncApply:(id)sender { [selectedPithosAccount updateSyncWithSyncActive:syncActive syncDirectoryPath:syncDirectoryPath syncAccountsDictionary:syncAccountsDictionary syncSkipHidden:syncSkipHidden]; [self updateSync]; pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate]; [delegate savePithosAccounts:self]; [delegate sync]; } - (IBAction)syncCancel:(id)sender { self.syncActive = selectedPithosAccount.syncActive; self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath; self.syncAccountsDictionary = selectedPithosAccount.syncAccountsDictionary; self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary]; self.syncSkipHidden = selectedPithosAccount.syncSkipHidden; } - (IBAction)syncRefresh:(id)sender { selectedPithosAccount.accountNode.forcedRefresh = YES; [selectedPithosAccount.accountNode invalidateChildrenRecursive]; selectedPithosAccount.sharingAccountsNode.forcedRefresh = YES; [selectedPithosAccount.sharingAccountsNode invalidateChildrenRecursive]; if (selectedPithosAccount.accountNode.children && selectedPithosAccount.sharingAccountsNode.children) { } } #pragma mark Groups Actions - (IBAction)groupsApply:(id)sender { [[self window] makeFirstResponder:nil]; if (selectedPithosAccount.active) [selectedPithosAccount.accountNode applyInfo]; } - (IBAction)groupsRevert:(id)sender { if (selectedPithosAccount.active && selectedPithosAccount.accountNode) [selectedPithosAccount.accountNode refreshInfo]; } #pragma mark - #pragma mark NSOutlineViewDataSource // [PithosEmptyNode] // - + [PithosContainerNode] // -- + [PithosSubdirNode] // -- [PithosEmptyNode] // + [PithosSharingAccountNode] // - + [PithosContainerNode] // -- + [PithosSubdirNode] // -- [PithosEmptyNode] - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (!selectedPithosAccount.active) return 0; if (outlineView == syncAccountsOutlineView) { if (item == nil) { // root: + # NSInteger accountsCount = 0; if ([selectedPithosAccount.accountNode.children count]) accountsCount = 1; if (selectedPithosAccount.sharingAccountsNode.children) accountsCount += selectedPithosAccount.sharingAccountsNode.children.count; return accountsCount; } else if (item == syncAccountsMyAccountNode) { // root/: # if (selectedPithosAccount.accountNode.children) { NSInteger containersCount = 0; for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) { if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) containersCount++; } return containersCount; } } else if ([item class] == [PithosAccountNode class]) { // root/: # PithosAccountNode *accountNode = (PithosAccountNode *)item; if (accountNode.children) return accountNode.children.count; } else if ([item class] == [PithosContainerNode class]) { // root/{, }/: # + PithosContainerNode *containerNode = (PithosContainerNode *)item; if (containerNode.children) { // We add 1 for the root files node NSInteger subdirCount = 1; for (PithosNode *node in containerNode.children) { if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."])) subdirCount++; } return subdirCount; } } } return 0; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (outlineView == syncAccountsOutlineView) { if (item == nil) { // root: [ , + ] if ([selectedPithosAccount.accountNode.children count]) { if (index == 0) return syncAccountsMyAccountNode; else return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:(index - 1)]; } else { return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:index]; } } else if (item == syncAccountsMyAccountNode) { // root/: [ + ] NSInteger currentContainerIndex = -1; for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) { if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) { currentContainerIndex++; if (currentContainerIndex == index) return node; } } } else if ([item class] == [PithosAccountNode class]) { // root/: [ + ] return [((PithosAccountNode *)item).children objectAtIndex:index]; } else if ([item class] == [PithosContainerNode class]) { // root/{, }/: [ +, ] PithosContainerNode *containerNode = (PithosContainerNode *)item; NSInteger currentSubdirIndex = -1; for (PithosNode *node in containerNode.children) { if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."])) { currentSubdirIndex++; if (currentSubdirIndex == index) return node; } } if (++currentSubdirIndex == index) { NSString *accountName = containerNode.sharingAccount; if (!accountName) accountName = @""; PithosEmptyNode *rootFilesNode = [[syncAccountsRootFilesNodes objectForKey:accountName] objectForKey:containerNode.displayName]; if (!rootFilesNode) { if (![syncAccountsRootFilesNodes objectForKey:accountName]) [syncAccountsRootFilesNodes setObject:[NSMutableDictionary dictionary] forKey:accountName]; rootFilesNode = [[[PithosEmptyNode alloc] initWithDisplayName:@"" icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]] autorelease]; rootFilesNode.parent = containerNode; [[syncAccountsRootFilesNodes objectForKey:accountName] setObject:rootFilesNode forKey:containerNode.displayName]; } return rootFilesNode; } } } return nil; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if (outlineView == syncAccountsOutlineView) { if ((item == syncAccountsMyAccountNode) || ([item class] == [PithosAccountNode class]) || ([item class] == [PithosContainerNode class])) return YES; } return NO; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if (outlineView == syncAccountsOutlineView) { if ([[tableColumn identifier] isEqualToString:@"sync"]) { if (item == syncAccountsMyAccountNode) { // root/ // My account is // off if not in dictionary // mixed if in dictionary with exclusions // on if in dictionary without exclusions NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:@""]; if (syncContainersDictionary) { for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) { if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) { NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName]; if (!containerExcludedDirectories || [containerExcludedDirectories count]) return [NSNumber numberWithUnsignedInteger:NSMixedState]; } } return [NSNumber numberWithUnsignedInteger:NSOnState]; } return [NSNumber numberWithUnsignedInteger:NSOffState]; } else if ([item class] == [PithosAccountNode class]) { // root/ // A sharing account is // off if not in dictionary // mixed if in dictionary with exclusions // on if in dictionary without exclusions PithosAccountNode *accountNode = (PithosAccountNode *)item; NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountNode.displayName]; if (syncContainersDictionary) { for (PithosContainerNode *node in accountNode.children) { NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName]; if (!containerExcludedDirectories || [containerExcludedDirectories count]) return [NSNumber numberWithUnsignedInteger:NSMixedState]; } return [NSNumber numberWithUnsignedInteger:NSOnState]; } return [NSNumber numberWithUnsignedInteger:NSOffState]; } else if ([item class] == [PithosContainerNode class]) { // root/{, }/ // A container is // off if not in dictionary // mixed if in dictionary with exclusions // on if in dictionary without exclusions PithosContainerNode *node = (PithosContainerNode *)item; NSString *accountName = node.sharingAccount; if (!accountName) accountName = @""; NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] objectForKey:node.displayName]; if (containerExcludedDirectories) { if ([containerExcludedDirectories count]) return [NSNumber numberWithUnsignedInteger:NSMixedState]; else return [NSNumber numberWithUnsignedInteger:NSOnState]; } return [NSNumber numberWithUnsignedInteger:NSOffState]; } else if ([item class] == [PithosSubdirNode class]) { // root/{, }// // Directory is off if parent container not in dictionary or if excluded // displayName should be localized and lowercased PithosSubdirNode *node = (PithosSubdirNode *)item; NSString *accountName = node.sharingAccount; if (!accountName) accountName = @""; NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] objectForKey:node.parent.displayName]; if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:[[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]]) return [NSNumber numberWithUnsignedInteger:NSOffState]; else return [NSNumber numberWithUnsignedInteger:NSOnState]; } else if ([item class] == [PithosEmptyNode class]) { // root/{, }// // Root files is off if parent container not in dictionary or if excluded PithosEmptyNode *node = (PithosEmptyNode *)item; NSString *accountName = node.parent.sharingAccount; if (!accountName) accountName = @""; NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] objectForKey:node.parent.displayName]; if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""]) return [NSNumber numberWithUnsignedInteger:NSOffState]; else return [NSNumber numberWithUnsignedInteger:NSOnState]; } return [NSNumber numberWithUnsignedInteger:NSOffState]; } else if ([[tableColumn identifier] isEqualToString:@"path"]) { return (PithosNode *)item; } } return nil; } - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if (outlineView == syncAccountsOutlineView) { if ([[tableColumn identifier] isEqualToString:@"sync"]) { NSCellStateValue newState = [object unsignedIntegerValue]; if (item == syncAccountsMyAccountNode) { // root/ // If new state is // mixed/on include my account with no exclusions // off exclude my account if ((newState == NSOnState) || (newState == NSMixedState)) { NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary]; for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) { if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName]; } [syncAccountsDictionary setObject:syncContainersDictionary forKey:@""]; } else { [syncAccountsDictionary removeObjectForKey:@""]; } [outlineView reloadItem:item reloadChildren:YES]; } else if ([item class] == [PithosAccountNode class]) { // root/ // If new state is // mixed/on include sharing account with no exclusions // off exclude sharing account PithosAccountNode *accountNode = (PithosAccountNode *)item; if ((newState == NSOnState) || (newState == NSMixedState)) { NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary]; for (PithosContainerNode *node in accountNode.children) { [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName]; } [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountNode.displayName]; } else { [syncAccountsDictionary removeObjectForKey:accountNode.displayName]; } [outlineView reloadItem:item reloadChildren:YES]; } else if ([item class] == [PithosContainerNode class]) { // root/{, }/ // If new state is // mixed/on include container with no excluded directories // off exclude container PithosContainerNode *node = (PithosContainerNode *)item; NSString *accountName = node.sharingAccount; PithosNode *accountNode = node.parent; if (!accountName) { accountName = @""; accountNode = syncAccountsMyAccountNode; } NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName]; if ((newState == NSOnState) || (newState == NSMixedState)) { if (!syncContainersDictionary) { syncContainersDictionary = [NSMutableDictionary dictionary]; [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName]; } [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName]; } else if (syncContainersDictionary) { [syncContainersDictionary removeObjectForKey:node.displayName]; if (![syncContainersDictionary count]) [syncAccountsDictionary removeObjectForKey:accountName]; } [outlineView reloadItem:accountNode reloadChildren:YES]; } else if ([item class] == [PithosSubdirNode class]) { // root/{, }// // If new state is // mixed/on include directory (if container not included, include and exclude all others) // off exclude directory PithosSubdirNode *node = (PithosSubdirNode *)item; NSString *accountName = node.sharingAccount; PithosNode *accountNode = node.parent.parent; if (!accountName) { accountName = @""; accountNode = syncAccountsMyAccountNode; } NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName]; NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName]; NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]; if ((newState == NSOnState) || (newState == NSMixedState)) { if (containerExcludedDirectories) { [containerExcludedDirectories removeObject:directoryName]; } else { if (!syncContainersDictionary) { syncContainersDictionary = [NSMutableDictionary dictionary]; [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName]; } NSMutableSet *newContainerExcludeDirectories = [NSMutableSet setWithObject:@""]; for (PithosNode *siblingNode in node.parent.children) { if (([siblingNode class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) { NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]; if (![siblingDirectoryName isEqualToString:directoryName] && ![newContainerExcludeDirectories containsObject:siblingDirectoryName]) [newContainerExcludeDirectories addObject:siblingDirectoryName]; } } [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName]; } } else if (syncContainersDictionary && containerExcludedDirectories && ![containerExcludedDirectories containsObject:directoryName]) { [containerExcludedDirectories addObject:directoryName]; } [outlineView reloadItem:accountNode reloadChildren:YES]; } else if ([item class] == [PithosEmptyNode class]) { // If new state is // mixed/on include root files (if container not included, include and exclude all others) // off exclude root files PithosEmptyNode *node = (PithosEmptyNode *)item; NSString *accountName = node.parent.sharingAccount; PithosNode *accountNode = node.parent.parent; if (!accountName) { accountName = @""; accountNode = syncAccountsMyAccountNode; } NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName]; NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName]; if ((newState == NSOnState) || (newState == NSMixedState)) { if (containerExcludedDirectories) { [containerExcludedDirectories removeObject:@""]; } else { if (!syncContainersDictionary) { syncContainersDictionary = [NSMutableDictionary dictionary]; [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName]; } NSMutableSet *newContainerExcludeDirectories = [NSMutableSet set]; for (PithosNode *siblingNode in node.parent.children) { if (([siblingNode class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) { NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]; if (![newContainerExcludeDirectories containsObject:siblingDirectoryName]) [newContainerExcludeDirectories addObject:siblingDirectoryName]; } } [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName]; } } else if (syncContainersDictionary && containerExcludedDirectories && ![containerExcludedDirectories containsObject:@""]) { [containerExcludedDirectories addObject:@""]; } [outlineView reloadItem:accountNode reloadChildren:YES]; } [self updateSync]; } } } @end