// // 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 "ASIDownloadCache.h" #import "LastCompletedSyncTransformer.h" @implementation pithos_macosAppDelegate @synthesize pithosBrowserController, alwaysNo, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; userDefaults = [[NSUserDefaults standardUserDefaults] retain]; 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 retain]; } if (!currentPithosAccount) self.currentPithosAccount = [pithosAccounts objectAtIndex:0]; if (currentPithosAccount.active) { [self savePithosAccounts:self]; [self showPithosBrowser: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] retain]; [syncTimer fire]; } // 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)] autorelease]; [smallImage lockFocus]; [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; [sourceImage setSize:NSMakeSize(18, 18)]; [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy]; [smallImage unlockFocus]; statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; [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:[url lastPathComponent]]; NSProcessInfo *processInfo = [NSProcessInfo processInfo]; if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [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]; } NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken); if ([authUser length] && [authToken length]) { [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken]; [self savePithosAccounts:self]; if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) { pithosPreferencesController.authUser = pithosAccount.authUser; pithosPreferencesController.authToken = pithosAccount.authToken; } self.activated = YES; if ([pithosAccount isEqualTo:currentPithosAccount]) { [self showPithosBrowser: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] autorelease]; [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; } return NSTerminateNow; } #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:@""] autorelease]; [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] autorelease] 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:@""] autorelease]; [menuItem setEnabled:NO]; [menuItem setState:NO]; [menu addItem:menuItem]; } [menu addItem:[NSMenuItem separatorItem]]; [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Next Sync" action:@selector(sync) keyEquivalent:@""] autorelease]]; } } #pragma mark - #pragma mark Actions - (IBAction)showPithosBrowser:(id)sender { if (!activated) return; [pithosBrowserController showWindow:sender]; [[pithosBrowserController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } - (IBAction)showPithosPreferences:(id)sender { [pithosPreferencesController showWindow:sender]; [[pithosPreferencesController window] makeKeyAndOrderFront: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; pithosBrowserController.pithos = currentPithosAccount.pithos; break; } } if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) { self.activated = NO; [pithosBrowserController.window close]; [pithosBrowserController resetBrowser]; self.currentPithosAccount = [pithosAccounts objectAtIndex:0]; } } if ([self.syncPithosAccount isEqualTo:removedPithosAccount]) self.syncPithosAccount = nil; } #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] autorelease]; [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 showPithosBrowser:self]; pithosBrowserController.pithos = currentPithosAccount.pithos; } } //- (void)menuChangeSyncActive:(NSMenuItem *)sender { // PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject]; // if (!pithosAccount.active) // return; // pithosAccount.syncActive = !pithosAccount.syncActive; // if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) // pithosPreferencesController.syncActive = pithosAccount.syncActive; // [self savePithosAccounts:self]; //} @end