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 "ASIPithosAccount.h"
48 #import "pithos_macosAppDelegate.h"
50 #import "ImageAndTextCell.h"
51 @interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {}
54 @implementation PithosPreferencesSyncOutlineViewCell
56 - (void)setObjectValue:(id)object {
57 if ([object isKindOfClass:[PithosNode class]]) {
58 PithosNode *node = (PithosNode *)object;
59 [self setStringValue:node.displayName];
60 [self setImage:node.icon];
61 [self setEditable:NO];
63 [super setObjectValue:object];
69 @implementation PithosPreferencesController
70 @synthesize selectedPithosAccount;
71 @synthesize accountsArrayController;
72 @synthesize accountRemoveEnable;
73 @synthesize serverURL, authUser, authToken, displayname, manual, loginEnable, loginCancelEnable;
74 @synthesize syncActive, syncSkipHidden, syncDirectoryPath, syncAccountsDictionary, syncApplyEnable, syncCancelEnable,
75 syncAccountsOutlineView, syncAccountsRootFilesNodes;
76 @synthesize groupsDictionaryController, selectedGroupMembersDictionaryController;
79 #pragma mark Object Lifecycle
82 return [super initWithWindowNibName:@"PithosPreferencesController"];
85 - (void)windowDidLoad {
86 [super windowDidLoad];
88 NSWindow *window = [self window];
89 [window setHidesOnDeactivate:NO];
90 [window setExcludedFromWindowsMenu:YES];
92 // // Select the first tab when the window is loaded for the first time.
93 // [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
95 [[[syncAccountsOutlineView tableColumns] objectAtIndex:1] setDataCell:[[PithosPreferencesSyncOutlineViewCell alloc] init]];
96 syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<my account>"
97 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]];
99 [groupsDictionaryController setInitialKey:@"group"];
100 [groupsDictionaryController setInitialValue:[NSMutableArray arrayWithObject:@"user"]];
101 [selectedGroupMembersDictionaryController setInitialKey:@"user"];
102 [selectedGroupMembersDictionaryController setInitialValue:@""];
104 [window setDelegate:self];
106 self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
107 [accountsArrayController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:NULL];
108 [[NSNotificationCenter defaultCenter] addObserver:self
109 selector:@selector(selectedPithosAccountNodeChildrenUpdated:)
110 name:@"SelectedPithosAccountNodeChildrenUpdated"
114 - (BOOL)windowShouldClose:(id)sender {
115 return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated];
118 //- (void)windowWillClose:(NSNotification *)notification {
121 //- (IBAction)toolbarItemSelected:(id)sender {
127 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
128 if ([object isEqualTo:accountsArrayController] &&
129 [keyPath isEqualToString:@"selection"] &&
130 [[accountsArrayController selectedObjects] count]) {
131 self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
135 - (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
136 [syncAccountsOutlineView reloadData];
137 // [syncAccountsOutlineView expandItem:nil expandChildren:YES];
143 - (void)updateAccounts {
144 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
145 self.accountRemoveEnable = (delegate.activated && ([delegate.pithosAccounts count] > 1));
148 - (void)updateLogin {
149 self.loginEnable = ([selectedPithosAccount urlIsValid:serverURL] && (!manual || ([authUser length] && [authToken length])));
150 self.loginCancelEnable = (![selectedPithosAccount.serverURL isEqualToString:serverURL] ||
151 (selectedPithosAccount.authUser && ![selectedPithosAccount.authUser isEqualToString:authUser]) ||
152 (selectedPithosAccount.authToken && ![selectedPithosAccount.authToken isEqualToString:authToken]));
157 self.syncApplyEnable = (selectedPithosAccount.active &&
158 ((selectedPithosAccount.syncActive != syncActive) ||
159 (selectedPithosAccount.syncSkipHidden != syncSkipHidden) ||
160 (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] &&
161 (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] ||
163 ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
164 self.syncCancelEnable = (selectedPithosAccount.active &&
165 ((selectedPithosAccount.syncActive != syncActive) ||
166 (selectedPithosAccount.syncSkipHidden != syncSkipHidden) ||
167 ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
168 ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
174 - (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
175 if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
176 selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
177 selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = nil;
178 selectedPithosAccount = aSelectedPithosAccount;
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 {
192 serverURL = [aServerURL copy];
196 - (void)setAuthUser:(NSString *)anAuthUser {
197 authUser = [anAuthUser copy];
199 self.displayname = [self.selectedPithosAccount displaynameForUUID:authUser safe:NO];
202 - (void)setAuthToken:(NSString *)anAuthToken {
203 authToken = [anAuthToken copy];
207 - (void)setManual:(BOOL)aManual {
211 self.authUser = selectedPithosAccount.authUser;
212 self.authToken = selectedPithosAccount.authToken;
216 #pragma Sync Properties
218 - (void)setSyncActive:(BOOL)aSyncActive {
219 syncActive = aSyncActive;
223 - (void)setSyncSkipHidden:(BOOL)aSyncSkipHidden {
224 syncSkipHidden = aSyncSkipHidden;
226 [self selectedPithosAccountNodeChildrenUpdated:nil];
229 - (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
230 syncDirectoryPath = [aSyncDirectoryPath copy];
234 - (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary {
235 syncAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncAccountsDictionary count]];
236 for (NSString *accountName in aSyncAccountsDictionary) {
237 NSDictionary *aSyncContainersDictionary = [aSyncAccountsDictionary objectForKey:accountName];
238 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
239 for (NSString *containerName in aSyncContainersDictionary) {
240 if (![accountName isEqualToString:@""] || ![[containerName lowercaseString] isEqualToString:@"shared with me"])
241 [syncContainersDictionary setObject:[NSMutableSet setWithSet:[aSyncContainersDictionary objectForKey:containerName]]
242 forKey:containerName];
244 if ([syncContainersDictionary count])
245 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
253 - (IBAction)addAccount:(id)sender {
254 [accountsArrayController addObject:[PithosAccount pithosAccount]];
255 [self updateAccounts];
256 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
257 [delegate.pithosAccountsDictionary setObject:selectedPithosAccount forKey:selectedPithosAccount.name];
258 [delegate savePithosAccounts:self];
261 - (IBAction)removeAccount:(id)sender {
262 [self updateAccounts];
263 if (!accountRemoveEnable)
265 PithosAccount *removedPithosAccount = selectedPithosAccount;
266 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
267 if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
268 NSAlert *alert = [[NSAlert alloc] init];
269 [alert setMessageText:@"Operations Pending"];
270 [alert setInformativeText:@"There are pending operations in the browser, do you want to remove the account and cancel them?"];
271 [alert addButtonWithTitle:@"OK"];
272 [alert addButtonWithTitle:@"Cancel"];
273 NSInteger choice = [alert runModal];
274 if (choice == NSAlertSecondButtonReturn) {
278 [accountsArrayController removeObject:selectedPithosAccount];
279 [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name];
280 [delegate removedPithosAccount:removedPithosAccount];
281 [delegate savePithosAccounts:self];
282 [self updateAccounts];
285 #pragma Login Actions
287 - (IBAction)login:(id)sender {
288 self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary];
290 [selectedPithosAccount loginWithServerURL:serverURL];
292 [selectedPithosAccount authenticateWithServerURL:serverURL authUser:authUser authToken:authToken];
294 self.displayname = [selectedPithosAccount displaynameForUUID:authUser safe:NO];
295 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
296 [delegate savePithosAccounts:self];
297 if (!delegate.activated) {
298 delegate.activated = YES;
299 [delegate showPithosBrowser:self];
301 if ([selectedPithosAccount isEqualTo:delegate.currentPithosAccount])
302 delegate.pithosBrowserController.pithos = selectedPithosAccount.pithos;
306 - (IBAction)loginCancel:(id)server {
307 self.serverURL = selectedPithosAccount.serverURL;
308 self.authUser = selectedPithosAccount.authUser;
309 self.authToken = selectedPithosAccount.authToken;
315 - (IBAction)syncApply:(id)sender {
316 [selectedPithosAccount updateSyncWithSyncActive:syncActive
317 syncDirectoryPath:syncDirectoryPath
318 syncAccountsDictionary:syncAccountsDictionary
319 syncSkipHidden:syncSkipHidden];
321 pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
322 [delegate savePithosAccounts:self];
326 - (IBAction)syncCancel:(id)sender {
327 self.syncActive = selectedPithosAccount.syncActive;
328 self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath;
329 self.syncAccountsDictionary = selectedPithosAccount.syncAccountsDictionary;
330 self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary];
331 self.syncSkipHidden = selectedPithosAccount.syncSkipHidden;
334 - (IBAction)syncRefresh:(id)sender {
335 selectedPithosAccount.accountNode.forcedRefresh = YES;
336 [selectedPithosAccount.accountNode invalidateChildrenRecursive];
337 selectedPithosAccount.sharingAccountsNode.forcedRefresh = YES;
338 [selectedPithosAccount.sharingAccountsNode invalidateChildrenRecursive];
339 if (selectedPithosAccount.accountNode.children && selectedPithosAccount.sharingAccountsNode.children) {
343 #pragma mark Groups Actions
345 - (IBAction)groupsApply:(id)sender {
346 [[self window] makeFirstResponder:nil];
347 if (selectedPithosAccount.active)
348 [selectedPithosAccount.accountNode applyInfo];
351 - (IBAction)groupsRevert:(id)sender {
352 if (selectedPithosAccount.active && selectedPithosAccount.accountNode)
353 [selectedPithosAccount.accountNode refreshInfo];
357 #pragma mark NSOutlineViewDataSource
359 // <my account> [PithosEmptyNode]
360 // - <container>+ [PithosContainerNode]
361 // -- <subdir>+ [PithosSubdirNode]
362 // -- <root files> [PithosEmptyNode]
363 // <sharing account>+ [PithosSharingAccountNode]
364 // - <container>+ [PithosContainerNode]
365 // -- <subdir>+ [PithosSubdirNode]
366 // -- <root files> [PithosEmptyNode]
368 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
369 if (!selectedPithosAccount.active)
371 if (outlineView == syncAccountsOutlineView) {
373 // root: <my account> + #<sharing account>
374 NSInteger accountsCount = 0;
375 if ([selectedPithosAccount.accountNode.children count])
377 if (selectedPithosAccount.sharingAccountsNode.children)
378 accountsCount += selectedPithosAccount.sharingAccountsNode.children.count;
379 return accountsCount;
380 } else if (item == syncAccountsMyAccountNode) {
381 // root/<my account>: #<container>
382 if (selectedPithosAccount.accountNode.children) {
383 NSInteger containersCount = 0;
384 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
385 if (![[node.displayName lowercaseString] isEqualToString:@"shared with me"])
388 return containersCount;
390 } else if ([item class] == [PithosAccountNode class]) {
391 // root/<sharing account>: #<container>
392 PithosAccountNode *accountNode = (PithosAccountNode *)item;
393 if (accountNode.children)
394 return accountNode.children.count;
395 } else if ([item class] == [PithosContainerNode class]) {
396 // root/{<my account>, <sharing account>}/<container>: #<subdir> + <root files>
397 PithosContainerNode *containerNode = (PithosContainerNode *)item;
398 if (containerNode.children) {
399 // We add 1 for the root files node
400 NSInteger subdirCount = 1;
401 for (PithosNode *node in containerNode.children) {
402 if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."]))
412 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
413 if (outlineView == syncAccountsOutlineView) {
415 // root: [ <my account>, <sharing account>+ ]
416 if ([selectedPithosAccount.accountNode.children count]) {
418 return syncAccountsMyAccountNode;
420 return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:(index - 1)];
422 return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:index];
424 } else if (item == syncAccountsMyAccountNode) {
425 // root/<my account>: [ <container>+ ]
426 NSInteger currentContainerIndex = -1;
427 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
428 if (![[node.displayName lowercaseString] isEqualToString:@"shared with me"]) {
429 currentContainerIndex++;
430 if (currentContainerIndex == index)
434 } else if ([item class] == [PithosAccountNode class]) {
435 // root/<sharing account>: [ <container>+ ]
436 return [((PithosAccountNode *)item).children objectAtIndex:index];
437 } else if ([item class] == [PithosContainerNode class]) {
438 // root/{<my account>, <sharing account>}/<container>: [ <subdir>+, <root files> ]
439 PithosContainerNode *containerNode = (PithosContainerNode *)item;
440 NSInteger currentSubdirIndex = -1;
441 for (PithosNode *node in containerNode.children) {
442 if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."])) {
443 currentSubdirIndex++;
444 if (currentSubdirIndex == index)
448 if (++currentSubdirIndex == index) {
449 NSString *accountName = containerNode.sharingAccount;
452 PithosEmptyNode *rootFilesNode = [[syncAccountsRootFilesNodes objectForKey:accountName]
453 objectForKey:containerNode.displayName];
454 if (!rootFilesNode) {
455 if (![syncAccountsRootFilesNodes objectForKey:accountName])
456 [syncAccountsRootFilesNodes setObject:[NSMutableDictionary dictionary] forKey:accountName];
457 rootFilesNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<root files>"
458 icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]];
459 rootFilesNode.parent = containerNode;
460 [[syncAccountsRootFilesNodes objectForKey:accountName] setObject:rootFilesNode forKey:containerNode.displayName];
462 return rootFilesNode;
469 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
470 if (outlineView == syncAccountsOutlineView) {
471 if ((item == syncAccountsMyAccountNode) ||
472 ([item class] == [PithosAccountNode class]) ||
473 ([item class] == [PithosContainerNode class]))
479 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
480 if (outlineView == syncAccountsOutlineView) {
481 if ([[tableColumn identifier] isEqualToString:@"sync"]) {
482 if (item == syncAccountsMyAccountNode) {
485 // off if not in dictionary
486 // mixed if in dictionary with exclusions
487 // on if in dictionary without exclusions
488 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:@""];
489 if (syncContainersDictionary) {
490 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
491 if (![[node.displayName lowercaseString] isEqualToString:@"shared with me"]) {
492 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
493 if (!containerExcludedDirectories || [containerExcludedDirectories count])
494 return [NSNumber numberWithUnsignedInteger:NSMixedState];
497 return [NSNumber numberWithUnsignedInteger:NSOnState];
499 return [NSNumber numberWithUnsignedInteger:NSOffState];
500 } else if ([item class] == [PithosAccountNode class]) {
501 // root/<sharing account>
502 // A sharing account is
503 // off if not in dictionary
504 // mixed if in dictionary with exclusions
505 // on if in dictionary without exclusions
506 PithosAccountNode *accountNode = (PithosAccountNode *)item;
507 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountNode.displayName];
508 if (syncContainersDictionary) {
509 for (PithosContainerNode *node in accountNode.children) {
510 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
511 if (!containerExcludedDirectories || [containerExcludedDirectories count])
512 return [NSNumber numberWithUnsignedInteger:NSMixedState];
514 return [NSNumber numberWithUnsignedInteger:NSOnState];
516 return [NSNumber numberWithUnsignedInteger:NSOffState];
517 } else if ([item class] == [PithosContainerNode class]) {
518 // root/{<my account>, <sharing account>}/<container>
520 // off if not in dictionary
521 // mixed if in dictionary with exclusions
522 // on if in dictionary without exclusions
523 PithosContainerNode *node = (PithosContainerNode *)item;
524 NSString *accountName = node.sharingAccount;
527 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
528 objectForKey:node.displayName];
529 if (containerExcludedDirectories) {
530 if ([containerExcludedDirectories count])
531 return [NSNumber numberWithUnsignedInteger:NSMixedState];
533 return [NSNumber numberWithUnsignedInteger:NSOnState];
535 return [NSNumber numberWithUnsignedInteger:NSOffState];
536 } else if ([item class] == [PithosSubdirNode class]) {
537 // root/{<my account>, <sharing account>}/<container>/<subdir>
538 // Directory is off if parent container not in dictionary or if excluded
539 // displayName should be localized and lowercased
540 PithosSubdirNode *node = (PithosSubdirNode *)item;
541 NSString *accountName = node.sharingAccount;
544 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
545 objectForKey:node.parent.displayName];
546 if (!containerExcludedDirectories ||
547 [containerExcludedDirectories
548 containsObject:[[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]])
549 return [NSNumber numberWithUnsignedInteger:NSOffState];
551 return [NSNumber numberWithUnsignedInteger:NSOnState];
552 } else if ([item class] == [PithosEmptyNode class]) {
553 // root/{<my account>, <sharing account>}/<container>/<root files>
554 // Root files is off if parent container not in dictionary or if excluded
555 PithosEmptyNode *node = (PithosEmptyNode *)item;
556 NSString *accountName = node.parent.sharingAccount;
559 NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
560 objectForKey:node.parent.displayName];
561 if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""])
562 return [NSNumber numberWithUnsignedInteger:NSOffState];
564 return [NSNumber numberWithUnsignedInteger:NSOnState];
566 return [NSNumber numberWithUnsignedInteger:NSOffState];
567 } else if ([[tableColumn identifier] isEqualToString:@"path"]) {
568 return (PithosNode *)item;
574 - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
575 if (outlineView == syncAccountsOutlineView) {
576 if ([[tableColumn identifier] isEqualToString:@"sync"]) {
577 NSCellStateValue newState = [object unsignedIntegerValue];
578 if (item == syncAccountsMyAccountNode) {
581 // mixed/on include my account with no exclusions
582 // off exclude my account
583 if ((newState == NSOnState) || (newState == NSMixedState)) {
584 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
585 for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
586 if (![[node.displayName lowercaseString] isEqualToString:@"shared with me"])
587 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
589 [syncAccountsDictionary setObject:syncContainersDictionary forKey:@""];
591 [syncAccountsDictionary removeObjectForKey:@""];
593 [outlineView reloadItem:item reloadChildren:YES];
594 } else if ([item class] == [PithosAccountNode class]) {
595 // root/<sharing account>
597 // mixed/on include sharing account with no exclusions
598 // off exclude sharing account
599 PithosAccountNode *accountNode = (PithosAccountNode *)item;
600 if ((newState == NSOnState) || (newState == NSMixedState)) {
601 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
602 for (PithosContainerNode *node in accountNode.children) {
603 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
605 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountNode.displayName];
607 [syncAccountsDictionary removeObjectForKey:accountNode.displayName];
609 [outlineView reloadItem:item reloadChildren:YES];
610 } else if ([item class] == [PithosContainerNode class]) {
611 // root/{<my account>, <sharing account>}/<container>
613 // mixed/on include container with no excluded directories
614 // off exclude container
615 PithosContainerNode *node = (PithosContainerNode *)item;
616 NSString *accountName = node.sharingAccount;
617 PithosNode *accountNode = node.parent;
620 accountNode = syncAccountsMyAccountNode;
622 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
623 if ((newState == NSOnState) || (newState == NSMixedState)) {
624 if (!syncContainersDictionary) {
625 syncContainersDictionary = [NSMutableDictionary dictionary];
626 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
628 [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
629 } else if (syncContainersDictionary) {
630 [syncContainersDictionary removeObjectForKey:node.displayName];
631 if (![syncContainersDictionary count])
632 [syncAccountsDictionary removeObjectForKey:accountName];
634 [outlineView reloadItem:accountNode reloadChildren:YES];
635 } else if ([item class] == [PithosSubdirNode class]) {
636 // root/{<my account>, <sharing account>}/<container>/<subdir>
638 // mixed/on include directory (if container not included, include and exclude all others)
639 // off exclude directory
640 PithosSubdirNode *node = (PithosSubdirNode *)item;
641 NSString *accountName = node.sharingAccount;
642 PithosNode *accountNode = node.parent.parent;
645 accountNode = syncAccountsMyAccountNode;
647 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
648 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
649 NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
650 if ((newState == NSOnState) || (newState == NSMixedState)) {
651 if (containerExcludedDirectories) {
652 [containerExcludedDirectories removeObject:directoryName];
654 if (!syncContainersDictionary) {
655 syncContainersDictionary = [NSMutableDictionary dictionary];
656 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
658 NSMutableSet *newContainerExcludeDirectories = [NSMutableSet setWithObject:@""];
659 for (PithosNode *siblingNode in node.parent.children) {
660 if (([siblingNode class] == [PithosSubdirNode class]) &&
661 (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
662 NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString]
663 stringByReplacingOccurrencesOfString:@"/" withString:@":"];
664 if (![siblingDirectoryName isEqualToString:directoryName] &&
665 ![newContainerExcludeDirectories containsObject:siblingDirectoryName])
666 [newContainerExcludeDirectories addObject:siblingDirectoryName];
669 [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
671 } else if (syncContainersDictionary &&
672 containerExcludedDirectories &&
673 ![containerExcludedDirectories containsObject:directoryName]) {
674 [containerExcludedDirectories addObject:directoryName];
676 [outlineView reloadItem:accountNode reloadChildren:YES];
677 } else if ([item class] == [PithosEmptyNode class]) {
679 // mixed/on include root files (if container not included, include and exclude all others)
680 // off exclude root files
681 PithosEmptyNode *node = (PithosEmptyNode *)item;
682 NSString *accountName = node.parent.sharingAccount;
683 PithosNode *accountNode = node.parent.parent;
686 accountNode = syncAccountsMyAccountNode;
688 NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
689 NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
690 if ((newState == NSOnState) || (newState == NSMixedState)) {
691 if (containerExcludedDirectories) {
692 [containerExcludedDirectories removeObject:@""];
694 if (!syncContainersDictionary) {
695 syncContainersDictionary = [NSMutableDictionary dictionary];
696 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
698 NSMutableSet *newContainerExcludeDirectories = [NSMutableSet set];
699 for (PithosNode *siblingNode in node.parent.children) {
700 if (([siblingNode class] == [PithosSubdirNode class]) &&
701 (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
702 NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString]
703 stringByReplacingOccurrencesOfString:@"/" withString:@":"];
704 if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
705 [newContainerExcludeDirectories addObject:siblingDirectoryName];
708 [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
710 } else if (syncContainersDictionary &&
711 containerExcludedDirectories &&
712 ![containerExcludedDirectories containsObject:@""]) {
713 [containerExcludedDirectories addObject:@""];
715 [outlineView reloadItem:accountNode reloadChildren:YES];