Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / pithos_macosAppDelegate.m @ 544b6f52

History | View | Annotate | Download (18.2 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, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount, activityFacilityTimeInterval;
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
    syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
60
    if (syncTimeInterval <= 0.0) {
61
        syncTimeInterval = 180.0;
62
        [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
63
        [userDefaults synchronize];
64
    }
65

    
66
    activityFacilityTimeInterval = [userDefaults doubleForKey:@"activityFacilityTimeInterval"];
67
    if (activityFacilityTimeInterval <= 0.0) {
68
        activityFacilityTimeInterval = 0.05;
69
        [userDefaults setDouble:activityFacilityTimeInterval forKey:@"activityFacilityTimeInterval"];
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 NSMenuDelegate
210

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

    
252
#pragma mark -
253
#pragma mark Actions
254

    
255
- (IBAction)showPithosBrowser:(id)sender {
256
    if (!activated)
257
        return;
258
    [pithosBrowserController showWindow:sender];
259
    [[pithosBrowserController window] makeKeyAndOrderFront:sender];
260
    [NSApp activateIgnoringOtherApps:YES];
261
}
262

    
263
- (IBAction)showPithosPreferences:(id)sender {
264
    [pithosPreferencesController showWindow:sender];
265
    [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
266
    [NSApp activateIgnoringOtherApps:YES];
267
}
268

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

    
337
- (void)savePithosAccounts:(id)sender {
338
    [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
339
    [userDefaults synchronize];
340
}
341

    
342
- (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
343
    if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
344
        for (PithosAccount *pithosAccount in pithosAccounts) {
345
            if (pithosAccount.active) {
346
                self.currentPithosAccount = pithosAccount;
347
                pithosBrowserController.pithos = currentPithosAccount.pithos;
348
                break;
349
            }
350
        }
351
        if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
352
            self.activated = NO;
353
            [pithosBrowserController.window close];
354
            [pithosBrowserController resetBrowser];
355
            self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
356
        }
357
    }
358
    if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
359
        self.syncPithosAccount = nil;
360
}
361

    
362
#pragma mark -
363
#pragma mark Menu Actions
364

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

    
386
//- (void)menuChangeSyncActive:(NSMenuItem *)sender {
387
//    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
388
//    if (!pithosAccount.active)
389
//        return;
390
//    pithosAccount.syncActive = !pithosAccount.syncActive;
391
//    if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
392
//        pithosPreferencesController.syncActive = pithosAccount.syncActive;
393
//    [self savePithosAccounts:self];
394
//}
395

    
396
@end