2 // pithos_macosAppDelegate.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 "pithos_macosAppDelegate.h"
39 #import "PithosAccount.h"
40 #import "PithosBrowserController.h"
41 #import "PithosPreferencesController.h"
42 #import "PithosSyncDaemon.h"
43 #import "ASIPithosRequest.h"
45 #import "LastCompletedSyncTransformer.h"
47 @implementation pithos_macosAppDelegate
48 @synthesize pithosBrowserController, pithosPreferencesController, alwaysNo, openAtLoginEnabled, openAtLogin, activated,
49 currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval, checkForUpdatesNotRunning;
51 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
52 [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
53 andSelector:@selector(handleAppleEvent:withReplyEvent:)
54 forEventClass:kInternetEventClass
55 andEventID:kAEGetURL];
57 // Based on: https://github.com/Mozketo/LaunchAtLoginController
58 // and: http://cocoatutorial.grapewave.com/2010/02/creating-andor-removing-a-login-item/
59 loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
61 LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self));
62 LSSharedFileListChanged(loginItems, (__bridge void *)(self));
63 self.openAtLoginEnabled = YES;
66 userDefaults = [NSUserDefaults standardUserDefaults];
68 syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
69 if (syncTimeInterval <= 0.0) {
70 syncTimeInterval = 180.0;
71 [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
72 [userDefaults synchronize];
75 activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"];
76 if (activityFacilityTimeInterval <= 0.0) {
77 activityFacilityTimeInterval = 0.05;
78 [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"];
79 [userDefaults synchronize];
82 NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
84 if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
85 self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
87 self.pithosAccounts = [NSMutableArray array];
89 if (![pithosAccounts count]) {
90 [pithosAccounts addObject:[PithosAccount pithosAccount]];
91 self.pithosAccounts = self.pithosAccounts;
96 pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]];
97 for (PithosAccount *pithosAccount in pithosAccounts) {
98 [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name];
99 if (!currentPithosAccount && pithosAccount.active)
100 currentPithosAccount = pithosAccount;
102 if (!currentPithosAccount)
103 self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
105 if (currentPithosAccount.active) {
106 [self savePithosAccounts:self];
107 self.pithosBrowserController.pithosAccountManager = currentPithosAccount;
108 [self showPithosBrowser:self];
109 self.pithosBrowserController.pithos = currentPithosAccount.pithos;
111 // XXX maybe call specifically to go to new account tab
112 [self showPithosPreferences:self];
115 syncTimer = [NSTimer scheduledTimerWithTimeInterval:syncTimeInterval
117 selector:@selector(sync)
122 @synchronized(self) {
123 self.checkForUpdatesNotRunning = YES;
125 [self checkForUpdates];
128 // Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
129 // and: http://www.cocoadev.com/index.pl?ThumbnailImages
130 - (void)awakeFromNib {
131 NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
133 NSImage *smallImage = [[NSImage alloc] initWithSize:NSMakeSize(18, 18)];
134 [smallImage lockFocus];
135 [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
136 [sourceImage setSize:NSMakeSize(18, 18)];
137 [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
138 [smallImage unlockFocus];
140 statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
141 [statusItem setMenu:statusMenu];
142 [statusItem setImage:sourceImage];
143 [statusItem setHighlightMode:YES];
148 - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
149 NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
150 NSString *host = [url host];
151 NSString *query = [url query];
152 PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[ASIPithosRequest decodeFromPercentEscape:[url lastPathComponent]]];
153 NSProcessInfo *processInfo = [NSProcessInfo processInfo];
154 if ([host isEqualToString:[NSString stringWithFormat:@"%d", [processInfo processIdentifier]]] && pithosAccount && query) {
157 NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
158 if (userRange.length == 0)
159 // XXX maybe show an error message?
161 NSUInteger authUserStartLocation = userRange.location + userRange.length;
162 NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
163 range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
164 if (userEndRange.length) {
165 authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
166 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
168 authUser = [[query substringFromIndex:authUserStartLocation]
169 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
173 NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
174 if (tokenRange.length == 0)
175 // XXX maybe show an error message?
177 NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
178 NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
179 range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
180 if (tokenEndRange.length) {
181 authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
182 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
184 authToken = [[query substringFromIndex:authTokenStartLocation]
185 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
188 DLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
189 if ([authUser length] && [authToken length]) {
190 [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken];
191 [self savePithosAccounts:self];
192 if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) {
193 self.pithosPreferencesController.authUser = pithosAccount.authUser;
194 self.pithosPreferencesController.authToken = pithosAccount.authToken;
196 self.activated = YES;
197 if ([pithosAccount isEqualTo:currentPithosAccount]) {
198 [self showPithosBrowser:self];
199 self.pithosBrowserController.pithos = pithosAccount.pithos;
202 // XXX else maybe show an error message?
204 // XXX else maybe show an error message?
207 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
208 [self savePithosAccounts:self];
209 if ([self.pithosBrowserController operationsPending]) {
210 NSAlert *alert = [[NSAlert alloc] init];
211 [alert setMessageText:@"Pending Operations"];
212 [alert setInformativeText:@"There are pending operations in the browser, do you want to quit and cancel them?"];
213 [alert addButtonWithTitle:@"OK"];
214 [alert addButtonWithTitle:@"Cancel"];
215 NSInteger choice = [alert runModal];
216 if (choice == NSAlertSecondButtonReturn)
217 return NSTerminateCancel;
220 LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self));
221 CFRelease(loginItems);
223 return NSTerminateNow;
227 #pragma mark Callbacks
229 - (void)loginItemsChanged {
230 NSURL *appURL = [[NSBundle mainBundle] bundleURL];
231 LSSharedFileListItemRef appItem = NULL;
232 NSArray *snapshot = (__bridge_transfer NSArray *)(LSSharedFileListCopySnapshot(loginItems, NULL));
233 for (id itemObject in snapshot) {
234 LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)itemObject;
235 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
236 CFURLRef currentItemURL = NULL;
237 LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL);
238 if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) {
239 CFRelease(currentItemURL);
244 CFRelease(currentItemURL);
247 if (appItem && (!openAtLogin || !openAtLoginEnabled))
248 self.openAtLogin = YES;
249 else if (!appItem && (openAtLogin || !openAtLoginEnabled))
250 self.openAtLogin = NO;
253 void LSSharedFileListChanged(LSSharedFileListRef inList, void *context) {
254 pithos_macosAppDelegate *self = (__bridge id)context;
255 [self loginItemsChanged];
259 #pragma mark Properties
261 - (PithosBrowserController *)pithosBrowserController {
262 if (!pithosBrowserController) {
263 pithosBrowserController = [[PithosBrowserController alloc] init];
265 return pithosBrowserController;
268 - (PithosPreferencesController *)pithosPreferencesController {
269 if (!pithosPreferencesController) {
270 pithosPreferencesController = [[PithosPreferencesController alloc] init];
272 return pithosPreferencesController;
275 - (void)setOpenAtLogin:(BOOL)anOpenAtLogin {
276 if (!openAtLoginEnabled) {
277 openAtLogin = anOpenAtLogin;
278 } else if (anOpenAtLogin != openAtLogin) {
279 NSURL *appURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
280 LSSharedFileListItemRef appItem = NULL;
281 NSArray *snapshot = (__bridge_transfer NSArray *)(LSSharedFileListCopySnapshot(loginItems, NULL));
282 for (id itemObject in snapshot) {
283 LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)itemObject;
284 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
285 CFURLRef currentItemURL = NULL;
286 LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL);
287 if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) {
288 CFRelease(currentItemURL);
293 CFRelease(currentItemURL);
298 LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, (__bridge CFURLRef)appURL, NULL, NULL);
302 LSSharedFileListItemRemove(loginItems, appItem);
309 #pragma mark NSMenuDelegate
311 - (void)menuNeedsUpdate:(NSMenu *)menu {
312 NSMenuItem *menuItem;
313 [menu removeAllItems];
314 if ([menu isEqualTo:accountsMenu]) {
315 [menu setAutoenablesItems:NO];
316 for (PithosAccount *pithosAccount in pithosAccounts) {
317 menuItem = [[NSMenuItem alloc] initWithTitle:pithosAccount.name
318 action:@selector(menuChangePithosAccount:)
320 [menuItem setRepresentedObject:pithosAccount];
321 [menuItem setEnabled:pithosAccount.active];
322 [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)];
323 [menu addItem:menuItem];
325 } else if ([menu isEqualTo:lastSyncMenu]) {
326 NSString *menuItemTitle;
327 [menu setAutoenablesItems:NO];
328 for (PithosAccount *pithosAccount in pithosAccounts) {
329 menuItemTitle = [NSString stringWithFormat:@"%@: %@",
331 [[[LastCompletedSyncTransformer alloc] init] transformedValue:pithosAccount.syncLastCompleted]];
332 if ([pithosAccount isEqualTo:syncPithosAccount] && [pithosAccount.syncDaemon isSyncing])
333 menuItemTitle = [menuItemTitle stringByAppendingString:@" (syncing)"];
334 // menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle
335 // action:@selector(menuChangeSyncActive:)
336 // keyEquivalent:@""] autorelease];
337 // [menuItem setRepresentedObject:pithosAccount];
338 // [menuItem setEnabled:pithosAccount.active];
339 // [menuItem setState:((pithosAccount.active && pithosAccount.syncActive) ? NSOnState : NSOffState)];
340 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:nil keyEquivalent:@""];
341 [menuItem setEnabled:NO];
342 [menuItem setState:NO];
343 [menu addItem:menuItem];
345 [menu addItem:[NSMenuItem separatorItem]];
346 [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Next Sync"
347 action:@selector(sync)
355 - (IBAction)showPithosBrowser:(id)sender {
358 [self.pithosBrowserController showWindow:sender];
359 [[self.pithosBrowserController window] makeKeyAndOrderFront:sender];
360 [NSApp activateIgnoringOtherApps:YES];
363 - (IBAction)showPithosPreferences:(id)sender {
364 [self.pithosPreferencesController showWindow:sender];
365 [[self.pithosPreferencesController window] makeKeyAndOrderFront:sender];
366 [NSApp activateIgnoringOtherApps:YES];
369 - (IBAction)showPithosAbout:(id)sender {
370 [NSApp orderFrontStandardAboutPanel:sender];
371 [NSApp activateIgnoringOtherApps:YES];
375 if (!activated || ![pithosAccounts count])
377 NSUInteger syncIndex;
378 BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount];
379 if (syncPithosAccountFound)
380 syncIndex = [pithosAccounts indexOfObject:syncPithosAccount];
382 PithosAccount *singleSyncPithosAccount = nil;
383 for (PithosAccount *pithosAccount in pithosAccounts) {
384 if (!singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
385 singleSyncPithosAccount = pithosAccount;
386 } else if (singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
387 singleSyncPithosAccount = nil;
392 if (syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
393 // An active syncDaemon was previously syncing
394 if (singleSyncPithosAccount && [singleSyncPithosAccount isEqualTo:syncPithosAccount]) {
395 // It's the only one, sync again
396 [syncPithosAccount.syncDaemon startDaemon];
397 [syncPithosAccount.syncDaemon sync];
399 } else if ([syncPithosAccount.syncDaemon isSyncing]) {
400 // It's still syncing, mark it as late and return
401 [syncPithosAccount.syncDaemon syncLate];
405 PithosAccount *newSyncPithosAccount = nil;
406 if (syncPithosAccountFound) {
407 for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(syncIndex + 1, [pithosAccounts count] - syncIndex - 1)]]) {
408 if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
409 newSyncPithosAccount = pithosAccount;
413 if (!newSyncPithosAccount) {
414 for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, syncIndex)]]) {
415 if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
416 newSyncPithosAccount = pithosAccount;
422 for (PithosAccount *pithosAccount in pithosAccounts) {
423 if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
424 newSyncPithosAccount = pithosAccount;
429 if (newSyncPithosAccount) {
430 // A different syncDaemon is found, sync it
431 self.syncPithosAccount = newSyncPithosAccount;
432 [syncPithosAccount.syncDaemon startDaemon];
433 [syncPithosAccount.syncDaemon sync];
434 } else if (syncPithosAccountFound && syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
435 [syncPithosAccount.syncDaemon startDaemon];
436 [syncPithosAccount.syncDaemon sync];
438 self.syncPithosAccount = nil;
442 - (void)savePithosAccounts:(id)sender {
443 [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
444 [userDefaults synchronize];
447 - (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
448 if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
449 for (PithosAccount *pithosAccount in pithosAccounts) {
450 if (pithosAccount.active) {
451 self.currentPithosAccount = pithosAccount;
452 self.pithosBrowserController.pithosAccountManager = currentPithosAccount;
453 self.pithosBrowserController.pithos = currentPithosAccount.pithos;
457 if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
459 [self.pithosBrowserController.window close];
460 [self.pithosBrowserController resetBrowser];
461 self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
464 if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
465 self.syncPithosAccount = nil;
468 - (void)checkForUpdates {
469 @synchronized(self) {
470 if (!checkForUpdatesNotRunning)
472 self.checkForUpdatesNotRunning = NO;
474 ASIHTTPRequest *checkForUpdatesRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"https://code.grnet.gr/projects/pithos-macos/repository/revisions/master/raw/pithos-macos/pithos-macos-Info.plist"]];
475 checkForUpdatesRequest.delegate = self;
476 checkForUpdatesRequest.didFinishSelector = @selector(checkForUpdatesRequestFinished:);
477 checkForUpdatesRequest.didFailSelector = @selector(checkForUpdatesRequestFailed:);
478 checkForUpdatesRequest.timeOutSeconds = 60;
479 checkForUpdatesRequest.numberOfTimesToRetryOnTimeout = 10;
480 [checkForUpdatesRequest startAsynchronous];
484 #pragma mark ASIHTTPRequestDelegate
486 - (void)checkForUpdatesRequestFinished:(ASIHTTPRequest *)request {
487 if (request.responseStatusCode == 200) {
488 NSError *error = nil;
489 NSDictionary *plistDictionary = [NSPropertyListSerialization propertyListWithData:[request.responseString dataUsingEncoding:NSUTF8StringEncoding]
490 options:NSPropertyListImmutable
494 NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
495 NSString *newVersion = [plistDictionary objectForKey:@"CFBundleVersion"];
496 NSURL *distributionURL = [NSURL URLWithString:[plistDictionary objectForKey:@"PithosDistributionURL"]];
497 if (newVersion && currentVersion && distributionURL && ([newVersion doubleValue] > [currentVersion doubleValue])) {
498 NSAlert *alert = [[NSAlert alloc] init];
499 [alert setMessageText:@"Updates Available"];
500 [alert setInformativeText:@"An updated version is available"];
501 [alert addButtonWithTitle:@"Download"];
503 [[NSWorkspace sharedWorkspace] openURL:distributionURL];
504 } else if (checkForUpdatesCalledFromMenu) {
505 NSAlert *alert = [[NSAlert alloc] init];
506 [alert setMessageText:@"No Updates Available"];
507 [alert setInformativeText:@"You are running the latest version"];
508 [alert addButtonWithTitle:@"OK"];
510 checkForUpdatesCalledFromMenu = NO;
512 [NSTimer scheduledTimerWithTimeInterval:86400
514 selector:@selector(checkForUpdates)
517 self.checkForUpdatesNotRunning = YES;
520 DLog(@"Check for update plist error: %@", error);
524 - (void)checkForUpdatesRequestFailed:(ASIHTTPRequest *)request {
525 if (checkForUpdatesCalledFromMenu) {
526 NSAlert *alert = [[NSAlert alloc] init];
527 [alert setMessageText:@"Check for Updates Error"];
528 [alert setInformativeText:@"Cannot check for updates now, try again later"];
529 [alert addButtonWithTitle:@"OK"];
531 checkForUpdatesCalledFromMenu = NO;
533 [NSTimer scheduledTimerWithTimeInterval:600
535 selector:@selector(checkForUpdates)
538 self.checkForUpdatesNotRunning = YES;
542 #pragma mark Menu Actions
544 - (void)menuChangePithosAccount:(NSMenuItem *)sender {
545 PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
546 if (!pithosAccount.active)
548 if (![currentPithosAccount isEqualTo:pithosAccount] && [pithosAccounts containsObject:pithosAccount]) {
549 if ([self.pithosBrowserController operationsPending]) {
550 NSAlert *alert = [[NSAlert alloc] init];
551 [alert setMessageText:@"Pending Operations"];
552 [alert setInformativeText:@"There are pending operations in the browser, do you want to change accounts and cancel them?"];
553 [alert addButtonWithTitle:@"OK"];
554 [alert addButtonWithTitle:@"Cancel"];
555 NSInteger choice = [alert runModal];
556 if (choice == NSAlertSecondButtonReturn)
559 self.currentPithosAccount = pithosAccount;
560 self.pithosBrowserController.pithosAccountManager = currentPithosAccount;
561 [self showPithosBrowser:self];
562 self.pithosBrowserController.pithos = currentPithosAccount.pithos;
566 //- (void)menuChangeSyncActive:(NSMenuItem *)sender {
567 // PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
568 // if (!pithosAccount.active)
570 // pithosAccount.syncActive = !pithosAccount.syncActive;
571 // if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
572 // self.pithosPreferencesController.syncActive = pithosAccount.syncActive;
573 // [self savePithosAccounts:self];
576 - (IBAction)menuCheckForUpdates:(NSMenuItem *)sender {
577 @synchronized(self) {
578 checkForUpdatesCalledFromMenu = YES;
580 [self checkForUpdates];