2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; version 2 of the License.
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
18 from threading
import *
19 from offlineimap
import threadutil
20 from offlineimap
.threadutil
import InstanceLimitedThread
21 from offlineimap
.ui
import UIBase
28 def suggeststhreads(self
):
29 """Returns true if this folder suggests using threads for actions;
30 false otherwise. Probably only IMAP will return true."""
33 def waitforthread(self
):
34 """For threading folders, waits until there is a resource available
35 before firing off a thread. For all others, returns immediately."""
38 def getcopyinstancelimit(self
):
39 """For threading folders, returns the instancelimitname for
40 InstanceLimitedThreads."""
41 raise NotImplementedException
43 def storesmessages(self
):
44 """Should be true for any backend that actually saves message bodies.
45 (Almost all of them). False for the LocalStatus backend. Saves
46 us from having to slurp up messages just for localstatus purposes."""
49 def getvisiblename(self
):
52 def getrepository(self
):
53 """Returns the repository object that this folder is within."""
54 return self
.repository
57 """Returns the root of the folder, in a folder-specific fashion."""
61 """Returns the separator for this folder type."""
64 def getfullname(self
):
66 return self
.getroot() + self
.getsep() + self
.getname()
70 def isuidvalidityok(self
, remotefolder
):
71 raise NotImplementedException
73 def getuidvalidity(self
):
74 raise NotImplementedException
76 def saveuidvalidity(self
, newval
):
77 raise NotImplementedException
79 def cachemessagelist(self
):
80 """Reads the message list from disk or network and stores it in
81 memory for later use. This list will not be re-read from disk or
82 memory unless this function is called again."""
83 raise NotImplementedException
85 def getmessagelist(self
):
86 """Gets the current message list.
87 You must call cachemessagelist() before calling this function!"""
88 raise NotImplementedException
90 def getmessage(self
, uid
):
91 """Returns the content of the specified message."""
92 raise NotImplementedException
94 def savemessage(self
, uid
, content
, flags
):
95 """Writes a new message, with the specified uid.
96 If the uid is < 0, the backend should assign a new uid and return it.
98 If the backend cannot assign a new uid, it returns the uid passed in
99 WITHOUT saving the message.
101 If the backend CAN assign a new uid, but cannot find out what this UID
102 is (as is the case with many IMAP servers), it returns 0 but DOES save
105 IMAP backend should be the only one that can assign a new uid.
107 If the uid is > 0, the backend should set the uid to this, if it can.
108 If it cannot set the uid to that, it will save it anyway.
109 It will return the uid assigned in any case.
111 raise NotImplementedException
113 def getmessageflags(self
, uid
):
114 """Returns the flags for the specified message."""
115 raise NotImplementedException
117 def savemessageflags(self
, uid
, flags
):
118 """Sets the specified message's flags to the given set."""
119 raise NotImplementedException
121 def addmessageflags(self
, uid
, flags
):
122 """Adds the specified flags to the message's flag set. If a given
123 flag is already present, it will not be duplicated."""
124 newflags
= self
.getmessageflags(uid
)
126 if not flag
in newflags
:
127 newflags
.append(flag
)
129 self
.savemessageflags(uid
, newflags
)
131 def addmessagesflags(self
, uidlist
, flags
):
133 self
.addmessageflags(uid
, flags
)
135 def deletemessageflags(self
, uid
, flags
):
136 """Removes each flag given from the message's flag set. If a given
137 flag is already removed, no action will be taken for that flag."""
138 newflags
= self
.getmessageflags(uid
)
141 newflags
.remove(flag
)
143 self
.savemessageflags(uid
, newflags
)
145 def deletemessagesflags(self
, uidlist
, flags
):
147 self
.deletemessageflags(uid
, flags
)
149 def deletemessage(self
, uid
):
150 raise NotImplementedException
152 def deletemessages(self
, uidlist
):
154 self
.deletemessage(uid
)
156 def syncmessagesto_neguid_msg(self
, uid
, dest
, applyto
, register
= 1):
158 UIBase
.getglobalui().registerthread(self
.getaccountname())
159 UIBase
.getglobalui().copyingmessage(uid
, self
, applyto
)
162 message
= self
.getmessage(uid
)
163 flags
= self
.getmessageflags(uid
)
164 for tryappend
in applyto
:
165 successuid
= tryappend
.savemessage(uid
, message
, flags
)
167 successobject
= tryappend
170 if successobject
!= None:
171 if successuid
: # Only if IMAP actually assigned a UID
172 # Copy the message to the other remote servers.
173 for appendserver
in \
174 [x
for x
in applyto
if x
!= successobject
]:
175 appendserver
.savemessage(successuid
, message
, flags
)
176 # Copy to its new name on the local server and delete
177 # the one without a UID.
178 self
.savemessage(successuid
, message
, flags
)
179 self
.deletemessage(uid
) # It'll be re-downloaded.
181 # Did not find any server to take this message. Ignore.
185 def syncmessagesto_neguid(self
, dest
, applyto
):
186 """Pass 1 of folder synchronization.
188 Look for messages in self with a negative uid. These are messages in
189 Maildirs that were not added by us. Try to add them to the dests,
190 and once that succeeds, get the UID, add it to the others for real,
191 add it to local for real, and delete the fake one."""
193 uidlist
= [uid
for uid
in self
.getmessagelist().keys() if uid
< 0]
198 usethread
= applyto
[0]
201 if usethread
and usethread
.suggeststhreads():
202 usethread
.waitforthread()
203 thread
= InstanceLimitedThread(\
204 usethread
.getcopyinstancelimit(),
205 target
= self
.syncmessagesto_neguid_msg
,
206 name
= "New msg sync from %s" % self
.getvisiblename(),
207 args
= (uid
, dest
, applyto
))
210 threads
.append(thread
)
212 self
.syncmessagesto_neguid_msg(uid
, dest
, applyto
, register
= 0)
213 for thread
in threads
:
216 def copymessageto(self
, uid
, applyto
, register
= 1):
217 # Sometimes, it could be the case that if a sync takes awhile,
218 # a message might be deleted from the maildir before it can be
219 # synced to the status cache. This is only a problem with
220 # self.getmessage(). So, don't call self.getmessage unless
223 UIBase
.getglobalui().registerthread(self
.getaccountname())
224 UIBase
.getglobalui().copyingmessage(uid
, self
, applyto
)
226 # If any of the destinations actually stores the message body,
228 for object in applyto
:
229 if object.storesmessages():
230 message
= self
.getmessage(uid
)
232 flags
= self
.getmessageflags(uid
)
233 for object in applyto
:
234 newuid
= object.savemessage(uid
, message
, flags
)
235 if newuid
> 0 and newuid
!= uid
:
236 # Change the local uid.
237 self
.savemessage(newuid
, message
, flags
)
238 self
.deletemessage(uid
)
242 def syncmessagesto_copy(self
, dest
, applyto
):
243 """Pass 2 of folder synchronization.
245 Look for messages present in self but not in dest. If any, add
249 for uid
in self
.getmessagelist().keys():
250 if uid
< 0: # Ignore messages that pass 1 missed.
252 if not uid
in dest
.getmessagelist():
253 if self
.suggeststhreads():
255 thread
= InstanceLimitedThread(\
256 self
.getcopyinstancelimit(),
257 target
= self
.copymessageto
,
258 name
= "Copy message %d from %s" % (uid
,
259 self
.getvisiblename()),
260 args
= (uid
, applyto
))
263 threads
.append(thread
)
265 self
.copymessageto(uid
, applyto
, register
= 0)
266 for thread
in threads
:
269 def syncmessagesto_delete(self
, dest
, applyto
):
270 """Pass 3 of folder synchronization.
272 Look for message present in dest but not in self.
273 If any, delete them."""
275 for uid
in dest
.getmessagelist().keys():
278 if not uid
in self
.getmessagelist():
279 deletelist
.append(uid
)
281 UIBase
.getglobalui().deletingmessages(deletelist
, applyto
)
282 for object in applyto
:
283 object.deletemessages(deletelist
)
285 def syncmessagesto_flags(self
, dest
, applyto
):
286 """Pass 4 of folder synchronization.
288 Look for any flag matching issues -- set dest message to have the
289 same flags that we have."""
291 # As an optimization over previous versions, we store up which flags
292 # are being used for an add or a delete. For each flag, we store
293 # a list of uids to which it should be added. Then, we can call
294 # addmessagesflags() to apply them in bulk, rather than one
295 # call per message as before. This should result in some significant
296 # performance improvements.
301 for uid
in self
.getmessagelist().keys():
302 if uid
< 0: # Ignore messages missed by pass 1
304 selfflags
= self
.getmessageflags(uid
)
305 destflags
= dest
.getmessageflags(uid
)
307 addflags
= [x
for x
in selfflags
if x
not in destflags
]
309 for flag
in addflags
:
310 if not flag
in addflaglist
:
311 addflaglist
[flag
] = []
312 addflaglist
[flag
].append(uid
)
314 delflags
= [x
for x
in destflags
if x
not in selfflags
]
315 for flag
in delflags
:
316 if not flag
in delflaglist
:
317 delflaglist
[flag
] = []
318 delflaglist
[flag
].append(uid
)
320 for object in applyto
:
321 for flag
in addflaglist
.keys():
322 UIBase
.getglobalui().addingflags(addflaglist
[flag
], flag
, [object])
323 object.addmessagesflags(addflaglist
[flag
], [flag
])
324 for flag
in delflaglist
.keys():
325 UIBase
.getglobalui().deletingflags(delflaglist
[flag
], flag
, [object])
326 object.deletemessagesflags(delflaglist
[flag
], [flag
])
328 def syncmessagesto(self
, dest
, applyto
= None):
329 """Syncs messages in this folder to the destination.
330 If applyto is specified, it should be a list of folders (don't forget
331 to include dest!) to which all write actions should be applied.
332 It defaults to [dest] if not specified. It is important that
333 the UID generator be listed first in applyto; that is, the other
334 applyto ones should be the ones that "copy" the main action."""
338 self
.syncmessagesto_neguid(dest
, applyto
)
339 self
.syncmessagesto_copy(dest
, applyto
)
340 self
.syncmessagesto_delete(dest
, applyto
)
342 # Now, the message lists should be identical wrt the uids present.
343 # (except for potential negative uids that couldn't be placed
346 self
.syncmessagesto_flags(dest
, applyto
)