11591fceadb9f0058814548920a09e5d02a1292b
[pithos-ios] / Classes / OpenStackAppDelegate.m
1 //
2 //  OpenStackAppDelegate.m
3 //  OpenStack
4 //
5 //  Created by Mike Mayo on 9/30/10.
6 //  The OpenStack project is provided under the Apache 2.0 license.
7 //
8
9 #import "OpenStackAppDelegate.h"
10 #import "RootViewController.h"
11 #import "Keychain.h"
12 #import "JSON.h"
13 #import "Archiver.h"
14 #import "Provider.h"
15 #import "OpenStackAccount.h"
16 #import "Container.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"
26
27 @implementation OpenStackAppDelegate
28
29 @synthesize window;
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;
39
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"];
45     }
46     if (![Keychain getStringForKey:@"passcode_lock_simple_passcode_on"]) {
47         [Keychain setString:@"YES" forKey:@"passcode_lock_simple_passcode_on"];
48     }
49     if (![Keychain getStringForKey:@"passcode_lock_erase_data_on"]) {
50         [Keychain setString:@"NO" forKey:@"passcode_lock_erase_data_on"];
51     }
52     
53     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
54     
55     self.cachedObjectsDictionary = [userDefaults objectForKey:@"cachedObjectsDictionary"];
56     if (!cachedObjectsDictionary) {
57         self.cachedObjectsDictionary = [NSMutableDictionary dictionary];
58         [userDefaults setValue:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
59     }
60     
61     objectDownloadRequests = [[NSMutableDictionary alloc] init];
62     
63     [userDefaults synchronize];
64 }
65
66 - (void)presentAndRelease:(NSTimer *)timer {
67     UIViewController *vc = [timer.userInfo objectForKey:@"vc"];
68     [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
69 }
70
71 - (void)showPasscodeLock {
72     if (gotToken) {
73         gotToken = NO;
74         return;
75     }
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];
82         }
83         
84         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
85             vc.modalPresentationStyle = UIModalPresentationFullScreen;
86             for (UIViewController *svc in self.splitViewController.viewControllers) {
87                 svc.view.alpha = 0.0;
88             }
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];
91         } else {
92             [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
93         }
94         [vc release];
95     }
96 }
97
98 - (void)presentRootPopover {
99     [self.rootViewController.popoverController presentPopoverFromBarButtonItem:self.barButtonItem
100                                                       permittedArrowDirections:UIPopoverArrowDirectionAny
101                                                                       animated:NO];
102 }
103
104 #pragma mark - Application lifecycle
105
106 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
107     [self loadSettingsDefaults];
108         
109     rootViewController = [navigationController.viewControllers objectAtIndex:0];
110     OpenStackAppDelegate <UINavigationControllerDelegate> *delegate = (OpenStackAppDelegate <UINavigationControllerDelegate> *)self;
111     navigationController.delegate = delegate;
112         
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;
121         
122         self.splitViewController.delegate = [navigationController.viewControllers objectAtIndex:0];
123         self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, self.masterNavigationController, nil];
124
125         window.rootViewController = splitViewController;
126         [window addSubview:splitViewController.view];
127         [window makeKeyAndVisible];
128         [vc release];
129         
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];
133         } else {
134             [self presentRootPopover];
135         }
136     } else {
137         window.rootViewController = navigationController;
138         [window addSubview:navigationController.view];
139         [window makeKeyAndVisible];
140     }
141
142     serviceUnavailableObserver = [[NSNotificationCenter defaultCenter]
143                                   addObserverForName:@"serviceUnavailable"
144                                   object:nil
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];
151                                       [alert show];
152                                       [alert release];
153                                       [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver];
154                                   }];
155     
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];
161     if (error) {
162         UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
163                                                         message:[NSString stringWithFormat:@"Error in creating cache directory\n%@", error.localizedDescription]
164                                                        delegate:nil 
165                                               cancelButtonTitle:@"OK"
166                                               otherButtonTitles:nil];
167         [alert show];
168         [alert release];
169     }
170
171     return YES;
172 }
173
174 - (void)applicationDidBecomeActive:(UIApplication *)application {
175     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
176     [userDefaults setBool:NO forKey:@"already_failed_on_connection"];
177     [userDefaults synchronize];
178     
179     [self showPasscodeLock];
180 }
181
182
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];
187 //}
188
189 - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
190     if (!url) 
191         return NO;
192
193     NSString *host = [url host];
194         NSString *query = [url query];
195
196     if ([host hasPrefix:@"login"] && query) {
197         // user=
198         NSString *authUser;
199         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
200         if (userRange.length == 0)
201             // XXX maybe show an error message?
202             return NO;
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];
209         } else {
210             authUser = [[query substringFromIndex:authUserStartLocation]
211                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
212         }
213         // token=
214         NSString *authToken;
215         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
216         if (tokenRange.length == 0)
217             // XXX maybe show an error message?
218             return NO;
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];
225         } else {
226             authToken = [[query substringFromIndex:authTokenStartLocation]
227                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
228         }
229         
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;
238             }
239             if (vc) {
240                 gotToken = YES;
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];
247                 }
248             }
249         }
250     }
251     // XXX else maybe show an error message?
252     return YES;
253 }
254
255 #pragma mark - Persistence
256
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];
262         }
263         [self saveCacheDictionary];
264         filePath = nil;
265     }
266     return filePath;
267 }
268
269 - (void)setCacheFilePath:(NSString *)filePath forHash:(NSString *)hash {
270     @synchronized(self.cachedObjectsDictionary) {
271         [self.cachedObjectsDictionary setObject:filePath forKey:hash];
272         [self saveCacheDictionary];
273     }
274 }
275
276 - (BOOL)removeCacheObjectForHash:(NSString *)hash {
277     NSFileManager *fileManager = [NSFileManager defaultManager];
278     NSError *error = nil;
279     NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
280     BOOL removed = NO;
281     @synchronized(self.cachedObjectsDictionary) {
282         [fileManager removeItemAtPath:filePath error:&error];
283         if (error) {
284             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
285                                                             message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
286                                                            delegate:nil
287                                                   cancelButtonTitle:@"OK"
288                                                   otherButtonTitles:nil];
289             [alert show];
290             [alert release];
291         } else {
292             removed = YES;
293             [self.cachedObjectsDictionary removeObjectForKey:hash];
294         }
295     }
296     [self saveCacheDictionary];
297     return removed;
298 }
299
300 - (void)removeAllCacheObjects {
301     NSFileManager *fileManager = [NSFileManager defaultManager];
302     NSError *error = nil;
303     NSString *filePath;
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];
308             if (error) {
309                 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
310                                                                 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
311                                                                delegate:nil
312                                                       cancelButtonTitle:@"OK"
313                                                       otherButtonTitles:nil];
314                 [alert show];
315                 [alert release];
316             } else {
317                 for (NSString *hash in [self.cachedObjectsDictionary allKeys])
318                     if ([[self.cachedObjectsDictionary objectForKey:hash] isEqualToString:filePath])
319                         [self.cachedObjectsDictionary removeObjectForKey:hash];
320             }
321         }
322     }
323     [self saveCacheDictionary];
324 }
325
326 - (void)saveCacheDictionary {
327     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
328     [userDefaults setObject:self.cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
329     [userDefaults synchronize];
330 }
331
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];
336 }
337
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];
346     }
347     NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
348     if (!containerObjectDownloadRequests) {
349         containerObjectDownloadRequests = [NSMutableDictionary dictionary];
350         [accountObjectDownloadRequests setObject:containerObjectDownloadRequests forKey:container.name];
351     }
352     [containerObjectDownloadRequests setObject:request forKey:object.fullPath];
353 }
354
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];
363         }
364     }
365 }
366
367 #pragma mark - Memory management
368
369 - (void)dealloc {
370         [navigationController release];
371     [splitViewController release];
372     [masterNavigationController release];
373     [barButtonItem release];
374     [rootViewController release];
375         [window release];
376     [cachedObjectsDictionary release];
377     [objectDownloadRequests release];
378         [super dealloc];
379 }
380
381 @end