Add UI for selective sync folders. Call next sync when an account has sync preference...
[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, syncContainersDictionary, 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     [syncContainersDictionary 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: %@, syncContainersDictionary: %@, syncLastCompleted: %@, serverURL: %@, versionResource: %@, loginResource: %@, publicResource: %@, authUser: %@, authToken: %@, storageURLPrefix: %@, authURL: %@, loginURLPrefix: %@, publicURLPrefix: %@", 
90             uniqueName, active, name, syncActive, syncDirectoryPath, syncContainersDictionary, 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 - (NSMutableDictionary *)syncContainersDictionary {
177     if (!syncContainersDictionary) {
178         syncContainersDictionary = [[NSMutableDictionary dictionaryWithObject:[NSMutableArray array] 
179                                                                        forKey:@"pithos"] retain];
180     }        
181     return syncContainersDictionary;
182 }
183
184 - (void)setSyncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
185     if (aSyncContainersDictionary && ![self.syncContainersDictionary isEqualToDictionary:aSyncContainersDictionary]) {
186         [syncContainersDictionary release];
187         syncContainersDictionary = [aSyncContainersDictionary retain];
188         // XXX maybe check also here the validity of the dictionary? 
189         
190         @synchronized(self) {
191             resetSyncDaemonLocalState = YES;
192             [syncLastCompleted release];
193             syncLastCompleted = nil;
194         }
195     }
196 }
197
198 - (NSDate *)syncLastCompleted {
199     if (self.syncDaemon.lastCompletedSync && ![self.syncDaemon.lastCompletedSync isEqualToDate:syncLastCompleted]) {
200         [syncLastCompleted release];
201         syncLastCompleted = [self.syncDaemon.lastCompletedSync copy];
202     }
203     return syncLastCompleted;
204 }
205
206 - (PithosSyncDaemon *)syncDaemon {
207     @synchronized(self) {
208         if (self.syncActive && !syncDaemon)
209             syncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:self.syncDirectoryPath 
210                                                            pithosAccount:self
211                                                     containersDictionary:self.syncContainersDictionary
212                                                          resetLocalState:resetSyncDaemonLocalState];
213         resetSyncDaemonLocalState = NO;
214     }
215     return syncDaemon;
216 }
217
218 - (NSString *)serverURL {
219     if (![self urlIsValid:serverURL]) {
220         [serverURL release];
221         serverURL = [[NSString stringWithString:@"https://pithos.okeanos.grnet.gr"] retain];
222     }
223     return serverURL;
224 }
225
226 - (void)setServerURL:(NSString *)aServerURL {
227     if (![self.serverURL isEqualToString:aServerURL] && [self urlIsValid:aServerURL]) {
228         [serverURL release];
229         serverURL = [aServerURL retain];
230         [storageURLPrefix release];
231         storageURLPrefix = nil;
232         [authURL release];
233         authURL = nil;
234         [publicURLPrefix release];
235         publicURLPrefix = nil;
236         [loginURLPrefix release];
237         loginURLPrefix = nil;
238
239         @synchronized(self) {
240             updatePithos = YES;
241             resetSyncDaemonLocalState = YES;
242             [syncLastCompleted release];
243             syncLastCompleted = nil;
244         }
245     }
246 }
247
248 - (void)setAuthUser:(NSString *)anAuthUser {
249     if ([anAuthUser length] && ![anAuthUser isEqualToString:authUser]) {
250         [authUser release];
251         authUser = [anAuthUser retain];
252         
253         @synchronized(self) {
254             updatePithos = YES;
255             resetSyncDaemonLocalState = YES;
256             [syncLastCompleted release];
257             syncLastCompleted = nil;
258
259         }
260     }
261 }
262
263 - (void)setAuthToken:(NSString *)anAuthToken {
264     if ([anAuthToken length] && ![anAuthToken isEqualToString:authToken]) {
265         [authToken release];
266         authToken = [anAuthToken retain];
267         
268         @synchronized(self) {
269             updatePithos = YES;
270         }
271     }
272 }
273
274 - (NSString *)storageURLPrefix {
275     if (![self urlIsValid:storageURLPrefix]) {
276         [storageURLPrefix release];
277         if (versionResource)
278             storageURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
279         else
280             storageURLPrefix = [self.serverURL copy];
281     }
282     return storageURLPrefix;
283 }
284
285 - (void)setStorageURLPrefix:(NSString *)aStorageURLPrefix {
286     if (![self.storageURLPrefix isEqualToString:aStorageURLPrefix] && [self urlIsValid:aStorageURLPrefix]) {
287         [storageURLPrefix release];
288         storageURLPrefix = [aStorageURLPrefix retain];
289     }
290 }
291
292 - (NSString *)authURL {
293     if (![self urlIsValid:authURL]) {
294         [authURL release];
295         if (versionResource)
296             authURL = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
297         else
298             authURL = [self.serverURL copy];
299     }
300     return authURL;
301 }
302
303 - (void)setAuthURL:(NSString *)anAuthURL {
304     if (![self.authURL isEqualToString:anAuthURL] && [self urlIsValid:anAuthURL]) {
305         [authURL release];
306         authURL = [anAuthURL retain];
307     }
308 }
309
310 - (NSString *)publicURLPrefix {
311     if (![self urlIsValid:publicURLPrefix]) {
312         [publicURLPrefix release];
313         if (publicResource)
314             publicURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", publicResource] retain];
315         else
316             publicURLPrefix = [self.serverURL copy];
317     }
318     return publicURLPrefix;
319 }
320
321 - (void)setPublicURLPrefix:(NSString *)aPublicURLPrefix {
322     if (![self.publicURLPrefix isEqualToString:aPublicURLPrefix] && [self urlIsValid:aPublicURLPrefix]) {
323         [publicURLPrefix release];
324         publicURLPrefix = [aPublicURLPrefix retain];
325     }
326 }
327
328 - (NSString *)loginURLPrefix {
329     if (![self urlIsValid:loginURLPrefix]) {
330         [loginURLPrefix release];
331         if (loginResource)
332             loginURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", loginResource] retain];
333         else
334             loginURLPrefix = [self.serverURL copy];
335     }
336     return loginURLPrefix;
337 }
338
339 - (void)setLoginURLPrefix:(NSString *)aLoginURLPrefix {
340     if (![self.loginURLPrefix isEqualToString:aLoginURLPrefix] && [self urlIsValid:aLoginURLPrefix]) {
341         [loginURLPrefix release];
342         loginURLPrefix = [aLoginURLPrefix retain];
343     }
344 }
345
346 - (ASIPithos *)pithos {
347     @synchronized(self) {
348         if (!pithos || updatePithos) {
349             [pithos release];
350             pithos = [[ASIPithos pithos] retain];
351             pithos.authUser = authUser;
352             pithos.authToken = authToken;
353             pithos.storageURLPrefix = self.storageURLPrefix;
354             pithos.authURL = self.authURL;
355             pithos.publicURLPrefix = self.publicURLPrefix;
356
357             if (accountNode && ![accountNode.pithos isEqualTo:pithos]) {
358                 accountNode.pithos = pithos;
359                 if (active)
360                     [accountNode refreshInfo];
361             }
362             
363             updatePithos = NO;
364         }
365     }
366     return pithos;
367 }
368
369 - (PithosAccountNode *)accountNode {
370     if (!accountNode) {
371         accountNode = [[PithosAccountNode alloc] initWithPithos:self.pithos];
372         accountNode.childrenUpdatedNotificationName = nil;
373         accountNode.inheritChildrenUpdatedNotificationName = YES;
374     }
375     return accountNode;
376 }
377
378 #pragma mark -
379 #pragma mark Actions
380
381 - (void)authenticateWithServerURL:(NSString *)aServerURL authUser:(NSString *)anAuthUser authToken:(NSString *)anAuthToken {
382     self.serverURL = aServerURL;
383     self.authUser = anAuthUser;
384     self.authToken = anAuthToken;
385     NSLog(@"Account: %@\nauthentication", self);
386     if (![authUser length] || ![authToken length]) {
387         self.active = NO;
388         self.syncActive = NO;
389         // XXX Show preferences with self as the selected account?
390     } else  {
391         self.active = YES;
392         if (syncDaemon) {
393             self.syncDaemon.pithos = self.pithos;
394             if (self.syncActive)
395                 [self.syncDaemon startDaemon];
396         }
397     }
398 }
399
400 - (void)loginWithServerURL:(NSString *)aServerURL {
401     self.serverURL = aServerURL;
402     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
403     NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d/%@", 
404                           self.loginURLPrefix, [processInfo processName], [processInfo processIdentifier], self.name];
405     NSLog(@"Account: %@\nloginURL: %@", self, loginURL);
406     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
407 }
408
409 - (void)updateSyncWithSyncActive:(BOOL)aSyncActive 
410                syncDirectoryPath:(NSString *)aSyncDirectoryPath 
411         syncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
412     self.syncContainersDictionary = aSyncContainersDictionary;
413     self.syncDirectoryPath = aSyncDirectoryPath;
414     self.syncActive = aSyncActive;
415     if (syncDaemon) {
416         self.syncDaemon.containersDictionary = self.syncContainersDictionary;
417         self.syncDaemon.directoryPath = self.syncDirectoryPath;
418         if (self.syncActive)
419             [self.syncDaemon startDaemon];
420     }    
421 }
422
423 #pragma mark -
424 #pragma mark NSCoding
425
426 - (id)initWithCoder:(NSCoder *)decoder {
427     if ((self = [super init])) {
428         self.uniqueName = [decoder decodeObjectForKey:@"uniqueName"];
429         self.active = [decoder decodeBoolForKey:@"active"];
430         name = [[decoder decodeObjectForKey:@"name"] retain];
431
432         self.syncActive = [decoder decodeBoolForKey:@"syncActive"];
433         self.syncDirectoryPath = [decoder decodeObjectForKey:@"syncDirectoryPath"];
434         NSDictionary *immutableContainersDictionary = [decoder decodeObjectForKey:@"syncContainersDictionary"];
435         syncContainersDictionary = [[NSMutableDictionary alloc] initWithCapacity:[immutableContainersDictionary count]];
436         for (NSString *containerName in immutableContainersDictionary) {
437             [syncContainersDictionary setObject:[NSMutableArray arrayWithArray:[immutableContainersDictionary objectForKey:containerName]] 
438                                          forKey:containerName];
439         }
440         self.syncLastCompleted = [decoder decodeObjectForKey:@"syncLastCompleted"];
441         
442         self.serverURL = [decoder decodeObjectForKey:@"serverURL"];
443         self.versionResource = [decoder decodeObjectForKey:@"versionResource"];
444         self.loginResource = [decoder decodeObjectForKey:@"loginResource"];
445         self.publicResource = [decoder decodeObjectForKey:@"publicResource"];
446         
447         self.authUser = [decoder decodeObjectForKey:@"authUser"];
448         self.authToken = [decoder decodeObjectForKey:@"authToken"];
449         self.storageURLPrefix = [decoder decodeObjectForKey:@"storageURLPrefix"];
450         self.authURL = [decoder decodeObjectForKey:@"authURL"];
451         self.publicURLPrefix = [decoder decodeObjectForKey:@"publicURLPrefix"];
452         self.loginURLPrefix = [decoder decodeObjectForKey:@"loginURLPrefix"];
453         
454         if (![authUser length] || ![authToken length] || ![self.storageURLPrefix length])
455             self.active = NO;
456         
457         resetSyncDaemonLocalState = NO;
458     }
459     return self;
460 }
461
462 - (void)encodeWithCoder:(NSCoder *)encoder {
463     [encoder encodeObject:uniqueName forKey:@"uniqueName"];
464     [encoder encodeBool:active forKey:@"active"];
465     [encoder encodeObject:name forKey:@"name"];
466     
467     [encoder encodeBool:syncActive forKey:@"syncActive"];
468     [encoder encodeObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
469     [encoder encodeObject:syncContainersDictionary forKey:@"syncContainersDictionary"];
470     [encoder encodeObject:self.syncLastCompleted forKey:@"syncLastCompleted"];
471
472     [encoder encodeObject:serverURL forKey:@"serverURL"];
473     [encoder encodeObject:versionResource forKey:@"versionResource"];
474     [encoder encodeObject:publicResource forKey:@"publicResource"];
475     [encoder encodeObject:loginResource forKey:@"loginResource"];
476     
477     [encoder encodeObject:authUser forKey:@"authUser"];
478     [encoder encodeObject:authToken forKey:@"authToken"];
479     [encoder encodeObject:storageURLPrefix forKey:@"storageURLPrefix"];
480     [encoder encodeObject:authURL forKey:@"authURL"];
481     [encoder encodeObject:publicURLPrefix forKey:@"publicURLPrefix"];
482     [encoder encodeObject:loginURLPrefix forKey:@"loginURLPrefix"];
483 }
484
485 @end