Add support for syncing multiple containers for an account. Fix sync bugs. Change...
[pithos-macos] / pithos-macos / PithosAccount.m
index efe9bcb..1d72e6b 100644 (file)
@@ -1,8 +1,8 @@
 //
-//  PithosNode.m
+//  PithosAccount.m
 //  pithos-macos
 //
-// Copyright 2011 GRNET S.A. All rights reserved.
+// Copyright 2012 GRNET S.A. All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or
 // without modification, are permitted provided that the following
 // or implied, of GRNET S.A.
 
 #import "PithosAccount.h"
-#import "PithosContainer.h"
-#import "ASIPithosAccountRequest.h"
-#import "ASIPithosContainer.h"
-#import "ASIDownloadCache.h"
+#import "PithosSyncDaemon.h"
+#import "ASIPithos.h"
+#import "PithosAccountNode.h"
+#import "pithos_macosAppDelegate.h"
+
+@interface PithosAccount (Internal)
+- (BOOL)urlIsValid:(NSString *)urlString;
+@end
 
 @implementation PithosAccount
+@synthesize uniqueName, active, name;
+@synthesize syncActive, syncDirectoryPath, syncContainersDictionary, syncLastCompleted, syncDaemon;
+@synthesize serverURL, versionResource, loginResource, publicResource;
+@synthesize authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix;
+@synthesize pithos, accountNode;
 
 #pragma mark -
 #pragma mark Object Lifecycle
 
