Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosPreferencesController.m @ e8dc9335

History | View | Annotate | Download (33.8 kB)

1
//
2
//  PithosPreferencesController.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
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.
19
// 
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.
32
// 
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.
37

    
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"
48

    
49
#import "ImageAndTextCell.h"
50
@interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {}
51
@end
52

    
53
@implementation PithosPreferencesSyncOutlineViewCell
54

    
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];
61
    } else {
62
        [super setObjectValue:object];
63
    }
64
}
65

    
66
@end
67

    
68
@implementation PithosPreferencesController
69
@synthesize selectedPithosAccount;
70
@synthesize accountsArrayController;
71
@synthesize accountRemoveEnable;
72
@synthesize serverURL, authUser, authToken, manual, loginEnable, loginCancelEnable;
73
@synthesize syncActive, syncDirectoryPath, syncAccountsDictionary, syncApplyEnable, syncCancelEnable, 
74
            syncAccountsOutlineView, syncAccountsRootFilesNodes;
75
@synthesize groupsDictionaryController, selectedGroupMembersDictionaryController;
76

    
77
#pragma mark -
78
#pragma mark Object Lifecycle
79

    
80
- (id)init {
81
    return [super initWithWindowNibName:@"PithosPreferencesController"];
82
}
83

    
84
- (void)windowDidLoad {
85
    [super windowDidLoad];
86
    
87
    NSWindow *window = [self window];
88
    [window setHidesOnDeactivate:NO];
89
    [window setExcludedFromWindowsMenu:YES];
90
    
91
//	// Select the first tab when the window is loaded for the first time.
92
//	[[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
93
    
94
    [[[syncAccountsOutlineView tableColumns] objectAtIndex:1] setDataCell:[[[PithosPreferencesSyncOutlineViewCell alloc] init] autorelease]];
95
    syncAccountsMyAccountNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<my account>" 
96
                                                                        icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]];
97
    
98
    [groupsDictionaryController setInitialKey:@"group"];
99
    [groupsDictionaryController setInitialValue:@"user"];
100
    [selectedGroupMembersDictionaryController setInitialKey:@"user"];
101
    [selectedGroupMembersDictionaryController setInitialValue:@""];
102

    
103
    [window setDelegate:self];
104
    
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" 
110
                                               object:nil];
111
}
112

    
113
- (BOOL)windowShouldClose:(id)sender {
114
    return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated];
115
}
116

    
117
//- (void)windowWillClose:(NSNotification *)notification {
118
//}
119

    
120
//- (IBAction)toolbarItemSelected:(id)sender {
121
//}
122

    
123
#pragma mark -
124
#pragma Observers
125

    
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];
131
    }
132
}
133

    
134
- (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
135
    [syncAccountsOutlineView reloadData];
136
    [syncAccountsOutlineView expandItem:nil expandChildren:YES];
137
}
138

    
139
#pragma mark -
140
#pragma Update
141

    
142
- (void)updateAccounts {
143
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
144
    self.accountRemoveEnable = (delegate.activated && ([delegate.pithosAccounts count] > 1));
145
}
146

    
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]));
152
}
153

    
154
- (void)updateSync {
155
    BOOL isDirectory;
156
    self.syncApplyEnable = (selectedPithosAccount.active && 
157
                            ((selectedPithosAccount.syncActive != syncActive) || 
158
                             (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] && 
159
                              (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] || 
160
                               isDirectory)) ||
161
                             ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
162
    self.syncCancelEnable = (selectedPithosAccount.active && 
163
                             ((selectedPithosAccount.syncActive != syncActive) || 
164
                              ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
165
                              ![selectedPithosAccount.syncAccountsDictionary isEqualToDictionary:syncAccountsDictionary]));
166
}
167

    
168
#pragma mark -
169
#pragma Properties
170

    
171
- (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
172
    if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
173
        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
174
        selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = nil;
175
        [selectedPithosAccount release];
176
        selectedPithosAccount = [aSelectedPithosAccount retain];
177
        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
178
        selectedPithosAccount.sharingAccountsNode.childrenUpdatedNotificationName = @"SelectedPithosAccountNodeChildrenUpdated";
179
        
180
        [self updateAccounts];
181
        [self loginCancel:self];
182
        [self syncCancel:self];
183
        [self groupsRevert:self];
184
    }
