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