Fix bugs
[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 "LastCompletedSyncTransformer.h"
46
47 @implementation pithos_macosAppDelegate
48 @synthesize pithosBrowserController, pithosPreferencesController, alwaysNo, openAtLoginEnabled, openAtLogin, activated, 
49 currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval, checkForUpdatesNotRunning;
50
51 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
52     [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
53                                                        andSelector:@selector(handleAppleEvent:withReplyEvent:) 
54                                                      forEventClass:kInternetEventClass 
55                                                         andEventID:kAEGetURL];
56     
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);
60     if (loginItems) {
61         LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self));
62         LSSharedFileListChanged(loginItems, (__bridge void *)(self));
63         self.openAtLoginEnabled = YES;
64     }
65     
66     userDefaults = [NSUserDefaults standardUserDefaults];
67     
68     syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
69     if (syncTimeInterval <= 0.0) {
70         syncTimeInterval = 180.0;
71         [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
72         [userDefaults synchronize];
73     }
74
75     activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"];
76     if (activityFacilityTimeInterval <= 0.0) {
77         activityFacilityTimeInterval = 0.05;
78         [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"];
79         [userDefaults synchronize];
80     }
81
82     NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
83     NSArray *tmpArray;
84     if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
85         self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
86     else
87         self.pithosAccounts = [NSMutableArray array];
88     
89     if (![pithosAccounts count]) {
90         [pithosAccounts addObject:[PithosAccount pithosAccount]];
91         self.pithosAccounts = self.pithosAccounts;
92     } else {
93         self.activated = YES;
94     }
95     
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;
101     }
102     if (!currentPithosAccount)
103         self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
104     
105     if (currentPithosAccount.active) {
106         [self savePithosAccounts:self];
107         self.pithosBrowserController.pithosAccountManager = currentPithosAccount;
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];
120     [syncTimer fire];
121     
122     @synchronized(self) {
123         self.checkForUpdatesNotRunning = YES;
124     }
125     [self checkForUpdates];
126 }
127
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"];
132     
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];
139     
140     statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
141     [statusItem setMenu:statusMenu];
142     [statusItem setImage:sourceImage];
143     [statusItem setHighlightMode:YES];
144     
145     self.alwaysNo = NO;
146 }
147
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) {
155         // user=
156         NSString *authUser;
157         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
158         if (userRange.length == 0)
159             // XXX maybe show an error message?
160             return;
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];
167         } else {
168             authUser = [[query substringFromIndex:authUserStartLocation]
169                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
170         }
171         // token=
172         NSString *authToken;
173         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
174         if (tokenRange.length == 0)
175             // XXX maybe show an error message?
176             return;
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];
183         } else {
184             authToken = [[query substringFromIndex:authTokenStartLocation]
185                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
186         }
187         
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;
195             }
196             self.activated = YES;
197             if ([pithosAccount isEqualTo:currentPithosAccount]) {
198                 [self showPithosBrowser:self];
199                 self.pithosBrowserController.pithos = pithosAccount.pithos;
200             }
201         }
202         // XXX else maybe show an error message?
203     }
204     // XXX else maybe show an error message?
205 }
206
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;
218     }
219     if (loginItems) {
220         LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), (CFStringRef)NSDefaultRunLoopMode, LSSharedFileListChanged, (__bridge void *)(self));
221         CFRelease(loginItems);
222     }
223     return NSTerminateNow;
224 }
225
226 #pragma mark -
227 #pragma mark Callbacks
228
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, &currentItemURL, NULL);
238         if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) {
239             CFRelease(currentItemURL);
240             appItem = item;
241             break;
242         }
243         if (currentItemURL)
244             CFRelease(currentItemURL);
245     }
246     
247     if (appItem && (!openAtLogin || !openAtLoginEnabled))
248         self.openAtLogin = YES;
249     else if (!appItem && (openAtLogin || !openAtLoginEnabled))
250         self.openAtLogin = NO;
251 }
252
253 void LSSharedFileListChanged(LSSharedFileListRef inList, void *context) {
254     pithos_macosAppDelegate *self = (__bridge id)context;
255     [self loginItemsChanged];
256 }
257
258 #pragma mark -
259 #pragma mark Properties
260
261 - (PithosBrowserController *)pithosBrowserController {
262     if (!pithosBrowserController) {
263         pithosBrowserController = [[PithosBrowserController alloc] init];
264     }
265     return pithosBrowserController;
266 }
267
268 - (PithosPreferencesController *)pithosPreferencesController {
269     if (!pithosPreferencesController) {
270         pithosPreferencesController = [[PithosPreferencesController alloc] init];
271     }
272     return pithosPreferencesController;
273 }
274
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, &currentItemURL, NULL);
287             if (currentItemURL && CFEqual(currentItemURL, (__bridge CFTypeRef)(appURL))) {
288                 CFRelease(currentItemURL);
289                 appItem = item;
290                 break;
291             }
292             if (currentItemURL)
293                 CFRelease(currentItemURL);
294         }
295         
296         if (anOpenAtLogin) {
297             if (!appItem)
298                 LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, (__bridge CFURLRef)appURL, NULL, NULL);
299             openAtLogin = YES;
300         } else {
301             if (appItem)
302                 LSSharedFileListItemRemove(loginItems, appItem);
303             openAtLogin = NO;
304         }
305     }
306 }
307
308 #pragma mark -
309 #pragma mark NSMenuDelegate
310
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:) 
319                                             keyEquivalent:@""];
320             [menuItem setRepresentedObject:pithosAccount];
321             [menuItem setEnabled:pithosAccount.active];
322             [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)];
323             [menu addItem:menuItem];
324         }
325     } else if ([menu isEqualTo:lastSyncMenu]) {
326         NSString *menuItemTitle;
327         [menu setAutoenablesItems:NO];
328         for (PithosAccount *pithosAccount in pithosAccounts) {
329             menuItemTitle = [NSString stringWithFormat:@"%@: %@", 
330                              pithosAccount.name, 
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];
344         }
345         [menu addItem:[NSMenuItem separatorItem]];
346         [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Next Sync" 
347                                                   action:@selector(sync) 
348                                            keyEquivalent:@""]];
349     }
350 }
351
352 #pragma mark -
353 #pragma mark Actions
354
355 - (IBAction)showPithosBrowser:(id)sender {
356     if (!activated)
357         return;
358     [self.pithosBrowserController showWindow:sender];
359     [[self.pithosBrowserController window] makeKeyAndOrderFront:sender];
360     [NSApp activateIgnoringOtherApps:YES];
361 }
362
363 - (IBAction)showPithosPreferences:(id)sender {
364     [self.pithosPreferencesController showWindow:sender];
365     [[self.pithosPreferencesController window] makeKeyAndOrderFront:sender];
366     [NSApp activateIgnoringOtherApps:YES];
367 }
368
369 - (IBAction)showPithosAbout:(id)sender {
370     [NSApp orderFrontStandardAboutPanel:sender];
371     [NSApp activateIgnoringOtherApps:YES];
372 }
373
374 - (void)sync {
375     if (!activated || ![pithosAccounts count])
376         return;
377     NSUInteger syncIndex;
378     BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount];
379     if (syncPithosAccountFound)
380          syncIndex = [pithosAccounts indexOfObject:syncPithosAccount];
381     
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;
388             break;
389         }
390     }
391     
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];
398             return;
399         } else if ([syncPithosAccount.syncDaemon isSyncing]) {
400             // It's still syncing, mark it as late and return
401             [syncPithosAccount.syncDaemon syncLate];
402             return;
403         }
404     }
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;
410                 break;
411             }
412         }
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;
417                     break;
418                 }
419             }
420         }
421     } else {
422         for (PithosAccount *pithosAccount in pithosAccounts) {
423             if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
424                 newSyncPithosAccount = pithosAccount;
425                 break;
426             }
427         }
428     }        
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];
437     } else {
438         self.syncPithosAccount = nil;
439     }
440 }
441
442 - (void)savePithosAccounts:(id)sender {
443     [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
444     [userDefaults synchronize];
445 }
446
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;
454                 break;
455             }
456         }
457         if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
458             self.activated = NO;
459             [self.pithosBrowserController.window close];
460             [self.pithosBrowserController resetBrowser];
461             self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
462         }
463     }
464     if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
465         self.syncPithosAccount = nil;
466 }
467
468 - (void)checkForUpdates {
469     @synchronized(self) {
470         if (!checkForUpdatesNotRunning)
471             return;
472         self.checkForUpdatesNotRunning = NO;
473     }
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];
481 }
482
483 #pragma mark -
484 #pragma mark ASIHTTPRequestDelegate
485
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 
491                                                                                    format:NULL 
492                                                                                     error:&error];
493         if (!error) {
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"];
502                 [alert runModal];
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"];
509                 [alert runModal];
510                 checkForUpdatesCalledFromMenu = NO;
511             }
512             [NSTimer scheduledTimerWithTimeInterval:86400 
513                                              target:self 
514                                            selector:@selector(checkForUpdates) 
515                                            userInfo:nil 
516                                             repeats:YES];
517             self.checkForUpdatesNotRunning = YES;
518             return;
519         }
520         DLog(@"Check for update plist error: %@", error);
521      }
522 }
523
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"];
530         [alert runModal];
531         checkForUpdatesCalledFromMenu = NO;
532     }
533     [NSTimer scheduledTimerWithTimeInterval:600 
534                                      target:self 
535                                    selector:@selector(checkForUpdates) 
536                                    userInfo:nil 
537                                     repeats:YES];
538     self.checkForUpdatesNotRunning = YES;
539 }
540
541 #pragma mark -
542 #pragma mark Menu Actions
543
544 - (void)menuChangePithosAccount:(NSMenuItem *)sender {
545     PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
546     if (!pithosAccount.active)
547         return;
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) 
557                 return;
558         }
559         self.currentPithosAccount = pithosAccount;
560         self.pithosBrowserController.pithosAccountManager = currentPithosAccount;
561         [self showPithosBrowser:self];
562         self.pithosBrowserController.pithos = currentPithosAccount.pithos;
563     }
564 }
565
566 //- (void)menuChangeSyncActive:(NSMenuItem *)sender {
567 //    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
568 //    if (!pithosAccount.active)
569 //        return;
570 //    pithosAccount.syncActive = !pithosAccount.syncActive;
571 //    if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
572 //        self.pithosPreferencesController.syncActive = pithosAccount.syncActive;
573 //    [self savePithosAccounts:self];
574 //}
575
576 - (IBAction)menuCheckForUpdates:(NSMenuItem *)sender {
577     @synchronized(self) {
578         checkForUpdatesCalledFromMenu = YES;
579     }
580     [self checkForUpdates];
581 }
582
583 @end