#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
#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"]