Some code refactoring in the sync daemon.
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Sun, 23 Oct 2011 19:40:22 +0000 (22:40 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Sun, 23 Oct 2011 19:40:22 +0000 (22:40 +0300)
pithos-macos/PithosLocalObjectState.h
pithos-macos/PithosLocalObjectState.m
pithos-macos/PithosSyncDaemon.m

index 50a8688..f4bec60 100644 (file)
     NSString *md5;
     NSString *hashMapHash;
     NSString *tmpDownloadFile;
-    BOOL isDirectory; 
+    BOOL isDirectory;
 }
 
-+ (id)nullObjectState;
+- (id)initWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize;
+- (id)initWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory;
++ (id)localObjectState;
++ (id)localObjectStateWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize;
++ (id)localObjectStateWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory;
+- (BOOL)isEqualToState:(PithosLocalObjectState *)aState;
 
 @property (nonatomic, retain) NSString *md5;
 @property (nonatomic, retain) NSString *hashMapHash;
 @property (nonatomic, retain) NSString *tmpDownloadFile;
 @property (nonatomic, assign) BOOL isDirectory;
 
+@property (nonatomic, readonly) BOOL exists;
+@property (nonatomic, retain) NSString *hash;
+
 @end
index 091cd19..71f54f9 100644 (file)
 
 #import "PithosLocalObjectState.h"
 #import "PithosUtilities.h"
+#import "FileMD5Hash.h"
+#import "HashMapHash.h"
 
 @implementation PithosLocalObjectState
-@synthesize md5, hashMapHash, tmpDownloadFile, isDirectory;
+@synthesize md5, hashMapHash, tmpDownloadFile, isDirectory, exists, hash;
 
 #pragma mark -
 #pragma mark Object Lifecycle
 
-+ (id)nullObjectState {
-       PithosLocalObjectState *localObjectState = [[[self alloc] init] autorelease];
-    localObjectState.md5 = @" ";
-    localObjectState.hashMapHash = @" ";
-    localObjectState.tmpDownloadFile = nil;
-    localObjectState.isDirectory = NO;
-    return localObjectState;
+- (id)initWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize {
+    if ((self = [self init])) {
+        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && !isDirectory) {
+            self.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath, 
+                                                             FileHashDefaultChunkSizeForReadingData);
+            self.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath 
+                                                                                       withBlockHash:blockHash 
+                                                                                        andBlockSize:blockSize]];
+        }
+    }
+    return self;
+}
+
+- (id)initWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory {
+    if ((self = [self init])) {
+        if (anIsDirectory)
+            isDirectory = YES;
+        else if ([aHash length] == 32)
+            self.md5 = aHash;
+        else if ([aHash length] == 64)
+            self.hashMapHash = aHash;
+    }
+    return self;
+}
+
++ (id)localObjectState {
+    return [[[self alloc] init] autorelease];
+}
+
++ (id)localObjectStateWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize {
+    return [[[self alloc] initWithFile:filePath blockHash:blockHash blockSize:blockSize] autorelease];
+}
+
++ (id)localObjectStateWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory {
+    return [[[self alloc] initWithHash:aHash directory:anIsDirectory] autorelease];
 }
 
 - (void)dealloc {
     [super dealloc];    
 }
 
+- (NSString *)description {
+    return [NSString stringWithFormat:@"md5: %@, hashMapHash: %@, tmpDownloadFile: %@, isDirectory: %d", 
+            md5, hashMapHash, tmpDownloadFile, isDirectory];
+}
+
+- (BOOL)isEqualToState:(PithosLocalObjectState *)aState {
+    if (self.isDirectory)
+        // Object is a directory, check the other
+        return aState.isDirectory;
+    else if (aState.isDirectory)
+        // Object is not a directory, while the other is
+        return NO;
+    else if (!self.exists)
+        // Object doesn't exist, check the other
+        return (!aState.exists);
+    else if (!aState.exists)
+        // Object exists, while the other doesn't
+        return NO;
+    else
+        // Both objects exist, check that they have at least one hash in common
+        return ([self.md5 isEqualToString:aState.md5] || [self.hashMapHash isEqualToString:aState.hashMapHash]);
+}
+
 #pragma mark -
 #pragma mark Properties
 
 - (void)setIsDirectory:(BOOL)anIsDirectory {
     isDirectory = anIsDirectory;
     if (isDirectory) {
-        self.md5 = @" ";
-        self.hashMapHash = @" ";
+        self.md5 = nil;
+        self.hashMapHash = nil;
         self.tmpDownloadFile = nil;
     }
 }
     }
 }
 
