Support open at login.
[pithos-macos] / pithos-macos / pithos_macosAppDelegate.m
1 //
2 //  pithos_macosAppDelegate.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 "pithos_macosAppDelegate.h"
39 #import "PithosAccount.h"
40 #import "PithosBrowserController.h"
41 #import "PithosPreferencesController.h"
42 #import "PithosSyncDaemon.h"
43 #import "ASIPithosRequest.h"
44 #import "ASIPithos.h"
45 #import "ASIDownloadCache.h"
46 #import "LastCompletedSyncTransformer.h"
47
48 @implementation pithos_macosAppDelegate
49 @synthesize pithosBrowserController, pithosPreferencesController, alwaysNo, openAtLoginEnabled, openAtLogin, activated, 
50 currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval;
51
52 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
53     [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
54                                                        andSelector:@selector(handleAppleEvent:withReplyEvent:) 
55                                                      forEventClass:kInternetEventClass 
56                                                         andEventID:kAEGetURL];
57     
58     // Based on: https://github.com/Mozketo/LaunchAtLoginController
59     // and: http://cocoatutorial.grapewave.com/2010/02/creating-andor-removing-a-login-item/
60     loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
61     if (loginItems) {
62         LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, self);
63         LSSharedFileListChanged(loginItems, self);
64         self.openAtLoginEnabled = YES;
65     }
66     
67     userDefaults = [[NSUserDefaults standardUserDefaults] retain];
68     
69     syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
70     if (syncTimeInterval <= 0.0) {
71         syncTimeInterval = 180.0;
72         [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
73         [userDefaults synchronize];
74     }
75
76     activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"];
77     if (activityFacilityTimeInterval <= 0.0) {
78         activityFacilityTimeInterval = 0.05;
79         [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"];
80         [userDefaults synchronize];
81     }
82
83     NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
84     NSArray *tmpArray;
85     if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
86         self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
87     else
88         self.pithosAccounts = [NSMutableArray array];
89     
90     if (![pithosAccounts count]) {
91         [pithosAccounts addObject:[PithosAccount pithosAccount]];
92         self.pithosAccounts = self.pithosAccounts;
93     } else {
94         self.activated = YES;
95     }
96     
97     pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]];
98     for (PithosAccount *pithosAccount in pithosAccounts) {
99         [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name];
100         if (!currentPithosAccount && pithosAccount.active)
101             currentPithosAccount = [pithosAccount retain];
102     }
103     if (!currentPithosAccount)
104         self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
105     
106     if (currentPithosAccount.active) {
107         [self savePithosAccounts:self];
108         [self showPithosBrowser:self];
109         self.pithosBrowserController.pithos = currentPithosAccount.pithos;
110     } else {
111         // XXX maybe call specifically to go to new account tab
112         [self showPithosPreferences:self];
113     }
114
115     syncTimer = [[NSTimer scheduledTimerWithTimeInterval:syncTimeInterval 
116                                                   target:self 
117                                                 selector:@selector(sync) 
118                                                 userInfo:nil 
119                                                  repeats:YES] retain];
120     [syncTimer fire];
121 }
122
123 // Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
124 // and: http://www.cocoadev.com/index.pl?ThumbnailImages
125 - (void)awakeFromNib {
126     NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
127     
128     NSImage *smallImage = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
129     [smallImage lockFocus];
130     [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
131     [sourceImage setSize:NSMakeSize(18, 18)];
132     [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
133     [smallImage unlockFocus];
134     
135     statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
136     [statusItem setMenu:statusMenu];
137     [statusItem setImage:sourceImage];
138     [statusItem setHighlightMode:YES];
139     
140     self.alwaysNo = NO;
141 }
142
143 - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
144     NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
145     NSString *host = [url host];
146         NSString *query = [url query];
147     PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[url lastPathComponent]];
148     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
149     if ([host isEqualToString:[NSString stringWithFormat:@"%d", [processInfo processIdentifier]]] && pithosAccount && query) {
150         // user=
151         NSString *authUser;
152         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
153         if (userRange.length == 0)
154             // XXX maybe show an error message?
155             return;
156         NSUInteger authUserStartLocation = userRange.location + userRange.length;
157         NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
158                                               range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
159         if (userEndRange.length) {
160             authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
161                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
162         } else {
163             authUser = [[query substringFromIndex:authUserStartLocation]
164                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
165         }
166         // token=
167         NSString *authToken;
168         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
169         if (tokenRange.length == 0)
170             // XXX maybe show an error message?
171             return;
172         NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
173         NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
174                                               range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
175         if (tokenEndRange.length) {
176             authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
177                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
178         } else {
179             authToken = [[query substringFromIndex:authTokenStartLocation]
180                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
181         }
182         
183         NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
184         if ([authUser length] && [authToken length]) {
185             [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken];
186             [self savePithosAccounts:self];
187             if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) {
188                 self.pithosPreferencesController.authUser = pithosAccount.authUser;
189                 self.pithosPreferencesController.authToken = pithosAccount.authToken;
190             }
191             self.activated = YES;
192             if ([pithosAccount isEqualTo:currentPithosAccount]) {
193                 [self showPithosBrowser:self];
194                 self.pithosBrowserController.pithos = pithosAccount.pithos;
195             }
196         }
197         // XXX else maybe show an error message?
198     }
199     // XXX else maybe show an error message?
200 }
201
202 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
203     [self savePithosAccounts:self];
204     if ([self.pithosBrowserController operationsPending]) {
205         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
206         [alert setMessageText:@"Pending Operations"];
207         [alert setInformativeText:@"There are pending operations in the browser, do you want to quit and cancel them?"];
208         [alert addButtonWithTitle:@"OK"];
209         [alert addButtonWithTitle:@"Cancel"];
210         NSInteger choice = [alert runModal];
211         if (choice == NSAlertSecondButtonReturn) 
212             return NSTerminateCancel;
213     }
214     if (loginItems) {
215         LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, self);
216         CFRelease(loginItems);
217     }
218     return NSTerminateNow;
219 }
220
221 #pragma mark -
222 #pragma mark Callbacks
223
224 - (void)loginItemsChanged {
225     NSURL *appURL = [[NSBundle mainBundle] bundleURL];
226     LSSharedFileListItemRef appItem = NULL;
227     NSArray *snapshot = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, NULL)) autorelease];
228     for (id itemObject in snapshot) {
229         LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
230         UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
231         CFURLRef currentItemURL = NULL;
232         LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
233         if (currentItemURL && CFEqual(currentItemURL, appURL)) {
234             CFRelease(currentItemURL);
235             appItem = item;
236             break;
237         }
238         if (currentItemURL)
239             CFRelease(currentItemURL);
240     }
241     
242     if (appItem && (!openAtLogin || !openAtLoginEnabled))
243         self.openAtLogin = YES;
244     else if (!appItem && (openAtLogin || !openAtLoginEnabled))
245         self.openAtLogin = NO;
246 }
247
248 void LSSharedFileListChanged(LSSharedFileListRef inList, void *context) {
249     pithos_macosAppDelegate *self = (id)context;
250     [self loginItemsChanged];
251 }
252
253 #pragma mark -
254 #pragma mark Properties
255
256 - (PithosBrowserController *)pithosBrowserController {
257     if (!pithosBrowserController) {
258         pithosBrowserController = [[PithosBrowserController alloc] init];
259     }
260     return pithosBrowserController;
261 }
262
263 - (PithosPreferencesController *)pithosPreferencesController {
264     if (!pithosPreferencesController) {
265         pithosPreferencesController = [[PithosPreferencesController alloc] init];
266     }
267     return pithosPreferencesController;
268 }
269
270 - (void)setOpenAtLogin:(BOOL)anOpenAtLogin {
271     if (!openAtLoginEnabled) {
272         openAtLogin = anOpenAtLogin;
273     } else if (anOpenAtLogin != openAtLogin) {
274         NSURL *appURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
275         LSSharedFileListItemRef appItem = NULL;
276         NSArray *snapshot = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, NULL)) autorelease];
277         for (id itemObject in snapshot) {
278             LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
279             UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
280             CFURLRef currentItemURL = NULL;
281             LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
282             if (currentItemURL && CFEqual(currentItemURL, appURL)) {
283                 CFRelease(currentItemURL);
284                 appItem = item;
285                 break;
286             }
287             if (currentItemURL)
288                 CFRelease(currentItemURL);
289         }
290         
291         if (anOpenAtLogin) {
292             if (!appItem)
293                 LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, (CFURLRef)appURL, NULL, NULL);
294             openAtLogin = YES;
295         } else {
296             if (appItem)
297                 LSSharedFileListItemRemove(loginItems, appItem);
298             openAtLogin = NO;
299         }
300     }
301 }
302
303 #pragma mark -
304 #pragma mark NSMenuDelegate
305
306 - (void)menuNeedsUpdate:(NSMenu *)menu {
307     NSMenuItem *menuItem;
308     [menu removeAllItems];
309     if ([menu isEqualTo:accountsMenu]) {
310         [menu setAutoenablesItems:NO];
311         for (PithosAccount *pithosAccount in pithosAccounts) {
312             menuItem = [[[NSMenuItem alloc] initWithTitle:pithosAccount.name 
313                                                    action:@selector(menuChangePithosAccount:) 
314                                             keyEquivalent:@""] autorelease];
315             [menuItem setRepresentedObject:pithosAccount];
316             [menuItem setEnabled:pithosAccount.active];
317             [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)];
318             [menu addItem:menuItem];
319         }
320     } else if ([menu isEqualTo:lastSyncMenu]) {
321         NSString *menuItemTitle;
322         [menu setAutoenablesItems:NO];
323         for (PithosAccount *pithosAccount in pithosAccounts) {
324             menuItemTitle = [NSString stringWithFormat:@"%@: %@", 
325                              pithosAccount.name, 
326                              [[[[LastCompletedSyncTransformer alloc] init] autorelease] transformedValue:pithosAccount.syncLastCompleted]];
327             if ([pithosAccount isEqualTo:syncPithosAccount] && [pithosAccount.syncDaemon isSyncing])
328                 menuItemTitle = [menuItemTitle stringByAppendingString:@" (syncing)"];
329 //            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle 
330 //                                                   action:@selector(menuChangeSyncActive:) 
331 //                                            keyEquivalent:@""] autorelease];
332 //            [menuItem setRepresentedObject:pithosAccount];
333 //            [menuItem setEnabled:pithosAccount.active];
334 //            [menuItem setState:((pithosAccount.active && pithosAccount.syncActive) ? NSOnState : NSOffState)];
335             menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:nil keyEquivalent:@""] autorelease];
336             [menuItem setEnabled:NO];
337             [menuItem setState:NO];
338             [menu addItem:menuItem];
339         }
340         [menu addItem:[NSMenuItem separatorItem]];
341         [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Next Sync" 
342                                                   action:@selector(sync) 
343                                            keyEquivalent:@""] autorelease]];
344     }
345 }
346
347 #pragma mark -
348 #pragma mark Actions
349
350 - (IBAction)showPithosBrowser:(id)sender {
351     if (!activated)
352         return;
353     [self.pithosBrowserController showWindow:sender];
354     [[self.pithosBrowserController window] makeKeyAndOrderFront:sender];
355     [NSApp activateIgnoringOtherApps:YES];
356 }
357
358 - (IBAction)showPithosPreferences:(id)sender {
359     [self.pithosPreferencesController showWindow:sender];
360     [[self.pithosPreferencesController window] makeKeyAndOrderFront:sender];
361     [NSApp activateIgnoringOtherApps:YES];
362 }
363
364 - (IBAction)showPithosAbout:(id)sender {
365     [NSApp orderFrontStandardAboutPanel:sender];
366     [NSApp activateIgnoringOtherApps:YES];
367 }
368
369 - (void)sync {
370     if (!activated || ![pithosAccounts count])
371         return;
372     NSUInteger syncIndex;
373     BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount];
374     if (syncPithosAccountFound)
375          syncIndex = [pithosAccounts indexOfObject:syncPithosAccount];
376     
377     PithosAccount *singleSyncPithosAccount = nil;
378     for (PithosAccount *pithosAccount in pithosAccounts) {
379         if (!singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
380             singleSyncPithosAccount = pithosAccount;
381         } else if (singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
382             singleSyncPithosAccount = nil;
383             break;
384         }
385     }
386     
387     if (syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
388         // An active syncDaemon was previously syncing
389         if (singleSyncPithosAccount && [singleSyncPithosAccount isEqualTo:syncPithosAccount]) {
390             // It's the only one, sync again
391             [syncPithosAccount.syncDaemon startDaemon];
392             [syncPithosAccount.syncDaemon sync];
393             return;
394         } else if ([syncPithosAccount.syncDaemon isSyncing]) {
395             // It's still syncing, mark it as late and return
396             [syncPithosAccount.syncDaemon syncLate];
397             return;
398         }
399     }
400     PithosAccount *newSyncPithosAccount = nil;
401     if (syncPithosAccountFound) {
402         for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(syncIndex + 1, [pithosAccounts count] - syncIndex - 1)]]) {
403             if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
404                 newSyncPithosAccount = pithosAccount;
405                 break;
406             }
407         }
408         if (!newSyncPithosAccount) {
409             for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, syncIndex)]]) {
410                 if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
411                     newSyncPithosAccount = pithosAccount;
412                     break;
413                 }
414             }
415         }
416     } else {
417         for (PithosAccount *pithosAccount in pithosAccounts) {
418             if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
419                 newSyncPithosAccount = pithosAccount;
420                 break;
421             }
422         }
423     }        
424     if (newSyncPithosAccount) {
425         // A different syncDaemon is found, sync it
426         self.syncPithosAccount = newSyncPithosAccount;
427         [syncPithosAccount.syncDaemon startDaemon];
428         [syncPithosAccount.syncDaemon sync];
429     } else if (syncPithosAccountFound && syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
430         [syncPithosAccount.syncDaemon startDaemon];
431         [syncPithosAccount.syncDaemon sync];
432     } else {
433         self.syncPithosAccount = nil;
434     }
435 }
436
437 - (void)savePithosAccounts:(id)sender {
438     [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
439     [userDefaults synchronize];
440 }
441
442 - (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
443     if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
444         for (PithosAccount *pithosAccount in pithosAccounts) {
445             if (pithosAccount.active) {
446                 self.currentPithosAccount = pithosAccount;
447                 self.pithosBrowserController.pithos = currentPithosAccount.pithos;
448                 break;
449             }
450         }
451         if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
452             self.activated = NO;
453             [self.pithosBrowserController.window close];
454             [self.pithosBrowserController resetBrowser];
455             self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
456         }
457     }
458     if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
459         self.syncPithosAccount = nil;
460 }
461
462 #pragma mark -
463 #pragma mark Menu Actions
464
465 - (void)menuChangePithosAccount:(NSMenuItem *)sender {
466     PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
467     if (!pithosAccount.active)
468         return;
469     if (![currentPithosAccount isEqualTo:pithosAccount] && [pithosAccounts containsObject:pithosAccount]) {
470         if ([self.pithosBrowserController operationsPending]) {
471             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
472             [alert setMessageText:@"Pending Operations"];
473             [alert setInformativeText:@"There are pending operations in the browser, do you want to change accounts and cancel them?"];
474             [alert addButtonWithTitle:@"OK"];
475             [alert addButtonWithTitle:@"Cancel"];
476             NSInteger choice = [alert runModal];
477             if (choice == NSAlertSecondButtonReturn) 
478                 return;
479         }
480         self.currentPithosAccount = pithosAccount;
481         [self showPithosBrowser:self];
482         self.pithosBrowserController.pithos = currentPithosAccount.pithos;
483     }
484 }
485
486 //- (void)menuChangeSyncActive:(NSMenuItem *)sender {
487 //    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
488 //    if (!pithosAccount.active)
489 //        return;
490 //    pithosAccount.syncActive = !pithosAccount.syncActive;
491 //    if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
492 //        self.pithosPreferencesController.syncActive = pithosAccount.syncActive;
493 //    [self savePithosAccounts:self];
494 //}
495
496 @end