185
}
186

    
187
#pragma Login Properties
188

    
189
- (void)setServerURL:(NSString *)aServerURL {
190
    [serverURL release];
191
    serverURL = [aServerURL copy];
192
    [self updateLogin];
193
}
194

    
195
- (void)setAuthUser:(NSString *)anAuthUser {
196
    [authUser release];
197
    authUser = [anAuthUser copy];
198
    [self updateLogin];
199
}
200

    
201
- (void)setAuthToken:(NSString *)anAuthToken {
202
    [authToken release];
203
    authToken = [anAuthToken copy];
204
    [self updateLogin];
205
}
206

    
207
- (void)setManual:(BOOL)aManual {
208
    manual = aManual;
209
    [self updateLogin];
210
    if (!manual) {
211
        self.authUser = selectedPithosAccount.authUser;
212
        self.authToken = selectedPithosAccount.authToken;
213
    }
214
}
215

    
216
#pragma Sync Properties
217

    
218
- (void)setSyncActive:(BOOL)aSyncActive {
219
    syncActive = aSyncActive;
220
    [self updateSync];
221
}
222

    
223
- (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
224
    [syncDirectoryPath release];
225
    syncDirectoryPath = [aSyncDirectoryPath copy];
226
    [self updateSync];
227
}
228

    
229
- (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary {
230
    [syncAccountsDictionary release];
231
    syncAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncAccountsDictionary count]];
232
    for (NSString *accountName in aSyncAccountsDictionary) {
233
        NSDictionary *aSyncContainersDictionary = [aSyncAccountsDictionary objectForKey:accountName];
234
        NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
235
        for (NSString *containerName in aSyncContainersDictionary) {
236
            [syncContainersDictionary setObject:[NSMutableArray arrayWithArray:[aSyncContainersDictionary objectForKey:containerName]] 
237
                                         forKey:containerName];
238
        }
239
        if ([syncContainersDictionary count])
240
            [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
241
    }
242
    [self updateSync];
243
}
244

    
245
#pragma mark -
246
#pragma Actions
247

    
248
- (IBAction)addAccount:(id)sender {
249
    [accountsArrayController addObject:[PithosAccount pithosAccount]];
250
    [self updateAccounts];
251
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
252
    [delegate.pithosAccountsDictionary setObject:selectedPithosAccount forKey:selectedPithosAccount.name];
253
    [delegate savePithosAccounts:self];
254
}
255

    
256
- (IBAction)removeAccount:(id)sender {
257
    [self updateAccounts];
258
    if (!accountRemoveEnable)
259
        return;
260
    PithosAccount *removedPithosAccount = [selectedPithosAccount retain];
261
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
262
    if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
263
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
264
        [alert setMessageText:@"Operations Pending"];
265
        [alert setInformativeText:@"There are pending operations in the browser, do you want to remove the account and cancel them?"];
266
        [alert addButtonWithTitle:@"OK"];
267
        [alert addButtonWithTitle:@"Cancel"];
268
        NSInteger choice = [alert runModal];
269
        if (choice == NSAlertSecondButtonReturn) {
270
            [removedPithosAccount release];
271
            return;
272
        }
273
    }
274
    [accountsArrayController removeObject:selectedPithosAccount];
275
    [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name];
276
    [delegate removedPithosAccount:removedPithosAccount];
277
    [delegate savePithosAccounts:self];
278
    [removedPithosAccount release];
279
    [self updateAccounts];
