1 {- offlineimap component
2 Copyright (C) 2002-2008 John Goerzen <jgoerzen@complete.org>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 The OfflineIMAP v6 algorithm worked like this:
22 call remoterepos.syncfoldersto(localrepos, [statusrepos])
25 syncfolder(remotename, remoterepos, remotefolder, localrepos, statusrepos, quick)
27 sets localfolder = local folder
28 adds localfolder to mbnames
29 sets statusfolder = status folder
30 if localfolder.getuidvalidity() == None, removes anything in statusfolder
32 statusfolder.cachemessagelist()
34 localfolder.cachemessagelist()
39 remotefolder.cachemessagelist()
41 if not statusfolder.isnewfolder():
42 # Delete local copies of remote messages. This way,
43 # if a message's flag is modified locally but it has been
44 # deleted remotely, we'll delete it locally. Otherwise, we
45 # try to modify a deleted message's flags! This step
46 # need only be taken if a statusfolder is present; otherwise,
47 # there is no action taken *to* the remote repository.
48 remotefolder.syncmessagesto_delete(localfolder, [localfolder,
50 localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder])
52 # Synchroonize remote changes
53 remotefolder.syncmessagesto(localfolder, [localfolder, statusfolder])
55 # Make sure the status folder is up-to-date.
56 ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder)
57 localfolder.syncmessagesto(statusfolder)
59 localrepos.restore_atime()
63 call forgetfolders on local and remote
66 module Data.Syncable where
67 import qualified Data.Map as Map
69 type SyncCollection k v = Map.Map k v
71 data (Eq k, Ord k, Show k, Show v) =>
75 deriving (Eq, Ord, Show)
77 {- | Perform a bi-directional sync. Compared to the last known state of
78 the child, evaluate the new states of the master and child. Return a list of
79 changes to make to the master and list of changes to make to the child to
80 bring them into proper sync.
82 This relationship should hold:
84 >let (masterCmds, childCmds) = syncBiDir masterState childState lastChildState
85 >unaryApplyChanges masterState masterCmds ==
86 > unaryApplyChanges childState childCmds
88 This relationship is validated in the test suite that accompanies this
91 syncBiDir :: (Ord k, Show k, Show v) =>
92 SyncCollection k v -- ^ Present state of master
93 -> SyncCollection k v -- ^ Present state of child
94 -> SyncCollection k v -- ^ Last state of child
95 -> ([SyncCommand k v], [SyncCommand k v]) -- ^ Changes to make to (master, child)
96 syncBiDir masterstate childstate lastchildstate =
97 (masterchanges, childchanges)
98 where masterchanges = (map DeleteItem .
99 findDeleted childstate masterstate $ lastchildstate)
101 (map (\(x, y) -> CopyItem x y) .
102 findAdded childstate masterstate $ lastchildstate)
103 childchanges = (map DeleteItem .
104 findDeleted masterstate childstate $ lastchildstate)
106 (map (\(x, y) -> CopyItem x y) .
107 findAdded masterstate childstate $ lastchildstate)
109 {- | Compares two SyncCollections, and returns the commands that, when
110 applied to the first collection, would yield the second. -}
111 diffCollection :: (Ord k, Show k, Show v) =>
113 -> SyncCollection k v
115 diffCollection coll1 coll2 =
116 (map DeleteItem . findDeleted coll2 coll1 $ coll1) ++
117 (map (\(k, v) -> CopyItem k v) . findAdded coll2 coll1 $ coll1)
119 {- | Returns a list of keys that exist in state2 and lastchildstate
121 findDeleted :: Ord k =>
122 SyncCollection k v -> SyncCollection k v -> SyncCollection k v ->
124 findDeleted state1 state2 lastchildstate =
125 Map.keys . Map.difference (Map.intersection state2 lastchildstate) $ state1
127 {- | Returns a list of keys that exist in state1 but in neither
128 state2 nor lastchildstate -}
129 findAdded :: (Ord k, Eq k) =>
130 SyncCollection k v -> SyncCollection k v -> SyncCollection k v ->
132 findAdded state1 state2 lastchildstate =
133 Map.toList . Map.difference state1 . Map.union state2 $ lastchildstate
135 {- | Apply the specified changes to the given SyncCollection. Returns
136 a new SyncCollection with the changes applied. If changes are specified
137 that would apply to UIDs that do not exist in the source list, these changes
138 are silently ignored. -}
139 unaryApplyChanges :: (Eq k, Ord k, Show k, Show v) =>
140 SyncCollection k v -> [SyncCommand k v] -> SyncCollection k v
141 unaryApplyChanges collection commands =
142 let makeChange collection (DeleteItem key) =
143 Map.delete key collection
144 makeChange collection (CopyItem key val) =
145 Map.insert key val collection
146 in foldl makeChange collection commands