-- (id)init {
-    if ((self = [super init])) {
-        refreshing = NO;
-    }
-    return self;
++ (id)pithosAccount {
+    PithosAccount *pithosAccount = [[[self alloc] init] autorelease];
+    pithosAccount.uniqueName = [NSString stringWithFormat:@"pithosAccount-%f", [NSDate timeIntervalSinceReferenceDate]];
+    pithosAccount.versionResource = [NSString stringWithString:@"v1"];
+    pithosAccount.loginResource = [NSString stringWithString:@"login"];
+    return pithosAccount;
 }
 
 - (void)dealloc {
-    [containers release];
+    [accountNode release];
+    [pithos release];
+    [publicURLPrefix release];
+    [loginURLPrefix release];
+    [authURL release];
+    [storageURLPrefix release];
+    [authToken release];
+    [authUser release];
+    [publicResource release];
+    [loginResource release];
+    [versionResource release];
+    [serverURL release];
+    [syncDaemon release];
+    [syncLastCompleted release];
+    [syncContainersDictionary release];
+    [syncDirectoryPath release];
+    [name release];
+    [uniqueName release];
     [super dealloc];
 }
 
+- (NSString *)description {
+    return [NSString stringWithFormat:@"uniqueName: %@, active: %d, name: %@, syncActive: %d, syncDirectoryPath: %@, syncContainersDictionary: %@, syncLastCompleted: %@, serverURL: %@, versionResource: %@, loginResource: %@, publicResource: %@, authUser: %@, authToken: %@, storageURLPrefix: %@, authURL: %@, loginURLPrefix: %@, publicURLPrefix: %@", 
+            uniqueName, active, name, syncActive, syncDirectoryPath, syncContainersDictionary, syncLastCompleted, serverURL, versionResource, loginResource, publicResource, authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix];
+}
+
 #pragma mark -
-#pragma mark ASIHTTPRequestDelegate
+#pragma mark Internal
 
-- (void)requestFinished:(ASIPithosAccountRequest *)accountRequest {
-    NSLog(@"URL: %@", [accountRequest url]);
-    NSLog(@"cached: %d", [accountRequest didUseCachedResponse]);
-    
-    NSArray *someContainers = [accountRequest containers];
-    if (containers == nil) {
-        containers = [[NSMutableArray alloc] initWithArray:someContainers];
-    } else {
-        [containers addObjectsFromArray:someContainers];
-    }
-    if ([someContainers count] < 10000) {
-        if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
-            // Save new children
-            NSLog(@"using newChildren");
-            NSMutableArray *newChildren = [NSMutableArray array];
-            for (ASIPithosContainer *container in containers) {
-                PithosContainer *node = [[[PithosContainer alloc] initWithContainerName:container.name] autorelease];
-                if (children) {
-                    NSUInteger oldIndex = [children indexOfObject:node];
-                    if (oldIndex != NSNotFound)
-                        // Use the same pointer value, if possible
-                        node = [children objectAtIndex:oldIndex];
-                }
-                [newChildren addObject:node];
-            }
-            [children release];
-            children = [newChildren retain];
-        }
-        // Else cache was used and all results were fetched during this request, so previousChildren can be reused
-        [containers release];
-        containers = nil;
-        // XXX sort children based on preferences
-        refreshing = NO;
-        // Notify observers that children are updated
-        [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosNodeChildrenUpdated" object:self];
-    } else {
-        // Do an additional request to fetch more objects
-        ASIPithosAccountRequest *newAccountRequest = [ASIPithosAccountRequest listContainersRequestWithLimit:0 
-                                                                                                      marker:[[someContainers lastObject] name] 
-                                                                                                      shared:NO 
-                                                                                                       until:nil];
-        newAccountRequest.delegate = self;
-        newAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
-        [newAccountRequest startAsynchronous];
-    }
-}
-
-- (void)requestFailed:(ASIPithosAccountRequest *)accountRequest {
-    // XXX do something on error, cleanup
-    NSLog(@"error:%@", [accountRequest error]);
-    [containers release];
-    containers = nil;
-    childrenDirty = YES;
-    refreshing = NO;
+- (BOOL)urlIsValid:(NSString *)urlString {
+    if (urlString) {
+        NSURL *url = [NSURL URLWithString:urlString];
+        if (url && url.scheme && url.host)
+            return YES;
+    }
+    return NO;
 }
 
 #pragma mark -
 #pragma mark Properties
 
-- (NSString *)url {
-    if (url == nil) 
-        url = [[ASIPithosRequest storageURL] copy];
-    return url;
-}
-
-- (NSArray *)children {
-    if (childrenDirty) {
-        @synchronized (self) {
-            if (!refreshing) {
-                refreshing = YES;
-                childrenDirty = NO;
-                ASIPithosAccountRequest *accountRequest = [ASIPithosAccountRequest listContainersRequestWithLimit:0 
-                                                                                                           marker:nil 
-                                                                                                           shared:NO 
-                                                                                                            until:nil];
-                accountRequest.delegate = self;
-                accountRequest.downloadCache = [ASIDownloadCache sharedCache];
-                [accountRequest startAsynchronous];
+- (NSString *)name {
+    if (![name length]) {
+        [name release];
+        NSDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
+        NSString *namePrefix = [NSString stringWithString:@"okeanos"];
+        NSUInteger nameSuffix = 1;
+        name = [NSString stringWithString:@"okeanos"];
+        NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        while ([pithosAccountsDictionary objectForKey:name] || 
+               [fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:name]]) {
+            name = [NSString stringWithFormat:@"%@%d", namePrefix, ++nameSuffix];
+        }
+        [name retain];
+    }
+    return name;
+}
+
+- (void)setName:(NSString *)aName {
+    NSMutableDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
+    if (![self.name isEqualToString:aName] && [aName length] && ![pithosAccountsDictionary objectForKey:aName]) {
+        [pithosAccountsDictionary setObject:self forKey:aName];
+        [pithosAccountsDictionary removeObjectForKey:name];
+        [name release];
+        name = [aName retain];
+    }
+}
+
+- (BOOL)syncActive {
+    if (active)
+        return syncActive;
+    else
+        return NO;
+}
+
+- (void)setSyncActive:(BOOL)aSyncActive {
+    syncActive = aSyncActive;
+    if (syncDaemon && !self.syncActive)
+        [syncDaemon resetDaemon];
+}
+
+- (NSString *)syncDirectoryPath {
+    if (![syncDirectoryPath length]) {
+        [syncDirectoryPath release];
+        syncDirectoryPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
+                              stringByAppendingPathComponent:self.name] retain];
+    }
+    return syncDirectoryPath;
+}
+
+- (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
+    if (![self.syncDirectoryPath isEqualToString:aSyncDirectoryPath] && [aSyncDirectoryPath length]) {
+        BOOL isDirectory;
+        if (![[NSFileManager defaultManager] fileExistsAtPath:aSyncDirectoryPath isDirectory:&isDirectory] || isDirectory) {
+            [syncDirectoryPath release];
+            syncDirectoryPath = [aSyncDirectoryPath retain];
+        } else {
+            return;
+        }
+
+        @synchronized(self) {
+            resetSyncDaemonLocalState = YES;
+            [syncLastCompleted release];
+            syncLastCompleted = nil;
+        }
+    }
+}
+
+- (NSMutableDictionary *)syncContainersDictionary {
+    if (!syncContainersDictionary) {
+        syncContainersDictionary = [[NSMutableDictionary dictionaryWithObject:[NSMutableArray array] 
+                                                                       forKey:@"pithos"] retain];
+    }        
+    return syncContainersDictionary;
+}
+
+- (void)setSyncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
+    if (![self.syncContainersDictionary isEqualTo:syncContainersDictionary]) {
+        [syncContainersDictionary release];
+        syncContainersDictionary = [aSyncContainersDictionary retain];
+        // XXX check for proper dictionary here
+        
+        @synchronized(self) {
+            resetSyncDaemonLocalState = YES;
+            [syncLastCompleted release];
+            syncLastCompleted = nil;
+        }
+    }
+}
+
+- (NSDate *)syncLastCompleted {
+    if (self.syncDaemon.lastCompletedSync && ![self.syncDaemon.lastCompletedSync isEqualToDate:syncLastCompleted]) {
+        [syncLastCompleted release];
+        syncLastCompleted = [self.syncDaemon.lastCompletedSync copy];
+    }
+    return syncLastCompleted;
+}
+
+- (PithosSyncDaemon *)syncDaemon {
+    @synchronized(self) {
+        if (self.syncActive && !syncDaemon)
+            syncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:self.syncDirectoryPath 
+                                                           pithosAccount:self
+                                                    containersDictionary:self.syncContainersDictionary
+                                                         resetLocalState:resetSyncDaemonLocalState];
+        resetSyncDaemonLocalState = NO;
+    }
+    return syncDaemon;
+}
+
+- (NSString *)serverURL {
+    if (![self urlIsValid:serverURL]) {
+        [serverURL release];
+        serverURL = [[NSString stringWithString:@"https://pithos.okeanos.grnet.gr"] retain];
+    }
+    return serverURL;
+}
+
+- (void)setServerURL:(NSString *)aServerURL {
+    if (![self.serverURL isEqualToString:aServerURL] && [self urlIsValid:aServerURL]) {
+        [serverURL release];
+        serverURL = [aServerURL retain];
+        [storageURLPrefix release];
+        storageURLPrefix = nil;
+        [authURL release];
+        authURL = nil;
+        [publicURLPrefix release];
+        publicURLPrefix = nil;
+        [loginURLPrefix release];
+        loginURLPrefix = nil;
+
+        @synchronized(self) {
+            updatePithos = YES;
+            resetSyncDaemonLocalState = YES;
+            [syncLastCompleted release];
+            syncLastCompleted = nil;
+        }
+    }
+}
+
+- (void)setAuthUser:(NSString *)anAuthUser {
+    if ([anAuthUser length] && ![anAuthUser isEqualToString:authUser]) {
+        [authUser release];
+        authUser = [anAuthUser retain];
+        
+        @synchronized(self) {
+            updatePithos = YES;
+            resetSyncDaemonLocalState = YES;
+            [syncLastCompleted release];
+            syncLastCompleted = nil;
+
+        }
+    }
+}
+
+- (void)setAuthToken:(NSString *)anAuthToken {
+    if ([anAuthToken length] && ![anAuthToken isEqualToString:authToken]) {
+        [authToken release];
+        authToken = [anAuthToken retain];
+        
+        @synchronized(self) {
+            updatePithos = YES;
+        }
+    }
+}
+
+- (NSString *)storageURLPrefix {
+    if (![self urlIsValid:storageURLPrefix]) {
+        [storageURLPrefix release];
+        if (versionResource)
+            storageURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
+        else
+            storageURLPrefix = [self.serverURL copy];
+    }
+    return storageURLPrefix;
+}
+
+- (void)setStorageURLPrefix:(NSString *)aStorageURLPrefix {
+    if (![self.storageURLPrefix isEqualToString:aStorageURLPrefix] && [self urlIsValid:aStorageURLPrefix]) {
+        [storageURLPrefix release];
+        storageURLPrefix = [aStorageURLPrefix retain];
+    }
+}
+
+- (NSString *)authURL {
+    if (![self urlIsValid:authURL]) {
+        [authURL release];
+        if (versionResource)
+            authURL = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
+        else
+            authURL = [self.serverURL copy];
+    }
+    return authURL;
+}
+
+- (void)setAuthURL:(NSString *)anAuthURL {
+    if (![self.authURL isEqualToString:anAuthURL] && [self urlIsValid:anAuthURL]) {
+        [authURL release];
+        authURL = [anAuthURL retain];
+    }
+}
+
+- (NSString *)publicURLPrefix {
+    if (![self urlIsValid:publicURLPrefix]) {
+        [publicURLPrefix release];
+        if (publicResource)
+            publicURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", publicResource] retain];
+        else
+            publicURLPrefix = [self.serverURL copy];
+    }
+    return publicURLPrefix;
+}
+
+- (void)setPublicURLPrefix:(NSString *)aPublicURLPrefix {
+    if (![self.publicURLPrefix isEqualToString:aPublicURLPrefix] && [self urlIsValid:aPublicURLPrefix]) {
+        [publicURLPrefix release];
+        publicURLPrefix = [aPublicURLPrefix retain];
+    }
+}
+
+- (NSString *)loginURLPrefix {
+    if (![self urlIsValid:loginURLPrefix]) {
+        [loginURLPrefix release];
+        if (loginResource)
+            loginURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", loginResource] retain];
+        else
+            loginURLPrefix = [self.serverURL copy];
+    }
+    return loginURLPrefix;
+}
+
+- (void)setLoginURLPrefix:(NSString *)aLoginURLPrefix {
+    if (![self.loginURLPrefix isEqualToString:aLoginURLPrefix] && [self urlIsValid:aLoginURLPrefix]) {
+        [loginURLPrefix release];
+        loginURLPrefix = [aLoginURLPrefix retain];
+    }
+}
+
+- (ASIPithos *)pithos {
+    @synchronized(self) {
+        if (!pithos || updatePithos) {
+            [pithos release];
+            pithos = [[ASIPithos pithos] retain];
+            pithos.authUser = authUser;
+            pithos.authToken = authToken;
+            pithos.storageURLPrefix = self.storageURLPrefix;
+            pithos.authURL = self.authURL;
+            pithos.publicURLPrefix = self.publicURLPrefix;
+
+            if (accountNode && ![accountNode.pithos isEqualTo:pithos]) {
+                accountNode.pithos = pithos;
+                if (active)
+                    [accountNode refreshInfo];
             }
+            
+            updatePithos = NO;
         }
     }
-    return children;
+    return pithos;
 }
 
-- (NSString *)displayName {
-    if (displayName == nil) {
-        displayName = [[NSString alloc] initWithString:@"account"];
+- (PithosAccountNode *)accountNode {
+    if (!accountNode) {
+        accountNode = [[PithosAccountNode alloc] initWithPithos:self.pithos];
     }
-    return displayName;
+    return accountNode;
+}
+
+#pragma mark -
+#pragma mark Actions
+
+- (void)authenticateWithServerURL:(NSString *)aServerURL authUser:(NSString *)anAuthUser authToken:(NSString *)anAuthToken {
+    self.serverURL = aServerURL;
+    self.authUser = anAuthUser;
+    self.authToken = anAuthToken;
+    NSLog(@"Account: %@\nauthentication", self);
+    if (![authUser length] || ![authToken length]) {
+        self.active = NO;
+        self.syncActive = NO;
+        // XXX Show preferences with self as the selected account?
+    } else  {
+        self.active = YES;
+        if (syncDaemon) {
+            self.syncDaemon.pithos = self.pithos;
+            if (self.syncActive)
+                [self.syncDaemon startDaemon];
+        }
+    }
+}
+
+- (void)loginWithServerURL:(NSString *)aServerURL {
+    self.serverURL = aServerURL;
+    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
+    NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d/%@", 
+                          self.loginURLPrefix, [processInfo processName], [processInfo processIdentifier], self.name];
+    NSLog(@"Account: %@\nloginURL: %@", self, loginURL);
+    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
 }
 
-- (NSImage *)icon {
-    return nil;
+- (void)updateSyncWithSyncActive:(BOOL)aSyncActive syncDirectoryPath:(NSString *)aSyncDirectoryPath {
+    self.syncDirectoryPath = aSyncDirectoryPath;
+    self.syncActive = aSyncActive;
+    if (syncDaemon) {
+        self.syncDaemon.directoryPath = self.syncDirectoryPath;
+        if (self.syncActive)
+            [self.syncDaemon startDaemon];
+    }    
 }
+
+#pragma mark -
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    if ((self = [super init])) {
+        self.uniqueName = [decoder decodeObjectForKey:@"uniqueName"];
+        self.active = [decoder decodeBoolForKey:@"active"];
+        name = [[decoder decodeObjectForKey:@"name"] retain];
+
+        self.syncActive = [decoder decodeBoolForKey:@"syncActive"];
+        self.syncDirectoryPath = [decoder decodeObjectForKey:@"syncDirectoryPath"];
+        self.syncContainersDictionary = [decoder decodeObjectForKey:@"syncContainersDictionary"];
+        self.syncLastCompleted = [decoder decodeObjectForKey:@"syncLastCompleted"];
+        
+        self.serverURL = [decoder decodeObjectForKey:@"serverURL"];
+        self.versionResource = [decoder decodeObjectForKey:@"versionResource"];
+        self.loginResource = [decoder decodeObjectForKey:@"loginResource"];
+        self.publicResource = [decoder decodeObjectForKey:@"publicResource"];
+        
+        self.authUser = [decoder decodeObjectForKey:@"authUser"];
+        self.authToken = [decoder decodeObjectForKey:@"authToken"];
+        self.storageURLPrefix = [decoder decodeObjectForKey:@"storageURLPrefix"];
+        self.authURL = [decoder decodeObjectForKey:@"authURL"];
+        self.publicURLPrefix = [decoder decodeObjectForKey:@"publicURLPrefix"];
+        self.loginURLPrefix = [decoder decodeObjectForKey:@"loginURLPrefix"];
+        
+        if (![authUser length] || ![authToken length] || ![self.storageURLPrefix length])
+            self.active = NO;
+        
+        resetSyncDaemonLocalState = NO;
+    }
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    [encoder encodeObject:uniqueName forKey:@"uniqueName"];
+    [encoder encodeBool:active forKey:@"active"];
+    [encoder encodeObject:name forKey:@"name"];
+    
+    [encoder encodeBool:syncActive forKey:@"syncActive"];
+    [encoder encodeObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
+    [encoder encodeObject:syncContainersDictionary forKey:@"syncContainersDictionary"];
+    [encoder encodeObject:self.syncLastCompleted forKey:@"syncLastCompleted"];
+
+    [encoder encodeObject:serverURL forKey:@"serverURL"];
+    [encoder encodeObject:versionResource forKey:@"versionResource"];
+    [encoder encodeObject:publicResource forKey:@"publicResource"];
+    [encoder encodeObject:loginResource forKey:@"loginResource"];
     
+    [encoder encodeObject:authUser forKey:@"authUser"];
+    [encoder encodeObject:authToken forKey:@"authToken"];
+    [encoder encodeObject:storageURLPrefix forKey:@"storageURLPrefix"];
+    [encoder encodeObject:authURL forKey:@"authURL"];
+    [encoder encodeObject:publicURLPrefix forKey:@"publicURLPrefix"];
+    [encoder encodeObject:loginURLPrefix forKey:@"loginURLPrefix"];
+}
+
 @end