+- (BOOL)exists {
+    return (isDirectory || md5 || hashMapHash);
+}
+
+- (NSString *)hash {
+    if (hashMapHash)
+        return hashMapHash;
+    else
+        return md5;
+}
+
+- (void)setHash:(NSString *)aHash {
+    self.md5 = nil;
+    self.hashMapHash = nil;
+    if ([aHash length] == 32) {
+        self.md5 = aHash;
+    } else if ([aHash length] == 64) {
+        self.hashMapHash = aHash;
+    }
+}
+
 #pragma mark -
 #pragma mark NSCoding
 
index 8631443..bf2fde4 100644 (file)
@@ -44,8 +44,6 @@
 #import "ASIPithosContainerRequest.h"
 #import "ASIPithosObjectRequest.h"
 #import "ASIPithosObject.h"
-#import "FileMD5Hash.h"
-#import "HashMapHash.h"
 
 #define DATA_MODEL_FILE @"localstate.archive"
 #define ARCHIVE_KEY @"Data"
 @interface PithosSyncDaemon (Private)
 - (NSString *)pithosStateFilePath;
 - (void)saveLocalState;
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState;
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState 
-             remoteObjectHash:(NSString *)remoteObjectHash 
-      remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory;
 - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
--(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
-                                object:(ASIPithosObject *)object 
-                         localFilePath:(NSString *)filePath;
+- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
+                                   object:(ASIPithosObject *)object 
+                            localFilePath:(NSString *)filePath;
 - (void)requestFailed:(ASIPithosRequest *)request;
 
 - (void)increaseSyncOperationCount;
     [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
 }
 
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState {
-    if (currentState.isDirectory)
-        // Currently a directory, check previous state
-        return (!storedState.isDirectory);
-    if (storedState.isDirectory)
-        // Previously a directory, currently a file or doesn't exist, state has changed
-        return YES;
-    if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "] && 
-        (![storedState.md5 isEqualToString:@" "] || ![storedState.hashMapHash isEqualToString:@" "]))
-        // Currently doesn't exist, previously a file, state has changed
-        return YES;
-    if (![storedState.md5 isEqualToString:currentState.md5] && ![storedState.hashMapHash isEqualToString:currentState.hashMapHash])
-        // Neither hash remained the same, different files, state has changed
-        return YES;
-    else
-        // At least one hash remained the same (the other is either the same or emmpty), state hasn't changed
-        return NO;
-}
-
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState 
-             remoteObjectHash:(NSString *)remoteObjectHash 
-      remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory {
-    if (remoteObjectIsDirectory)
-        // Remotely a directory, check previous state
-        return (!storedState.isDirectory);
-    if (storedState.isDirectory)
-        // Previously a directory, remotely a file or doesn't exist, state has changed
-        return YES;
-    if ([remoteObjectHash length] == 32)
-        // Remotely a file, check previous state
-        return ![remoteObjectHash isEqualToString:storedState.md5];
-    else if ([remoteObjectHash length] == 64)
-        // Remotely a file, check previous state
-        return ![remoteObjectHash isEqualToString:storedState.hashMapHash];
-    else if ([remoteObjectHash isEqualToString:@" "]) {
-        // Remotely doesn't exist
-        if ([storedState.md5 isEqualToString:@" "] && [storedState.hashMapHash isEqualToString:@" "])
-            // Previously didn't exist, state hasn't changed
-            return NO;
-        else
-            // Previously did exist, state has changed
-            return YES;
-    }
-    // Only if the server doesn't respond properly this will be reached, leave as is for now
-    return NO;
-}
-
 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
                      localFilePath:(NSString *)filePath {
     NSFileManager *fileManager = [NSFileManager defaultManager];
     BOOL isDirectory;
     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
     PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
-    if ([object.hash isEqualToString:@" "]) {
+    if (!object.hash) {
         // Delete local object
         // XXX move to local trash instead
         NSLog(@"Sync::delete local object: %@", filePath);
                                                           error:error];
             } else {
                 fileCreated = YES;
-                if ([object.hash length] == 32) {
-                    storedState.md5 = object.hash;
-                    storedState.hashMapHash = @" ";
-                } else if([object.hash length] == 64) {
-                    storedState.md5 = @" ";
-                    storedState.hashMapHash = object.hash;
-                } else {
-                    storedState.md5 = @" ";
-                    storedState.hashMapHash = @" ";
-                }
+                storedState.hash = object.hash;
                 storedState.tmpDownloadFile = nil;
                 [self saveLocalState];
             }
                                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                   nil];
         [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
-    } else if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "]) {
+    } else if (!currentState.exists) {
         // Delete remote object
         NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
                                                                         objectName:object.name];
         }
         for (NSString *objectName in subPaths) {
             if (![storedLocalObjectStates objectForKey:objectName]) {
-                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
             }
         }
         [self saveLocalState];
 
         for (NSString *objectName in remoteObjects) {
             if (![storedLocalObjectStates objectForKey:objectName])
-                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
         }
 
         for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
             NSLog(@"Sync::object name: %@", objectName);
             
             PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
