// // OpenStackAppDelegate.m // OpenStack // // Created by Mike Mayo on 9/30/10. // The OpenStack project is provided under the Apache 2.0 license. // #import "OpenStackAppDelegate.h" #import "RootViewController.h" #import "OpenStackAccount.h" #import "Keychain.h" #import "JSON.h" #import "Server.h" #import "Archiver.h" #import "Provider.h" #import "Image.h" #import "SettingsPluginHandler.h" #import "AddServerPluginHandler.h" #import "OpenStackAccount.h" #import "RSSFeedViewController.h" #import "PithosImageViewController.h" #import "RootViewController.h" #import "PasscodeViewController.h" #import "UIViewController+Conveniences.h" #import "HTNotifier.h" #import "Analytics.h" #import "ProvidersViewController.h" @implementation OpenStackAppDelegate @synthesize window; @synthesize navigationController; @synthesize splitViewController; @synthesize masterNavigationController; @synthesize barButtonItem; @synthesize rootViewController; @synthesize cachedObjectsDictionary; @synthesize cacheDictionaryFilePath; @synthesize cacheDirectoryPath; - (void)loadSettingsDefaults { // if settings haven't been set up yet, let's go ahead and set some sensible defaults // passcode settings are ALL sensitive, so they will all go in the keychain if (![Keychain getStringForKey:@"passcode_lock_passcode_on"]) { [Keychain setString:@"NO" forKey:@"passcode_lock_passcode_on"]; } if (![Keychain getStringForKey:@"passcode_lock_simple_passcode_on"]) { [Keychain setString:@"YES" forKey:@"passcode_lock_simple_passcode_on"]; } if (![Keychain getStringForKey:@"passcode_lock_erase_data_on"]) { [Keychain setString:@"NO" forKey:@"passcode_lock_erase_data_on"]; } userDefaults = [[NSUserDefaults standardUserDefaults] retain]; if (![userDefaults stringForKey:@"api_logging_level"]) { [userDefaults setValue:@"all" forKey:@"api_logging_level"]; } self.cachedObjectsDictionary = [userDefaults objectForKey:@"cachedObjectsDictionary"]; if (!cachedObjectsDictionary) { self.cachedObjectsDictionary = [NSMutableDictionary dictionary]; [userDefaults setValue:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"]; } [userDefaults synchronize]; } - (void)presentAndRelease:(NSTimer *)timer { UIViewController *vc = [timer.userInfo objectForKey:@"vc"]; [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO]; [vc release]; } - (void)showPasscodeLock { if ([[Keychain getStringForKey:@"passcode_lock_passcode_on"] isEqualToString:@"YES"]) { PasscodeViewController *vc = [[PasscodeViewController alloc] initWithNibName:@"PasscodeViewController" bundle:nil]; vc.mode = kModeEnterPasscode; //vc.rootViewController = self; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { vc.modalPresentationStyle = UIModalPresentationFullScreen; } if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate]; for (UIViewController *svc in app.splitViewController.viewControllers) { svc.view.alpha = 0.0; } // for some reason, this needs to be delayed [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(presentAndRelease:) userInfo:[NSDictionary dictionaryWithObject:vc forKey:@"vc"] repeats:NO]; } else { [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO]; [vc release]; } } } #pragma mark - #pragma mark Application lifecycle - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // [self setupDependencies]; [self loadSettingsDefaults]; rootViewController = [navigationController.viewControllers objectAtIndex:0]; OpenStackAppDelegate *delegate = (OpenStackAppDelegate *)self; navigationController.delegate = delegate; // Add the navigation controller's view to the window and display. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { /*RSSFeedViewController *vc = [[RSSFeedViewController alloc] initWithNibName:@"RSSFeedViewController" bundle:nil]; vc.feed = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Cloud Servers Status", @"feed://status.rackspacecloud.com/cloudservers/rss.xml", kCloudServersIcon, nil] forKeys:[NSArray arrayWithObjects:@"name", @"url", @"logo", nil]];*/ PithosImageViewController *vc = [[PithosImageViewController alloc] initWithNibName:@"PithosImageViewController" bundle:nil]; self.masterNavigationController = [[[UINavigationController alloc] initWithRootViewController:vc] autorelease]; self.masterNavigationController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor; self.masterNavigationController.navigationBar.translucent = self.navigationController.navigationBar.translucent; self.masterNavigationController.navigationBar.opaque = self.navigationController.navigationBar.opaque; self.masterNavigationController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle; self.splitViewController.delegate = [navigationController.viewControllers objectAtIndex:0]; self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, self.masterNavigationController, nil]; [window addSubview:splitViewController.view]; [window makeKeyAndVisible]; [vc release]; } else { [window addSubview:navigationController.view]; [window makeKeyAndVisible]; } serviceUnavailableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"serviceUnavailable" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Service Unavailable" message:@"The API is currently unavailable. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver]; }]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); self.cacheDirectoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"DownloadedFiles"]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; [fileManager createDirectoryAtPath:cacheDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[NSString stringWithFormat:@"Error in creating cache directory\n%@",error.localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; } return YES; } //- (void) setupDependencies { //#if TARGET_OS_EMBEDDED // // NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"Constants" ofType:@"plist"]; // // if ([[NSFileManager defaultManager] fileExistsAtPath:path]){ // // NSDictionary *constants = [NSDictionary dictionaryWithContentsOfFile:path]; // // [HTNotifier startNotifierWithAPIKey:[constants objectForKey:@"HOPTOAD_ACCOUNT_KEY"] // environmentName:HTNotifierAppStoreEnvironment]; // [[GANTracker sharedTracker] startTrackerWithAccountID:[constants objectForKey:@"ANALYTICS_ACCOUNT_KEY"] dispatchPeriod:10 delegate:nil]; // // // track the app version // NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; // [[GANTracker sharedTracker] setCustomVariableAtIndex:1 name:@"app_version" value:version withError:nil]; // // DispatchAnalytics(); // // } else { // [HTNotifier startNotifierWithAPIKey:@"HOPTOAD_ACCOUNT_KEY" environmentName:HTNotifierAppStoreEnvironment]; // [[GANTracker sharedTracker] startTrackerWithAccountID:@"ANALYTICS_ACCOUNT_KEY" dispatchPeriod:10 delegate:nil]; // } // //#endif //} - (void)applicationWillResignActive:(UIApplication *)application { // DispatchAnalytics(); } - (void)applicationDidEnterBackground:(UIApplication *)application { /* Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. If your application supports background execution, called instead of applicationWillTerminate: when the user quits. */ } - (void)applicationWillEnterForeground:(UIApplication *)application { /* Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background. */ } - (void)applicationDidBecomeActive:(UIApplication *)application { /* Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. */ [userDefaults setBool:NO forKey:@"already_failed_on_connection"]; [userDefaults synchronize]; [self showPasscodeLock]; //DispatchAnalytics(); } - (void)applicationWillTerminate:(UIApplication *)application { /* Called when the application is about to terminate. See also applicationDidEnterBackground:. */ // TODO: perhaps this is a good place to release all the stuff allocated in // +(void)initialize methods all over the place // [[SettingsPluginHandler plugins] release]; // [[AddServerPluginHandler plugins] release]; // [[OpenStackAccount accounts] release]; } - (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{ TrackViewController(viewController); } - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { if (!url) return NO; NSString *host = [url host]; NSString *query = [url query]; if ([host hasPrefix:@"login"] && query) { // user= NSString *authUser; NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch]; if (userRange.length == 0) // XXX maybe show an error message? return NO; 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 NO; 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]; } if (authToken || authUser) { UIViewController *vc = nil; if (([navigationController.visibleViewController class] == [AccountDetailsViewController class]) || ([navigationController.visibleViewController class] == [AccountSettingsViewController class])) vc = navigationController.visibleViewController; else if (([masterNavigationController.visibleViewController class] == [AccountDetailsViewController class]) || ([masterNavigationController.visibleViewController class] == [AccountSettingsViewController class])) vc = masterNavigationController.visibleViewController; if (vc) { if (authToken) [vc setValue:authToken forKey:@"apiKey"]; if (authUser) [vc setValue:authUser forKey:@"userName"]; [((UITableViewController *)vc).tableView reloadData]; } } } // XXX else maybe show an error message? return YES; } #pragma mark - persistence - (void)saveCacheDictionary { [userDefaults setObject:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"]; [userDefaults synchronize]; } #pragma mark - #pragma mark Memory management - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { /* Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later. */ } - (void)dealloc { [navigationController release]; [splitViewController release]; [masterNavigationController release]; [barButtonItem release]; [rootViewController release]; [window release]; [cachedObjectsDictionary release]; [userDefaults release]; [super dealloc]; } @end