2 // pithos_macosAppDelegate.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "pithos_macosAppDelegate.h"
39 #import "PithosBrowserController.h"
40 #import "PithosPreferencesController.h"
41 #import "PithosSyncDaemon.h"
42 #import "ASIPithosRequest.h"
44 #import "ASIDownloadCache.h"
46 @implementation pithos_macosAppDelegate
47 @synthesize pithos, pithosBrowserController, pithosSyncDaemon, alwaysNo;
49 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
50 userDefaults = [[NSUserDefaults standardUserDefaults] retain];
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"];
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"];
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"];
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"];
74 NSString *syncDirectoryPath = [userDefaults stringForKey:@"syncDirectoryPath"];
75 if (!syncDirectoryPath || ![syncDirectoryPath length]) {
76 syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
78 NSFileManager *fileManager = [NSFileManager defaultManager];
80 BOOL fileExists = [fileManager fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory];
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"];
87 [userDefaults setObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
89 NSString *syncContainerName = [userDefaults stringForKey:@"syncContainerName"];
90 if (!syncContainerName || ![syncContainerName length] || [syncContainerName isEqualToString:@"trash"])
91 [userDefaults setObject:@"pithos" forKey:@"syncContainerName"];
93 double syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
94 if (syncTimeInterval <= 0)
95 [userDefaults setDouble:180.0 forKey:@"syncTimeInterval"];
97 [userDefaults synchronize];
99 [userDefaults addObserver:self
100 forKeyPath:@"syncDirectoryPath"
101 options:NSKeyValueObservingOptionNew
104 [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
105 andSelector:@selector(handleAppleEvent:withReplyEvent:)
106 forEventClass:kInternetEventClass
107 andEventID:kAEGetURL];
109 [self showPithosBrowser:self];
110 self.pithos = [ASIPithos pithos];
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"];
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];
126 statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
127 [statusItem setMenu:statusMenu];
128 [statusItem setImage:sourceImage];
129 [statusItem setHighlightMode:YES];
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) {
143 NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
144 if (userRange.length == 0)
145 // XXX maybe show an error message?
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];
154 authUser = [[query substringFromIndex:authUserStartLocation]
155 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
159 NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
160 if (tokenRange.length == 0)
161 // XXX maybe show an error message?
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];
170 authToken = [[query substringFromIndex:authTokenStartLocation]
171 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
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];
182 // XXX else maybe show an error message?
184 // XXX else maybe show an error message?
190 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
191 if ([object isEqualTo:userDefaults] && [keyPath isEqualToString:@"syncDirectoryPath"]) {
199 - (IBAction)showPithosBrowser:(id)sender {
200 [pithosBrowserController showWindow:sender];
201 [[pithosBrowserController window] makeKeyAndOrderFront:sender];
202 [NSApp activateIgnoringOtherApps:YES];
205 - (IBAction)showPithosPreferences:(id)sender {
206 [pithosPreferencesController showWindow:sender];
207 [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
208 [NSApp activateIgnoringOtherApps:YES];
211 - (IBAction)aboutPithos:(id)sender {
212 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
215 - (IBAction)syncNow:(id)sender {
216 [pithosSyncDaemon sync];
220 #pragma Authentication
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]];
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"];
246 [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
247 [[ASIPithosRequest sharedQueue] cancelAllOperations];
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;
257 pithosBrowserController.pithos = pithos;
258 self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath
260 containerName:syncContainerName
261 timeInterval:syncTimeInterval
262 resetLocalState:NO] autorelease];
263 } else if (![pithosSyncDaemon.directoryPath isEqualToString:syncDirectoryPath]) {
264 self.pithosSyncDaemon = [[[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath
266 containerName:syncContainerName
267 timeInterval:syncTimeInterval
268 resetLocalState:YES] autorelease];