Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / pithos_macosAppDelegate.m @ d8426ffb

History | View | Annotate | Download (18.5 kB)

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 "PithosAccount.h"
40
#import "PithosBrowserController.h"
41
#import "PithosPreferencesController.h"
42
#import "PithosSyncDaemon.h"
43
#import "ASIPithosRequest.h"
44
#import "ASIPithos.h"
45
#import "ASIDownloadCache.h"
46
#import "LastCompletedSyncTransformer.h"
47

    
48
@implementation pithos_macosAppDelegate
49
@synthesize pithosBrowserController, alwaysNo, aboutVersion, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount;
50

    
51
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
52
    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
53
                                                       andSelector:@selector(handleAppleEvent:withReplyEvent:) 
54
                                                     forEventClass:kInternetEventClass 
55
                                                        andEventID:kAEGetURL];
56
    
57
    userDefaults = [[NSUserDefaults standardUserDefaults] retain];
58

    
59
    NSString *stringURL = [userDefaults stringForKey:@"aboutURL"];
60
    NSURL *testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
61
    if (!testURL || !testURL.scheme || !testURL.host) {
62
        [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs/pithos" forKey:@"aboutURL"];
63
        [userDefaults synchronize];
64
    }
65
    
66
    syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
67
    if (syncTimeInterval <= 0.0) {
68
        syncTimeInterval = 180.0;
69
        [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
70
        [userDefaults synchronize];
71
    }
72
    
73
    NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
74
    NSArray *tmpArray;
75
    if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
76
        self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
77
    else
78
        self.pithosAccounts = [NSMutableArray array];
79
    
80
    if (![pithosAccounts count]) {
81
        [pithosAccounts addObject:[PithosAccount pithosAccount]];
82
        self.pithosAccounts = self.pithosAccounts;
83
    } else {
84
        self.activated = YES;
85
    }
86
    
87
    pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]];
88
    for (PithosAccount *pithosAccount in pithosAccounts) {
89
        [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name];
90
        if (!currentPithosAccount && pithosAccount.active)
91
            currentPithosAccount = [pithosAccount retain];
92
    }
93
    if (!currentPithosAccount)
94
        self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
95
    
96
    if (currentPithosAccount.active) {
97
        [self savePithosAccounts:self];
98
        [self showPithosBrowser:self];
99
        pithosBrowserController.pithos = currentPithosAccount.pithos;
100
    } else {
101
        // XXX maybe call specifically to go to new account tab
102
        [self showPithosPreferences:self];
103
    }
104

    
105
    syncTimer = [[NSTimer scheduledTimerWithTimeInterval:syncTimeInterval 
106
                                                  target:self 
107
                                                selector:@selector(sync) 
108
                                                userInfo:nil 
109
                                                 repeats:YES] retain];
110
    [syncTimer fire];
111
}
112

    
113
// Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
114
// and: http://www.cocoadev.com/index.pl?ThumbnailImages
115
- (void)awakeFromNib {
116
    NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
117
    
118
    NSImage *smallImage = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
119
    [smallImage lockFocus];
120
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
121
    [sourceImage setSize:NSMakeSize(18, 18)];
122
    [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
123
    [smallImage unlockFocus];
124
    
125
    statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
126
    [statusItem setMenu:statusMenu];
127
    [statusItem setImage:sourceImage];
128
    [statusItem setHighlightMode:YES];
129
    
130
    self.alwaysNo = NO;
131
}
132

    
133
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
134
    NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
135
    NSString *host = [url host];
136
	NSString *query = [url query];
137
    PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[url lastPathComponent]];
138
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
139
    if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && 
140
        pithosAccount && 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
            [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken];
177
            [self savePithosAccounts:self];
178
            if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) {
179
                pithosPreferencesController.authUser = pithosAccount.authUser;
180
                pithosPreferencesController.authToken = pithosAccount.authToken;
181
            }
182
            self.activated = YES;
183
            if ([pithosAccount isEqualTo:currentPithosAccount]) {
184
                [self showPithosBrowser:self];
185
                pithosBrowserController.pithos = pithosAccount.pithos;
186
            }
187
        }
188
        // XXX else maybe show an error message?
189
    }
190
    // XXX else maybe show an error message?