-            PithosLocalObjectState *currentLocalObjectState = [PithosLocalObjectState nullObjectState];
-            BOOL isDirectory;
-            if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
-                if (isDirectory) {
-                    currentLocalObjectState.isDirectory = YES;
-                } else {
-                    currentLocalObjectState.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath, 
-                                                                                        FileHashDefaultChunkSizeForReadingData);
-                    currentLocalObjectState.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath 
-                                                                                                                  withBlockHash:blockHash 
-                                                                                                                   andBlockSize:blockSize]];
-                }
-            }
+            PithosLocalObjectState *currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
+                                                                                                     blockHash:blockHash 
+                                                                                                     blockSize:blockSize];
             if (currentLocalObjectState.isDirectory)
                 object.contentType = @"application/directory";
             
-            NSString *remoteObjectHash = @" ";
-            BOOL remoteObjectIsDirectory = NO;
+            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
             ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
             if (remoteObject) {
-                remoteObjectHash = remoteObject.hash;
                 if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
-                    remoteObjectIsDirectory = YES;
+                    remoteObjectState.isDirectory = YES;
                     object.contentType = @"application/directory";
+                } else {
+                    remoteObjectState.hash = remoteObject.hash;
                 }
             }
-            NSLog(@"Sync::remote object is directory: %d", remoteObjectIsDirectory);
-            
-            BOOL localStateHasChanged = [self localStateHasChanged:storedLocalObjectState currentState:currentLocalObjectState];
-            BOOL serverStateHasChanged = [self serverStateHasChanged:storedLocalObjectState 
-                                                    remoteObjectHash:remoteObjectHash 
-                                             remoteObjectIsDirectory:remoteObjectIsDirectory];
+
+            BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
+            BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
             NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
             // XXX shouldn't we first do all the deletes? in order not to face a dir that becomes a file and vice versa
             if (!localStateHasChanged) {
                 if (serverStateHasChanged) {
                     // Server state has changed
                     // Update local state to match that of the server 
-                    object.bytes = [remoteObject bytes];
-                    object.version = [remoteObject version];
-                    object.contentType = [remoteObject contentType];
-                    object.hash = remoteObjectHash;
+                    object.bytes = remoteObject.bytes;
+                    object.version = remoteObject.version;
+                    object.contentType = remoteObject.contentType;
+                    object.hash = remoteObject.hash;
                     [self updateLocalStateWithObject:object localFilePath:filePath];
+                } else if (!remoteObject && !currentLocalObjectState.exists) {
+                    // Server state hasn't changed
+                    // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
+                    [storedLocalObjectStates removeObjectForKey:objectName];
+                    [self saveLocalState];
                 }
             } else {
                 // Local state has changed
                                               localFilePath:filePath];
                 } else {
                     // Server state has also changed
-                    if (remoteObjectIsDirectory && currentLocalObjectState.isDirectory) {
+                    if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
                         // Both did the same change (directory)
                         storedLocalObjectState.isDirectory = YES;
                         [self saveLocalState];
-                    } else if ([remoteObjectHash isEqualToString:currentLocalObjectState.md5] || 
-                               [remoteObjectHash isEqualToString:currentLocalObjectState.hashMapHash]) {
+                    } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
                         // Both did the same change (object edit or delete)
-                        if ([remoteObjectHash length] == 32) 
-                            storedLocalObjectState.md5 = remoteObjectHash;
-                        else if ([remoteObjectHash length] == 64)
-                            storedLocalObjectState.hashMapHash = remoteObjectHash;
-                        else if ([remoteObjectHash isEqualToString:@" "])
+                        if (!remoteObjectState.exists)
                             [storedLocalObjectStates removeObjectForKey:object.name];
+                        else
+                            storedLocalObjectState.hash = remoteObjectState.hash;
                         [self saveLocalState];
                     } else {
                         // Conflict, we ask the user which change to keep
                         NSString *firstButtonText;
                         NSString *secondButtonText;
                         
-                        if ([remoteObjectHash isEqualToString:@" "]) {
+                        if (!remoteObjectState.exists) {
                             // Remote object has been deleted
                             informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
                             firstButtonText = @"Delete local file";
                             secondButtonText = @"Upload file to server";
-                        } else if ([currentLocalObjectState.md5 isEqualToString:@" "] && [currentLocalObjectState.hashMapHash isEqualToString:@" "]) {
+                        } else if (!currentLocalObjectState.exists) {
                             informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
                             firstButtonText = @"Download file from server";
                             secondButtonText = @"Delete file on server";
                         [alert addButtonWithTitle:@"Do nothing"];
                         NSInteger choice = [alert runModal];
                         if (choice == NSAlertFirstButtonReturn) {
-                            object.bytes = [remoteObject bytes];
-                            object.version = [remoteObject version];
-                            object.contentType = [remoteObject contentType];
-                            object.hash = remoteObjectHash;
+                            object.bytes = remoteObject.bytes;
+                            object.version = remoteObject.version;
+                            object.contentType = remoteObject.contentType;
+                            object.hash = remoteObject.hash;
                             [self updateLocalStateWithObject:object localFilePath:filePath];
                         } if (choice == NSAlertSecondButtonReturn) {
                             [self updateServerStateWithCurrentState:currentLocalObjectState 
                                totalBytes:activity.totalBytes 
                              currentBytes:activity.totalBytes];
 
-            if ([object.hash length] == 32) {
-                storedState.md5 = object.hash;
-                storedState.hashMapHash = @" ";
-            } else if([object.hash length] == 64) {
-                storedState.md5 = @" ";
-                storedState.hashMapHash = object.hash;
-            } else {
-                storedState.md5 = @" ";
-                storedState.hashMapHash = @" ";
-            }
-            
+            storedState.hash = object.hash;
             storedState.tmpDownloadFile = nil;
             [self saveLocalState];
             
     NSUInteger currentBytes = activity.currentBytes;
     if (objectRequest.responseStatusCode == 201) {
         NSLog(@"Sync::object created: %@", objectRequest.url);
-        NSString *eTag = [objectRequest eTag];
-        if ([eTag length] == 32)
-            storedState.md5 = eTag;
-        else if([eTag length] == 64)
-            storedState.hashMapHash = eTag;
+        storedState.hash = [objectRequest eTag];
         [self saveLocalState];
         [activityFacility endActivity:activity 
                           withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]