Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / pithos_macosAppDelegate.m @ 904acde2

History | View | Annotate | Download (18.8 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, pithosPreferencesController, alwaysNo, activated, 
50
currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval;
51

    
52
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
53
    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
54
                                                       andSelector:@selector(handleAppleEvent:withReplyEvent:) 
55
                                                     forEventClass:kInternetEventClass 
56
                                                        andEventID:kAEGetURL];
57
    
58
    userDefaults = [[NSUserDefaults standardUserDefaults] retain];
59
    
60
    syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
61
    if (syncTimeInterval <= 0.0) {
62
        syncTimeInterval = 180.0;
63
        [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
64
        [userDefaults synchronize];
65
    }
66

    
67
    activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"];
68
    if (activityFacilityTimeInterval <= 0.0) {
69
        activityFacilityTimeInterval = 0.05;
70
        [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"];
71
        [userDefaults synchronize];
72
    }
73

    
74
    NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
75
    NSArray *tmpArray;
76
    if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
77
        self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
78
    else
79
        self.pithosAccounts = [NSMutableArray array];
80
    
81
    if (![pithosAccounts count]) {
82
        [pithosAccounts addObject:[PithosAccount pithosAccount]];
83
        self.pithosAccounts = self.pithosAccounts;
84
    } else {
85
        self.activated = YES;
86
    }
87
    
88
    pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]];
89
    for (PithosAccount *pithosAccount in pithosAccounts) {
90
        [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name];
91
        if (!currentPithosAccount && pithosAccount.active)
92
            currentPithosAccount = [pithosAccount retain];
93
    }
94
    if (!currentPithosAccount)
95
        self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
96
    
97
    if (currentPithosAccount.active) {
98
        [self savePithosAccounts:self];
99
        [self showPithosBrowser:self];
100
        self.pithosBrowserController.pithos = currentPithosAccount.pithos;
101
    } else {
102
        // XXX maybe call specifically to go to new account tab
103
        [self showPithosPreferences:self];
104
    }
105

    
106
    syncTimer = [[NSTimer scheduledTimerWithTimeInterval:syncTimeInterval 
107
                                                  target:self 
108
                                                selector:@selector(sync) 
109
                                                userInfo:nil 
110
                                                 repeats:YES] retain];
111
    [syncTimer fire];
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
    PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[url lastPathComponent]];
139
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
140
    if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && 
141
        pithosAccount && query) {
142
        // user=
143
        NSString *authUser;
144
        NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
145
        if (userRange.length == 0)
146
            // XXX maybe show an error message?
147
            return;
148
        NSUInteger authUserStartLocation = userRange.location + userRange.length;
149
        NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
150
                                              range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
151
        if (userEndRange.length) {
152
            authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
153
                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
154
        } else {
155
            authUser = [[query substringFromIndex:authUserStartLocation]
156
                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
157
        }
158
        // token=
159
        NSString *authToken;
160
        NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
161
        if (tokenRange.length == 0)
162
            // XXX maybe show an error message?
163
            return;
164
        NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
165
        NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
166
                                              range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
167
        if (tokenEndRange.length) {
168
            authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
169
                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
170
        } else {
171
            authToken = [[query substringFromIndex:authTokenStartLocation]
172
                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
173
        }
174
        
175
        NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
176
        if ([authUser length] && [authToken length]) {
177
            [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken];
178
            [self savePithosAccounts:self];
179
            if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) {
180
                self.pithosPreferencesController.authUser = pithosAccount.authUser;
181
                self.pithosPreferencesController.authToken = pithosAccount.authToken;
182
            }
183
            self.activated = YES;
184
            if ([pithosAccount isEqualTo:currentPithosAccount]) {
185
                [self showPithosBrowser:self];
186
                self.pithosBrowserController.pithos = pithosAccount.pithos;
187
            }
188
        }
189
        // XXX else maybe show an error message?
190
    }
191
    // XXX else maybe show an error message?
192
}
193

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

    
209
#pragma mark -
210
#pragma mark Properties
211

    
212
- (PithosBrowserController *)pithosBrowserController {
213
    if (!pithosBrowserController) {
214
        pithosBrowserController = [[PithosBrowserController alloc] init];
215
    }
216
    return pithosBrowserController;
217
}
218

    
219
- (PithosPreferencesController *)pithosPreferencesController {
220
    if (!pithosPreferencesController) {
221
        pithosPreferencesController = [[PithosPreferencesController alloc] init];
222
    }
223
    return pithosPreferencesController;
224
}
225

    
226
#pragma mark -
227
#pragma mark NSMenuDelegate
228

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

    
270
#pragma mark -
271
#pragma mark Actions
272

    
273
- (IBAction)showPithosBrowser:(id)sender {
274
    if (!activated)
275
        return;
276
    [self.pithosBrowserController showWindow:sender];
277
    [[self.pithosBrowserController window] makeKeyAndOrderFront:sender];
278
    [NSApp activateIgnoringOtherApps:YES];
279
}
280

    
281
- (IBAction)showPithosPreferences:(id)sender {
282
    [self.pithosPreferencesController showWindow:sender];
283
    [[self.pithosPreferencesController window] makeKeyAndOrderFront:sender];
284
    [NSApp activateIgnoringOtherApps:YES];
285
}
286

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

    
355
- (void)savePithosAccounts:(id)sender {
356
    [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
357
    [userDefaults synchronize];
358
}
359

    
360
- (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
361
    if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
362
        for (PithosAccount *pithosAccount in pithosAccounts) {
363
            if (pithosAccount.active) {
364
                self.currentPithosAccount = pithosAccount;
365
                self.pithosBrowserController.pithos = currentPithosAccount.pithos;
366
                break;
367
            }
368
        }
369
        if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
370
            self.activated = NO;
371
            [self.pithosBrowserController.window close];
372
            [self.pithosBrowserController resetBrowser];
373
            self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
374
        }
375
    }
376
    if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
377
        self.syncPithosAccount = nil;
378
}
379

    
380
#pragma mark -
381
#pragma mark Menu Actions
382

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

    
404
//- (void)menuChangeSyncActive:(NSMenuItem *)sender {
405
//    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
406
//    if (!pithosAccount.active)
407
//        return;
408
//    pithosAccount.syncActive = !pithosAccount.syncActive;
409
//    if (self.pithosPreferencesController && [self.pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
410
//        self.pithosPreferencesController.syncActive = pithosAccount.syncActive;
411
//    [self savePithosAccounts:self];
412
//}
413

    
414
@end