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 [userDefaults synchronize];
66 - (void)presentAndRelease:(NSTimer *)timer {
67 UIViewController *vc = [timer.userInfo objectForKey:@"vc"];
68 [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
71 - (void)showPasscodeLock {
76 if ([[Keychain getStringForKey:@"passcode_lock_passcode_on"] isEqualToString:@"YES"]) {
77 PasscodeViewController *vc = [[PasscodeViewController alloc] initWithNibName:@"PasscodeViewController" bundle:nil];
78 vc.mode = kModeEnterPasscode;
79 vc.rootViewController = self.rootViewController;
80 if (self.navigationController.modalViewController) {
81 [self.navigationController dismissModalViewControllerAnimated:NO];
84 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
85 vc.modalPresentationStyle = UIModalPresentationFullScreen;
86 for (UIViewController *svc in self.splitViewController.viewControllers) {
89 // for some reason, this needs to be delayed
90 [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(presentAndRelease:) userInfo:[NSDictionary dictionaryWithObject:vc forKey:@"vc"] repeats:NO];
92 [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
98 - (void)presentRootPopover {
99 [self.rootViewController.popoverController presentPopoverFromBarButtonItem:self.barButtonItem
100 permittedArrowDirections:UIPopoverArrowDirectionAny
104 #pragma mark - Application lifecycle
106 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
107 [self loadSettingsDefaults];
109 rootViewController = [navigationController.viewControllers objectAtIndex:0];
110 OpenStackAppDelegate <UINavigationControllerDelegate> *delegate = (OpenStackAppDelegate <UINavigationControllerDelegate> *)self;
111 navigationController.delegate = delegate;
113 // Add the navigation controller's view to the window and display.
114 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
115 PithosImageViewController *vc = [[PithosImageViewController alloc] initWithNibName:@"PithosImageViewController" bundle:nil];
116 self.masterNavigationController = [[[UINavigationController alloc] initWithRootViewController:vc] autorelease];
117 self.masterNavigationController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;
118 self.masterNavigationController.navigationBar.translucent = self.navigationController.navigationBar.translucent;
119 self.masterNavigationController.navigationBar.opaque = self.navigationController.navigationBar.opaque;
120 self.masterNavigationController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle;
122 self.splitViewController.delegate = [navigationController.viewControllers objectAtIndex:0];
123 self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, self.masterNavigationController, nil];
125 window.rootViewController = splitViewController;
126 [window addSubview:splitViewController.view];
127 [window makeKeyAndVisible];
130 if ([[UIDevice currentDevice].systemVersion hasPrefix:@"5.1"]) {
131 // In 5.1, without any delay, the master view controller has full screen size.
132 [self performSelector:@selector(presentRootPopover) withObject:nil afterDelay:0.1];
134 [self presentRootPopover];
137 window.rootViewController = navigationController;
138 [window addSubview:navigationController.view];
139 [window makeKeyAndVisible];
142 serviceUnavailableObserver = [[NSNotificationCenter defaultCenter]
143 addObserverForName:@"serviceUnavailable"
145 queue:[NSOperationQueue mainQueue]
146 usingBlock:^(NSNotification* notification) {
147 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Service Unavailable"
148 message:@"The API is currently unavailable. Please try again later."
149 delegate:nil cancelButtonTitle:@"OK"
150 otherButtonTitles:nil];
153 [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver];
156 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
157 self.cacheDirectoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"DownloadedFiles"];
158 NSFileManager *fileManager = [NSFileManager defaultManager];
159 NSError *error = nil;
160 [fileManager createDirectoryAtPath:cacheDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
162 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
163 message:[NSString stringWithFormat:@"Error in creating cache directory\n%@", error.localizedDescription]
165 cancelButtonTitle:@"OK"
166 otherButtonTitles:nil];
174 - (void)applicationDidBecomeActive:(UIApplication *)application {
175 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
176 [userDefaults setBool:NO forKey:@"already_failed_on_connection"];
177 [userDefaults synchronize];
179 [self showPasscodeLock];
183 //- (void)applicationWillTerminate:(UIApplication *)application {
184 // // TODO: perhaps this is a good place to release all the stuff allocated in
185 // // +(void)initialize methods all over the place
186 //// [[OpenStackAccount accounts] release];
189 - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
193 NSString *host = [url host];
194 NSString *query = [url query];
196 if ([host hasPrefix:@"login"] && query) {
199 NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
200 if (userRange.length == 0)
201 // XXX maybe show an error message?
203 NSUInteger authUserStartLocation = userRange.location + userRange.length;
204 NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
205 range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
206 if (userEndRange.length) {
207 authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
208 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
210 authUser = [[query substringFromIndex:authUserStartLocation]
211 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
215 NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
216 if (tokenRange.length == 0)
217 // XXX maybe show an error message?
219 NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
220 NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch
221 range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
222 if (tokenEndRange.length) {
223 authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
224 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
226 authToken = [[query substringFromIndex:authTokenStartLocation]
227 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
230 if (authToken || authUser) {
231 UIViewController *vc = nil;
232 if (([navigationController.visibleViewController class] == [AccountDetailsViewController class]) ||
233 ([navigationController.visibleViewController class] == [AccountSettingsViewController class])) {
234 vc = navigationController.visibleViewController;
235 } else if (([masterNavigationController.visibleViewController class] == [AccountDetailsViewController class]) ||
236 ([masterNavigationController.visibleViewController class] == [AccountSettingsViewController class])) {
237 vc = masterNavigationController.visibleViewController;
241 if ([vc class] == [AccountDetailsViewController class]) {
242 [((AccountDetailsViewController *)vc) setUsername:authUser andAuthToken:authToken];
243 [((AccountDetailsViewController *)vc).tableView reloadData];
244 } else if ([vc class] == [AccountSettingsViewController class]) {
245 [((AccountSettingsViewController *)vc) setUsername:authUser andAuthToken:authToken];
246 [((AccountSettingsViewController *)vc).tableView reloadData];
251 // XXX else maybe show an error message?
255 #pragma mark - Persistence
257 - (NSString *)cacheFilePathForHash:(NSString *)hash {
258 NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
259 if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
260 @synchronized(self.cachedObjectsDictionary) {
261 [self.cachedObjectsDictionary removeObjectForKey:hash];
263 [self saveCacheDictionary];
269 - (void)setCacheFilePath:(NSString *)filePath forHash:(NSString *)hash {
270 @synchronized(self.cachedObjectsDictionary) {
271 [self.cachedObjectsDictionary setObject:filePath forKey:hash];
272 [self saveCacheDictionary];
276 - (BOOL)removeCacheObjectForHash:(NSString *)hash {
277 NSFileManager *fileManager = [NSFileManager defaultManager];
278 NSError *error = nil;
279 NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
281 @synchronized(self.cachedObjectsDictionary) {
282 [fileManager removeItemAtPath:filePath error:&error];
284 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
285 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
287 cancelButtonTitle:@"OK"
288 otherButtonTitles:nil];
293 [self.cachedObjectsDictionary removeObjectForKey:hash];
296 [self saveCacheDictionary];
300 - (void)removeAllCacheObjects {
301 NSFileManager *fileManager = [NSFileManager defaultManager];
302 NSError *error = nil;
304 NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:self.cacheDirectoryPath];
305 @synchronized(self.cachedObjectsDictionary) {
306 while (filePath = [directoryEnumerator nextObject]) {
307 [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@", self.cacheDirectoryPath, filePath] error:&error];
309 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
310 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
312 cancelButtonTitle:@"OK"
313 otherButtonTitles:nil];
317 for (NSString *hash in [self.cachedObjectsDictionary allKeys])
318 if ([[self.cachedObjectsDictionary objectForKey:hash] isEqualToString:filePath])
319 [self.cachedObjectsDictionary removeObjectForKey:hash];
323 [self saveCacheDictionary];
326 - (void)saveCacheDictionary {
327 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
328 [userDefaults setObject:self.cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
329 [userDefaults synchronize];
332 - (OpenStackRequest *)objectDownloadRequestForAccount:(OpenStackAccount *)account
333 container:(Container *)container
334 object:(StorageObject *)object {
335 return [[[self.objectDownloadRequests objectForKey:account.uuid] objectForKey:container.name] objectForKey:object.fullPath];
338 - (void)setObjectDownloadRequest:(OpenStackRequest *)request
339 forAccount:(OpenStackAccount *)account
340 container:(Container *)container
341 object:(StorageObject *)object {
342 NSMutableDictionary *accountObjectDownloadRequests = [self.objectDownloadRequests objectForKey:account.uuid];
343 if (!accountObjectDownloadRequests) {
344 accountObjectDownloadRequests = [NSMutableDictionary dictionary];
345 [self.objectDownloadRequests setObject:accountObjectDownloadRequests forKey:account.uuid];
347 NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
348 if (!containerObjectDownloadRequests) {
349 containerObjectDownloadRequests = [NSMutableDictionary dictionary];
350 [accountObjectDownloadRequests setObject:containerObjectDownloadRequests forKey:container.name];
352 [containerObjectDownloadRequests setObject:request forKey:object.fullPath];
355 - (void)removeObjectDownloadRequestForAccount:(OpenStackAccount *)account
356 container:(Container *)container
357 object:(StorageObject *)object {
358 NSMutableDictionary *accountObjectDownloadRequests = [self.objectDownloadRequests objectForKey:account.uuid];
359 if (accountObjectDownloadRequests) {
360 NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
361 if (containerObjectDownloadRequests) {
362 [containerObjectDownloadRequests removeObjectForKey:object.fullPath];
367 #pragma mark - Memory management
370 [navigationController release];
371 [splitViewController release];
372 [masterNavigationController release];
373 [barButtonItem release];
374 [rootViewController release];
376 [cachedObjectsDictionary release];
377 [objectDownloadRequests release];