2 // PithosPreferencesController.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosPreferencesController.h"
39 #import "PithosBrowserController.h"
40 #import "PithosAccountNode.h"
41 #import "PithosSharingAccountsNode.h"
42 #import "PithosContainerNode.h"
43 #import "PithosSubdirNode.h"
44 #import "PithosObjectNode.h"
45 #import "PithosEmptyNode.h"
46 #import "PithosAccount.h"
47 #import "pithos_macosAppDelegate.h"
49 #import "ImageAndTextCell.h"
50 @interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {}
53 @implementation PithosPreferencesSyncOutlineViewCell
55 - (void)setObjectValue:(id)object {
56 if ([object isKindOfClass:[PithosNode class]]) {
57 PithosNode *node = (PithosNode *)object;
58 [self setStringValue:node.displayName];
59 [self setImage:node.icon];
60 [self setEditable:NO];
62 [super setObjectValue:object];
68 @implementation PithosPreferencesController
69 @synthesize selectedPithosAccount;
70 @synthesize accountsArrayController;
71 @synthesize accountRemoveEnable;
72 @synthesize serverURL, authUser, authToken, manual, loginEnable, loginCancelEnable;
73 @synthesize syncActive, syncSkipHidden, syncDirectoryPath, syncAccountsDictionary, syncApplyEnable, syncCancelEnable,
74 syncAccountsOutlineView, syncAccountsRootFilesNodes;
75 @synthesize groupsDictionaryController, selectedGroupMembersDictionaryController;
78 #pragma mark Object Lifecycle
81 return [super initWithWindowNibName:@"PithosPreferencesController"];
84 - (void)windowDidLoad {
85 [super windowDidLoad];
87 NSWindow *window = [self window];
88 [window setHidesOnDeactivate:NO];
89 [window setExcludedFromWindowsMenu:YES];
91 // // Select the first tab when the window is loaded for the first time.
92 // [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
94 [[[syncAccountsOutlineView tableColumns] objectAtIndex:1] setDataCell:[[[PithosPreferencesSyncOutlineViewCell alloc] init] autorelease]];
95 syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<my account>"
96 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]];
98 [groupsDictionaryController setInitialKey:@"group"];
99 [groupsDictionaryController setInitialValue:@"user"];
100 [selectedGroupMembersDictionaryController setInitialKey:@"user"];
101 [selectedGroupMembersDictionaryController setInitialValue:@""];
103 [window setDelegate:self];
105 self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
106 [accountsArrayController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:NULL];
107 [[NSNotificationCenter defaultCenter] addObserver:self
108 selector:@selector(selectedPithosAccountNodeChildrenUpdated:)
109 name:@"SelectedPithosAccountNodeChildrenUpdated"
113 - (BOOL)windowShouldClose:(id)sender {
114 return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated];
117 //- (void)windowWillClose:(NSNotification *)notification {
120 //- (IBAction)toolbarItemSelected:(id)sender {
126 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
127 if ([object isEqualTo:accountsArrayController] &&
128 [keyPath isEqualToString:@"selection"] &&
129 [[accountsArrayController selectedObjects] count]) {
130 self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
134 - (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
135 [syncAccountsOutlineView reloadData];
136 // [syncAccountsOutlineView expandItem:nil expandChildren:YES];
142 - (void)updateAccounts {
143 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
144 self.accountRemoveEnable = (delegate.activated && ([delegate.pithosAccounts count] > 1));
147 - (void)updateLogin {
148 self.loginEnable = ([selectedPithosAccount urlIsValid:serverURL] && (!manual || ([authUser length] && [authToken length])));
149 self.loginCancelEnable = (![selectedPithosAccount.serverURL isEqualToString:serverURL] ||
150 (selectedPithosAccount.authUser && ![selectedPithosAccount.authUser isEqualToString:authUser]) ||
151 (selectedPithosAccount.authToken && ![selectedPithosAccount.authToken isEqualToString:authToken]));
156 self.syncApplyEnable = (selectedPithosAccount.active &&
157 ((selectedPithosAccount.syncActive != syncActive) ||
158 (selectedPithosAccount.syncSkipHidden != syncSkipHidden) ||
159 (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] &&
160 (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] ||
162 ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
163 self.syncCancelEnable = (selectedPithosAccount.active &&
164 ((selectedPithosAccount.syncActive != syncActive) ||
165 (selectedPithosAccount.syncSkipHidden != syncSkipHidden) ||
166 ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
167 ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
173 - (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
174 if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
175 selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
176 selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = nil;
177 [selectedPithosAccount release];
178 selectedPithosAccount = [aSelectedPithosAccount retain];
179 selectedPithosAccount.accountNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
180 selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
182 [self updateAccounts];
183 [self loginCancel:self];
184 [self syncCancel:self];
185 [self groupsRevert:self];
189 #pragma Login Properties
191 - (void)setServerURL:(NSString *)aServerURL {
193 serverURL = [aServerURL copy];
197 - (void)setAuthUser:(NSString *)anAuthUser {
199 authUser = [anAuthUser copy];
203 - (void)setAuthToken:(NSString *)anAuthToken {
205 authToken = [anAuthToken copy];
209 - (void)setManual:(BOOL)aManual {
213 self.authUser = selectedPithosAccount.authUser;
214 self.authToken = selectedPithosAccount.authToken;
218 #pragma Sync Properties
220 - (void)setSyncActive:(BOOL)aSyncActive {
221 syncActive = aSyncActive;
225 - (void)setSyncSkipHidden:(BOOL)aSyncSkipHidden {
226 syncSkipHidden = aSyncSkipHidden;
228 [self selectedPithosAccountNodeChildrenUpdated:nil];
231 - (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
232 [syncDirectoryPath release];
233 syncDirectoryPath = [aSyncDirectoryPath copy];
237 - (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary {
238 [syncAccountsDictionary release];
239 syncAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncAccountsDictionary count]];
240 for (NSString *accountName in aSyncAccountsDictionary) {
241 NSDictionary *aSyncContainersDictionary = [aSyncAccountsDictionary objectForKey:accountName];
242 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
243 for (NSString *containerName in aSyncContainersDictionary) {
244 if (![accountName isEqualToString:@""] || ![[containerName lowercaseString] isEqualToString:@"shared to me"])
245 [syncContainersDictionary setObject:[NSMutableSet setWithSet:[aSyncContainersDictionary objectForKey:containerName]]
246 forKey:containerName];
248 if ([syncContainersDictionary count])
249 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
257 - (IBAction)addAccount:(id)sender {
258 [accountsArrayController addObject:[PithosAccount pithosAccount]];
259 [self updateAccounts];
260 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
261 [delegate.pithosAccountsDictionary setObject:selectedPithosAccount forKey:selectedPithosAccount.name];
262 [delegate savePithosAccounts:self];
265 - (IBAction)removeAccount:(id)sender {
266 [self updateAccounts];
267 if (!accountRemoveEnable)
269 PithosAccount *removedPithosAccount = [selectedPithosAccount retain];
270 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
271 if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
272 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
273 [alert setMessageText:@"Operations Pending"];
274 [alert setInformativeText:@"There are pending operations in the browser, do you want to remove the account and cancel them?"];
275 [alert addButtonWithTitle:@"OK"];
276 [alert addButtonWithTitle:@"Cancel"];
277 NSInteger choice = [alert runModal];
278 if (choice == NSAlertSecondButtonReturn) {
279 [removedPithosAccount release];
283 [accountsArrayController removeObject:selectedPithosAccount];
284 [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name];
285 [delegate removedPithosAccount:removedPithosAccount];
286 [delegate savePithosAccounts:self];
287 [removedPithosAccount release];
288 [self updateAccounts];
291 #pragma Login Actions
293 - (IBAction)login:(id)sender {
294 self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary];
296 [selectedPithosAccount loginWithServerURL:serverURL];
298 [selectedPithosAccount authenticateWithServerURL:serverURL authUser:authUser authToken:authToken];
300 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
301 [delegate savePithosAccounts:self];
302 if (!delegate.activated) {
303 delegate.activated = YES;
304 [delegate showPithosBrowser:self];
306 if ([selectedPithosAccount isEqualTo:delegate.currentPithosAccount])
307 delegate.pithosBrowserController.pithos = selectedPithosAccount.pithos;
311 - (IBAction)loginCancel:(id)server {
312 self.serverURL = selectedPithosAccount.serverURL;
313 self.authUser = selectedPithosAccount.authUser;
314 self.authToken = selectedPithosAccount.authToken;
320 - (IBAction)syncApply:(id)sender {
321 [selectedPithosAccount updateSyncWithSyncActive:syncActive
322 syncDirectoryPath:syncDirectoryPath
323 syncAccountsDictionary:syncAccountsDictionary
324 syncSkipHidden:syncSkipHidden];
326 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
327 [delegate savePithosAccounts:self];
331 - (IBAction)syncCancel:(id)sender {
332 self.syncActive = selectedPithosAccount.syncActive;
333 self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath;
334 self.syncAccountsDictionary = selectedPithosAccount.syncAccountsDictionary;
335 self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary];
336 self.syncSkipHidden = selectedPithosAccount.syncSkipHidden;
339 - (IBAction)syncRefresh:(id)sender {
340 selectedPithosAccount.accountNode.forcedRefresh = YES;
341 [selectedPithosAccount.accountNode invalidateChildrenRecursive];
342 selectedPithosAccount.sharingAccountsNode.forcedRefresh = YES;
343 [selectedPithosAccount.sharingAccountsNode invalidateChildrenRecursive];
344 if (selectedPithosAccount.accountNode.children && selectedPithosAccount.sharingAccountsNode.children) {
348 #pragma mark Groups Actions
350 - (IBAction)groupsApply:(id)sender {
351 [[self window] makeFirstResponder:nil];
352 if (selectedPithosAccount.active)
353 [selectedPithosAccount.accountNode applyInfo];
356 - (IBAction)groupsRevert:(id)sender {
357 if (selectedPithosAccount.active && selectedPithosAccount.accountNode)
358 [selectedPithosAccount.accountNode refreshInfo];
362 #pragma mark NSOutlineViewDataSource
364 // <my account> [PithosEmptyNode]
365 // - <container>+ [PithosContainerNode]
366 // -- <subdir>+ [PithosSubdirNode]
367 // -- <root files> [PithosEmptyNode]
368 // <sharing account>+ [PithosSharingAccountNode]
369 // - <container>+ [PithosContainerNode]
370 // -- <subdir>+ [PithosSubdirNode]
371 // -- <root files> [PithosEmptyNode]
373 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
374 if (!selectedPithosAccount.active)
376 if (outlineView == syncAccountsOutlineView) {
378 // root: <my account> + #<sharing account>
379 NSInteger accountsCount = 0;
380 if ([selectedPithosAccount.accountNode.children count])
382 if (selectedPithosAccount.sharingAccountsNode.children)
383 accountsCount += selectedPithosAccount.sharingAccountsNode.children.count;
384 return accountsCount;
385 } else if (item == syncAccountsMyAccountNode) {
386 // root/<my account>: #<container>
387 if (selectedPithosAccount.accountNode.children) {
388 NSInteger containersCount = 0;
389 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
390 if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"])
393 return containersCount;
395 } else if ([item class] == [PithosAccountNode class]) {
396 // root/<sharing account>: #<container>
397 PithosAccountNode *accountNode = (PithosAccountNode *)item;
398 if (accountNode.children)
399 return accountNode.children.count;
400 } else if ([item class] == [PithosContainerNode class]) {
401 // root/{<my account>, <sharing account>}/<container>: #<subdir> + <root files>
402 PithosContainerNode *containerNode = (PithosContainerNode *)item;
403 if (containerNode.children) {
404 // We add 1 for the root files node
405 NSInteger subdirCount = 1;
406 for (PithosNode *node in containerNode.children) {
407 if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."]))
417 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
418 if (outlineView == syncAccountsOutlineView) {
420 // root: [ <my account>, <sharing account>+ ]
421 if ([selectedPithosAccount.accountNode.children count]) {
423 return syncAccountsMyAccountNode;
425 return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:(index - 1)];
427 return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:index];
429 } else if (item == syncAccountsMyAccountNode) {
430 // root/<my account>: [ <container>+ ]
431 NSInteger currentContainerIndex = -1;
432 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
433 if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) {
434 currentContainerIndex++;
435 if (currentContainerIndex == index)
439 } else if ([item class] == [PithosAccountNode class]) {
440 // root/<sharing account>: [ <container>+ ]
441 return [((PithosAccountNode *)item).children objectAtIndex:index];
442 } else if ([item class] == [PithosContainerNode class]) {
443 // root/{<my account>, <sharing account>}/<container>: [ <subdir>+, <root files> ]
444 PithosContainerNode *containerNode = (PithosContainerNode *)item;
445 NSInteger currentSubdirIndex = -1;
446 for (PithosNode *node in containerNode.children) {
447 if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."])) {
448 currentSubdirIndex++;
449 if (currentSubdirIndex == index)
453 if (++currentSubdirIndex == index) {
454 NSString *accountName = containerNode.sharingAccount;
457 PithosEmptyNode *rootFilesNode = [[syncAccountsRootFilesNodes objectForKey:accountName]
458 objectForKey:containerNode.displayName];
459 if (!rootFilesNode) {
460 if (![syncAccountsRootFilesNodes objectForKey:accountName])
461 [syncAccountsRootFilesNodes setObject:[NSMutableDictionary dictionary] forKey:accountName];
462 rootFilesNode = [[[PithosEmptyNode alloc] initWithDisplayName:@"<root files>"
463 icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]]
465 rootFilesNode.parent = containerNode;
466 [[syncAccountsRootFilesNodes objectForKey:accountName] setObject:rootFilesNode forKey:containerNode.displayName];
468 return rootFilesNode;
475 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
476 if (outlineView == syncAccountsOutlineView) {
477 if ((item == syncAccountsMyAccountNode) ||
478 ([item class] == [PithosAccountNode class]) ||
479 ([item class] == [PithosContainerNode class]))
485 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
486 if (outlineView == syncAccountsOutlineView) {
487 if ([[tableColumn identifier] isEqualToString:@"sync"]) {
488 if (item == syncAccountsMyAccountNode) {
491 // off if not in dictionary
492 // mixed if in dictionary with exclusions
493 // on if in dictionary without exclusions
494 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:@""];
495 if (syncContainersDictionary) {
496 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
497 if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) {
498 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
499 if (!containerExcludedDirectories || [containerExcludedDirectories count])
500 return [NSNumber numberWithUnsignedInteger:NSMixedState];
503 return [NSNumber numberWithUnsignedInteger:NSOnState];
505 return [NSNumber numberWithUnsignedInteger:NSOffState];
506 } else if ([item class] == [PithosAccountNode class]) {
507 // root/<sharing account>
508 // A sharing account is
509 // off if not in dictionary
510 // mixed if in dictionary with exclusions
511 // on if in dictionary without exclusions
512 PithosAccountNode *accountNode = (PithosAccountNode *)item;
513 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountNode.displayName];
514 if (syncContainersDictionary) {
515 for (PithosContainerNode *node in accountNode.children) {
516 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
517 if (!containerExcludedDirectories || [containerExcludedDirectories count])
518 return [NSNumber numberWithUnsignedInteger:NSMixedState];
520 return [NSNumber numberWithUnsignedInteger:NSOnState];
522 return [NSNumber numberWithUnsignedInteger:NSOffState];
523 } else if ([item class] == [PithosContainerNode class]) {
524 // root/{<my account>, <sharing account>}/<container>
526 // off if not in dictionary
527 // mixed if in dictionary with exclusions
528 // on if in dictionary without exclusions
529 PithosContainerNode *node = (PithosContainerNode *)item;
530 NSString *accountName = node.sharingAccount;
533 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
534 objectForKey:node.displayName];
535 if (containerExcludedDirectories) {
536 if ([containerExcludedDirectories count])
537 return [NSNumber numberWithUnsignedInteger:NSMixedState];
539 return [NSNumber numberWithUnsignedInteger:NSOnState];
541 return [NSNumber numberWithUnsignedInteger:NSOffState];
542 } else if ([item class] == [PithosSubdirNode class]) {
543 // root/{<my account>, <sharing account>}/<container>/<subdir>
544 // Directory is off if parent container not in dictionary or if excluded
545 // displayName should be localized and lowercased
546 PithosSubdirNode *node = (PithosSubdirNode *)item;
547 NSString *accountName = node.sharingAccount;
550 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
551 objectForKey:node.parent.displayName];
552 if (!containerExcludedDirectories ||
553 [containerExcludedDirectories
554 containsObject:[[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]])
555 return [NSNumber numberWithUnsignedInteger:NSOffState];
557 return [NSNumber numberWithUnsignedInteger:NSOnState];
558 } else if ([item class] == [PithosEmptyNode class]) {
559 // root/{<my account>, <sharing account>}/<container>/<root files>
560 // Root files is off if parent container not in dictionary or if excluded
561 PithosEmptyNode *node = (PithosEmptyNode *)item;
562 NSString *accountName = node.parent.sharingAccount;
565 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
566 objectForKey:node.parent.displayName];
567 if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""])
568 return [NSNumber numberWithUnsignedInteger:NSOffState];
570 return [NSNumber numberWithUnsignedInteger:NSOnState];
572 return [NSNumber numberWithUnsignedInteger:NSOffState];
573 } else if ([[tableColumn identifier] isEqualToString:@"path"]) {
574 return (PithosNode *)item;
580 - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
581 if (outlineView == syncAccountsOutlineView) {
582 if ([[tableColumn identifier] isEqualToString:@"sync"]) {
583 NSCellStateValue newState = [object unsignedIntegerValue];
584 if (item == syncAccountsMyAccountNode) {
587 // mixed/on include my account with no exclusions
588 // off exclude my account
589 if ((newState == NSOnState) || (newState == NSMixedState)) {
590 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
591 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
592 if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"])
593 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
595 [syncAccountsDictionary setObject:syncContainersDictionary forKey:@""];
597 [syncAccountsDictionary removeObjectForKey:@""];
599 [outlineView reloadItem:item reloadChildren:YES];
600 } else if ([item class] == [PithosAccountNode class]) {
601 // root/<sharing account>
603 // mixed/on include sharing account with no exclusions
604 // off exclude sharing account
605 PithosAccountNode *accountNode = (PithosAccountNode *)item;
606 if ((newState == NSOnState) || (newState == NSMixedState)) {
607 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
608 for (PithosContainerNode *node in accountNode.children) {
609 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
611 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountNode.displayName];
613 [syncAccountsDictionary removeObjectForKey:accountNode.displayName];
615 [outlineView reloadItem:item reloadChildren:YES];
616 } else if ([item class] == [PithosContainerNode class]) {
617 // root/{<my account>, <sharing account>}/<container>
619 // mixed/on include container with no excluded directories
620 // off exclude container
621 PithosContainerNode *node = (PithosContainerNode *)item;
622 NSString *accountName = node.sharingAccount;
623 PithosNode *accountNode = node.parent;
626 accountNode = syncAccountsMyAccountNode;
628 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
629 if ((newState == NSOnState) || (newState == NSMixedState)) {
630 if (!syncContainersDictionary) {
631 syncContainersDictionary = [NSMutableDictionary dictionary];
632 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
634 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
635 } else if (syncContainersDictionary) {
636 [syncContainersDictionary removeObjectForKey:node.displayName];
637 if (![syncContainersDictionary count])
638 [syncAccountsDictionary removeObjectForKey:accountName];
640 [outlineView reloadItem:accountNode reloadChildren:YES];
641 } else if ([item class] == [PithosSubdirNode class]) {
642 // root/{<my account>, <sharing account>}/<container>/<subdir>
644 // mixed/on include directory (if container not included, include and exclude all others)
645 // off exclude directory
646 PithosSubdirNode *node = (PithosSubdirNode *)item;
647 NSString *accountName = node.sharingAccount;
648 PithosNode *accountNode = node.parent.parent;
651 accountNode = syncAccountsMyAccountNode;
653 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
654 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
655 NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
656 if ((newState == NSOnState) || (newState == NSMixedState)) {
657 if (containerExcludedDirectories) {
658 [containerExcludedDirectories removeObject:directoryName];
660 if (!syncContainersDictionary) {
661 syncContainersDictionary = [NSMutableDictionary dictionary];
662 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
664 NSMutableSet *newContainerExcludeDirectories = [NSMutableSet setWithObject:@""];
665 for (PithosNode *siblingNode in node.parent.children) {
666 if (([siblingNode class] == [PithosSubdirNode class]) &&
667 (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
668 NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString]
669 stringByReplacingOccurrencesOfString:@"/" withString:@":"];
670 if (![siblingDirectoryName isEqualToString:directoryName] &&
671 ![newContainerExcludeDirectories containsObject:siblingDirectoryName])
672 [newContainerExcludeDirectories addObject:siblingDirectoryName];
675 [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
677 } else if (syncContainersDictionary &&
678 containerExcludedDirectories &&
679 ![containerExcludedDirectories containsObject:directoryName]) {
680 [containerExcludedDirectories addObject:directoryName];
682 [outlineView reloadItem:accountNode reloadChildren:YES];
683 } else if ([item class] == [PithosEmptyNode class]) {
685 // mixed/on include root files (if container not included, include and exclude all others)
686 // off exclude root files
687 PithosEmptyNode *node = (PithosEmptyNode *)item;
688 NSString *accountName = node.parent.sharingAccount;
689 PithosNode *accountNode = node.parent.parent;
692 accountNode = syncAccountsMyAccountNode;
694 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
695 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
696 if ((newState == NSOnState) || (newState == NSMixedState)) {
697 if (containerExcludedDirectories) {
698 [containerExcludedDirectories removeObject:@""];
700 if (!syncContainersDictionary) {
701 syncContainersDictionary = [NSMutableDictionary dictionary];
702 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
704 NSMutableSet *newContainerExcludeDirectories = [NSMutableSet set];
705 for (PithosNode *siblingNode in node.parent.children) {
706 if (([siblingNode class] == [PithosSubdirNode class]) &&
707 (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
708 NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString]
709 stringByReplacingOccurrencesOfString:@"/" withString:@":"];
710 if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
711 [newContainerExcludeDirectories addObject:siblingDirectoryName];
714 [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
716 } else if (syncContainersDictionary &&
717 containerExcludedDirectories &&
718 ![containerExcludedDirectories containsObject:@""]) {
719 [containerExcludedDirectories addObject:@""];
721 [outlineView reloadItem:accountNode reloadChildren:YES];