Bug fix regarding sync folder.
[pithos-macos] / pithos-macos / pithos_macosAppDelegate.m
1 //
2 //  pithos_macosAppDelegate.m
3 //  pithos-macos
4 //
5 // Copyright 2011 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 "ASIDownloadCache.h"
44
45 @implementation pithos_macosAppDelegate
46 @synthesize pithosBrowserController, pithosSyncDaemon, alwaysNo;
47
48 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
49     userDefaults = [[NSUserDefaults standardUserDefaults] retain];
50     NSString *stringURL;
51     NSURL *testURL;
52     
53     stringURL = [userDefaults stringForKey:@"storageURLPrefix"];
54     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
55     if (!testURL || !testURL.scheme || !testURL.host)
56         [userDefaults setObject:@"https://plus.pithos.grnet.gr/v1" forKey:@"storageURLPrefix"];
57
58     stringURL = [userDefaults stringForKey:@"publicURLPrefix"];
59     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
60     if (!testURL || !testURL.scheme || !testURL.host)
61         [userDefaults setObject:@"https://plus.pithos.grnet.gr" forKey:@"publicURLPrefix"];
62         
63     stringURL = [userDefaults stringForKey:@"loginURLPrefix"];
64     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
65     if (!testURL || !testURL.scheme || !testURL.host)
66         [userDefaults setObject:@"https://plus.pithos.grnet.gr/login" forKey:@"loginURLPrefix"];
67
68     stringURL = [userDefaults stringForKey:@"aboutURL"];
69     testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
70     if (!testURL || !testURL.scheme || !testURL.host)
71         [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs" forKey:@"aboutURL"];
72     
73     NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
74     if (!syncDirectoryPath || ![syncDirectoryPath length]) {
75         syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
76     } else {
77         NSFileManager *fileManager = [NSFileManager defaultManager];
78         BOOL isDirectory;
79         BOOL fileExists = [fileManager fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory];
80         NSError *error = nil;
81         if ((fileExists && !isDirectory) || 
82             (!fileExists && (![fileManager createDirectoryAtPath:syncDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || error))) {
83             syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
84         }   
85     }
86     [userDefaults setObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
87     
88     NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
89     if (!syncContainerName || ![syncContainerName length] || [syncContainerName isEqualToString:@"trash"])
90         [userDefaults setObject:@"pithos" forKey:@"syncContainerName"];
91
92     double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
93     if (syncTimeInterval <= 0)
94         [userDefaults setDouble:180.0 forKey:@"syncTimeInterval"];
95     
96     [userDefaults synchronize];
97     
98     [userDefaults addObserver:self 
99                    forKeyPath:@"syncDirectoryPath"
100                       options:NSKeyValueObservingOptionNew
101                       context:NULL];
102      
103     [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
104                                                        andSelector:@selector(handleAppleEvent:withReplyEvent:) 
105                                                      forEventClass:kInternetEventClass 
106                                                         andEventID:kAEGetURL];
107     [self showPithosBrowser:self];
108     
109     [self authenticate];
110 }
111
112 // Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
113 // and: http://www.cocoadev.com/index.pl?ThumbnailImages
114 - (void)awakeFromNib {
115     NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
116     
117     NSImage *smallImage = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
118     [smallImage lockFocus];
119     [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
120     [sourceImage setSize:NSMakeSize(18, 18)];
121     [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
122     [smallImage unlockFocus];
123     
124     statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
125     [statusItem setMenu:statusMenu];
126     [statusItem setImage:sourceImage];
127     [statusItem setHighlightMode:YES];
128     
129     self.alwaysNo = NO;
130 }
131
132 - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
133     NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
134     NSString *host = [url host];
135         NSString *query = [url query];
136     NSLog(@"host : '%@', query: '%@'", host, query);
137     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
138     if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && query) {
139         // user=
140         NSString *authUser;
141         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
142         if (userRange.length == 0)
143             // XXX maybe show an error message?
144             return;
145         NSUInteger authUserStartLocation = userRange.location + userRange.length;
146         NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
147                                               range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
148         if (userEndRange.length) {
149             authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
150                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
151         } else {
152             authUser = [[query substringFromIndex:authUserStartLocation]
153                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
154         }
155         // token=
156         NSString *authToken;
157         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
158         if (tokenRange.length == 0)
159             // XXX maybe show an error message?
160             return;
161         NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
162         NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
163                                               range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
164         if (tokenEndRange.length) {
165             authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
166                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
167         } else {
168             authToken = [[query substringFromIndex:authTokenStartLocation]
169                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
170         }
171         
172         NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
173         if ([authUser length] && [authToken length]) {
174             [userDefaults setObject:authUser forKey:@"authUser"];
175             [userDefaults setObject:authToken forKey:@"authToken"];
176             [userDefaults synchronize];
177
178             [self authenticate];
179         }
180         // XXX else maybe show an error message?
181     }
182     // XXX else maybe show an error message?
183 }
184
185 #pragma mark -
186 #pragma Observers
187
188 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
189     if ([object isEqualTo:userDefaults] && [keyPath isEqualToString:@"syncDirectoryPath"]) {
190         [self authenticate];
191     }
192 }
193
194 #pragma mark -
195 #pragma Actions
196
197 - (IBAction)showPithosBrowser:(id)sender {
198     [pithosBrowserController showWindow:sender];
199     [[pithosBrowserController window] makeKeyAndOrderFront:sender];
200     [NSApp activateIgnoringOtherApps:YES];
201 }
202
203 - (IBAction)showPithosPreferences:(id)sender {
204     [pithosPreferencesController showWindow:sender];
205     [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
206     [NSApp activateIgnoringOtherApps:YES];
207 }
208
209 - (IBAction)aboutPithos:(id)sender {
210     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
211 }
212
213 - (IBAction)syncNow:(id)sender {
214     [pithosSyncDaemon sync];
215 }
216
217 #pragma mark -
218 #pragma Authentication
219
220 - (void)login {
221     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
222     NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d", 
223                           [userDefaults stringForKey:@"loginURLPrefix"], [processInfo processName], [processInfo processIdentifier]];
224     NSLog(@"loginURL: %@", loginURL);
225     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
226 }
227
228 - (void)authenticate {
229     NSString *authUser = [userDefaults stringForKey:@"authUser"];
230     NSString *authToken = [userDefaults stringForKey:@"authToken"];
231     NSString *storageURLPrefix = [userDefaults stringForKey:@"storageURLPrefix"];
232     NSString *publicURLPrefix = [userDefaults stringForKey:@"publicURLPrefix"];
233     NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
234     NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
235     double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
236     NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
237     if (([authUser length] == 0) || ([authToken length] == 0)) {
238         [self showPithosPreferences:self];
239     } else if ([authUser length] && [authToken length] && 
240                (![[ASIPithosRequest authUser] isEqualToString:authUser] || ![[ASIPithosRequest authToken] isEqualToString:authToken])) {
241         [userDefaults setObject:authUser forKey:@"previousAuthUser"];
242         [userDefaults setObject:authToken forKey:@"previousAuthToken"];
243         
244         [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
245         [[ASIPithosRequest sharedQueue] cancelAllOperations];
246         
247         [ASIPithosRequest setAuthURL:storageURLPrefix];
248         [ASIPithosRequest setStorageURLPrefix:storageURLPrefix];
249         [ASIPithosRequest setAuthUser:authUser];
250         [ASIPithosRequest setAuthToken:authToken];
251         [ASIPithosRequest setPublicURLPrefix:publicURLPrefix];
252         
253         self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath 
254                                                                    containerName:syncContainerName 
255                                                                     timeInterval:syncTimeInterval 
256                                                                  resetLocalState:NO] autorelease];
257         
258         [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAuthenticationCredentialsUpdated" object:self];
259     } else if (![pithosSyncDaemon.directoryPath isEqualToString:syncDirectoryPath]) {
260         self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath 
261                                                                    containerName:syncContainerName 
262                                                                     timeInterval:syncTimeInterval 
263                                                                  resetLocalState:YES] autorelease];
264     }
265 }
266
267 @end