44 |
44 |
#import "ASIPithosContainerRequest.h"
|
45 |
45 |
#import "ASIPithosObjectRequest.h"
|
46 |
46 |
#import "ASIPithosObject.h"
|
47 |
|
#import "FileMD5Hash.h"
|
48 |
|
#import "HashMapHash.h"
|
49 |
47 |
|
50 |
48 |
#define DATA_MODEL_FILE @"localstate.archive"
|
51 |
49 |
#define ARCHIVE_KEY @"Data"
|
... | ... | |
53 |
51 |
@interface PithosSyncDaemon (Private)
|
54 |
52 |
- (NSString *)pithosStateFilePath;
|
55 |
53 |
- (void)saveLocalState;
|
56 |
|
- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState;
|
57 |
|
- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState
|
58 |
|
remoteObjectHash:(NSString *)remoteObjectHash
|
59 |
|
remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory;
|
60 |
54 |
- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
|
61 |
|
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
|
62 |
|
object:(ASIPithosObject *)object
|
63 |
|
localFilePath:(NSString *)filePath;
|
|
55 |
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
|
|
56 |
object:(ASIPithosObject *)object
|
|
57 |
localFilePath:(NSString *)filePath;
|
64 |
58 |
- (void)requestFailed:(ASIPithosRequest *)request;
|
65 |
59 |
|
66 |
60 |
- (void)increaseSyncOperationCount;
|
... | ... | |
276 |
270 |
[queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
|
277 |
271 |
}
|
278 |
272 |
|
279 |
|
- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState {
|
280 |
|
if (currentState.isDirectory)
|
281 |
|
// Currently a directory, check previous state
|
282 |
|
return (!storedState.isDirectory);
|
283 |
|
if (storedState.isDirectory)
|
284 |
|
// Previously a directory, currently a file or doesn't exist, state has changed
|
285 |
|
return YES;
|
286 |
|
if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "] &&
|
287 |
|
(![storedState.md5 isEqualToString:@" "] || ![storedState.hashMapHash isEqualToString:@" "]))
|
288 |
|
// Currently doesn't exist, previously a file, state has changed
|
289 |
|
return YES;
|
290 |
|
if (![storedState.md5 isEqualToString:currentState.md5] && ![storedState.hashMapHash isEqualToString:currentState.hashMapHash])
|
291 |
|
// Neither hash remained the same, different files, state has changed
|
292 |
|
return YES;
|
293 |
|
else
|
294 |
|
// At least one hash remained the same (the other is either the same or emmpty), state hasn't changed
|
295 |
|
return NO;
|
296 |
|
}
|
297 |
|
|
298 |
|
- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState
|
299 |
|
remoteObjectHash:(NSString *)remoteObjectHash
|
300 |
|
remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory {
|
301 |
|
if (remoteObjectIsDirectory)
|
302 |
|
// Remotely a directory, check previous state
|
303 |
|
return (!storedState.isDirectory);
|
304 |
|
if (storedState.isDirectory)
|
305 |
|
// Previously a directory, remotely a file or doesn't exist, state has changed
|
306 |
|
return YES;
|
307 |
|
if ([remoteObjectHash length] == 32)
|
308 |
|
// Remotely a file, check previous state
|
309 |
|
return ![remoteObjectHash isEqualToString:storedState.md5];
|
310 |
|
else if ([remoteObjectHash length] == 64)
|
311 |
|
// Remotely a file, check previous state
|
312 |
|
return ![remoteObjectHash isEqualToString:storedState.hashMapHash];
|
313 |
|
else if ([remoteObjectHash isEqualToString:@" "]) {
|
314 |
|
// Remotely doesn't exist
|
315 |
|
if ([storedState.md5 isEqualToString:@" "] && [storedState.hashMapHash isEqualToString:@" "])
|
316 |
|
// Previously didn't exist, state hasn't changed
|
317 |
|
return NO;
|
318 |
|
else
|
319 |
|
// Previously did exist, state has changed
|
320 |
|
return YES;
|
321 |
|
}
|
322 |
|
// Only if the server doesn't respond properly this will be reached, leave as is for now
|
323 |
|
return NO;
|
324 |
|
}
|
325 |
|
|
326 |
273 |
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
|
327 |
274 |
localFilePath:(NSString *)filePath {
|
328 |
275 |
NSFileManager *fileManager = [NSFileManager defaultManager];
|
... | ... | |
330 |
277 |
BOOL isDirectory;
|
331 |
278 |
BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
|
332 |
279 |
PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
|
333 |
|
if ([object.hash isEqualToString:@" "]) {
|
|
280 |
if (!object.hash) {
|
334 |
281 |
// Delete local object
|
335 |
282 |
// XXX move to local trash instead
|
336 |
283 |
NSLog(@"Sync::delete local object: %@", filePath);
|
... | ... | |
404 |
351 |
error:error];
|
405 |
352 |
} else {
|
406 |
353 |
fileCreated = YES;
|
407 |
|
if ([object.hash length] == 32) {
|
408 |
|
storedState.md5 = object.hash;
|
409 |
|
storedState.hashMapHash = @" ";
|
410 |
|
} else if([object.hash length] == 64) {
|
411 |
|
storedState.md5 = @" ";
|
412 |
|
storedState.hashMapHash = object.hash;
|
413 |
|
} else {
|
414 |
|
storedState.md5 = @" ";
|
415 |
|
storedState.hashMapHash = @" ";
|
416 |
|
}
|
|
354 |
storedState.hash = object.hash;
|
417 |
355 |
storedState.tmpDownloadFile = nil;
|
418 |
356 |
[self saveLocalState];
|
419 |
357 |
}
|
... | ... | |
518 |
456 |
[NSNumber numberWithUnsignedInteger:10], @"retries",
|
519 |
457 |
nil];
|
520 |
458 |
[queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
|
521 |
|
} else if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "]) {
|
|
459 |
} else if (!currentState.exists) {
|
522 |
460 |
// Delete remote object
|
523 |
461 |
NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash"
|
524 |
462 |
objectName:object.name];
|
... | ... | |
665 |
603 |
}
|
666 |
604 |
for (NSString *objectName in subPaths) {
|
667 |
605 |
if (![storedLocalObjectStates objectForKey:objectName]) {
|
668 |
|
[storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
|
|
606 |
[storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
|
669 |
607 |
}
|
670 |
608 |
}
|
671 |
609 |
[self saveLocalState];
|
672 |
610 |
|
673 |
611 |
for (NSString *objectName in remoteObjects) {
|
674 |
612 |
if (![storedLocalObjectStates objectForKey:objectName])
|
675 |
|
[storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
|
|
613 |
[storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
|
676 |
614 |
}
|
677 |
615 |
|
678 |
616 |
for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
... | ... | |
684 |
622 |
NSLog(@"Sync::object name: %@", objectName);
|
685 |
623 |
|
686 |
624 |
PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
|
687 |
|
PithosLocalObjectState *currentLocalObjectState = [PithosLocalObjectState nullObjectState];
|
688 |
|
BOOL isDirectory;
|
689 |
|
if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
|
690 |
|
if (isDirectory) {
|
691 |
|
currentLocalObjectState.isDirectory = YES;
|
692 |
|
} else {
|
693 |
|
currentLocalObjectState.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath,
|
694 |
|
FileHashDefaultChunkSizeForReadingData);
|
695 |
|
currentLocalObjectState.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath
|
696 |
|
withBlockHash:blockHash
|
697 |
|
andBlockSize:blockSize]];
|
698 |
|
}
|
699 |
|
}
|
|
625 |
PithosLocalObjectState *currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
|
|
626 |
blockHash:blockHash
|
|
627 |
blockSize:blockSize];
|
700 |
628 |
if (currentLocalObjectState.isDirectory)
|
701 |
629 |
object.contentType = @"application/directory";
|
702 |
630 |
|
703 |
|
NSString *remoteObjectHash = @" ";
|
704 |
|
BOOL remoteObjectIsDirectory = NO;
|
|
631 |
PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
|
705 |
632 |
ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
|
706 |
633 |
if (remoteObject) {
|
707 |
|
remoteObjectHash = remoteObject.hash;
|
708 |
634 |
if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
|
709 |
|
remoteObjectIsDirectory = YES;
|
|
635 |
remoteObjectState.isDirectory = YES;
|
710 |
636 |
object.contentType = @"application/directory";
|
|
637 |
} else {
|
|
638 |
remoteObjectState.hash = remoteObject.hash;
|
711 |
639 |
}
|
712 |
640 |
}
|
713 |
|
NSLog(@"Sync::remote object is directory: %d", remoteObjectIsDirectory);
|
714 |
|
|
715 |
|
BOOL localStateHasChanged = [self localStateHasChanged:storedLocalObjectState currentState:currentLocalObjectState];
|
716 |
|
BOOL serverStateHasChanged = [self serverStateHasChanged:storedLocalObjectState
|
717 |
|
remoteObjectHash:remoteObjectHash
|
718 |
|
remoteObjectIsDirectory:remoteObjectIsDirectory];
|
|
641 |
|
|
642 |
BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
|
|
643 |
BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
|
719 |
644 |
NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
|
720 |
645 |
// XXX shouldn't we first do all the deletes? in order not to face a dir that becomes a file and vice versa
|
721 |
646 |
if (!localStateHasChanged) {
|
... | ... | |
723 |
648 |
if (serverStateHasChanged) {
|
724 |
649 |
// Server state has changed
|
725 |
650 |
// Update local state to match that of the server
|
726 |
|
object.bytes = [remoteObject bytes];
|
727 |
|
object.version = [remoteObject version];
|
728 |
|
object.contentType = [remoteObject contentType];
|
729 |
|
object.hash = remoteObjectHash;
|
|
651 |
object.bytes = remoteObject.bytes;
|
|
652 |
object.version = remoteObject.version;
|
|
653 |
object.contentType = remoteObject.contentType;
|
|
654 |
object.hash = remoteObject.hash;
|
730 |
655 |
[self updateLocalStateWithObject:object localFilePath:filePath];
|
|
656 |
} else if (!remoteObject && !currentLocalObjectState.exists) {
|
|
657 |
// Server state hasn't changed
|
|
658 |
// If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
|
|
659 |
[storedLocalObjectStates removeObjectForKey:objectName];
|
|
660 |
[self saveLocalState];
|
731 |
661 |
}
|
732 |
662 |
} else {
|
733 |
663 |
// Local state has changed
|
... | ... | |
738 |
668 |
localFilePath:filePath];
|
739 |
669 |
} else {
|
740 |
670 |
// Server state has also changed
|
741 |
|
if (remoteObjectIsDirectory && currentLocalObjectState.isDirectory) {
|
|
671 |
if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
|
742 |
672 |
// Both did the same change (directory)
|
743 |
673 |
storedLocalObjectState.isDirectory = YES;
|
744 |
674 |
[self saveLocalState];
|
745 |
|
} else if ([remoteObjectHash isEqualToString:currentLocalObjectState.md5] ||
|
746 |
|
[remoteObjectHash isEqualToString:currentLocalObjectState.hashMapHash]) {
|
|
675 |
} else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
|
747 |
676 |
// Both did the same change (object edit or delete)
|
748 |
|
if ([remoteObjectHash length] == 32)
|
749 |
|
storedLocalObjectState.md5 = remoteObjectHash;
|
750 |
|
else if ([remoteObjectHash length] == 64)
|
751 |
|
storedLocalObjectState.hashMapHash = remoteObjectHash;
|
752 |
|
else if ([remoteObjectHash isEqualToString:@" "])
|
|
677 |
if (!remoteObjectState.exists)
|
753 |
678 |
[storedLocalObjectStates removeObjectForKey:object.name];
|
|
679 |
else
|
|
680 |
storedLocalObjectState.hash = remoteObjectState.hash;
|
754 |
681 |
[self saveLocalState];
|
755 |
682 |
} else {
|
756 |
683 |
// Conflict, we ask the user which change to keep
|
... | ... | |
758 |
685 |
NSString *firstButtonText;
|
759 |
686 |
NSString *secondButtonText;
|
760 |
687 |
|
761 |
|
if ([remoteObjectHash isEqualToString:@" "]) {
|
|
688 |
if (!remoteObjectState.exists) {
|
762 |
689 |
// Remote object has been deleted
|
763 |
690 |
informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
|
764 |
691 |
firstButtonText = @"Delete local file";
|
765 |
692 |
secondButtonText = @"Upload file to server";
|
766 |
|
} else if ([currentLocalObjectState.md5 isEqualToString:@" "] && [currentLocalObjectState.hashMapHash isEqualToString:@" "]) {
|
|
693 |
} else if (!currentLocalObjectState.exists) {
|
767 |
694 |
informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
|
768 |
695 |
firstButtonText = @"Download file from server";
|
769 |
696 |
secondButtonText = @"Delete file on server";
|
... | ... | |
780 |
707 |
[alert addButtonWithTitle:@"Do nothing"];
|
781 |
708 |
NSInteger choice = [alert runModal];
|
782 |
709 |
if (choice == NSAlertFirstButtonReturn) {
|
783 |
|
object.bytes = [remoteObject bytes];
|
784 |
|
object.version = [remoteObject version];
|
785 |
|
object.contentType = [remoteObject contentType];
|
786 |
|
object.hash = remoteObjectHash;
|
|
710 |
object.bytes = remoteObject.bytes;
|
|
711 |
object.version = remoteObject.version;
|
|
712 |
object.contentType = remoteObject.contentType;
|
|
713 |
object.hash = remoteObject.hash;
|
787 |
714 |
[self updateLocalStateWithObject:object localFilePath:filePath];
|
788 |
715 |
} if (choice == NSAlertSecondButtonReturn) {
|
789 |
716 |
[self updateServerStateWithCurrentState:currentLocalObjectState
|
... | ... | |
971 |
898 |
totalBytes:activity.totalBytes
|
972 |
899 |
currentBytes:activity.totalBytes];
|
973 |
900 |
|
974 |
|
if ([object.hash length] == 32) {
|
975 |
|
storedState.md5 = object.hash;
|
976 |
|
storedState.hashMapHash = @" ";
|
977 |
|
} else if([object.hash length] == 64) {
|
978 |
|
storedState.md5 = @" ";
|
979 |
|
storedState.hashMapHash = object.hash;
|
980 |
|
} else {
|
981 |
|
storedState.md5 = @" ";
|
982 |
|
storedState.hashMapHash = @" ";
|
983 |
|
}
|
984 |
|
|
|
901 |
storedState.hash = object.hash;
|
985 |
902 |
storedState.tmpDownloadFile = nil;
|
986 |
903 |
[self saveLocalState];
|
987 |
904 |
|
... | ... | |
1140 |
1057 |
NSUInteger currentBytes = activity.currentBytes;
|
1141 |
1058 |
if (objectRequest.responseStatusCode == 201) {
|
1142 |
1059 |
NSLog(@"Sync::object created: %@", objectRequest.url);
|
1143 |
|
NSString *eTag = [objectRequest eTag];
|
1144 |
|
if ([eTag length] == 32)
|
1145 |
|
storedState.md5 = eTag;
|
1146 |
|
else if([eTag length] == 64)
|
1147 |
|
storedState.hashMapHash = eTag;
|
|
1060 |
storedState.hash = [objectRequest eTag];
|
1148 |
1061 |
[self saveLocalState];
|
1149 |
1062 |
[activityFacility endActivity:activity
|
1150 |
1063 |
withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
|