280
}
281

    
282
#pragma Login Actions
283

    
284
- (IBAction)login:(id)sender {
285
    if (!manual) {
286
        [selectedPithosAccount loginWithServerURL:serverURL];
287
    } else {
288
        [selectedPithosAccount authenticateWithServerURL:serverURL authUser:authUser authToken:authToken];
289
        self.manual = NO;
290
        pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
291
        [delegate savePithosAccounts:self];
292
        if (!delegate.activated) {
293
            delegate.activated = YES;
294
            [delegate showPithosBrowser:self];
295
        }
296
        if ([selectedPithosAccount isEqualTo:delegate.currentPithosAccount])
297
            delegate.pithosBrowserController.pithos = selectedPithosAccount.pithos;
298
    }
299
}
300

    
301
- (IBAction)loginCancel:(id)server {
302
    self.serverURL = selectedPithosAccount.serverURL;
303
    self.authUser = selectedPithosAccount.authUser;
304
    self.authToken = selectedPithosAccount.authToken;
305
}
306

    
307
#pragma Sync Actions
308

    
309
- (IBAction)syncApply:(id)sender {
310
    [selectedPithosAccount updateSyncWithSyncActive:syncActive 
311
                                  syncDirectoryPath:syncDirectoryPath 
312
                           syncAccountsDictionary:syncAccountsDictionary];
313
    [self updateSync];
314
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
315
    [delegate savePithosAccounts:self];
316
    [delegate sync];
317
}
318

    
319
- (IBAction)syncCancel:(id)sender {
320
    self.syncActive = selectedPithosAccount.syncActive;
321
    self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath;
322
    self.syncAccountsDictionary = selectedPithosAccount.syncAccountsDictionary;
323
    self.syncAccountsRootFilesNodes = [NSMutableDictionary dictionary];
324
    [self selectedPithosAccountNodeChildrenUpdated:nil];
325
}
326

    
327
- (IBAction)syncRefresh:(id)sender {
328
    selectedPithosAccount.accountNode.forcedRefresh = YES;
329
    [selectedPithosAccount.accountNode invalidateChildrenRecursive];
330
    selectedPithosAccount.sharingAccountsNode.forcedRefresh = YES;
331
    [selectedPithosAccount.sharingAccountsNode invalidateChildrenRecursive];
332
    if (selectedPithosAccount.accountNode.children && selectedPithosAccount.sharingAccountsNode.children) {
333
    }
334
}
335

    
336
#pragma mark Groups Actions
337

    
338
- (IBAction)groupsApply:(id)sender {
339
    [[self window] makeFirstResponder:nil];
340
    if (selectedPithosAccount.active)
341
        [selectedPithosAccount.accountNode applyInfo];
342
}
343

    
344
- (IBAction)groupsRevert:(id)sender {
345
    if (selectedPithosAccount.active && selectedPithosAccount.accountNode)
346
        [selectedPithosAccount.accountNode refreshInfo];
347
}
348

    
349
#pragma mark -
350
#pragma mark NSOutlineViewDataSource
351

    
352
// <my account> [PithosEmptyNode]
353
// - <container>+ [PithosContainerNode]
354
// -- <subdir>+ [PithosSubdirNode]
355
// -- <root files> [PithosEmptyNode]
356
// <sharing account>+ [PithosSharingAccountNode]
357
// - <container>+ [PithosContainerNode]
358
// -- <subdir>+ [PithosSubdirNode]
359
// -- <root files> [PithosEmptyNode]
360

    
361
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
362
    if (!selectedPithosAccount.active)
363
        return 0;
364
    if (outlineView == syncAccountsOutlineView) {
365
        if (item == nil) {
366
            // root: <my account> + #<sharing account>
367
            if (selectedPithosAccount.sharingAccountsNode.children)
368
                return (selectedPithosAccount.sharingAccountsNode.children.count + 1);
369
            else
370
                return 1;
371
        } else if (item == syncAccountsMyAccountNode) {
372
            // root/<my account>: #<container>
373
            if (selectedPithosAccount.accountNode.children)
374
                return selectedPithosAccount.accountNode.children.count;
375
        } else if ([item class] == [PithosAccountNode class]) {
376
            // root/<sharing account>: #<container>
377
            PithosAccountNode *accountNode = (PithosAccountNode *)item;
378
            if (accountNode.children)
379
                return accountNode.children.count;
380
        } else if ([item class] == [PithosContainerNode class]) {
381
            // root/{<my account>, <sharing account>}/<container>: #<subdir> + <root files>
382
            PithosContainerNode *containerNode = (PithosContainerNode *)item;
383
            if (containerNode.children) {
384
                // We add 1 for the root files node
385
                NSInteger subdirCount = 1;
386
                for (PithosNode *node in containerNode.children) {
387
                    if ([node class] == [PithosSubdirNode class])
388
                        subdirCount++;
389
                }
390
                return subdirCount;
391
            }
392
        }
393
    }
394
    return 0;
