// 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, pithosSyncDaemon, alwaysNo;
+@synthesize pithosBrowserController, alwaysNo, aboutVersion, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
- userDefaults = [[NSUserDefaults standardUserDefaults] retain];
- NSString *stringURL;
- NSURL *testURL;
+ [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
+ andSelector:@selector(handleAppleEvent:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
- stringURL = [userDefaults stringForKey:@"storageURLPrefix"];
- testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
- if (!testURL || !testURL.scheme || !testURL.host)
- [userDefaults setObject:@"https://plus.pithos.grnet.gr/v1" forKey:@"storageURLPrefix"];
-
- stringURL = [userDefaults stringForKey:@"publicURLPrefix"];
- testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
- if (!testURL || !testURL.scheme || !testURL.host)
- [userDefaults setObject:@"https://plus.pithos.grnet.gr" forKey:@"publicURLPrefix"];
-
- stringURL = [userDefaults stringForKey:@"loginURLPrefix"];
- testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
- if (!testURL || !testURL.scheme || !testURL.host)
- [userDefaults setObject:@"https://plus.pithos.grnet.gr/login" forKey:@"loginURLPrefix"];
-
- stringURL = [userDefaults stringForKey:@"aboutURL"];
- testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
- if (!testURL || !testURL.scheme || !testURL.host)
- [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs" forKey:@"aboutURL"];
+ 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];
+ }
- NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
- if (!syncDirectoryPath || ![syncDirectoryPath length]) {
- syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
- } else {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL isDirectory;
- BOOL fileExists = [fileManager fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory];
- NSError *error = nil;
- if ((fileExists && !isDirectory) ||
- (!fileExists && (![fileManager createDirectoryAtPath:syncDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || error))) {
- syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
- }
+ syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
+ if (syncTimeInterval <= 0.0) {
+ syncTimeInterval = 180.0;
+ [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
+ [userDefaults synchronize];
}
- [userDefaults setObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
- NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
- if (!syncContainerName || ![syncContainerName length] || [syncContainerName isEqualToString:@"trash"])
- [userDefaults setObject:@"pithos" forKey:@"syncContainerName"];
-
- double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInteral"];
- if (syncTimeInterval <= 0)
- [userDefaults setDouble:180.0 forKey:@"syncTimeInteral"];
+ NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
+ NSArray *tmpArray;
+ if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
+ self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
+ else
+ self.pithosAccounts = [NSMutableArray array];
- [userDefaults synchronize];
+ if (![pithosAccounts count]) {
+ [pithosAccounts addObject:[PithosAccount pithosAccount]];
+ self.pithosAccounts = self.pithosAccounts;
+ } else {
+ self.activated = YES;
+ }
- [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
- andSelector:@selector(handleAppleEvent:withReplyEvent:)
- forEventClass:kInternetEventClass
- andEventID:kAEGetURL];
- [self showPithosBrowser:self];
+ 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];
- [self authenticate];
+ 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/
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];
NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
if ([authUser length] && [authToken length]) {
- [userDefaults setObject:authUser forKey:@"authUser"];
- [userDefaults setObject:authToken forKey:@"authToken"];
- [userDefaults synchronize];
-
- [self authenticate];
+ [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 Actions
+#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];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
}
-- (IBAction)syncNow:(id)sender {
- [pithosSyncDaemon sync];
+- (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 Authentication
+#pragma mark Menu Actions
-- (void)login {
- NSProcessInfo *processInfo = [NSProcessInfo processInfo];
- NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d",
- [userDefaults stringForKey:@"loginURLPrefix"], [processInfo processName], [processInfo processIdentifier]];
- NSLog(@"loginURL: %@", loginURL);
- [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
+- (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)authenticate {
- NSString *authUser = [userDefaults stringForKey:@"authUser"];
- NSString *authToken = [userDefaults stringForKey:@"authToken"];
- NSString *storageURLPrefix = [userDefaults stringForKey:@"storageURLPrefix"];
- NSString *publicURLPrefix = [userDefaults stringForKey:@"publicURLPrefix"];
- NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
- NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
- double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
- NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
- if (([authUser length] == 0) || ([authToken length] == 0)) {
- [self showPithosPreferences:self];
- } else if ([authUser length] && [authToken length] &&
- (![[ASIPithosRequest authUser] isEqualToString:authUser] || ![[ASIPithosRequest authToken] isEqualToString:authToken])) {
- [userDefaults setObject:authUser forKey:@"previousAuthUser"];
- [userDefaults setObject:authToken forKey:@"previousAuthToken"];
-
- [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
- [[ASIPithosRequest sharedQueue] cancelAllOperations];
-
- [ASIPithosRequest setAuthURL:storageURLPrefix];
- [ASIPithosRequest setStorageURLPrefix:storageURLPrefix];
- [ASIPithosRequest setAuthUser:authUser];
- [ASIPithosRequest setAuthToken:authToken];
- [ASIPithosRequest setPublicURLPrefix:publicURLPrefix];
-
- self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath
- containerName:syncContainerName
- timeInterval:syncTimeInterval
- resetLocalState:NO] autorelease];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAuthenticationCredentialsUpdated" object:self];
- } else if (![pithosSyncDaemon.directoryPath isEqualToString:syncDirectoryPath]) {
- self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath
- containerName:syncContainerName
- timeInterval:syncTimeInterval
- resetLocalState:YES] autorelease];
- }
+- (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