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 = Map.Map k ()
71 data (Eq k, Ord k, Show k) =>
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) =>
92 SyncCollection k -- ^ Present state of master
93 -> SyncCollection k -- ^ Present state of child
94 -> SyncCollection k -- ^ Last state of child
95 -> ([SyncCommand k], [SyncCommand k]) -- ^ Changes to make to (master, child)
96 syncBiDir masterstate childstate lastchildstate =
97 (masterchanges, childchanges)
98 where masterchanges = (map DeleteItem .
99 findDeleted childstate masterstate $ lastchildstate)
102 findAdded childstate masterstate $ lastchildstate)
103 childchanges = (map DeleteItem .
104 findDeleted masterstate childstate $ lastchildstate)
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) =>
115 diffCollection coll1 coll2 =
116 (map DeleteItem . findDeleted coll2 coll1 $ coll1) ++
117 (map CopyItem . findDeleted coll1 coll2 $ coll2)
119 {- | Returns a list of keys that exist in state2 and lastchildstate
121 findDeleted :: Ord k =>
122 SyncCollection k -> SyncCollection k -> SyncCollection k ->
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 -> SyncCollection k -> SyncCollection k ->
132 findAdded state1 state2 lastchildstate =
133 Map.keys . Map.difference state1 . Map.union state2 $ lastchildstate
135 {- | Returns a list of keys that exist in the passed state -}
136 filterKeys :: (Ord k) =>
137 SyncCollection k -> [k] -> [k]
138 filterKeys state keylist =
139 concatMap keyfunc keylist
141 case Map.lookup k state of
145 {- | Apply the specified changes to the given SyncCollection. Returns
146 a new SyncCollection with the changes applied. If changes are specified
147 that would apply to UIDs that do not exist in the source list, these changes
148 are silently ignored. -}
149 unaryApplyChanges :: (Eq k, Ord k, Show k) =>
150 SyncCollection k -> [SyncCommand k] -> SyncCollection k
151 unaryApplyChanges collection commands =
152 let makeChange collection (DeleteItem key) =
153 Map.delete key collection
154 makeChange collection (CopyItem key) =
155 Map.insert key () collection
156 in foldl makeChange collection commands