From e637fedf70b3519eed31c3f9f4a8e8f4cf5195cd Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Sun, 23 Oct 2011 22:40:22 +0300 Subject: [PATCH] Some code refactoring in the sync daemon. --- pithos-macos/PithosLocalObjectState.h | 12 ++- pithos-macos/PithosLocalObjectState.m | 94 +++++++++++++++++-- pithos-macos/PithosSyncDaemon.m | 167 ++++++++------------------------- 3 files changed, 134 insertions(+), 139 deletions(-) diff --git a/pithos-macos/PithosLocalObjectState.h b/pithos-macos/PithosLocalObjectState.h index 50a8688..f4bec60 100644 --- a/pithos-macos/PithosLocalObjectState.h +++ b/pithos-macos/PithosLocalObjectState.h @@ -41,14 +41,22 @@ 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 diff --git a/pithos-macos/PithosLocalObjectState.m b/pithos-macos/PithosLocalObjectState.m index 091cd19..71f54f9 100644 --- a/pithos-macos/PithosLocalObjectState.m +++ b/pithos-macos/PithosLocalObjectState.m @@ -37,20 +37,50 @@ #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 { @@ -60,14 +90,37 @@ [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; } } @@ -89,6 +142,27 @@ } } +- (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 diff --git a/pithos-macos/PithosSyncDaemon.m b/pithos-macos/PithosSyncDaemon.m index 8631443..bf2fde4 100644 --- a/pithos-macos/PithosSyncDaemon.m +++ b/pithos-macos/PithosSyncDaemon.m @@ -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" @@ -53,14 +51,10 @@ @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; @@ -276,53 +270,6 @@ [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]; @@ -330,7 +277,7 @@ 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); @@ -404,16 +351,7 @@ 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]; } @@ -518,7 +456,7 @@ [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]; @@ -665,14 +603,14 @@ } 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:)]) { @@ -684,38 +622,25 @@ 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) { @@ -723,11 +648,16 @@ 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 @@ -738,19 +668,16 @@ 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 @@ -758,12 +685,12 @@ 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"; @@ -780,10 +707,10 @@ [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 @@ -971,17 +898,7 @@ 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]; @@ -1140,11 +1057,7 @@ 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"] -- 1.7.10.4