2 // OpenStackAppDelegate.m
5 // Created by Mike Mayo on 9/30/10.
6 // The OpenStack project is provided under the Apache 2.0 license.
9 #import "OpenStackAppDelegate.h"
10 #import "RootViewController.h"
15 #import "OpenStackAccount.h"
17 #import "StorageObject.h"
18 #import "OpenStackRequest.h"
19 #import "PithosImageViewController.h"
20 #import "RootViewController.h"
21 #import "PasscodeViewController.h"
22 #import "AccountDetailsViewController.h"
23 #import "AccountSettingsViewController.h"
24 #import "UIViewController+Conveniences.h"
25 #import "ProvidersViewController.h"
27 @implementation OpenStackAppDelegate
30 @synthesize navigationController;
31 @synthesize splitViewController;
32 @synthesize masterNavigationController;
33 @synthesize barButtonItem;
34 @synthesize rootViewController;
35 @synthesize cachedObjectsDictionary;
36 @synthesize cacheDictionaryFilePath;
37 @synthesize cacheDirectoryPath;
38 @synthesize objectDownloadRequests;
40 - (void)loadSettingsDefaults {
41 // if settings haven't been set up yet, let's go ahead and set some sensible defaults
42 // passcode settings are ALL sensitive, so they will all go in the keychain
43 if (![Keychain getStringForKey:@"passcode_lock_passcode_on"]) {
44 [Keychain setString:@"NO" forKey:@"passcode_lock_passcode_on"];
46 if (![Keychain getStringForKey:@"passcode_lock_simple_passcode_on"]) {
47 [Keychain setString:@"YES" forKey:@"passcode_lock_simple_passcode_on"];
49 if (![Keychain getStringForKey:@"passcode_lock_erase_data_on"]) {
50 [Keychain setString:@"NO" forKey:@"passcode_lock_erase_data_on"];
53 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
55 self.cachedObjectsDictionary = [userDefaults objectForKey:@"cachedObjectsDictionary"];
56 if (!cachedObjectsDictionary) {
57 self.cachedObjectsDictionary = [NSMutableDictionary dictionary];
58 [userDefaults setValue:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
61 objectDownloadRequests = [[NSMutableDictionary alloc] init];
63 // Load and persist accounts once to allow any migration checks to take effect.
64 [OpenStackAccount persist:[OpenStackAccount accounts]];
66 [userDefaults synchronize];
69 - (void)presentAndRelease:(NSTimer *)timer {
70 UIViewController *vc = [timer.userInfo objectForKey:@"vc"];
71 [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
74 - (void)showPasscodeLock {
79 if ([[Keychain getStringForKey:@"passcode_lock_passcode_on"] isEqualToString:@"YES"]) {
80 PasscodeViewController *vc = [[PasscodeViewController alloc] initWithNibName:@"PasscodeViewController" bundle:nil];
81 vc.mode = kModeEnterPasscode;
82 vc.rootViewController = self.rootViewController;
83 if (self.navigationController.modalViewController) {
84 [self.navigationController dismissModalViewControllerAnimated:NO];
87 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
88 vc.modalPresentationStyle = UIModalPresentationFullScreen;
89 for (UIViewController *svc in self.splitViewController.viewControllers) {
92 // for some reason, this needs to be delayed
93 [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(presentAndRelease:) userInfo:[NSDictionary dictionaryWithObject:vc forKey:@"vc"] repeats:NO];
95 [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
101 - (void)presentRootPopover {
102 [self.rootViewController.popoverController presentPopoverFromBarButtonItem:self.barButtonItem
103 permittedArrowDirections:UIPopoverArrowDirectionAny
107 #pragma mark - Application lifecycle
109 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
110 [self loadSettingsDefaults];
112 rootViewController = [navigationController.viewControllers objectAtIndex:0];
113 OpenStackAppDelegate <UINavigationControllerDelegate> *delegate = (OpenStackAppDelegate <UINavigationControllerDelegate> *)self;
114 navigationController.delegate = delegate;
116 // Add the navigation controller's view to the window and display.
117 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
118 PithosImageViewController *vc = [[PithosImageViewController alloc] initWithNibName:@"PithosImageViewController" bundle:nil];
119 self.masterNavigationController = [[[UINavigationController alloc] initWithRootViewController:vc] autorelease];
120 self.masterNavigationController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;
121 self.masterNavigationController.navigationBar.translucent = self.navigationController.navigationBar.translucent;
122 self.masterNavigationController.navigationBar.opaque = self.navigationController.navigationBar.opaque;
123 self.masterNavigationController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle;
125 self.splitViewController.delegate = [navigationController.viewControllers objectAtIndex:0];
126 self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, self.masterNavigationController, nil];
128 window.rootViewController = splitViewController;
129 [window addSubview:splitViewController.view];
130 [window makeKeyAndVisible];
133 if ([[UIDevice currentDevice].systemVersion hasPrefix:@"5.1"]) {
134 // In 5.1, without any delay, the master view controller has full screen size.
135 [self performSelector:@selector(presentRootPopover) withObject:nil afterDelay:0.1];
137 [self presentRootPopover];
140 window.rootViewController = navigationController;
141 [window addSubview:navigationController.view];
142 [window makeKeyAndVisible];
145 serviceUnavailableObserver = [[NSNotificationCenter defaultCenter]
146 addObserverForName:@"serviceUnavailable"
148 queue:[NSOperationQueue mainQueue]
149 usingBlock:^(NSNotification* notification) {
150 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Service Unavailable"
151 message:@"The API is currently unavailable. Please try again later."
152 delegate:nil cancelButtonTitle:@"OK"
153 otherButtonTitles:nil];
156 [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver];
159 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
160 self.cacheDirectoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"DownloadedFiles"];
161 NSFileManager *fileManager = [NSFileManager defaultManager];
162 NSError *error = nil;
163 [fileManager createDirectoryAtPath:cacheDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
165 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
166 message:[NSString stringWithFormat:@"Error in creating cache directory\n%@", error.localizedDescription]
168 cancelButtonTitle:@"OK"
169 otherButtonTitles:nil];
177 - (void)applicationDidBecomeActive:(UIApplication *)application {
178 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
179 [userDefaults setBool:NO forKey:@"already_failed_on_connection"];
180 [userDefaults synchronize];
182 [self showPasscodeLock];
186 //- (void)applicationWillTerminate:(UIApplication *)application {
187 // // TODO: perhaps this is a good place to release all the stuff allocated in
188 // // +(void)initialize methods all over the place
189 //// [[OpenStackAccount accounts] release];
192 - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
196 NSString *host = [url host];
197 NSString *query = [url query];
199 if ([host hasPrefix:@"login"] && query) {
202 NSString *authUser = nil;
203 NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
204 if (userRange.length) {
205 NSUInteger authUserStartLocation = userRange.location + userRange.length;
206 NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
207 range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
208 if (userEndRange.length) {
209 authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
210 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
212 authUser = [[query substringFromIndex:authUserStartLocation]
213 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
218 NSString *authToken = nil;
219 NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
220 if (tokenRange.length == 0)
221 // XXX maybe show an error message?
223 NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
224 NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
225 range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
226 if (tokenEndRange.length) {
227 authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
228 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
230 authToken = [[query substringFromIndex:authTokenStartLocation]
231 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
234 if (authToken || authUser) {
235 UIViewController *vc = nil;
236 if (([navigationController.visibleViewController class] == [AccountDetailsViewController class]) ||
237 ([navigationController.visibleViewController class] == [AccountSettingsViewController class])) {
238 vc = navigationController.visibleViewController;
239 } else if (([masterNavigationController.visibleViewController class] == [AccountDetailsViewController class]) ||
240 ([masterNavigationController.visibleViewController class] == [AccountSettingsViewController class])) {
241 vc = masterNavigationController.visibleViewController;
245 if ([vc class] == [AccountDetailsViewController class]) {
246 ((AccountDetailsViewController *)vc).authToken = authToken;
247 ((AccountDetailsViewController *)vc).username = authUser;
248 [((AccountDetailsViewController *)vc).tableView reloadData];
249 } else if ([vc class] == [AccountSettingsViewController class]) {
250 ((AccountSettingsViewController *)vc).authToken = authToken;
251 ((AccountSettingsViewController *)vc).username = authUser;
252 [((AccountSettingsViewController *)vc).tableView reloadData];
257 // XXX else maybe show an error message?
261 #pragma mark - Persistence
263 - (NSString *)cacheFilePathForHash:(NSString *)hash {
266 NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
267 if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
268 @synchronized(self.cachedObjectsDictionary) {
269 [self.cachedObjectsDictionary removeObjectForKey:hash];
271 [self saveCacheDictionary];
277 - (void)setCacheFilePath:(NSString *)filePath forHash:(NSString *)hash {
278 @synchronized(self.cachedObjectsDictionary) {
279 [self.cachedObjectsDictionary setObject:filePath forKey:hash];
280 [self saveCacheDictionary];
284 - (BOOL)removeCacheObjectForHash:(NSString *)hash {
285 NSFileManager *fileManager = [NSFileManager defaultManager];
286 NSError *error = nil;
287 NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
289 @synchronized(self.cachedObjectsDictionary) {
290 [fileManager removeItemAtPath:filePath error:&error];
292 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
293 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
295 cancelButtonTitle:@"OK"
296 otherButtonTitles:nil];
301 [self.cachedObjectsDictionary removeObjectForKey:hash];
304 [self saveCacheDictionary];
308 - (void)removeAllCacheObjects {
309 NSFileManager *fileManager = [NSFileManager defaultManager];
310 NSError *error = nil;
312 NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:self.cacheDirectoryPath];
313 @synchronized(self.cachedObjectsDictionary) {
314 while (filePath = [directoryEnumerator nextObject]) {
315 [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@", self.cacheDirectoryPath, filePath] error:&error];
317 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
318 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
320 cancelButtonTitle:@"OK"
321 otherButtonTitles:nil];
325 for (NSString *hash in [self.cachedObjectsDictionary allKeys])
326 if ([[self.cachedObjectsDictionary objectForKey:hash] isEqualToString:filePath])
327 [self.cachedObjectsDictionary removeObjectForKey:hash];
331 [self saveCacheDictionary];
334 - (void)saveCacheDictionary {
335 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
336 [userDefaults setObject:self.cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
337 [userDefaults synchronize];
340 - (OpenStackRequest *)objectDownloadRequestForAccount:(OpenStackAccount *)account
341 container:(Container *)container
342 object:(StorageObject *)object {
343 return [[[self.objectDownloadRequests objectForKey:account.uuid] objectForKey:container.name] objectForKey:object.fullPath];
346 - (void)setObjectDownloadRequest:(OpenStackRequest *)request
347 forAccount:(OpenStackAccount *)account
348 container:(Container *)container
349 object:(StorageObject *)object {
350 NSMutableDictionary *accountObjectDownloadRequests = [self.objectDownloadRequests objectForKey:account.uuid];
351 if (!accountObjectDownloadRequests) {
352 accountObjectDownloadRequests = [NSMutableDictionary dictionary];
353 [self.objectDownloadRequests setObject:accountObjectDownloadRequests forKey:account.uuid];
355 NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
356 if (!containerObjectDownloadRequests) {
357 containerObjectDownloadRequests = [NSMutableDictionary dictionary];
358 [accountObjectDownloadRequests setObject:containerObjectDownloadRequests forKey:container.name];
360 [containerObjectDownloadRequests setObject:request forKey:object.fullPath];
363 - (void)removeObjectDownloadRequestForAccount:(OpenStackAccount *)account
364 container:(Container *)container
365 object:(StorageObject *)object {
366 NSMutableDictionary *accountObjectDownloadRequests = [self.objectDownloadRequests objectForKey:account.uuid];
367 if (accountObjectDownloadRequests) {
368 NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
369 if (containerObjectDownloadRequests) {
370 [containerObjectDownloadRequests removeObjectForKey:object.fullPath];
375 #pragma mark - Memory management
378 [navigationController release];
379 [splitViewController release];
380 [masterNavigationController release];
381 [barButtonItem release];
382 [rootViewController release];
384 [cachedObjectsDictionary release];
385 [objectDownloadRequests release];