Convert to Objective-C ARC
[pithos-macos] / pithos-macos / PithosPreferencesController.m
index 4cbf3ac..c978c02 100644 (file)
 #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;
-@synthesize syncActive, syncDirectoryPath, syncApplyEnable, syncCancelEnable;
 
 #pragma mark -
 #pragma mark Object Lifecycle
     
 //     // 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]];
+    syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<my account>" 
+                                                                        icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]];
+    
     [groupsDictionaryController setInitialKey:@"group"];
     [groupsDictionaryController setInitialValue:@"user"];
     [selectedGroupMembersDictionaryController setInitialKey:@"user"];
     
     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 {
     }
 }
 
+- (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
+    [syncAccountsOutlineView reloadData];
+//    [syncAccountsOutlineView expandItem:nil expandChildren:YES];
+}
+
 #pragma mark -
 #pragma Update
 
     BOOL isDirectory;
     self.syncApplyEnable = (selectedPithosAccount.active && 
                             ((selectedPithosAccount.syncActive != syncActive) || 
+                             (selectedPithosAccount.syncSkipHidden != syncSkipHidden) || 
                              (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] && 
                               (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] || 
-                               isDirectory))));
+                               isDirectory)) ||
+                             ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
     self.syncCancelEnable = (selectedPithosAccount.active && 
                              ((selectedPithosAccount.syncActive != syncActive) || 
-                              ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath]));
+                              (selectedPithosAccount.syncSkipHidden != syncSkipHidden) || 
+                              ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
+                              ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
 }
 
 #pragma mark -
 
 - (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
     if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
-        [selectedPithosAccount release];
-        selectedPithosAccount = [aSelectedPithosAccount retain];
+        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
+        selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = nil;
+        selectedPithosAccount = aSelectedPithosAccount;
+        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
+        selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
         
         [self updateAccounts];
         [self loginCancel: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];
 }
     manual = aManual;
     [self updateLogin];
     if (!manual) {
-        self.authUser = [[selectedPithosAccount.authUser copy] autorelease];
-        self.authToken = [[selectedPithosAccount.authToken copy] autorelease];
+        self.authUser = selectedPithosAccount.authUser;
+        self.authToken = selectedPithosAccount.authToken;
     }
 }
 
     [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 = [[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
 
     [self updateAccounts];
     if (!accountRemoveEnable)
         return;
-    PithosAccount *removedPithosAccount = [selectedPithosAccount retain];
+    PithosAccount *removedPithosAccount = selectedPithosAccount;
     pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
     if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
-        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+        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) {
-            [removedPithosAccount release];
             return;
         }
     }
     [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 {
 }
 
 - (IBAction)loginCancel:(id)server {
-    self.serverURL = [[selectedPithosAccount.serverURL copy] autorelease];
-    self.authUser = [[selectedPithosAccount.authUser copy] autorelease];
-    self.authToken = [[selectedPithosAccount.authToken copy] autorelease];
+    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];
+    [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 copy] autorelease];
+    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
         [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