// // pithos_macosAppDelegate.m // pithos-macos // // Copyright 2011-2012 GRNET S.A. All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed // or implied, of GRNET S.A. #import "pithos_macosAppDelegate.h" #import "PithosAccount.h" #import "PithosBrowserController.h" #import "PithosPreferencesController.h" #import "PithosSyncDaemon.h" #import "ASIPithosRequest.h" #import "ASIPithos.h" #import "LastCompletedSyncTransformer.h" @implementation pithos_macosAppDelegate @synthesize pithosBrowserController, pithosPreferencesController, alwaysNo, openAtLoginEnabled, openAtLogin, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval, checkForUpdatesNotRunning; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; // Based on: https://github.com/Mozketo/LaunchAtLoginController // and: http://cocoatutorial.grapewave.com/2010/02/creating-andor-removing-a-login-item/ loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); if (loginItems) { LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self)); LSSharedFileListChanged(loginItems, (__bridge void *)(self)); self.openAtLoginEnabled = YES; } userDefaults = [NSUserDefaults standardUserDefaults]; syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"]; if (syncTimeInterval <= 0.0) { syncTimeInterval = 180.0; [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"]; [userDefaults synchronize]; } activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"]; if (activityFacilityTimeInterval <= 0.0) { activityFacilityTimeInterval = 0.05; [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"]; [userDefaults synchronize]; } NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"]; NSArray *tmpArray; if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData])) self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray]; else self.pithosAccounts = [NSMutableArray array]; if (![pithosAccounts count]) { [pithosAccounts addObject:[PithosAccount pithosAccount]]; self.pithosAccounts = self.pithosAccounts; } else { self.activated = YES; } pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]]; for (PithosAccount *pithosAccount in pithosAccounts) { [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name]; if (!currentPithosAccount && pithosAccount.active) currentPithosAccount = pithosAccount; } if (!currentPithosAccount) self.currentPithosAccount = [pithosAccounts objectAtIndex:0]; if (currentPithosAccount.active) { [self savePithosAccounts:self]; self.pithosBrowserController.pithosAccountManager = currentPithosAccount; [self showPithosBrowser:self]; self.pithosBrowserController.pithos = currentPithosAccount.pithos; } else { // XXX maybe call specifically to go to new account tab [self showPithosPreferences:self]; } syncTimer = [NSTimer scheduledTimerWithTimeInterval:syncTimeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES]; [syncTimer fire]; @synchronized(self) { self.checkForUpdatesNotRunning = YES; } [self checkForUpdates]; } // Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/ // and: http://www.cocoadev.com/index.pl?ThumbnailImages - (void)awakeFromNib { NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"]; NSImage *smallImage = [[NSImage alloc] initWithSize:NSMakeSize(18, 18)]; [smallImage lockFocus]; [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; [sourceImage setSize:NSMakeSize(18, 18)]; [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy]; [smallImage unlockFocus]; statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; [statusItem setMenu:statusMenu]; [statusItem setImage:sourceImage]; [statusItem setHighlightMode:YES]; self.alwaysNo = NO; } - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; NSString *host = [url host]; NSString *query = [url query]; PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[ASIPithosRequest decodeFromPercentEscape:[url lastPathComponent]]]; NSProcessInfo *processInfo = [NSProcessInfo processInfo]; if ([host isEqualToString:[NSString stringWithFormat:@"%d", [processInfo processIdentifier]]] && pithosAccount && query) { // user= NSString *authUser; NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch]; if (userRange.length == 0) // XXX maybe show an error message? return; NSUInteger authUserStartLocation = userRange.location + userRange.length; NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)]; if (userEndRange.length) { authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } else { authUser = [[query substringFromIndex:authUserStartLocation] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } // token= NSString *authToken; NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch]; if (tokenRange.length == 0) // XXX maybe show an error message? return; NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length; NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)]; if (tokenEndRange.length) { authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } else { authToken = [[query substringFromIndex:authTokenStartLocation] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } DLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken); if ([authUser length] && [authToken length]) { [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken]; [self savePithosAccounts:self]; if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) { self.pithosPreferencesController.authUser = pithosAccount.authUser; self.pithosPreferencesController.authToken = pithosAccount.authToken; } self.activated = YES; if ([pithosAccount isEqualTo:currentPithosAccount]) { [self showPithosBrowser:self]; self.pithosBrowserController.pithos = pithosAccount.pithos; } } // XXX else maybe show an error message? } // XXX else maybe show an error message? } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { [self savePithosAccounts:self]; if ([self.pithosBrowserController operationsPending]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Pending Operations"]; [alert setInformativeText:@"There are pending operations in the browser, do you want to quit and cancel them?"]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) return NSTerminateCancel; } if (loginItems) { LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self)); CFRelease(loginItems); } return NSTerminateNow; } #pragma mark - #pragma mark Callbacks - (void)loginItemsChanged { NSURL *appURL = [[NSBundle mainBundle] bundleURL]; LSSharedFileListItemRef appItem = NULL; NSArray *snapshot = (__bridge_transfer NSArray *)(LSSharedFileListCopySnapshot(loginItems, NULL)); for (id itemObject in snapshot) { LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)itemObject; UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef currentItemURL = NULL; LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL); if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) { CFRelease(currentItemURL); appItem = item; break; } if (currentItemURL) CFRelease(currentItemURL); } if (appItem && (!openAtLogin || !openAtLoginEnabled)) self.openAtLogin = YES; else if (!appItem && (openAtLogin || !openAtLoginEnabled)) self.openAtLogin = NO; } void LSSharedFileListChanged(LSSharedFileListRef inList, void *context) { pithos_macosAppDelegate *self = (__bridge id)context; [self loginItemsChanged]; } #pragma mark - #pragma mark Properties - (PithosBrowserController *)pithosBrowserController { if (!pithosBrowserController) { pithosBrowserController = [[PithosBrowserController alloc] init]; } return pithosBrowserController; } - (PithosPreferencesController *)pithosPreferencesController { if (!pithosPreferencesController) { pithosPreferencesController = [[PithosPreferencesController alloc] init]; } return pithosPreferencesController; } - (void)setOpenAtLogin:(BOOL)anOpenAtLogin { if (!openAtLoginEnabled) { openAtLogin = anOpenAtLogin; } else if (anOpenAtLogin != openAtLogin) { NSURL *appURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; LSSharedFileListItemRef appItem = NULL; NSArray *snapshot = (__bridge_transfer NSArray *)(LSSharedFileListCopySnapshot(loginItems, NULL)); for (id itemObject in snapshot) { LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)itemObject; UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef currentItemURL = NULL; LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL); if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) { CFRelease(currentItemURL); appItem = item; break; } if (currentItemURL) CFRelease(currentItemURL); } if (anOpenAtLogin) { if (!appItem) LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, (__bridge CFURLRef)appURL, NULL, NULL); openAtLogin = YES; } else { if (appItem) LSSharedFileListItemRemove(loginItems, appItem); openAtLogin = NO; } } } #pragma mark - #pragma mark NSMenuDelegate - (void)menuNeedsUpdate:(NSMenu *)menu { NSMenuItem *menuItem; [menu removeAllItems]; if ([menu isEqualTo:accountsMenu]) { [menu setAutoenablesItems:NO]; for (PithosAccount *pithosAccount in pithosAccounts) { menuItem = [[NSMenuItem alloc] initWithTitle:pithosAccount.name action:@selector(menuChangePithosAccount:) keyEquivalent:@""]; [menuItem setRepresentedObject:pithosAccount]; [menuItem setEnabled:pithosAccount.active]; [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)]; [menu addItem:menuItem]; } } else if ([menu isEqualTo:lastSyncMenu]) { NSString *menuItemTitle; [menu setAutoenablesItems:NO]; for (PithosAccount *pithosAccount in pithosAccounts) { menuItemTitle = [NSString stringWithFormat:@"%@: %@", pithosAccount.name, [[[LastCompletedSyncTransformer alloc] init] transformedValue:pithosAccount.syncLastCompleted]]; if ([pithosAccount isEqualTo:syncPithosAccount] && [pithosAccount.syncDaemon isSyncing]) menuItemTitle = [menuItemTitle stringByAppendingString:@" (syncing)"]; // menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle // action:@selector(menuChangeSyncActive:) // keyEquivalent:@""] autorelease]; // [menuItem setRepresentedObject:pithosAccount]; // [menuItem setEnabled:pithosAccount.active]; // [menuItem setState:((pithosAccount.active && pithosAccount.syncActive) ? NSOnState : NSOffState)]; menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:nil keyEquivalent:@""]; [menuItem setEnabled:NO]; [menuItem setState:NO]; [menu addItem:menuItem]; } [menu addItem:[NSMenuItem separatorItem]]; [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Next Sync" action:@selector(sync) keyEquivalent:@""]]; } } #pragma mark - #pragma mark Actions - (IBAction)showPithosBrowser:(id)sender { if (!activated) return; [self.pithosBrowserController showWindow:sender]; [[self.pithosBrowserController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } - (IBAction)showPithosPreferences:(id)sender { [self.pithosPreferencesController showWindow:sender]; [[self.pithosPreferencesController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } - (IBAction)showPithosAbout:(id)sender { [NSApp orderFrontStandardAboutPanel:sender]; [NSApp activateIgnoringOtherApps:YES]; } - (void)sync { if (!activated || ![pithosAccounts count]) return; NSUInteger syncIndex; BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount]; if (syncPithosAccountFound) syncIndex = [pithosAccounts indexOfObject:syncPithosAccount]; PithosAccount *singleSyncPithosAccount = nil; for (PithosAccount *pithosAccount in pithosAccounts) { if (!singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) { singleSyncPithosAccount = pithosAccount; } else if (singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) { singleSyncPithosAccount = nil; break; } } if (syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) { // An active syncDaemon was previously syncing if (singleSyncPithosAccount && [singleSyncPithosAccount isEqualTo:syncPithosAccount]) { // It's the only one, sync again [syncPithosAccount.syncDaemon startDaemon]; [syncPithosAccount.syncDaemon sync]; return; } else if ([syncPithosAccount.syncDaemon isSyncing]) { // It's still syncing, mark it as late and return [syncPithosAccount.syncDaemon syncLate]; return; } } PithosAccount *newSyncPithosAccount = nil; if (syncPithosAccountFound) { for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(syncIndex + 1, [pithosAccounts count] - syncIndex - 1)]]) { if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) { newSyncPithosAccount = pithosAccount; break; } } if (!newSyncPithosAccount) { for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, syncIndex)]]) { if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) { newSyncPithosAccount = pithosAccount; break; } } } } else { for (PithosAccount *pithosAccount in pithosAccounts) { if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) { newSyncPithosAccount = pithosAccount; break; } } } if (newSyncPithosAccount) { // A different syncDaemon is found, sync it self.syncPithosAccount = newSyncPithosAccount; [syncPithosAccount.syncDaemon startDaemon]; [syncPithosAccount.syncDaemon sync]; } else if (syncPithosAccountFound && syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) { [syncPithosAccount.syncDaemon startDaemon]; [syncPithosAccount.syncDaemon sync]; } else { self.syncPithosAccount = nil; } } - (void)savePithosAccounts:(id)sender { [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"]; [userDefaults synchronize]; } - (void)removedPithosAccount:(PithosAccount *)removedPithosAccount { if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) { for (PithosAccount *pithosAccount in pithosAccounts) { if (pithosAccount.active) { self.currentPithosAccount = pithosAccount; self.pithosBrowserController.pithosAccountManager = currentPithosAccount; self.pithosBrowserController.pithos = currentPithosAccount.pithos; break; } } if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) { self.activated = NO; [self.pithosBrowserController.window close]; [self.pithosBrowserController resetBrowser]; self.currentPithosAccount = [pithosAccounts objectAtIndex:0]; } } if ([self.syncPithosAccount isEqualTo:removedPithosAccount]) self.syncPithosAccount = nil; } - (void)checkForUpdates { @synchronized(self) { if (!checkForUpdatesNotRunning) return; self.checkForUpdatesNotRunning = NO; } ASIHTTPRequest *checkForUpdatesRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"https://code.grnet.gr/projects/pithos-macos/repository/revisions/master/raw/pithos-macos/pithos-macos-Info.plist"]]; checkForUpdatesRequest.delegate = self; checkForUpdatesRequest.didFinishSelector = @selector(checkForUpdatesRequestFinished:); checkForUpdatesRequest.didFailSelector = @selector(checkForUpdatesRequestFailed:); checkForUpdatesRequest.timeOutSeconds = 60; checkForUpdatesRequest.numberOfTimesToRetryOnTimeout = 10; [checkForUpdatesRequest startAsynchronous]; } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)checkForUpdatesRequestFinished:(ASIHTTPRequest *)request { if (request.responseStatusCode == 200) { NSError *error = nil; NSDictionary *plistDictionary = [NSPropertyListSerialization propertyListWithData:[request.responseString dataUsingEncoding:NSUTF8StringEncoding] options:NSPropertyListImmutable format:NULL error:&error]; if (!error) { NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; NSString *newVersion = [plistDictionary objectForKey:@"CFBundleVersion"]; NSURL *distributionURL = [NSURL URLWithString:[plistDictionary objectForKey:@"PithosDistributionURL"]]; if (newVersion && currentVersion && distributionURL && ([newVersion doubleValue] > [currentVersion doubleValue])) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Updates Available"]; [alert setInformativeText:@"An updated version is available"]; [alert addButtonWithTitle:@"Download"]; [alert runModal]; [[NSWorkspace sharedWorkspace] openURL:distributionURL]; } else if (checkForUpdatesCalledFromMenu) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"No Updates Available"]; [alert setInformativeText:@"You are running the latest version"]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; checkForUpdatesCalledFromMenu = NO; } [NSTimer scheduledTimerWithTimeInterval:86400 target:self selector:@selector(checkForUpdates) userInfo:nil repeats:YES]; self.checkForUpdatesNotRunning = YES; return; } DLog(@"Check for update plist error: %@", error); } } - (void)checkForUpdatesRequestFailed:(ASIHTTPRequest *)request { if (checkForUpdatesCalledFromMenu) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Check for Updates Error"]; [alert setInformativeText:@"Cannot check for updates now, try again later"]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; checkForUpdatesCalledFromMenu = NO; } [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(checkForUpdates) userInfo:nil repeats:YES]; self.checkForUpdatesNotRunning = YES; } #pragma mark - #pragma mark Menu Actions - (void)menuChangePithosAccount:(NSMenuItem *)sender { PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject]; if (!pithosAccount.active) return; if (![currentPithosAccount isEqualTo:pithosAccount] && [pithosAccounts containsObject:pithosAccount]) { if ([self.pithosBrowserController operationsPending]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Pending Operations"]; [alert setInformativeText:@"There are pending operations in the browser, do you want to change accounts and cancel them?"]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) return; } self.currentPithosAccount = pithosAccount; self.pithosBrowserController.pithosAccountManager = currentPithosAccount; [self showPithosBrowser:self]; self.pithosBrowserController.pithos = currentPithosAccount.pithos; } } //- (void)menuChangeSyncActive:(NSMenuItem *)sender { // PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject]; // if (!pithosAccount.active) // return; // pithosAccount.syncActive = !pithosAccount.syncActive; // if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) // self.pithosPreferencesController.syncActive = pithosAccount.syncActive; // [self savePithosAccounts:self]; //} - (IBAction)menuCheckForUpdates:(NSMenuItem *)sender { @synchronized(self) { checkForUpdatesCalledFromMenu = YES; } [self checkForUpdates]; } @end