191
}
192

    
193
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
194
    [self savePithosAccounts:self];
195
    if ([self.pithosBrowserController operationsPending]) {
196
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
197
        [alert setMessageText:@"Pending Operations"];
198
        [alert setInformativeText:@"There are pending operations in the browser, do you want to quit and cancel them?"];
199
        [alert addButtonWithTitle:@"OK"];
200
        [alert addButtonWithTitle:@"Cancel"];
201
        NSInteger choice = [alert runModal];
202
        if (choice == NSAlertSecondButtonReturn) 
203
            return NSTerminateCancel;
204
    }
205
    return NSTerminateNow;
206
}
207

    
208
#pragma mark -
209
#pragma mark Properties
210

    
211
- (NSString *)aboutVersion {
212
    return [NSString stringWithFormat:@"About Pithos+ %@ (%@)", 
213
            [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], 
214
            [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
215
}
216

    
217
#pragma mark -
218
#pragma mark NSMenuDelegate
219

    
220
- (void)menuNeedsUpdate:(NSMenu *)menu {
221
    NSMenuItem *menuItem;
222
    [menu removeAllItems];
223
    if ([menu isEqualTo:accountsMenu]) {
224
        [menu setAutoenablesItems:NO];
225
        for (PithosAccount *pithosAccount in pithosAccounts) {
226
            menuItem = [[[NSMenuItem alloc] initWithTitle:pithosAccount.name 
227
                                                   action:@selector(menuChangePithosAccount:) 
228
                                            keyEquivalent:@""] autorelease];
229
            [menuItem setRepresentedObject:pithosAccount];
230
            [menuItem setEnabled:pithosAccount.active];
231
            [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)];
232
            [menu addItem:menuItem];
233
        }
234
    } else if ([menu isEqualTo:lastSyncMenu]) {
235
        NSString *menuItemTitle;
236
        [menu setAutoenablesItems:NO];
237
        for (PithosAccount *pithosAccount in pithosAccounts) {
238
            menuItemTitle = [NSString stringWithFormat:@"%@: %@", 
239
                             pithosAccount.name, 
240
                             [[[[LastCompletedSyncTransformer alloc] init] autorelease] transformedValue:pithosAccount.syncLastCompleted]];
241
            if ([pithosAccount isEqualTo:syncPithosAccount] && [pithosAccount.syncDaemon isSyncing])
242
                menuItemTitle = [menuItemTitle stringByAppendingString:@" (syncing)"];
243
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle 
244
                                                   action:@selector(menuChangeSyncActive:) 
245
                                            keyEquivalent:@""] autorelease];
246
            [menuItem setRepresentedObject:pithosAccount];
247
            [menuItem setEnabled:pithosAccount.active];
248
            [menuItem setState:((pithosAccount.active && pithosAccount.syncActive) ? NSOnState : NSOffState)];
249
            [menu addItem:menuItem];
250
        }
251
        [menu addItem:[NSMenuItem separatorItem]];
252
        [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Next Sync" 
253
                                                  action:@selector(sync) 
254
                                           keyEquivalent:@""] autorelease]];
255
    }
256
}
257

    
258
#pragma mark -
259
#pragma mark Actions
260

    
261
- (IBAction)showPithosBrowser:(id)sender {
262
    if (!activated)
263
        return;
264
    [pithosBrowserController showWindow:sender];
265
    [[pithosBrowserController window] makeKeyAndOrderFront:sender];
266
    [NSApp activateIgnoringOtherApps:YES];
267
}
268

    
269
- (IBAction)showPithosPreferences:(id)sender {
270
    [pithosPreferencesController showWindow:sender];
271
    [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
272
    [NSApp activateIgnoringOtherApps:YES];
273
}
274

    
275
- (IBAction)aboutPithos:(id)sender {
276
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
277
}
278

    
279
- (void)sync {
280
    if (!activated || ![pithosAccounts count])
281
        return;
282
    NSUInteger syncIndex;
283
    BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount];
284
    if (syncPithosAccountFound)
285
         syncIndex = [pithosAccounts indexOfObject:syncPithosAccount];
286
    
287
    PithosAccount *singleSyncPithosAccount = nil;
288
    for (PithosAccount *pithosAccount in pithosAccounts) {
289
        if (!singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
290
            singleSyncPithosAccount = pithosAccount;
291
        } else if (singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
292
            singleSyncPithosAccount = nil;
293
            break;
294
        }
295
    }
296
    
297
    if (syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
298
        // An active syncDaemon was previously syncing
299
        if (singleSyncPithosAccount && [singleSyncPithosAccount isEqualTo:syncPithosAccount]) {
300
            // It's the only one, sync again
301
            [syncPithosAccount.syncDaemon startDaemon];
302
            [syncPithosAccount.syncDaemon sync];
303
            return;
304
        } else if ([syncPithosAccount.syncDaemon isSyncing]) {
305
            // It's still syncing, mark it as late and return
306
            [syncPithosAccount.syncDaemon syncLate];
307
            return;
308
        }
309
    }
310
    PithosAccount *newSyncPithosAccount = nil;
311
    if (syncPithosAccountFound) {
312
        for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(syncIndex + 1, [pithosAccounts count] - syncIndex - 1)]]) {
313
            if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
314
                newSyncPithosAccount = pithosAccount;
315
                break;
316
            }
