]> code.delx.au - offlineimap/blob - head/offlineimap/folder/Base.py
/head: changeset 69
[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 args = (uid, applyto))
203 thread.start()
204 threads.append(thread)
205 else:
206 self.copymessageto(uid, applyto)
207 for thread in threads:
208 thread.join()
209
210 def syncmessagesto_delete(self, dest, applyto):
211 """Pass 3 of folder synchronization.
212
213 Look for message present in dest but not in self.
214 If any, delete them."""
215 deletelist = []
216 for uid in dest.getmessagelist().keys():
217 if uid < 0:
218 continue
219 if not uid in self.getmessagelist():
220 deletelist.append(uid)
221 if len(deletelist):
222 __main__.ui.deletingmessages(deletelist, applyto)
223 for object in applyto:
224 object.deletemessages(deletelist)
225
226 def syncmessagesto_flags(self, dest, applyto):
227 """Pass 4 of folder synchronization.
228
229 Look for any flag matching issues -- set dest message to have the
230 same flags that we have."""
231 for uid in self.getmessagelist().keys():
232 if uid < 0: # Ignore messages missed by pass 1
233 continue
234 selfflags = self.getmessageflags(uid)
235 destflags = dest.getmessageflags(uid)
236
237 addflags = [x for x in selfflags if x not in destflags]
238 if len(addflags):
239 __main__.ui.addingflags(uid, addflags, applyto)
240 for object in applyto:
241 object.addmessageflags(uid, addflags)
242
243 delflags = [x for x in destflags if x not in selfflags]
244 if len(delflags):
245 __main__.ui.deletingflags(uid, delflags, applyto)
246 for object in applyto:
247 object.deletemessageflags(uid, delflags)
248
249 def syncmessagesto(self, dest, applyto = None):
250 """Syncs messages in this folder to the destination.
251 If applyto is specified, it should be a list of folders (don't forget
252 to include dest!) to which all write actions should be applied.
253 It defaults to [dest] if not specified. It is important that
254 the UID generator be listed first in applyto; that is, the other
255 applyto ones should be the ones that "copy" the main action."""
256 if applyto == None:
257 applyto = [dest]
258
259 self.syncmessagesto_neguid(dest, applyto)
260 self.syncmessagesto_copy(dest, applyto)
261 self.syncmessagesto_delete(dest, applyto)
262
263 # Now, the message lists should be identical wrt the uids present.
264 # (except for potential negative uids that couldn't be placed
265 # anywhere)
266
267 self.syncmessagesto_flags(dest, applyto)
268
269