395
}
396

    
397
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
398
    if (outlineView == syncAccountsOutlineView) {
399
        if (item == nil) {
400
            // root: [ <my account>, <sharing account>+ ]
401
            if (index == 0)
402
                return syncAccountsMyAccountNode;
403
            else
404
                return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:(index - 1)];
405
        } else if (item == syncAccountsMyAccountNode) {
406
            // root/<my account>: [ <container>+ ]
407
            return [selectedPithosAccount.accountNode.children objectAtIndex:index];
408
        } else if ([item class] == [PithosAccountNode class]) {
409
            // root/<sharing account>: [ <container>+ ]
410
            return [((PithosAccountNode *)item).children objectAtIndex:index];
411
        } else if ([item class] == [PithosContainerNode class]) {
412
            // root/{<my account>, <sharing account>}/<container>: [ <subdir>+, <root files> ]
413
            PithosContainerNode *containerNode = (PithosContainerNode *)item;
414
            NSInteger currentSubdirIndex = -1;
415
            for (PithosNode *node in containerNode.children) {
416
                if ([node class] == [PithosSubdirNode class]) {
417
                    currentSubdirIndex++;
418
                    if (currentSubdirIndex == index)
419
                        return node;
420
                }
421
            }
422
            if (++currentSubdirIndex == index) {
423
                NSString *accountName = containerNode.sharingAccount;
424
                if (!accountName)
425
                    accountName = @"";
426
                PithosEmptyNode *rootFilesNode = [[syncAccountsRootFilesNodes objectForKey:accountName] 
427
                                                  objectForKey:containerNode.displayName];
428
                if (!rootFilesNode) {
429
                    if (![syncAccountsRootFilesNodes objectForKey:accountName])
430
                        [syncAccountsRootFilesNodes setObject:[NSMutableDictionary dictionary] forKey:accountName];
431
                    rootFilesNode = [[[PithosEmptyNode alloc] initWithDisplayName:@"<root files>" 
432
                                                                             icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]] 
433
                                     autorelease];
434
                    rootFilesNode.parent = containerNode;
435
                    [[syncAccountsRootFilesNodes objectForKey:accountName] setObject:rootFilesNode forKey:containerNode.displayName];
436
                }
437
                return rootFilesNode;
438
            }
439
        }
440
    }
441
    return nil;
442
}
443

    
444
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
445
    if (outlineView == syncAccountsOutlineView) {
446
        if ((item == syncAccountsMyAccountNode) || 
447
            ([item class] == [PithosAccountNode class]) || 
448
            ([item class] == [PithosContainerNode class]))
449
            return YES;
450
    }
451
    return NO;
452
}
453

    
454
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
455
    if (outlineView == syncAccountsOutlineView) {
456
        if ([[tableColumn identifier] isEqualToString:@"sync"]) {
457
            if (item == syncAccountsMyAccountNode) {
458
                // root/<my account>
459
                // My account is 
460
                // off if not in dictionary
461
                // mixed if in dictionary with exclusions
462
                // on if in dictionary without exclusions
463
                NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:@""];
464
                if (syncContainersDictionary) {
465
                    for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
466
                        NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
467
                        if (!containerExcludedDirectories || [containerExcludedDirectories count])
468
                            return [NSNumber numberWithUnsignedInteger:NSMixedState];
469
                    }
470
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
471
                }
472
                return [NSNumber numberWithUnsignedInteger:NSOffState];
473
            } else if ([item class] == [PithosAccountNode class]) {
474
                // root/<sharing account>
475
                // A sharing account is 
476
                // off if not in dictionary
477
                // mixed if in dictionary with exclusions
478
                // on if in dictionary without exclusions
479
                PithosAccountNode *accountNode = (PithosAccountNode *)item;
480
                NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountNode.displayName];
481
                if (syncContainersDictionary) {
482
                    for (PithosContainerNode *node in accountNode.children) {
483
                        NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
484
                        if (!containerExcludedDirectories || [containerExcludedDirectories count])
485
                            return [NSNumber numberWithUnsignedInteger:NSMixedState];
486
                    }
487
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
488
                }
489
                return [NSNumber numberWithUnsignedInteger:NSOffState];
490
            } else if ([item class] == [PithosContainerNode class]) {
491
                // root/{<my account>, <sharing account>}/<container>
492
                // A container is 
493
                // off if not in dictionary
494
                // mixed if in dictionary with exclusions
495
                // on if in dictionary without exclusions
496
                PithosContainerNode *node = (PithosContainerNode *)item;
497
                NSString *accountName = node.sharingAccount;
498
                if (!accountName)
499
                    accountName = @"";
500
                NSMutableArray *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] 
