282eda17b0eb85b052ae29d101c58ec21bd0d2dc
[pithos-macos] / pithos-macos / pithos_macosAppDelegate.m
1 //
2 //  pithos_macosAppDelegate.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
15 //   2. Redistributions in binary form must reproduce the above
16 //      copyright notice, this list of conditions and the following
17 //      disclaimer in the documentation and/or other materials
18 //      provided with the distribution.
19 // 
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 // 
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
37
38 #import "pithos_macosAppDelegate.h"
39 #import "PithosBrowserController.h"
40 #import "PithosPreferencesController.h"
41 #import "PithosSyncDaemon.h"
42 #import "ASIPithosRequest.h"
43 #import "ASIPithos.h"
44 #import "ASIDownloadCache.h"
45
46 @implementation pithos_macosAppDelegate
47 @synthesize pithos, pithosBrowserController, pithosSyncDaemon, alwaysNo;
48
49 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
50     userDefaults = [[NSUserDefaults standardUserDefaults] retain];
51     NSString *stringURL;
52     NSURL *testURL;
53     
54     stringURL = [userDefaults stringForKey:@"storageURLPrefix"];
55     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
56     if (!testURL || !testURL.scheme || !testURL.host)
57         [userDefaults setObject:@"https://plus.pithos.grnet.gr/v1" forKey:@"storageURLPrefix"];
58
59     stringURL = [userDefaults stringForKey:@"publicURLPrefix"];
60     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
61     if (!testURL || !testURL.scheme || !testURL.host)
62         [userDefaults setObject:@"https://plus.pithos.grnet.gr" forKey:@"publicURLPrefix"];
63         
64     stringURL = [userDefaults stringForKey:@"loginURLPrefix"];
65     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
66     if (!testURL || !testURL.scheme || !testURL.host)
67         [userDefaults setObject:@"https://plus.pithos.grnet.gr/login" forKey:@"loginURLPrefix"];
68
69     stringURL = [userDefaults stringForKey:@"aboutURL"];
70     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
71     if (!testURL || !testURL.scheme || !testURL.host)
72         [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs" forKey:@"aboutURL"];
73     
74     NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
75     if (!syncDirectoryPath || ![syncDirectoryPath length]) {
76         syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
77     } else {
78         NSFileManager *fileManager = [NSFileManager defaultManager];
79         BOOL isDirectory;
80         BOOL fileExists = [fileManager fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory];
81         NSError *error = nil;
82         if ((fileExists && !isDirectory) || 
83             (!fileExists && (![fileManager createDirectoryAtPath:syncDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || error))) {
84             syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
85         }   
86     }
87     [userDefaults setObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
88     
89     NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
90     if (!syncContainerName || ![syncContainerName length] || [syncContainerName isEqualToString:@"trash"])
91         [userDefaults setObject:@"pithos" forKey:@"syncContainerName"];
92
93     double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
94     if (syncTimeInterval <= 0)
95         [userDefaults setDouble:180.0 forKey:@"syncTimeInterval"];
96     
97     [userDefaults synchronize];
98     
99     [userDefaults addObserver:self 
100                    forKeyPath:@"syncDirectoryPath"
101                       options:NSKeyValueObservingOptionNew
102                       context:NULL];
103      
104     [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
105                                                        andSelector:@selector(handleAppleEvent:withReplyEvent:) 
106                                                      forEventClass:kInternetEventClass 
107                                                         andEventID:kAEGetURL];
108     
109     [self showPithosBrowser:self];
110     self.pithos = [ASIPithos pithos];
111     [self authenticate];
112 }
113
114 // Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
115 // and: http://www.cocoadev.com/index.pl?ThumbnailImages
116 - (void)awakeFromNib {
117     NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
118     
119     NSImage *smallImage = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
120     [smallImage lockFocus];
121     [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
122     [sourceImage setSize:NSMakeSize(18, 18)];
123     [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
124     [smallImage unlockFocus];
125     
126     statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
127     [statusItem setMenu:statusMenu];
128     [statusItem setImage:sourceImage];
129     [statusItem setHighlightMode:YES];
130     
131     self.alwaysNo = NO;
132 }
133
134 - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
135     NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
136     NSString *host = [url host];
137         NSString *query = [url query];
138     NSLog(@"host : '%@', query: '%@'", host, query);
139     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
140     if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && query) {
141         // user=
142         NSString *authUser;
143         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
144         if (userRange.length == 0)
145             // XXX maybe show an error message?
146             return;
147         NSUInteger authUserStartLocation = userRange.location + userRange.length;
148         NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
149                                               range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
150         if (userEndRange.length) {
151             authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
152                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
153         } else {
154             authUser = [[query substringFromIndex:authUserStartLocation]
155                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
156         }
157         // token=
158         NSString *authToken;
159         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
160         if (tokenRange.length == 0)
161             // XXX maybe show an error message?
162             return;
163         NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
164         NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
165                                               range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
166         if (tokenEndRange.length) {
167             authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
168                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
169         } else {
170             authToken = [[query substringFromIndex:authTokenStartLocation]
171                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
172         }
173         
174         NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
175         if ([authUser length] && [authToken length]) {
176             [userDefaults setObject:authUser forKey:@"authUser"];
177             [userDefaults setObject:authToken forKey:@"authToken"];
178             [userDefaults synchronize];
179
180             [self authenticate];
181         }
182         // XXX else maybe show an error message?
183     }
184     // XXX else maybe show an error message?
185 }
186
187 #pragma mark -
188 #pragma Observers
189
190 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
191     if ([object isEqualTo:userDefaults] && [keyPath isEqualToString:@"syncDirectoryPath"]) {
192         [self authenticate];
193     }
194 }
195
196 #pragma mark -
197 #pragma Actions
198
199 - (IBAction)showPithosBrowser:(id)sender {
200     [pithosBrowserController showWindow:sender];
201     [[pithosBrowserController window] makeKeyAndOrderFront:sender];
202     [NSApp activateIgnoringOtherApps:YES];
203 }
204
205 - (IBAction)showPithosPreferences:(id)sender {
206     [pithosPreferencesController showWindow:sender];
207     [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
208     [NSApp activateIgnoringOtherApps:YES];
209 }
210
211 - (IBAction)aboutPithos:(id)sender {
212     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
213 }
214
215 - (IBAction)syncNow:(id)sender {
216     [pithosSyncDaemon sync];
217 }
218
219 #pragma mark -
220 #pragma Authentication
221
222 - (void)login {
223     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
224     NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d", 
225                           [userDefaults stringForKey:@"loginURLPrefix"], [processInfo processName], [processInfo processIdentifier]];
226     NSLog(@"loginURL: %@", loginURL);
227     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
228 }
229
230 - (void)authenticate {
231     NSString *authUser = [userDefaults stringForKey:@"authUser"];
232     NSString *authToken = [userDefaults stringForKey:@"authToken"];
233     NSString *storageURLPrefix = [userDefaults stringForKey:@"storageURLPrefix"];
234     NSString *publicURLPrefix = [userDefaults stringForKey:@"publicURLPrefix"];
235     NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
236     NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
237     double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
238     NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
239     if (([authUser length] == 0) || ([authToken length] == 0)) {
240         [self showPithosPreferences:self];
241     } else if ([authUser length] && [authToken length] && 
242                (![pithos.authUser isEqualToString:authUser] || ![pithos.authToken isEqualToString:authToken])) {
243         [userDefaults setObject:authUser forKey:@"previousAuthUser"];
244         [userDefaults setObject:authToken forKey:@"previousAuthToken"];
245         
246         [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
247         [[ASIPithosRequest sharedQueue] cancelAllOperations];
248         
249         ASIPithos *newPithos = [ASIPithos pithos];
250         newPithos.authURL = storageURLPrefix;
251         newPithos.storageURLPrefix = storageURLPrefix;
252         newPithos.authUser = authUser;
253         newPithos.authToken = authToken;
254         newPithos.publicURLPrefix = publicURLPrefix;
255         self.pithos = newPithos;
256         
257         pithosBrowserController.pithos = pithos;
258         self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath 
259                                                                           pithos:pithos
260                                                                    containerName:syncContainerName 
261                                                                     timeInterval:syncTimeInterval 
262                                                                  resetLocalState:NO] autorelease];
263     } else if (![pithosSyncDaemon.directoryPath isEqualToString:syncDirectoryPath]) {
264         self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath 
265                                                                           pithos:pithos
266                                                                    containerName:syncContainerName 
267                                                                     timeInterval:syncTimeInterval 
268                                                                  resetLocalState:YES] autorelease];
269     }
270 }
271
272 @end