// pithos_macosAppDelegate.m
// pithos-macos
//
-// Copyright 2011 GRNET S.A. All rights reserved.
+// 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
// 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;
+@synthesize pithosBrowserController, alwaysNo, aboutVersion, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
andSelector:@selector(handleAppleEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
- [pithosBrowserController showWindow:self];
+
+ userDefaults = [[NSUserDefaults standardUserDefaults] retain];
+
+ NSString *stringURL = [userDefaults stringForKey:@"aboutURL"];
+ NSURL *testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
+ if (!testURL || !testURL.scheme || !testURL.host) {
+ [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs/pithos" forKey:@"aboutURL"];
+ [userDefaults synchronize];
+ }
+
+ syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
+ if (syncTimeInterval <= 0.0) {
+ syncTimeInterval = 180.0;
+ [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
+ [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];
- NSLog(@"host : '%@', query: '%@'", host, query);
+ PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[url lastPathComponent]];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
- if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && query) {
+ if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] &&
+ pithosAccount && query) {
// user=
NSString *authUser;
NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
authToken = [[query substringFromIndex:authTokenStartLocation]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
+
NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
- [pithosBrowserController authenticateFromURLWithAuthUser:authUser authToken: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 Properties
+
+- (NSString *)aboutVersion {
+ return [NSString stringWithFormat:@"About Pithos+ %@ (%@)",
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
+}
+
+#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)];
+ [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];
+}
+
+- (IBAction)aboutPithos:(id)sender {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
+}
+
+- (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