501
                                                                objectForKey:node.displayName];
502
                if (containerExcludedDirectories) {
503
                    if ([containerExcludedDirectories count])
504
                        return [NSNumber numberWithUnsignedInteger:NSMixedState];
505
                    else
506
                        return [NSNumber numberWithUnsignedInteger:NSOnState];
507
                }
508
                return [NSNumber numberWithUnsignedInteger:NSOffState];
509
            } else if ([item class] == [PithosSubdirNode class]) {
510
                // root/{<my account>, <sharing account>}/<container>/<subdir>
511
                // Directory is off if parent container not in dictionary or if excluded
512
                // displayName should be localized and lowercased
513
                PithosSubdirNode *node = (PithosSubdirNode *)item;
514
                NSString *accountName = node.sharingAccount;
515
                if (!accountName)
516
                    accountName = @"";
517
                NSMutableArray *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] 
518
                                                                objectForKey:node.parent.displayName];
519
                if (!containerExcludedDirectories || 
520
                    [containerExcludedDirectories 
521
                     containsObject:[[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]])
522
                    return [NSNumber numberWithUnsignedInteger:NSOffState];
523
                else
524
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
525
            } else if ([item class] == [PithosEmptyNode class]) {
526
                // root/{<my account>, <sharing account>}/<container>/<root files>
527
                // Root files is off if parent container not in dictionary or if excluded
528
                PithosEmptyNode *node = (PithosEmptyNode *)item;
529
                NSString *accountName = node.parent.sharingAccount;
530
                if (!accountName)
531
                    accountName = @"";
532
                NSMutableArray *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName] 
533
                                                                objectForKey:node.parent.displayName];
534
                if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""])
535
                    return [NSNumber numberWithUnsignedInteger:NSOffState];
536
                else
537
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
538
            }
539
            return [NSNumber numberWithUnsignedInteger:NSOffState];
540
        } else if ([[tableColumn identifier] isEqualToString:@"path"]) {
541
            return (PithosNode *)item;
542
        }
543
    }
544
    return nil;
