5 // Copyright 2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosAccount.h"
39 #import "PithosSyncDaemon.h"
41 #import "ASIPithosRequest.h"
42 #import "PithosAccountNode.h"
43 #import "PithosSharingAccountsNode.h"
44 #import "PithosUtilities.h"
45 #import "pithos_macosAppDelegate.h"
47 @interface PithosAccount (Internal)
48 - (BOOL)urlIsValid:(NSString *)urlString;
51 @implementation PithosAccount
52 @synthesize uniqueName, active, name, clientVersion;
53 @synthesize syncActive, syncDirectoryPath, syncAccountsDictionary, syncSkipHidden, syncLastCompleted, syncDaemon;
54 @synthesize serverURL, versionResource, loginResource, publicResource, userCatalogResource;
55 @synthesize authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix, userCatalogURL, userCatalog;
56 @synthesize pithos, accountNode, sharingAccountsNode;
59 #pragma mark Object Lifecycle
62 PithosAccount *pithosAccount = [[self alloc] init];
63 pithosAccount.uniqueName = [NSString stringWithFormat:@"pithosAccount-%f", [NSDate timeIntervalSinceReferenceDate]];
64 pithosAccount.clientVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
65 pithosAccount.syncSkipHidden = YES;
70 - (NSString *)description {
71 return [NSString stringWithFormat:@"uniqueName: %@, active: %d, name: %@, clientVersion: %@, syncActive: %d, syncDirectoryPath: %@, syncAccountsDictionary: %@, syncSkipHidden: %d, syncLastCompleted: %@, serverURL: %@, versionResource: %@, loginResource: %@, publicResource: %@, userCatalogResource: %@, authUser: %@, authToken: %@, storageURLPrefix: %@, authURL: %@, loginURLPrefix: %@, publicURLPrefix: %@, userCatalogResource: %@",
72 uniqueName, active, name, clientVersion, syncActive, syncDirectoryPath, syncAccountsDictionary, syncSkipHidden, syncLastCompleted, serverURL, versionResource, loginResource, publicResource, userCatalogResource, authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix, userCatalogResource];
78 - (BOOL)urlIsValid:(NSString *)urlString {
80 NSURL *url = [NSURL URLWithString:urlString];
81 if (url && url.scheme && url.host)
88 #pragma mark Properties
92 NSDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
93 NSString *namePrefix = @"okeanos";
94 NSUInteger nameSuffix = 1;
96 NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
97 NSFileManager *fileManager = [NSFileManager defaultManager];
98 while ([pithosAccountsDictionary objectForKey:name] ||
99 [fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:name]]) {
100 name = [NSString stringWithFormat:@"%@%ld", namePrefix, ++nameSuffix];
106 - (void)setName:(NSString *)aName {
107 NSMutableDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
108 if (![self.name isEqualToString:aName] && [aName length] && ![pithosAccountsDictionary objectForKey:aName]) {
109 [pithosAccountsDictionary setObject:self forKey:aName];
110 [pithosAccountsDictionary removeObjectForKey:name];
122 - (void)setSyncActive:(BOOL)aSyncActive {
123 syncActive = aSyncActive;
124 if (syncDaemon && !self.syncActive)
125 [syncDaemon resetDaemon];
128 - (NSString *)syncDirectoryPath {
129 if (![syncDirectoryPath length]) {
130 syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
131 stringByAppendingPathComponent:self.name];
133 return syncDirectoryPath;
136 - (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
137 if (![self.syncDirectoryPath isEqualToString:aSyncDirectoryPath] && [aSyncDirectoryPath length]) {
139 if (![[NSFileManager defaultManager] fileExistsAtPath:aSyncDirectoryPath isDirectory:&isDirectory] || isDirectory) {
140 syncDirectoryPath = aSyncDirectoryPath;
145 @synchronized(self) {
146 resetSyncDaemonLocalState = YES;
147 syncLastCompleted = nil;
152 - (NSMutableDictionary *)syncAccountsDictionary {
153 if (!syncAccountsDictionary) {
154 syncAccountsDictionary = [NSMutableDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObject:[NSMutableSet set]
158 return syncAccountsDictionary;
161 - (void)setSyncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary {
162 if (aSyncAccountsDictionary && ![self.syncAccountsDictionary isEqualToDictionary:aSyncAccountsDictionary]) {
163 syncAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncAccountsDictionary count]];
164 for (NSString *accountName in aSyncAccountsDictionary) {
165 NSDictionary *aSyncContainersDictionary = [aSyncAccountsDictionary objectForKey:accountName];
166 NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
167 for (NSString *containerName in aSyncContainersDictionary) {
168 if (![accountName isEqualToString:@""] || ![[containerName lowercaseString] isEqualToString:@"shared with me"])
169 [syncContainersDictionary setObject:[NSMutableSet setWithSet:[aSyncContainersDictionary objectForKey:containerName]]
170 forKey:containerName];
172 if ([syncContainersDictionary count])
173 [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
176 @synchronized(self) {
177 resetSyncDaemonLocalState = YES;
178 syncLastCompleted = nil;
183 - (NSDate *)syncLastCompleted {
184 if (self.syncDaemon.lastCompletedSync && ![self.syncDaemon.lastCompletedSync isEqualToDate:syncLastCompleted]) {
185 syncLastCompleted = [self.syncDaemon.lastCompletedSync copy];
187 return syncLastCompleted;
190 - (PithosSyncDaemon *)syncDaemon {
191 @synchronized(self) {
192 if (self.syncActive && !syncDaemon)
193 syncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:self.syncDirectoryPath
195 accountsDictionary:self.syncAccountsDictionary
196 skipHidden:self.syncSkipHidden
197 resetLocalState:resetSyncDaemonLocalState];
198 resetSyncDaemonLocalState = NO;
203 - (NSString *)serverURL {
204 if (![self urlIsValid:serverURL]) {
205 serverURL = @"https://pithos.okeanos.grnet.gr";
210 - (void)setServerURL:(NSString *)aServerURL {
211 if (![self.serverURL isEqualToString:aServerURL] && [self urlIsValid:aServerURL]) {
212 serverURL = aServerURL;
213 storageURLPrefix = nil;
215 publicURLPrefix = nil;
216 loginURLPrefix = nil;
217 userCatalogURL = nil;
219 @synchronized(self) {
221 resetSyncDaemonLocalState = YES;
222 syncLastCompleted = nil;
227 - (NSString *)versionResource {
228 if (!versionResource) {
229 versionResource = @"v1";
231 return versionResource;
234 - (NSString *)loginResource {
235 if (!loginResource) {
236 loginResource = @"login";
238 return loginResource;
241 - (NSString *)userCatalogResource {
242 if (!userCatalogResource) {
243 userCatalogResource = @"user_catalogs";
245 return userCatalogResource;
248 - (void)setAuthUser:(NSString *)anAuthUser {
249 if ([anAuthUser length] && ![anAuthUser isEqualToString:authUser]) {
250 authUser = anAuthUser;
252 @synchronized(self) {
254 resetSyncDaemonLocalState = YES;
255 syncLastCompleted = nil;
261 - (void)setAuthToken:(NSString *)anAuthToken {
262 if ([anAuthToken length] && ![anAuthToken isEqualToString:authToken]) {
263 authToken = anAuthToken;
265 @synchronized(self) {
271 - (NSString *)storageURLPrefix {
272 if (![self urlIsValid:storageURLPrefix]) {
273 storageURLPrefix = [self.serverURL stringByAppendingFormat:@"/%@", self.versionResource];
275 return storageURLPrefix;
278 - (void)setStorageURLPrefix:(NSString *)aStorageURLPrefix {
279 if (![self.storageURLPrefix isEqualToString:aStorageURLPrefix] && [self urlIsValid:aStorageURLPrefix]) {
280 storageURLPrefix = aStorageURLPrefix;
284 - (NSString *)authURL {
285 if (![self urlIsValid:authURL]) {
286 authURL = [self.serverURL stringByAppendingFormat:@"/%@", self.versionResource];
291 - (void)setAuthURL:(NSString *)anAuthURL {
292 if (![self.authURL isEqualToString:anAuthURL] && [self urlIsValid:anAuthURL]) {
297 - (NSString *)publicURLPrefix {
298 if (![self urlIsValid:publicURLPrefix]) {
300 publicURLPrefix = [self.serverURL stringByAppendingFormat:@"/%@", publicResource];
302 publicURLPrefix = [self.serverURL copy];
304 return publicURLPrefix;
307 - (void)setPublicURLPrefix:(NSString *)aPublicURLPrefix {
308 if (![self.publicURLPrefix isEqualToString:aPublicURLPrefix] && [self urlIsValid:aPublicURLPrefix]) {
309 publicURLPrefix = aPublicURLPrefix;
313 - (NSString *)loginURLPrefix {
314 if (![self urlIsValid:loginURLPrefix]) {
315 loginURLPrefix = [self.serverURL stringByAppendingFormat:@"/%@", self.loginResource];
317 return loginURLPrefix;
320 - (void)setLoginURLPrefix:(NSString *)aLoginURLPrefix {
321 if (![self.loginURLPrefix isEqualToString:aLoginURLPrefix] && [self urlIsValid:aLoginURLPrefix]) {
322 loginURLPrefix = aLoginURLPrefix;
326 - (NSString *)userCatalogURL {
327 if (![self urlIsValid:userCatalogURL]) {
328 userCatalogURL = [self.serverURL stringByAppendingFormat:@"/%@", self.userCatalogResource];
330 return userCatalogURL;
333 - (void)setUserCatalogURL:(NSString *)aUserCatalogURL {
334 if (![self.userCatalogURL isEqualToString:aUserCatalogURL] && [self urlIsValid:aUserCatalogURL]) {
335 userCatalogURL = aUserCatalogURL;
339 - (NSMutableDictionary *)userCatalog {
341 userCatalog = [NSMutableDictionary dictionary];
346 - (ASIPithos *)pithos {
347 @synchronized(self) {
348 if (!pithos || updatePithos) {
349 pithos = [ASIPithos pithos];
350 pithos.authUser = authUser;
351 pithos.authToken = authToken;
352 pithos.storageURLPrefix = self.storageURLPrefix;
353 pithos.authURL = self.authURL;
354 pithos.publicURLPrefix = self.publicURLPrefix;
355 pithos.userCatalogURL = self.userCatalogURL;
362 - (PithosAccountNode *)accountNode {
364 accountNode = [[PithosAccountNode alloc] initWithPithos:self.pithos];
365 accountNode.childrenUpdatedNotificationName = nil;
366 accountNode.inheritChildrenUpdatedNotificationName = YES;
367 accountNode.pithosAccountManager = self;
372 - (PithosSharingAccountsNode *)sharingAccountsNode {
373 if (!sharingAccountsNode) {
374 sharingAccountsNode = [[PithosSharingAccountsNode alloc] initWithPithos:self.pithos];
375 sharingAccountsNode.childrenUpdatedNotificationName = nil;
376 sharingAccountsNode.inheritChildrenUpdatedNotificationName = YES;
377 sharingAccountsNode.pithosAccountManager = self;
379 return sharingAccountsNode;
385 - (void)authenticateWithServerURL:(NSString *)aServerURL authUser:(NSString *)anAuthUser authToken:(NSString *)anAuthToken {
386 self.serverURL = aServerURL;
387 self.authUser = anAuthUser;
388 self.authToken = anAuthToken;
389 DLog(@"Account: %@\nauthentication", self);
390 if (![authUser length] || ![authToken length]) {
392 self.syncActive = NO;
393 // XXX Show preferences with self as the selected account?
397 self.syncDaemon.pithos = self.pithos;
399 [self.syncDaemon startDaemon];
402 self.accountNode.pithos = self.pithos;
403 if (sharingAccountsNode)
404 self.sharingAccountsNode.pithos = self.pithos;
406 if (self.accountNode.children) {
408 [self.accountNode refreshInfo];
410 if (sharingAccountsNode && self.sharingAccountsNode.children) {
413 [self updateUserCatalogForForDisplaynames:nil UUIDs:[NSArray arrayWithObject:authUser]];
417 - (void)loginWithServerURL:(NSString *)aServerURL {
418 self.serverURL = aServerURL;
419 NSProcessInfo *processInfo = [NSProcessInfo processInfo];
420 NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%d/%@&force=",
421 self.loginURLPrefix, [processInfo processIdentifier], [ASIPithosRequest encodeToPercentEscape:self.name]];
422 DLog(@"Account: %@\nloginURL: %@", self, loginURL);
423 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
426 - (void)updateSyncWithSyncActive:(BOOL)aSyncActive
427 syncDirectoryPath:(NSString *)aSyncDirectoryPath
428 syncAccountsDictionary:(NSMutableDictionary *)aSyncAccountsDictionary
429 syncSkipHidden:(BOOL)aSyncSkipHidden {
430 self.syncAccountsDictionary = aSyncAccountsDictionary;
431 self.syncDirectoryPath = aSyncDirectoryPath;
432 self.syncSkipHidden = aSyncSkipHidden;
433 self.syncActive = aSyncActive;
435 self.syncDaemon.accountsDictionary = self.syncAccountsDictionary;
436 self.syncDaemon.directoryPath = self.syncDirectoryPath;
437 self.syncDaemon.skipHidden = self.syncSkipHidden;
439 [self.syncDaemon startDaemon];
443 - (ASIPithosRequest *)updateUserCatalogForForDisplaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
444 ASIPithosRequest *userCatalogRequest = [ASIPithosRequest userCatalogRequestWithPithos:self.pithos
445 displaynames:displaynames
447 [PithosUtilities startAndWaitForRequest:userCatalogRequest];
448 if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
449 // Don't show alert on 404, since it can be a pre-UUID server.
450 [PithosUtilities httpRequestErrorAlertWithRequest:userCatalogRequest];
451 } else if (userCatalogRequest.responseStatusCode == 200) {
452 NSDictionary *catalogs = [userCatalogRequest catalogs];
453 NSDictionary *displaynameCatalog = [catalogs objectForKey:@"displayname_catalog"];
454 for (NSString *displayname in displaynameCatalog) {
455 [self.userCatalog setObject:displayname forKey:[displaynameCatalog objectForKey:displayname]];
458 NSDictionary *UUIDCatalog = [catalogs objectForKey:@"uuid_catalog"];
459 for (NSString *UUID in UUIDs) {
460 NSString *displayname = [UUIDCatalog objectForKey:UUID];
462 [self.userCatalog setObject:displayname forKey:UUID];
464 [self.userCatalog removeObjectForKey:UUID];
469 return userCatalogRequest;
472 - (NSString *)displaynameForUUID:(NSString *)UUID safe:(BOOL)safe {
473 NSString *displayName = [userCatalog objectForKey:UUID];
474 if (safe && !displayName) {
481 - (NSString *)displaynameForUUID:(NSString *)UUID {
482 return [self displaynameForUUID:UUID safe:NO];
486 #pragma mark NSCoding
488 - (id)initWithCoder:(NSCoder *)decoder {
489 if ((self = [super init])) {
490 self.uniqueName = [decoder decodeObjectForKey:@"uniqueName"];
491 self.active = [decoder decodeBoolForKey:@"active"];
492 name = [decoder decodeObjectForKey:@"name"];
493 self.clientVersion = [decoder decodeObjectForKey:@"clientVersion"];
495 self.syncActive = [decoder decodeBoolForKey:@"syncActive"];
496 self.syncDirectoryPath = [decoder decodeObjectForKey:@"syncDirectoryPath"];
497 self.syncAccountsDictionary = [decoder decodeObjectForKey:@"syncAccountsDictionary"];
498 self.syncSkipHidden = [decoder decodeBoolForKey:@"syncSkipHidden"];
499 self.syncLastCompleted = [decoder decodeObjectForKey:@"syncLastCompleted"];
501 self.serverURL = [decoder decodeObjectForKey:@"serverURL"];
502 self.versionResource = [decoder decodeObjectForKey:@"versionResource"];
503 self.loginResource = [decoder decodeObjectForKey:@"loginResource"];
504 self.publicResource = [decoder decodeObjectForKey:@"publicResource"];
505 self.userCatalogResource = [decoder decodeObjectForKey:@"userCatalogResource"];
507 self.authUser = [decoder decodeObjectForKey:@"authUser"];
508 self.authToken = [decoder decodeObjectForKey:@"authToken"];
509 self.storageURLPrefix = [decoder decodeObjectForKey:@"storageURLPrefix"];
510 self.authURL = [decoder decodeObjectForKey:@"authURL"];
511 self.publicURLPrefix = [decoder decodeObjectForKey:@"publicURLPrefix"];
512 self.loginURLPrefix = [decoder decodeObjectForKey:@"loginURLPrefix"];
513 self.userCatalogURL = [decoder decodeObjectForKey:@"userCatalogURL"];
514 self.userCatalog = [decoder decodeObjectForKey:@"userCatalog"];
516 if (![authUser length] || ![authToken length] || ![self.storageURLPrefix length])
519 NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
520 if (!clientVersion || ![clientVersion isEqualToString:currentVersion]) {
521 resetSyncDaemonLocalState = YES;
522 self.clientVersion = currentVersion;
524 resetSyncDaemonLocalState = NO;
530 - (void)encodeWithCoder:(NSCoder *)encoder {
531 [encoder encodeObject:uniqueName forKey:@"uniqueName"];
532 [encoder encodeBool:active forKey:@"active"];
533 [encoder encodeObject:name forKey:@"name"];
534 [encoder encodeObject:clientVersion forKey:@"clientVersion"];
536 [encoder encodeBool:syncActive forKey:@"syncActive"];
537 [encoder encodeObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
538 [encoder encodeObject:syncAccountsDictionary forKey:@"syncAccountsDictionary"];
539 [encoder encodeBool:syncSkipHidden forKey:@"syncSkipHidden"];
540 [encoder encodeObject:self.syncLastCompleted forKey:@"syncLastCompleted"];
542 [encoder encodeObject:serverURL forKey:@"serverURL"];
543 [encoder encodeObject:versionResource forKey:@"versionResource"];
544 [encoder encodeObject:publicResource forKey:@"publicResource"];
545 [encoder encodeObject:loginResource forKey:@"loginResource"];
546 [encoder encodeObject:loginResource forKey:@"userCatalogResource"];
548 [encoder encodeObject:authUser forKey:@"authUser"];
549 [encoder encodeObject:authToken forKey:@"authToken"];
550 [encoder encodeObject:storageURLPrefix forKey:@"storageURLPrefix"];
551 [encoder encodeObject:authURL forKey:@"authURL"];
552 [encoder encodeObject:publicURLPrefix forKey:@"publicURLPrefix"];
553 [encoder encodeObject:loginURLPrefix forKey:@"loginURLPrefix"];
554 [encoder encodeObject:userCatalogURL forKey:@"userCatalogURL"];
555 [encoder encodeObject:userCatalog forKey:@"userCatalog"];