// PithosPreferencesController.m
// pithos-macos
//
-// Copyright 2011 GRNET S.A. All rights reserved.
+// 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
// 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 userDefaultsController;
-@synthesize authenticationUserTextField, authenticationTokenTextField, authenticationRenewCheckBox;
+@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)dealloc {
- [authenticationRenewCheckBox release];
- [authenticationTokenTextField release];
- [authenticationUserTextField release];
- [userDefaultsController release];
- [super dealloc];
-}
-
- (void)windowDidLoad {
[super windowDidLoad];
[window setHidesOnDeactivate:NO];
[window setExcludedFromWindowsMenu:YES];
- // Select the first tab when the window is loaded for the first time.
- [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
+// // Select the first tab when the window is loaded for the first time.
+// [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
-// // Monitor changes.
-// [userDefaultsController addObserver:self
-// forKeyPath:@"values.authUser"
-// options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
-// context:NULL];
-// [userDefaultsController addObserver:self
-// forKeyPath:@"values.authToken"
-// options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
-// context:NULL];
+ [[[syncAccountsOutlineView tableColumns] objectAtIndex:1] setDataCell:[[PithosPreferencesSyncOutlineViewCell alloc] init]];
+ syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<my account>"
+ 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];
}
-// We do this to catch the case where the user enters a value into one of
-// the text fields but closes the window without hitting enter or tab.
-// TODO: Does not seem to work.
-- (BOOL)windowShouldClose:(NSWindow *)window {
- return [window makeFirstResponder:nil];
+- (BOOL)windowShouldClose:(id)sender {
+ return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated];
}
-- (IBAction)toolbarItemSelected:(id)sender {
-}
+//- (void)windowWillClose:(NSNotification *)notification {
+//}
-//- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
-// NSLog(@"observeValueForKeyPath: %@ %@", keyPath, [userDefaultsController valueForKeyPath:keyPath]);
+//- (IBAction)toolbarItemSelected:(id)sender {
//}
#pragma mark -
-#pragma Authentication
-
-- (IBAction)authenticationLogin:(id)sender {
- // XXX hardcoded for now
- NSProcessInfo *processInfo = [NSProcessInfo processInfo];
- NSString *loginURL = [NSString stringWithFormat:@"https://pithos.dev.grnet.gr/login?next=pithos://%@_%d",
- [processInfo processName], [processInfo processIdentifier]];
- if ([authenticationRenewCheckBox state] == NSOnState)
- loginURL = [loginURL stringByAppendingString:@"&renew="];
- NSLog(@"loginURL: %@", loginURL);
- [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
- // XXX Should we wait for results or do something else?
- // XXX check the case where this happens for the first time
-}
-
-- (IBAction)authenticationManual:(id)sender {
- [userDefaultsController save:sender];
- // Because of delayed saves of the userDefaultsController, we use the TextField values directly, instead of
- //NSString *authUser = [[userDefaultsController values] valueForKey:@"authUser"];
- //NSString *authToken = [[userDefaultsController values] valueForKey:@"authToken"];
- [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
+#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 = aSelectedPithosAccount;
+ 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 = [aServerURL copy];
+ [self updateLogin];
+}
+
+- (void)setAuthUser:(NSString *)anAuthUser {
+ authUser = [anAuthUser copy];
+ [self updateLogin];
+}
+
+- (void)setAuthToken:(NSString *)anAuthToken {
+ 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 = [aSyncDirectoryPath copy];
+ [self updateSync];
+}
+
+- (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary {
+ 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;
+ pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
+ if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [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) {
+ return;
+ }
+ }
+ [accountsArrayController removeObject:selectedPithosAccount];
+ [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name];
+ [delegate removedPithosAccount:removedPithosAccount];
+ [delegate savePithosAccounts:self];
+ [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
+
+// <my account> [PithosEmptyNode]
+// - <container>+ [PithosContainerNode]
+// -- <subdir>+ [PithosSubdirNode]
+// -- <root files> [PithosEmptyNode]
+// <sharing account>+ [PithosSharingAccountNode]
+// - <container>+ [PithosContainerNode]
+// -- <subdir>+ [PithosSubdirNode]
+// -- <root files> [PithosEmptyNode]
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+ if (!selectedPithosAccount.active)
+ return 0;
+ if (outlineView == syncAccountsOutlineView) {
+ if (item == nil) {
+ // root: <my account> + #<sharing account>
+ 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/<my account>: #<container>
+ 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/<sharing account>: #<container>
+ PithosAccountNode *accountNode = (PithosAccountNode *)item;
+ if (accountNode.children)
+ return accountNode.children.count;
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>: #<subdir> + <root files>
+ 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: [ <my account>, <sharing account>+ ]
+ 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/<my account>: [ <container>+ ]
+ 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/<sharing account>: [ <container>+ ]
+ return [((PithosAccountNode *)item).children objectAtIndex:index];
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>: [ <subdir>+, <root files> ]
+ 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:@"<root files>"
+ icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]];
+ 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>
+ // 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/<sharing account>
+ // 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/{<my account>, <sharing account>}/<container>
+ // 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/{<my account>, <sharing account>}/<container>/<subdir>
+ // 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/{<my account>, <sharing account>}/<container>/<root files>
+ // 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/<my account>
+ // 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/<sharing account>
+ // 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/{<my account>, <sharing account>}/<container>
+ // 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/{<my account>, <sharing account>}/<container>/<subdir>
+ // 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