545
}
546

    
547
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
548
    if (outlineView == syncAccountsOutlineView) {
549
        if ([[tableColumn identifier] isEqualToString:@"sync"]) {
550
            NSCellStateValue newState = [object unsignedIntegerValue];
551
            if (item == syncAccountsMyAccountNode) {
552
                // root/<my account>
553
                // If new state is
554
                // mixed/on include my account with no exclusions
555
                // off exclude my account
556
                if ((newState == NSOnState) || (newState == NSMixedState)) {
557
                    NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
558
                    for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
559
                        [syncContainersDictionary setObject:[NSMutableArray array] forKey:node.displayName];
560
                    }
561
                    [syncAccountsDictionary setObject:syncContainersDictionary forKey:@""];
562
                } else {
563
                    [syncAccountsDictionary removeObjectForKey:@""];
564
                }
565
                [outlineView reloadItem:item reloadChildren:YES];
566
            } else if ([item class] == [PithosAccountNode class]) {
567
                // root/<sharing account>
568
                // If new state is
569
                // mixed/on include sharing account with no exclusions
570
                // off exclude sharing account
571
                PithosAccountNode *accountNode = (PithosAccountNode *)item;
572
                if ((newState == NSOnState) || (newState == NSMixedState)) {
573
                    NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
574
                    for (PithosContainerNode *node in accountNode.children) {
575
                        [syncContainersDictionary setObject:[NSMutableArray array] forKey:node.displayName];
576
                    }
577
                    [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountNode.displayName];
578
                } else {
579
                    [syncAccountsDictionary removeObjectForKey:accountNode.displayName];
580
                }
581
                [outlineView reloadItem:item reloadChildren:YES];
582
            } else if ([item class] == [PithosContainerNode class]) {
583
                // root/{<my account>, <sharing account>}/<container>
584
                // If new state is
585
                // mixed/on include container with no excluded directories
586
                // off exclude container
587
                PithosContainerNode *node = (PithosContainerNode *)item;
588
                NSString *accountName = node.sharingAccount;
589
                PithosNode *accountNode = node.parent;
590
                if (!accountName) {
591
                    accountName = @"";
592
                    accountNode = syncAccountsMyAccountNode;
593
                }
594
                NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
595
                if ((newState == NSOnState) || (newState == NSMixedState)) {
596
                    if (!syncContainersDictionary) {
597
                        syncContainersDictionary = [NSMutableDictionary dictionary];
598
                        [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
599
                    }
600
                    [syncContainersDictionary setObject:[NSMutableArray array] forKey:node.displayName];
601
                } else if (syncContainersDictionary) {
602
                    [syncContainersDictionary removeObjectForKey:node.displayName];
603
                    if (![syncContainersDictionary count])
604
                        [syncAccountsDictionary removeObjectForKey:accountName];
605
                }
606
                [outlineView reloadItem:accountNode reloadChildren:YES];
607
            } else if ([item class] == [PithosSubdirNode class]) {
608
                // root/{<my account>, <sharing account>}/<container>/<subdir>
609
                // If new state is
610
                // mixed/on include directory (if container not included, include and exclude all others)
611
                // off exclude directory
612
                PithosSubdirNode *node = (PithosSubdirNode *)item;
613
                NSString *accountName = node.sharingAccount;
614
                PithosNode *accountNode = node.parent.parent;
615
                if (!accountName) {
616
                    accountName = @"";
617
                    accountNode = syncAccountsMyAccountNode;
618
                }
619
                NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
620
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
621
                NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
622
                if ((newState == NSOnState) || (newState == NSMixedState)) {
623
                    if (containerExcludedDirectories) {
624
                        [containerExcludedDirectories removeObject:directoryName];
625
                    } else {
626
                        if (!syncContainersDictionary) {
627
                            syncContainersDictionary = [NSMutableDictionary dictionary];
628
                            [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
629
                        }
630
                        NSMutableArray *newContainerExcludeDirectories = [NSMutableArray arrayWithObject:@""];
631
                        for (PithosNode *siblingNode in node.parent.children) {
632
                            if ([siblingNode class] == [PithosSubdirNode class]) {
633
                                NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] 
634
                                                                  stringByReplacingOccurrencesOfString:@"/" withString:@":"];
635
                                if (![siblingDirectoryName isEqualToString:directoryName] && 
636
                                    ![newContainerExcludeDirectories containsObject:siblingDirectoryName])
637
                                    [newContainerExcludeDirectories addObject:siblingDirectoryName];
638
                            }
639
                        }
640
                        [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
641
                    }
642
                } else if (syncContainersDictionary && 
643
                           containerExcludedDirectories && 
644
                           ![containerExcludedDirectories containsObject:directoryName]) {
645
                    [containerExcludedDirectories addObject:directoryName];
646
                }
647
                [outlineView reloadItem:accountNode reloadChildren:YES];
648
            } else if ([item class] == [PithosEmptyNode class]) {
649
                // If new state is
650
                // mixed/on include root files (if container not included, include and exclude all others)
651
                // off exclude root files
652
                PithosEmptyNode *node = (PithosEmptyNode *)item;
653
                NSString *accountName = node.parent.sharingAccount;
654
                PithosNode *accountNode = node.parent.parent;
655
                if (!accountName) {
656
                    accountName = @"";
657
                    accountNode = syncAccountsMyAccountNode;
658
                }
659
                NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
660
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
661
                if ((newState == NSOnState) || (newState == NSMixedState)) {
662
                    if (containerExcludedDirectories) {
663
                        [containerExcludedDirectories removeObject:@""];
664
                    } else {
665
                        if (!syncContainersDictionary) {
666
                            syncContainersDictionary = [NSMutableDictionary dictionary];
667
                            [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
668
                        }
669
                        NSMutableArray *newContainerExcludeDirectories = [NSMutableArray array];
670
                        for (PithosNode *siblingNode in node.parent.children) {
671
                            if ([siblingNode class] == [PithosSubdirNode class]) {
672
                                NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] 
673
                                                                  stringByReplacingOccurrencesOfString:@"/" withString:@":"];
674
                                if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
675
                                    [newContainerExcludeDirectories addObject:siblingDirectoryName];
676
                            }
677
                        }
678
                        [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
679
                    }
680
                } else if (syncContainersDictionary && 
681
                           containerExcludedDirectories && 
682
                           ![containerExcludedDirectories containsObject:@""]) {
683
                    [containerExcludedDirectories addObject:@""];
684
                }
685
                [outlineView reloadItem:accountNode reloadChildren:YES];
686
            }
687
            [self updateSync];
688
        }
689
    }
690
}
691

    
692
@end