Update version
[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     // Load and persist accounts once to allow any migration checks to take effect.
64     [OpenStackAccount persist:[OpenStackAccount accounts]];
65     
66     [userDefaults synchronize];
67 }
68
69 - (void)presentAndRelease:(NSTimer *)timer {
70     UIViewController *vc = [timer.userInfo objectForKey:@"vc"];
71     [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
72 }
73
74 - (void)showPasscodeLock {
75     if (gotToken) {
76         gotToken = NO;
77         return;
78     }
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];
85         }
86         
87         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
88             vc.modalPresentationStyle = UIModalPresentationFullScreen;
89             for (UIViewController *svc in self.splitViewController.viewControllers) {
90                 svc.view.alpha = 0.0;
91             }
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];
94         } else {
95             [[self.navigationController topViewController] presentModalViewControllerWithNavigation:vc animated:NO];
96         }
97         [vc release];
98     }
99 }
100
101 - (void)presentRootPopover {
102     [self.rootViewController.popoverController presentPopoverFromBarButtonItem:self.barButtonItem
103                                                       permittedArrowDirections:UIPopoverArrowDirectionAny
104                                                                       animated:NO];
105 }
106
107 #pragma mark - Application lifecycle
108
109 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
110     [self loadSettingsDefaults];
111         
112     rootViewController = [navigationController.viewControllers objectAtIndex:0];
113     OpenStackAppDelegate <UINavigationControllerDelegate> *delegate = (OpenStackAppDelegate <UINavigationControllerDelegate> *)self;
114     navigationController.delegate = delegate;
115         
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;
124         
125         self.splitViewController.delegate = [navigationController.viewControllers objectAtIndex:0];
126         self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, self.masterNavigationController, nil];
127
128         window.rootViewController = splitViewController;
129         [window addSubview:splitViewController.view];
130         [window makeKeyAndVisible];
131         [vc release];
132         
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];
136         } else {
137             [self presentRootPopover];
138         }
139     } else {
140         window.rootViewController = navigationController;
141         [window addSubview:navigationController.view];
142         [window makeKeyAndVisible];
143     }
144
145     serviceUnavailableObserver = [[NSNotificationCenter defaultCenter]
146                                   addObserverForName:@"serviceUnavailable"
147                                   object:nil
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];
154                                       [alert show];
155                                       [alert release];
156                                       [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver];
157                                   }];
158     
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];
164     if (error) {
165         UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
166                                                         message:[NSString stringWithFormat:@"Error in creating cache directory\n%@", error.localizedDescription]
167                                                        delegate:nil 
168                                               cancelButtonTitle:@"OK"
169                                               otherButtonTitles:nil];
170         [alert show];
171         [alert release];
172     }
173
174     return YES;
175 }
176
177 - (void)applicationDidBecomeActive:(UIApplication *)application {
178     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
179     [userDefaults setBool:NO forKey:@"already_failed_on_connection"];
180     [userDefaults synchronize];
181     
182     [self showPasscodeLock];
183 }
184
185
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];
190 //}
191
192 - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
193     if (!url) 
194         return NO;
195
196     NSString *host = [url host];
197         NSString *query = [url query];
198
199     if ([host hasPrefix:@"login"] && query) {
200         // user=
201         // optional
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];
211             } else {
212                 authUser = [[query substringFromIndex:authUserStartLocation]
213                             stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
214             }
215         }
216         // token=
217         // required
218         NSString *authToken = nil;
219         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
220         if (tokenRange.length == 0)
221             // XXX maybe show an error message?
222             return NO;
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];
229         } else {
230             authToken = [[query substringFromIndex:authTokenStartLocation]
231                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
232         }
233         
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;
242             }
243             if (vc) {
244                 gotToken = YES;
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];
253                 }
254             }
255         }
256     }
257     // XXX else maybe show an error message?
258     return YES;
259 }
260
261 #pragma mark - Persistence
262
263 - (NSString *)cacheFilePathForHash:(NSString *)hash {
264     if (!hash)
265         return nil;
266     NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
267     if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
268         @synchronized(self.cachedObjectsDictionary) {
269             [self.cachedObjectsDictionary removeObjectForKey:hash];
270         }
271         [self saveCacheDictionary];
272         filePath = nil;
273     }
274     return filePath;
275 }
276
277 - (void)setCacheFilePath:(NSString *)filePath forHash:(NSString *)hash {
278     @synchronized(self.cachedObjectsDictionary) {
279         [self.cachedObjectsDictionary setObject:filePath forKey:hash];
280         [self saveCacheDictionary];
281     }
282 }
283
284 - (BOOL)removeCacheObjectForHash:(NSString *)hash {
285     NSFileManager *fileManager = [NSFileManager defaultManager];
286     NSError *error = nil;
287     NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
288     BOOL removed = NO;
289     @synchronized(self.cachedObjectsDictionary) {
290         [fileManager removeItemAtPath:filePath error:&error];
291         if (error) {
292             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
293                                                             message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
294                                                            delegate:nil
295                                                   cancelButtonTitle:@"OK"
296                                                   otherButtonTitles:nil];
297             [alert show];
298             [alert release];
299         } else {
300             removed = YES;
301             [self.cachedObjectsDictionary removeObjectForKey:hash];
302         }
303     }
304     [self saveCacheDictionary];
305     return removed;
306 }
307
308 - (void)removeAllCacheObjects {
309     NSFileManager *fileManager = [NSFileManager defaultManager];
310     NSError *error = nil;
311     NSString *filePath;
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];
316             if (error) {
317                 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
318                                                                 message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]
319                                                                delegate:nil
320                                                       cancelButtonTitle:@"OK"
321                                                       otherButtonTitles:nil];
322                 [alert show];
323                 [alert release];
324             } else {
325                 for (NSString *hash in [self.cachedObjectsDictionary allKeys])
326                     if ([[self.cachedObjectsDictionary objectForKey:hash] isEqualToString:filePath])
327                         [self.cachedObjectsDictionary removeObjectForKey:hash];
328             }
329         }
330     }
331     [self saveCacheDictionary];
332 }
333
334 - (void)saveCacheDictionary {
335     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
336     [userDefaults setObject:self.cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
337     [userDefaults synchronize];
338 }
339
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];
344 }
345
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];
354     }
355     NSMutableDictionary *containerObjectDownloadRequests = [accountObjectDownloadRequests objectForKey:container.name];
356     if (!containerObjectDownloadRequests) {
357         containerObjectDownloadRequests = [NSMutableDictionary dictionary];
358         [accountObjectDownloadRequests setObject:containerObjectDownloadRequests forKey:container.name];
359     }
360     [containerObjectDownloadRequests setObject:request forKey:object.fullPath];
361 }
362
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];
371         }
372     }
373 }
374
375 #pragma mark - Memory management
376
377 - (void)dealloc {
378         [navigationController release];
379     [splitViewController release];
380     [masterNavigationController release];
381     [barButtonItem release];
382     [rootViewController release];
383         [window release];
384     [cachedObjectsDictionary release];
385     [objectDownloadRequests release];
386         [super dealloc];
387 }
388
389 @end