Support multiple accounts. Bug fixes. Improve concurrency.
[pithos-macos] / pithos-macos / PithosAccount.m
1 //
2 //  PithosAccount.m
3 //  pithos-macos
4 //
5 // Copyright 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 "PithosAccount.h"
39 #import "PithosSyncDaemon.h"
40 #import "ASIPithos.h"
41 #import "PithosAccountNode.h"
42 #import "pithos_macosAppDelegate.h"
43
44 @interface PithosAccount (Internal)
45 - (BOOL)urlIsValid:(NSString *)urlString;
46 @end
47
48 @implementation PithosAccount
49 @synthesize uniqueName, active, name;
50 @synthesize syncActive, syncDirectoryPath, syncContainerName, syncLastCompleted, syncDaemon;
51 @synthesize serverURL, versionResource, loginResource, publicResource;
52 @synthesize authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix;
53 @synthesize pithos, accountNode;
54
55 #pragma mark -
56 #pragma mark Object Lifecycle
57
58 + (id)pithosAccount {
59     PithosAccount *pithosAccount = [[[self alloc] init] autorelease];
60     pithosAccount.uniqueName = [NSString stringWithFormat:@"pithosAccount-%f", [NSDate timeIntervalSinceReferenceDate]];
61     pithosAccount.versionResource = [NSString stringWithString:@"v1"];
62     pithosAccount.loginResource = [NSString stringWithString:@"login"];
63     return pithosAccount;
64 }
65
66 - (void)dealloc {
67     [accountNode release];
68     [pithos release];
69     [publicURLPrefix release];
70     [loginURLPrefix release];
71     [authURL release];
72     [storageURLPrefix release];
73     [authToken release];
74     [authUser release];
75     [publicResource release];
76     [loginResource release];
77     [versionResource release];
78     [serverURL release];
79     [syncDaemon release];
80     [syncLastCompleted release];
81     [syncContainerName release];
82     [syncDirectoryPath release];
83     [name release];
84     [uniqueName release];
85     [super dealloc];
86 }
87
88 - (NSString *)description {
89     return [NSString stringWithFormat:@"uniqueName: %@, active: %d, name: %@, syncActive: %d, syncDirectoryPath: %@, syncContainerName: %@, syncLastCompleted: %@, serverURL: %@, versionResource: %@, loginResource: %@, publicResource: %@, authUser: %@, authToken: %@, storageURLPrefix: %@, authURL: %@, loginURLPrefix: %@, publicURLPrefix: %@", 
90             uniqueName, active, name, syncActive, syncDirectoryPath, syncContainerName, syncLastCompleted, serverURL, versionResource, loginResource, publicResource, authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix];
91 }
92
93 #pragma mark -
94 #pragma mark Internal
95
96 - (BOOL)urlIsValid:(NSString *)urlString {
97     if (urlString) {
98         NSURL *url = [NSURL URLWithString:urlString];
99         if (url && url.scheme && url.host)
100             return YES;
101     }
102     return NO;
103 }
104
105 #pragma mark -
106 #pragma mark Properties
107
108 - (NSString *)name {
109     if (![name length]) {
110         [name release];
111         NSDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
112         NSString *namePrefix = [NSString stringWithString:@"okeanos"];
113         NSUInteger nameSuffix = 1;
114         name = [NSString stringWithString:@"okeanos"];
115         NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
116         NSFileManager *fileManager = [NSFileManager defaultManager];
117         while ([pithosAccountsDictionary objectForKey:name] || 
118                [fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:name]]) {
119             name = [NSString stringWithFormat:@"%@%d", namePrefix, ++nameSuffix];
120         }
121         [name retain];
122     }
123     return name;
124 }
125
126 - (void)setName:(NSString *)aName {
127     NSMutableDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
128     if (![self.name isEqualToString:aName] && [aName length] && ![pithosAccountsDictionary objectForKey:aName]) {
129         [pithosAccountsDictionary setObject:self forKey:aName];
130         [pithosAccountsDictionary removeObjectForKey:name];
131         [name release];
132         name = [aName retain];
133     }
134 }
135
136 - (BOOL)syncActive {
137     if (active)
138         return syncActive;
139     else
140         return NO;
141 }
142
143 - (void)setSyncActive:(BOOL)aSyncActive {
144     syncActive = aSyncActive;
145     if (syncDaemon && !self.syncActive)
146         [syncDaemon resetDaemon];
147 }
148
149 - (NSString *)syncDirectoryPath {
150     if (![syncDirectoryPath length]) {
151         [syncDirectoryPath release];
152         syncDirectoryPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
153                               stringByAppendingPathComponent:self.name] retain];
154     }
155     return syncDirectoryPath;
156 }
157
158 - (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
159     if (![self.syncDirectoryPath isEqualToString:aSyncDirectoryPath] && [aSyncDirectoryPath length]) {
160         BOOL isDirectory;
161         if (![[NSFileManager defaultManager] fileExistsAtPath:aSyncDirectoryPath isDirectory:&isDirectory] || isDirectory) {
162             [syncDirectoryPath release];
163             syncDirectoryPath = [aSyncDirectoryPath retain];
164         } else {
165             return;
166         }
167
168         @synchronized(self) {
169             resetSyncDaemonLocalState = YES;
170             [syncLastCompleted release];
171             syncLastCompleted = nil;
172         }
173     }
174 }
175
176 - (NSString *)syncContainerName {
177     if (![syncContainerName length]) {
178         [syncContainerName release];
179         syncContainerName = [[NSString stringWithString:@"pithos"] retain];
180     }        
181     return syncContainerName;
182 }
183
184 - (void)setSyncContainerName:(NSString *)aSyncContainerName {
185     if (![self.syncContainerName isEqualToString:aSyncContainerName] && [aSyncContainerName length]) {
186         [syncContainerName release];
187         syncContainerName = [aSyncContainerName retain];
188         
189         @synchronized(self) {
190             resetSyncDaemonLocalState = YES;
191             [syncLastCompleted release];
192             syncLastCompleted = nil;
193         }
194     }
195 }
196
197 - (NSDate *)syncLastCompleted {
198     if (self.syncDaemon.lastCompletedSync && ![self.syncDaemon.lastCompletedSync isEqualToDate:syncLastCompleted]) {
199         [syncLastCompleted release];
200         syncLastCompleted = [self.syncDaemon.lastCompletedSync copy];
201     }
202     return syncLastCompleted;
203 }
204
205 - (PithosSyncDaemon *)syncDaemon {
206     @synchronized(self) {
207         if (self.syncActive && !syncDaemon)
208             syncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:self.syncDirectoryPath 
209                                                            pithosAccount:self 
210                                                            containerName:self.syncContainerName 
211                                                          resetLocalState:resetSyncDaemonLocalState];
212         resetSyncDaemonLocalState = NO;
213     }
214     return syncDaemon;
215 }
216
217 - (NSString *)serverURL {
218     if (![self urlIsValid:serverURL]) {
219         [serverURL release];
220         serverURL = [[NSString stringWithString:@"https://plus.pithos.grnet.gr"] retain];
221     }
222     return serverURL;
223 }
224
225 - (void)setServerURL:(NSString *)aServerURL {
226     if (![self.serverURL isEqualToString:aServerURL] && [self urlIsValid:aServerURL]) {
227         [serverURL release];
228         serverURL = [aServerURL retain];
229         [storageURLPrefix release];
230         storageURLPrefix = nil;
231         [authURL release];
232         authURL = nil;
233         [publicURLPrefix release];
234         publicURLPrefix = nil;
235         [loginURLPrefix release];
236         loginURLPrefix = nil;
237
238         @synchronized(self) {
239             updatePithos = YES;
240             resetSyncDaemonLocalState = YES;
241             [syncLastCompleted release];
242             syncLastCompleted = nil;
243         }
244     }
245 }
246
247 - (void)setAuthUser:(NSString *)anAuthUser {
248     if ([anAuthUser length] && ![anAuthUser isEqualToString:authUser]) {
249         [authUser release];
250         authUser = [anAuthUser retain];
251         
252         @synchronized(self) {
253             updatePithos = YES;
254             resetSyncDaemonLocalState = YES;
255             [syncLastCompleted release];
256             syncLastCompleted = nil;
257
258         }
259     }
260 }
261
262 - (void)setAuthToken:(NSString *)anAuthToken {
263     if ([anAuthToken length] && ![anAuthToken isEqualToString:authToken]) {
264         [authToken release];
265         authToken = [anAuthToken retain];
266         
267         @synchronized(self) {
268             updatePithos = YES;
269         }
270     }
271 }
272
273 - (NSString *)storageURLPrefix {
274     if (![self urlIsValid:storageURLPrefix]) {
275         [storageURLPrefix release];
276         if (versionResource)
277             storageURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
278         else
279             storageURLPrefix = [self.serverURL copy];
280     }
281     return storageURLPrefix;
282 }
283
284 - (void)setStorageURLPrefix:(NSString *)aStorageURLPrefix {
285     if (![self.storageURLPrefix isEqualToString:aStorageURLPrefix] && [self urlIsValid:aStorageURLPrefix]) {
286         [storageURLPrefix release];
287         storageURLPrefix = [aStorageURLPrefix retain];
288     }
289 }
290
291 - (NSString *)authURL {
292     if (![self urlIsValid:authURL]) {
293         [authURL release];
294         if (versionResource)
295             authURL = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
296         else
297             authURL = [self.serverURL copy];
298     }
299     return authURL;
300 }
301
302 - (void)setAuthURL:(NSString *)anAuthURL {
303     if (![self.authURL isEqualToString:anAuthURL] && [self urlIsValid:anAuthURL]) {
304         [authURL release];
305         authURL = [anAuthURL retain];
306     }
307 }
308
309 - (NSString *)publicURLPrefix {
310     if (![self urlIsValid:publicURLPrefix]) {
311         [publicURLPrefix release];
312         if (publicResource)
313             publicURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", publicResource] retain];
314         else
315             publicURLPrefix = [self.serverURL copy];
316     }
317     return publicURLPrefix;
318 }
319
320 - (void)setPublicURLPrefix:(NSString *)aPublicURLPrefix {
321     if (![self.publicURLPrefix isEqualToString:aPublicURLPrefix] && [self urlIsValid:aPublicURLPrefix]) {
322         [publicURLPrefix release];
323         publicURLPrefix = [aPublicURLPrefix retain];
324     }
325 }
326
327 - (NSString *)loginURLPrefix {
328     if (![self urlIsValid:loginURLPrefix]) {
329         [loginURLPrefix release];
330         if (loginResource)
331             loginURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", loginResource] retain];
332         else
333             loginURLPrefix = [self.serverURL copy];
334     }
335     return loginURLPrefix;
336 }
337
338 - (void)setLoginURLPrefix:(NSString *)aLoginURLPrefix {
339     if (![self.loginURLPrefix isEqualToString:aLoginURLPrefix] && [self urlIsValid:aLoginURLPrefix]) {
340         [loginURLPrefix release];
341         loginURLPrefix = [aLoginURLPrefix retain];
342     }
343 }
344
345 - (ASIPithos *)pithos {
346     @synchronized(self) {
347         if (!pithos || updatePithos) {
348             [pithos release];
349             pithos = [[ASIPithos pithos] retain];
350             pithos.authUser = authUser;
351             pithos.authToken = authToken;
352             pithos.storageURLPrefix = self.storageURLPrefix;
353             pithos.authURL = self.authURL;
354             pithos.publicURLPrefix = self.publicURLPrefix;
355
356             if (accountNode && ![accountNode.pithos isEqualTo:pithos]) {
357                 accountNode.pithos = pithos;
358                 if (active)
359                     [accountNode refreshInfo];
360             }
361             
362             updatePithos = NO;
363         }
364     }
365     return pithos;
366 }
367
368 - (PithosAccountNode *)accountNode {
369     if (!accountNode) {
370         accountNode = [[PithosAccountNode alloc] initWithPithos:self.pithos];
371     }
372     return accountNode;
373 }
374
375 #pragma mark -
376 #pragma mark Actions
377
378 - (void)authenticateWithServerURL:(NSString *)aServerURL authUser:(NSString *)anAuthUser authToken:(NSString *)anAuthToken {
379     self.serverURL = aServerURL;
380     self.authUser = anAuthUser;
381     self.authToken = anAuthToken;
382     NSLog(@"Account: %@\nauthentication", self);
383     if (![authUser length] || ![authToken length]) {
384         self.active = NO;
385         self.syncActive = NO;
386         // XXX Show preferences with self as the selected account?
387     } else  {
388         self.active = YES;
389         if (syncDaemon) {
390             self.syncDaemon.pithos = self.pithos;
391             if (self.syncActive)
392                 [self.syncDaemon startDaemon];
393         }
394     }
395 }
396
397 - (void)loginWithServerURL:(NSString *)aServerURL {
398     self.serverURL = aServerURL;
399     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
400     NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d/%@", 
401                           self.loginURLPrefix, [processInfo processName], [processInfo processIdentifier], self.name];
402     NSLog(@"Account: %@\nloginURL: %@", self, loginURL);
403     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
404 }
405
406 - (void)updateSyncWithSyncActive:(BOOL)aSyncActive syncDirectoryPath:(NSString *)aSyncDirectoryPath {
407     self.syncDirectoryPath = aSyncDirectoryPath;
408     self.syncActive = aSyncActive;
409     if (syncDaemon) {
410         self.syncDaemon.directoryPath = self.syncDirectoryPath;
411         if (self.syncActive)
412             [self.syncDaemon startDaemon];
413     }    
414 }
415
416 #pragma mark -
417 #pragma mark NSCoding
418
419 - (id)initWithCoder:(NSCoder *)decoder {
420     if ((self = [super init])) {
421         self.uniqueName = [decoder decodeObjectForKey:@"uniqueName"];
422         self.active = [decoder decodeBoolForKey:@"active"];
423         name = [[decoder decodeObjectForKey:@"name"] retain];
424
425         self.syncActive = [decoder decodeBoolForKey:@"syncActive"];
426         self.syncDirectoryPath = [decoder decodeObjectForKey:@"syncDirectoryPath"];
427         self.syncContainerName = [decoder decodeObjectForKey:@"syncContainerName"];
428         self.syncLastCompleted = [decoder decodeObjectForKey:@"syncLastCompleted"];
429         
430         self.serverURL = [decoder decodeObjectForKey:@"serverURL"];
431         self.versionResource = [decoder decodeObjectForKey:@"versionResource"];
432         self.loginResource = [decoder decodeObjectForKey:@"loginResource"];
433         self.publicResource = [decoder decodeObjectForKey:@"publicResource"];
434         
435         self.authUser = [decoder decodeObjectForKey:@"authUser"];
436         self.authToken = [decoder decodeObjectForKey:@"authToken"];
437         self.storageURLPrefix = [decoder decodeObjectForKey:@"storageURLPrefix"];
438         self.authURL = [decoder decodeObjectForKey:@"authURL"];
439         self.publicURLPrefix = [decoder decodeObjectForKey:@"publicURLPrefix"];
440         self.loginURLPrefix = [decoder decodeObjectForKey:@"loginURLPrefix"];
441         
442         if (![authUser length] || ![authToken length] || ![self.storageURLPrefix length])
443             self.active = NO;
444         
445         resetSyncDaemonLocalState = NO;
446     }
447     return self;
448 }
449
450 - (void)encodeWithCoder:(NSCoder *)encoder {
451     [encoder encodeObject:uniqueName forKey:@"uniqueName"];
452     [encoder encodeBool:active forKey:@"active"];
453     [encoder encodeObject:name forKey:@"name"];
454     
455     [encoder encodeBool:syncActive forKey:@"syncActive"];
456     [encoder encodeObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
457     [encoder encodeObject:syncContainerName forKey:@"syncContainerName"];
458     [encoder encodeObject:self.syncLastCompleted forKey:@"syncLastCompleted"];
459
460     [encoder encodeObject:serverURL forKey:@"serverURL"];
461     [encoder encodeObject:versionResource forKey:@"versionResource"];
462     [encoder encodeObject:publicResource forKey:@"publicResource"];
463     [encoder encodeObject:loginResource forKey:@"loginResource"];
464     
465     [encoder encodeObject:authUser forKey:@"authUser"];
466     [encoder encodeObject:authToken forKey:@"authToken"];
467     [encoder encodeObject:storageURLPrefix forKey:@"storageURLPrefix"];
468     [encoder encodeObject:authURL forKey:@"authURL"];
469     [encoder encodeObject:publicURLPrefix forKey:@"publicURLPrefix"];
470     [encoder encodeObject:loginURLPrefix forKey:@"loginURLPrefix"];
471 }
472
473 @end