317
        }
318
        if (!newSyncPithosAccount) {
319
            for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, syncIndex)]]) {
320
                if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
321
                    newSyncPithosAccount = pithosAccount;
322
                    break;
323
                }
324
            }
325
        }
326
    } else {
327
        for (PithosAccount *pithosAccount in pithosAccounts) {
328
            if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
329
                newSyncPithosAccount = pithosAccount;
330
                break;
331
            }
332
        }
333
    }        
334
    if (newSyncPithosAccount) {
335
        // A different syncDaemon is found, sync it
336
        self.syncPithosAccount = newSyncPithosAccount;
337
        [syncPithosAccount.syncDaemon startDaemon];
338
        [syncPithosAccount.syncDaemon sync];
339
    } else if (syncPithosAccountFound && syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
340
        [syncPithosAccount.syncDaemon startDaemon];
341
        [syncPithosAccount.syncDaemon sync];
342
    } else {
343
        self.syncPithosAccount = nil;
344
    }
345
}
346

    
347
- (void)savePithosAccounts:(id)sender {
348
    [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
349
    [userDefaults synchronize];
350
}
351

    
352
- (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
353
    if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
354
        for (PithosAccount *pithosAccount in pithosAccounts) {
355
            if (pithosAccount.active) {
356
                self.currentPithosAccount = pithosAccount;
357
                pithosBrowserController.pithos = currentPithosAccount.pithos;
358
                break;
359
            }
360
        }
361
        if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
362
            self.activated = NO;
363
            [pithosBrowserController.window close];
364
            [pithosBrowserController resetBrowser];
365
            self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
366
        }
367
    }
368
    if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
369
        self.syncPithosAccount = nil;
370
}
371

    
372
#pragma mark -
373
#pragma mark Menu Actions
374

    
375
- (void)menuChangePithosAccount:(NSMenuItem *)sender {
376
    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
377
    if (!pithosAccount.active)
378
        return;
379
    if (![currentPithosAccount isEqualTo:pithosAccount] && [pithosAccounts containsObject:pithosAccount]) {
380
        if ([self.pithosBrowserController operationsPending]) {
381
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
382
            [alert setMessageText:@"Pending Operations"];
383
            [alert setInformativeText:@"There are pending operations in the browser, do you want to change accounts and cancel them?"];
384
            [alert addButtonWithTitle:@"OK"];
385
            [alert addButtonWithTitle:@"Cancel"];
386
            NSInteger choice = [alert runModal];
387
            if (choice == NSAlertSecondButtonReturn) 
388
                return;
389
        }
390
        self.currentPithosAccount = pithosAccount;
391
        [self showPithosBrowser:self];
392
        pithosBrowserController.pithos = currentPithosAccount.pithos;
393
    }
394
}
395

    
396
- (void)menuChangeSyncActive:(NSMenuItem *)sender {
397
    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
398
    if (!pithosAccount.active)
399
        return;
400
    pithosAccount.syncActive = !pithosAccount.syncActive;
401
    if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
402
        pithosPreferencesController.syncActive = pithosAccount.syncActive;
403
    [self savePithosAccounts:self];
404
}
405

    
406
@end