]> code.delx.au - offlineimap/blob - head/offlineimap/folder/Base.py
2cca5648bb47e205a27a82a66f23e50ab059a26f
[offlineimap] / head / offlineimap / folder / Base.py
1 # Base folder support
2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
4 #
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.
9 #
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.
14 #
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
18
19 import __main__
20 from threading import *
21 from offlineimap import threadutil
22 from offlineimap.threadutil import InstanceLimitedThread
23
24 class BaseFolder:
25 def getname(self):
26 """Returns name"""
27 return self.name
28
29 def suggeststhreads(self):
30 """Returns true if this folder suggests using threads for actions;
31 false otherwise. Probably only IMAP will return true."""
32 return 0
33
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."""
37 pass
38
39 def getcopyinstancelimit(self):
40 """For threading folders, returns the instancelimitname for
41 InstanceLimitedThreads."""
42 raise NotImplementedException
43
44 def getvisiblename(self):
45 return self.name
46
47 def getroot(self):
48 """Returns the root of the folder, in a folder-specific fashion."""
49 return self.root
50
51 def getsep(self):
52 """Returns the separator for this folder type."""
53 return self.sep
54
55 def getfullname(self):
56 if self.getroot():
57 return self.getroot() + self.getsep() + self.getname()
58 else:
59 return self.getname()
60
61 def isuidvalidityok(self, remotefolder):
62 raise NotImplementedException
63
64 def getuidvalidity(self):
65 raise NotImplementedException
66
67 def saveuidvalidity(self, newval):
68 raise NotImplementedException
69
70 def cachemessagelist(self):
71 """Reads the message list from disk or network and stores it in
72 memory for later use. This list will not be re-read from disk or
73 memory unless this function is called again."""
74 raise NotImplementedException
75
76 def getmessagelist(self):
77 """Gets the current message list.
78 You must call cachemessagelist() before calling this function!"""
79 raise NotImplementedException
80
81 def getmessage(self, uid):
82 """Returns the content of the specified message."""
83 raise NotImplementedException
84
85 def savemessage(self, uid, content, flags):
86 """Writes a new message, with the specified uid.
87 If the uid is < 0, the backend should assign a new uid and return it.
88
89 If the backend cannot assign a new uid, it returns the uid passed in
90 WITHOUT saving the message.
91
92 IMAP backend should be the only one that can assign a new uid.
93
94 If the uid is > 0, the backend should set the uid to this, if it can.
95 If it cannot set the uid to that, it will save it anyway.
96 It will return the uid assigned in any case.
97 """
98 raise NotImplementedException
99
100 def getmessageflags(self, uid):
101 """Returns the flags for the specified message."""
102 raise NotImplementedException
103
104 def savemessageflags(self, uid, flags):
105 """Sets the specified message's flags to the given set."""
106 raise NotImplementedException
107
108 def addmessageflags(self, uid, flags):
109 """Adds the specified flags to the message's flag set. If a given
110 flag is already present, it will not be duplicated."""
111 newflags = self.getmessageflags(uid)
112 for flag in flags:
113 if not flag in newflags:
114 newflags.append(flag)
115 newflags.sort()
116 self.savemessageflags(uid, newflags)
117
118 def addmessagesflags(self, uidlist, flags):
119 for uid in uidlist:
120 self.addmessageflags(uid)
121
122 def deletemessageflags(self, uid, flags):
123 """Removes each flag given from the message's flag set. If a given
124 flag is already removed, no action will be taken for that flag."""
125 newflags = self.getmessageflags(uid)
126 for flag in flags:
127 if flag in newflags:
128 newflags.remove(flag)
129 newflags.sort()
130 self.savemessageflags(uid, newflags)
131
132 def deletemessage(self, uid):
133 raise NotImplementedException
134
135 def deletemessages(self, uidlist):
136 for uid in uidlist:
137 self.deletemessage(uid)
138
139 def syncmessagesto_neguid(self, dest, applyto):
140 """Pass 1 of folder synchronization.
141
142 Look for messages in self with a negative uid. These are messages in
143 Maildirs that were not added by us. Try to add them to the dests,
144 and once that succeeds, get the UID, add it to the others for real,
145 add it to local for real, and delete the fake one."""
146
147 for uid in self.getmessagelist().keys():
148 if uid >= 0:
149 continue
150 __main__.ui.copyingmessage(uid, self, applyto)
151 successobject = None
152 successuid = None
153 message = self.getmessage(uid)
154 flags = self.getmessageflags(uid)
155 for tryappend in applyto:
156 successuid = tryappend.savemessage(uid, message, flags)
157 if successuid > 0:
158 successobject = tryappend
159 break
160 # Did we succeed?
161 if successobject != None:
162 # Copy the message to the other remote servers.
163 for appendserver in [x for x in applyto if x != successobject]:
164 appendserver.savemessage(successuid, message, flags)
165 # Copy it to its new name on the local server and delete
166 # the one without a UID.
167 self.savemessage(successuid, message, flags)
168 self.deletemessage(uid)
169 else:
170 # Did not find any server to take this message. Ignore.
171 pass
172
173 def copymessageto(self, uid, applyto):
174 __main__.ui.copyingmessage(uid, self, applyto)
175 message = self.getmessage(uid)
176 flags = self.getmessageflags(uid)
177 for object in applyto:
178 newuid = object.savemessage(uid, message, flags)
179 if newuid > 0 and newuid != uid:
180 # Change the local uid.
181 self.savemessage(newuid, message, flags)
182 self.deletemessage(uid)
183 uid = newuid
184
185
186 def syncmessagesto_copy(self, dest, applyto):
187 """Pass 2 of folder synchronization.
188
189 Look for messages present in self but not in dest. If any, add
190 them to dest."""
191 threads = []
192
193 for uid in self.getmessagelist().keys():
194 if uid < 0: # Ignore messages that pass 1 missed.
195 continue
196 if not uid in dest.getmessagelist():
197 if self.suggeststhreads():
198 self.waitforthread()
199 thread = InstanceLimitedThread(\
200 self.getcopyinstancelimit(),
201 target = self.copymessageto,
202 name = "Copy message %d from %s" % (uid,
203 self.getvisiblename()),
204 args = (uid, applyto))
205 thread.setDaemon(1)
206 thread.start()
207 threads.append(thread)
208 else:
209 self.copymessageto(uid, applyto)
210 for thread in threads:
211 thread.join()
212
213 def syncmessagesto_delete(self, dest, applyto):
214 """Pass 3 of folder synchronization.
215
216 Look for message present in dest but not in self.
217 If any, delete them."""
218 deletelist = []
219 for uid in dest.getmessagelist().keys():
220 if uid < 0:
221 continue
222 if not uid in self.getmessagelist():
223 deletelist.append(uid)
224 if len(deletelist):
225 __main__.ui.deletingmessages(deletelist, applyto)
226 for object in applyto:
227 object.deletemessages(deletelist)
228
229 def syncmessagesto_flags(self, dest, applyto):
230 """Pass 4 of folder synchronization.
231
232 Look for any flag matching issues -- set dest message to have the
233 same flags that we have."""
234 for uid in self.getmessagelist().keys():
235 if uid < 0: # Ignore messages missed by pass 1
236 continue
237 selfflags = self.getmessageflags(uid)
238 destflags = dest.getmessageflags(uid)
239
240 addflags = [x for x in selfflags if x not in destflags]
241 if len(addflags):
242 __main__.ui.addingflags(uid, addflags, applyto)
243 for object in applyto:
244 object.addmessageflags(uid, addflags)
245
246 delflags = [x for x in destflags if x not in selfflags]
247 if len(delflags):
248 __main__.ui.deletingflags(uid, delflags, applyto)
249 for object in applyto:
250 object.deletemessageflags(uid, delflags)
251
252 def syncmessagesto(self, dest, applyto = None):
253 """Syncs messages in this folder to the destination.
254 If applyto is specified, it should be a list of folders (don't forget
255 to include dest!) to which all write actions should be applied.
256 It defaults to [dest] if not specified. It is important that
257 the UID generator be listed first in applyto; that is, the other
258 applyto ones should be the ones that "copy" the main action."""
259 if applyto == None:
260 applyto = [dest]
261
262 self.syncmessagesto_neguid(dest, applyto)
263 self.syncmessagesto_copy(dest, applyto)
264 self.syncmessagesto_delete(dest, applyto)
265
266 # Now, the message lists should be identical wrt the uids present.
267 # (except for potential negative uids that couldn't be placed
268 # anywhere)
269
270 self.syncmessagesto_flags(dest, applyto)
271
272