Revision e637fedf

b/pithos-macos/PithosLocalObjectState.h
41 41
    NSString *md5;
42 42
    NSString *hashMapHash;
43 43
    NSString *tmpDownloadFile;
44
    BOOL isDirectory; 
44
    BOOL isDirectory;
45 45
}
46 46

  
47
+ (id)nullObjectState;
47
- (id)initWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize;
48
- (id)initWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory;
49
+ (id)localObjectState;
50
+ (id)localObjectStateWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize;
51
+ (id)localObjectStateWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory;
52
- (BOOL)isEqualToState:(PithosLocalObjectState *)aState;
48 53

  
49 54
@property (nonatomic, retain) NSString *md5;
50 55
@property (nonatomic, retain) NSString *hashMapHash;
51 56
@property (nonatomic, retain) NSString *tmpDownloadFile;
52 57
@property (nonatomic, assign) BOOL isDirectory;
53 58

  
59
@property (nonatomic, readonly) BOOL exists;
60
@property (nonatomic, retain) NSString *hash;
61

  
54 62
@end
b/pithos-macos/PithosLocalObjectState.m
37 37

  
38 38
#import "PithosLocalObjectState.h"
39 39
#import "PithosUtilities.h"
40
#import "FileMD5Hash.h"
41
#import "HashMapHash.h"
40 42

  
41 43
@implementation PithosLocalObjectState
42
@synthesize md5, hashMapHash, tmpDownloadFile, isDirectory;
44
@synthesize md5, hashMapHash, tmpDownloadFile, isDirectory, exists, hash;
43 45

  
44 46
#pragma mark -
45 47
#pragma mark Object Lifecycle
46 48

  
47
+ (id)nullObjectState {
48
	PithosLocalObjectState *localObjectState = [[[self alloc] init] autorelease];
49
    localObjectState.md5 = @" ";
50
    localObjectState.hashMapHash = @" ";
51
    localObjectState.tmpDownloadFile = nil;
52
    localObjectState.isDirectory = NO;
53
    return localObjectState;
49
- (id)initWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize {
50
    if ((self = [self init])) {
51
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && !isDirectory) {
52
            self.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath, 
53
                                                             FileHashDefaultChunkSizeForReadingData);
54
            self.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath 
55
                                                                                       withBlockHash:blockHash 
56
                                                                                        andBlockSize:blockSize]];
57
        }
58
    }
59
    return self;
60
}
61

  
62
- (id)initWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory {
63
    if ((self = [self init])) {
64
        if (anIsDirectory)
65
            isDirectory = YES;
66
        else if ([aHash length] == 32)
67
            self.md5 = aHash;
68
        else if ([aHash length] == 64)
69
            self.hashMapHash = aHash;
70
    }
71
    return self;
72
}
73

  
74
+ (id)localObjectState {
75
    return [[[self alloc] init] autorelease];
76
}
77

  
78
+ (id)localObjectStateWithFile:(NSString *)filePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize {
79
    return [[[self alloc] initWithFile:filePath blockHash:blockHash blockSize:blockSize] autorelease];
80
}
81

  
82
+ (id)localObjectStateWithHash:(NSString *)aHash directory:(BOOL)anIsDirectory {
83
    return [[[self alloc] initWithHash:aHash directory:anIsDirectory] autorelease];
54 84
}
55 85

  
56 86
- (void)dealloc {
......
60 90
    [super dealloc];    
61 91
}
62 92

  
93
- (NSString *)description {
94
    return [NSString stringWithFormat:@"md5: %@, hashMapHash: %@, tmpDownloadFile: %@, isDirectory: %d", 
95
            md5, hashMapHash, tmpDownloadFile, isDirectory];
96
}
97

  
98
- (BOOL)isEqualToState:(PithosLocalObjectState *)aState {
99
    if (self.isDirectory)
100
        // Object is a directory, check the other
101
        return aState.isDirectory;
102
    else if (aState.isDirectory)
103
        // Object is not a directory, while the other is
104
        return NO;
105
    else if (!self.exists)
106
        // Object doesn't exist, check the other
107
        return (!aState.exists);
108
    else if (!aState.exists)
109
        // Object exists, while the other doesn't
110
        return NO;
111
    else
112
        // Both objects exist, check that they have at least one hash in common
113
        return ([self.md5 isEqualToString:aState.md5] || [self.hashMapHash isEqualToString:aState.hashMapHash]);
114
}
115

  
63 116
#pragma mark -
64 117
#pragma mark Properties
65 118

  
66 119
- (void)setIsDirectory:(BOOL)anIsDirectory {
67 120
    isDirectory = anIsDirectory;
68 121
    if (isDirectory) {
69
        self.md5 = @" ";
70
        self.hashMapHash = @" ";
122
        self.md5 = nil;
123
        self.hashMapHash = nil;
71 124
        self.tmpDownloadFile = nil;
72 125
    }
73 126
}
......
89 142
    }
90 143
}
91 144

  
145
- (BOOL)exists {
146
    return (isDirectory || md5 || hashMapHash);
147
}
148

  
149
- (NSString *)hash {
150
    if (hashMapHash)
151
        return hashMapHash;
152
    else
153
        return md5;
154
}
155

  
156
- (void)setHash:(NSString *)aHash {
157
    self.md5 = nil;
158
    self.hashMapHash = nil;
159
    if ([aHash length] == 32) {
160
        self.md5 = aHash;
161
    } else if ([aHash length] == 64) {
162
        self.hashMapHash = aHash;
163
    }
164
}
165

  
92 166
#pragma mark -
93 167
#pragma mark NSCoding
94 168

  
b/pithos-macos/PithosSyncDaemon.m
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"] 

Also available in: Unified diff