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; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 from threading
import *
20 from offlineimap
import threadutil
21 from offlineimap
.threadutil
import InstanceLimitedThread
22 from offlineimap
.ui
import UIBase
29 def suggeststhreads(self
):
30 """Returns true if this folder suggests using threads for actions;
31 false otherwise. Probably only IMAP will return true."""
34 def waitforthread(self
):
35 """For threading folders, waits until there is a resource available
36 before firing off a thread. For all others, returns immediately."""
39 def getcopyinstancelimit(self
):
40 """For threading folders, returns the instancelimitname for
41 InstanceLimitedThreads."""
42 raise NotImplementedException
44 def storesmessages(self
):
45 """Should be true for any backend that actually saves message bodies.
46 (Almost all of them). False for the LocalStatus backend. Saves
47 us from having to slurp up messages just for localstatus purposes."""
50 def getvisiblename(self
):
53 def getrepository(self
):
54 """Returns the repository object that this folder is within."""
55 return self
.repository
58 """Returns the root of the folder, in a folder-specific fashion."""
62 """Returns the separator for this folder type."""
65 def getfullname(self
):
67 return self
.getroot() + self
.getsep() + self
.getname()
71 def isuidvalidityok(self
, remotefolder
):
72 raise NotImplementedException
74 def getuidvalidity(self
):
75 raise NotImplementedException
77 def saveuidvalidity(self
, newval
):
78 raise NotImplementedException
80 def cachemessagelist(self
):
81 """Reads the message list from disk or network and stores it in
82 memory for later use. This list will not be re-read from disk or
83 memory unless this function is called again."""
84 raise NotImplementedException
86 def getmessagelist(self
):
87 """Gets the current message list.
88 You must call cachemessagelist() before calling this function!"""
89 raise NotImplementedException
91 def getmessage(self
, uid
):
92 """Returns the content of the specified message."""
93 raise NotImplementedException
95 def savemessage(self
, uid
, content
, flags
):
96 """Writes a new message, with the specified uid.
97 If the uid is < 0, the backend should assign a new uid and return it.
99 If the backend cannot assign a new uid, it returns the uid passed in
100 WITHOUT saving the message.
102 If the backend CAN assign a new uid, but cannot find out what this UID
103 is (as is the case with many IMAP servers), it returns 0 but DOES save
106 IMAP backend should be the only one that can assign a new uid.
108 If the uid is > 0, the backend should set the uid to this, if it can.
109 If it cannot set the uid to that, it will save it anyway.
110 It will return the uid assigned in any case.
112 raise NotImplementedException
114 def getmessageflags(self
, uid
):
115 """Returns the flags for the specified message."""
116 raise NotImplementedException
118 def savemessageflags(self
, uid
, flags
):
119 """Sets the specified message's flags to the given set."""
120 raise NotImplementedException
122 def addmessageflags(self
, uid
, flags
):
123 """Adds the specified flags to the message's flag set. If a given
124 flag is already present, it will not be duplicated."""
125 newflags
= self
.getmessageflags(uid
)
127 if not flag
in newflags
:
128 newflags
.append(flag
)
130 self
.savemessageflags(uid
, newflags
)
132 def addmessagesflags(self
, uidlist
, flags
):
134 self
.addmessageflags(uid
, flags
)
136 def deletemessageflags(self
, uid
, flags
):
137 """Removes each flag given from the message's flag set. If a given
138 flag is already removed, no action will be taken for that flag."""
139 newflags
= self
.getmessageflags(uid
)
142 newflags
.remove(flag
)
144 self
.savemessageflags(uid
, newflags
)
146 def deletemessagesflags(self
, uidlist
, flags
):
148 self
.deletemessageflags(uid
, flags
)
150 def deletemessage(self
, uid
):
151 raise NotImplementedException
153 def deletemessages(self
, uidlist
):
155 self
.deletemessage(uid
)
157 def syncmessagesto_neguid_msg(self
, uid
, dest
, applyto
, register
= 1):
159 UIBase
.getglobalui().registerthread(self
.getaccountname())
160 UIBase
.getglobalui().copyingmessage(uid
, self
, applyto
)
163 message
= self
.getmessage(uid
)
164 flags
= self
.getmessageflags(uid
)
165 for tryappend
in applyto
:
166 successuid
= tryappend
.savemessage(uid
, message
, flags
)
168 successobject
= tryappend
171 if successobject
!= None:
172 if successuid
: # Only if IMAP actually assigned a UID
173 # Copy the message to the other remote servers.
174 for appendserver
in \
175 [x
for x
in applyto
if x
!= successobject
]:
176 appendserver
.savemessage(successuid
, message
, flags
)
177 # Copy to its new name on the local server and delete
178 # the one without a UID.
179 self
.savemessage(successuid
, message
, flags
)
180 self
.deletemessage(uid
) # It'll be re-downloaded.
182 # Did not find any server to take this message. Ignore.
186 def syncmessagesto_neguid(self
, dest
, applyto
):
187 """Pass 1 of folder synchronization.
189 Look for messages in self with a negative uid. These are messages in
190 Maildirs that were not added by us. Try to add them to the dests,
191 and once that succeeds, get the UID, add it to the others for real,
192 add it to local for real, and delete the fake one."""
194 uidlist
= [uid
for uid
in self
.getmessagelist().keys() if uid
< 0]
199 usethread
= applyto
[0]
202 if usethread
and usethread
.suggeststhreads():
203 usethread
.waitforthread()
204 thread
= InstanceLimitedThread(\
205 usethread
.getcopyinstancelimit(),
206 target
= self
.syncmessagesto_neguid_msg
,
207 name
= "New msg sync from %s" % self
.getvisiblename(),
208 args
= (uid
, dest
, applyto
))
211 threads
.append(thread
)
213 self
.syncmessagesto_neguid_msg(uid
, dest
, applyto
, register
= 0)
214 for thread
in threads
:
217 def copymessageto(self
, uid
, applyto
, register
= 1):
218 # Sometimes, it could be the case that if a sync takes awhile,
219 # a message might be deleted from the maildir before it can be
220 # synced to the status cache. This is only a problem with
221 # self.getmessage(). So, don't call self.getmessage unless
224 UIBase
.getglobalui().registerthread(self
.getaccountname())
225 UIBase
.getglobalui().copyingmessage(uid
, self
, applyto
)
227 # If any of the destinations actually stores the message body,
229 for object in applyto
:
230 if object.storesmessages():
231 message
= self
.getmessage(uid
)
233 flags
= self
.getmessageflags(uid
)
234 for object in applyto
:
235 newuid
= object.savemessage(uid
, message
, flags
)
236 if newuid
> 0 and newuid
!= uid
:
237 # Change the local uid.
238 self
.savemessage(newuid
, message
, flags
)
239 self
.deletemessage(uid
)
243 def syncmessagesto_copy(self
, dest
, applyto
):
244 """Pass 2 of folder synchronization.
246 Look for messages present in self but not in dest. If any, add
250 for uid
in self
.getmessagelist().keys():
251 if uid
< 0: # Ignore messages that pass 1 missed.
253 if not uid
in dest
.getmessagelist():
254 if self
.suggeststhreads():
256 thread
= InstanceLimitedThread(\
257 self
.getcopyinstancelimit(),
258 target
= self
.copymessageto
,
259 name
= "Copy message %d from %s" % (uid
,
260 self
.getvisiblename()),
261 args
= (uid
, applyto
))
264 threads
.append(thread
)
266 self
.copymessageto(uid
, applyto
, register
= 0)
267 for thread
in threads
:
270 def syncmessagesto_delete(self
, dest
, applyto
):
271 """Pass 3 of folder synchronization.
273 Look for message present in dest but not in self.
274 If any, delete them."""
276 for uid
in dest
.getmessagelist().keys():
279 if not uid
in self
.getmessagelist():
280 deletelist
.append(uid
)
282 UIBase
.getglobalui().deletingmessages(deletelist
, applyto
)
283 for object in applyto
:
284 object.deletemessages(deletelist
)
286 def syncmessagesto_flags(self
, dest
, applyto
):
287 """Pass 4 of folder synchronization.
289 Look for any flag matching issues -- set dest message to have the
290 same flags that we have."""
292 # As an optimization over previous versions, we store up which flags
293 # are being used for an add or a delete. For each flag, we store
294 # a list of uids to which it should be added. Then, we can call
295 # addmessagesflags() to apply them in bulk, rather than one
296 # call per message as before. This should result in some significant
297 # performance improvements.
302 for uid
in self
.getmessagelist().keys():
303 if uid
< 0: # Ignore messages missed by pass 1
305 selfflags
= self
.getmessageflags(uid
)
306 destflags
= dest
.getmessageflags(uid
)
308 addflags
= [x
for x
in selfflags
if x
not in destflags
]
310 for flag
in addflags
:
311 if not flag
in addflaglist
:
312 addflaglist
[flag
] = []
313 addflaglist
[flag
].append(uid
)
315 delflags
= [x
for x
in destflags
if x
not in selfflags
]
316 for flag
in delflags
:
317 if not flag
in delflaglist
:
318 delflaglist
[flag
] = []
319 delflaglist
[flag
].append(uid
)
321 for object in applyto
:
322 for flag
in addflaglist
.keys():
323 UIBase
.getglobalui().addingflags(addflaglist
[flag
], flag
, [object])
324 object.addmessagesflags(addflaglist
[flag
], [flag
])
325 for flag
in delflaglist
.keys():
326 UIBase
.getglobalui().deletingflags(delflaglist
[flag
], flag
, [object])
327 object.deletemessagesflags(delflaglist
[flag
], [flag
])
329 def syncmessagesto(self
, dest
, applyto
= None):
330 """Syncs messages in this folder to the destination.
331 If applyto is specified, it should be a list of folders (don't forget
332 to include dest!) to which all write actions should be applied.
333 It defaults to [dest] if not specified. It is important that
334 the UID generator be listed first in applyto; that is, the other
335 applyto ones should be the ones that "copy" the main action."""
339 self
.syncmessagesto_neguid(dest
, applyto
)
340 self
.syncmessagesto_copy(dest
, applyto
)
341 self
.syncmessagesto_delete(dest
, applyto
)
343 # Now, the message lists should be identical wrt the uids present.
344 # (except for potential negative uids that couldn't be placed
347 self
.syncmessagesto_flags(dest
, applyto
)