]> code.delx.au - offlineimap/blobdiff - src/Data/Syncable.hs
More attempts
[offlineimap] / src / Data / Syncable.hs
index f84ea27a5b7393510f16f5d60c89096c155f8111..08cd82a10c5d19532a26ef22fd39f565ff803454 100644 (file)
@@ -72,13 +72,22 @@ data (Eq k, Ord k, Show k, Show v) =>
     SyncCommand k v = 
            DeleteItem k
          | CopyItem k v
+         | ModifyContent k v
     deriving (Eq, Ord, Show)
 
+pairToFunc :: (a -> b -> c) -> (a, b) -> c
+pairToFunc func (a, b) = func a b
+
 {- | Perform a bi-directional sync.  Compared to the last known state of
 the child, evaluate the new states of the master and child.  Return a list of
 changes to make to the master and list of changes to make to the child to
 bring them into proper sync.
 
+In the event that both master and child previously had an item, and the payload
+of the item has changed on both ends, the payload as given in the child
+will take precedence.  If both previously had an item, and it changed on only
+one end, the new value "wins".
+
 This relationship should hold:
 
 >let (masterCmds, childCmds) = syncBiDir masterState childState lastChildState
@@ -87,8 +96,9 @@ This relationship should hold:
 
 This relationship is validated in the test suite that accompanies this
 software.
+
 -}
-syncBiDir :: (Ord k, Show k, Show v) =>
+syncBiDir :: (Ord k, Show k, Show v, Eq v) =>
             SyncCollection k v  -- ^ Present state of master
          -> SyncCollection k v  -- ^ Present state of child
          -> SyncCollection k v  -- ^ Last state of child
@@ -98,13 +108,27 @@ syncBiDir masterstate childstate lastchildstate =
     where masterchanges = (map DeleteItem .
                           findDeleted childstate masterstate $ lastchildstate)
                           ++ 
-                          (map (\(x, y) -> CopyItem x y) .
+                          (map (pairToFunc CopyItem) .
                            findAdded childstate masterstate $ lastchildstate)
+                          ++ (map (pairToFunc ModifyContent) . Map.toList $ masterPayloadChanges)
           childchanges = (map DeleteItem . 
                           findDeleted masterstate childstate $ lastchildstate)
                          ++
-                         (map (\(x, y) -> CopyItem x y) .
+                         (map (pairToFunc CopyItem) .
                           findAdded masterstate childstate $ lastchildstate)
+                         ++ (map (pairToFunc ModifyContent) . Map.toList $ childPayloadChanges)
+          masterPayloadChanges = 
+              Map.union (findModified masterstate childstate lastchildstate)
+                        (findModified masterstate childstate masterstate)
+                 
+          -- The child's payload takes precedence, so we are going to
+          -- calculate the changes made on the master to apply to the client,
+          -- then subtract out any items in the master changes that have the
+          -- same key.
+          childPayloadChanges = 
+              foldl (\m (k, v) -> Map.adjust (\_ -> v) k m)
+                        (findModified childstate masterstate lastchildstate)
+                        (Map.toList $ findModified masterstate childstate lastchildstate)
 
 {- | Compares two SyncCollections, and returns the commands that, when
 applied to the first collection, would yield the second. -}
@@ -114,7 +138,7 @@ diffCollection :: (Ord k, Show k, Show v) =>
                -> [SyncCommand k v]
 diffCollection coll1 coll2 = 
     (map DeleteItem . findDeleted coll2 coll1 $ coll1) ++
-    (map (\(k, v) -> CopyItem k v) . findAdded coll2 coll1 $ coll1)
+    (map (pairToFunc CopyItem) . findAdded coll2 coll1 $ coll1)
 
 {- | Returns a list of keys that exist in state2 and lastchildstate
 but not in state1 -}
@@ -132,6 +156,24 @@ findAdded :: (Ord k, Eq k) =>
 findAdded state1 state2 lastchildstate =
     Map.toList . Map.difference state1 . Map.union state2 $ lastchildstate
 
+{- Finds all items that exist in both state1 and lastchildstate in which the payload
+is different in state1 than it was in lastchildstate.  Returns the key and new
+payload for each such item found. -}
+findModified :: (Ord k, Eq v) =>
+                SyncCollection k v -> SyncCollection k v -> SyncCollection k v -> SyncCollection k v
+findModified basestate state1 lastchildstate =
+    Map.mapMaybe id .  
+    Map.intersectionWithKey comparefunc state1 $ lastchildstate
+    where comparefunc k v1 v2 =
+              if v1 /= v2
+                 then case Map.lookup k basestate of
+                        Nothing -> Nothing
+                        Just baseval ->
+                            if baseval == v1
+                               then Nothing
+                               else Just v1
+                 else Nothing
+
 {- | Apply the specified changes to the given SyncCollection.  Returns
 a new SyncCollection with the changes applied.  If changes are specified
 that would apply to UIDs that do not exist in the source list, these changes
@@ -143,4 +185,6 @@ unaryApplyChanges collection commands =
             Map.delete key collection
         makeChange collection (CopyItem key val) =
             Map.insert key val collection
+        makeChange collection (ModifyContent key val) =
+            Map.adjust (\_ -> val) key collection
     in foldl makeChange collection commands