]> code.delx.au - offlineimap/blob - offlineimap/folder/UIDMaps.py
Update FSF address
[offlineimap] / offlineimap / folder / UIDMaps.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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 from threading import *
20 from offlineimap import threadutil
21 from offlineimap.threadutil import InstanceLimitedThread
22 from offlineimap.ui import UIBase
23 from IMAP import IMAPFolder
24 import os.path, re
25
26 class MappingFolderMixIn:
27 def _initmapping(self):
28 self.maplock = Lock()
29 (self.diskr2l, self.diskl2r) = self._loadmaps()
30 self._mb = self.__class__.__bases__[1]
31
32 def _getmapfilename(self):
33 return os.path.join(self.repository.getmapdir(),
34 self.getfolderbasename())
35
36 def _loadmaps(self):
37 self.maplock.acquire()
38 try:
39 mapfilename = self._getmapfilename()
40 if not os.path.exists(mapfilename):
41 return ({}, {})
42 file = open(mapfilename, 'rt')
43 r2l = {}
44 l2r = {}
45 while 1:
46 line = file.readline()
47 if not len(line):
48 break
49 line = line.strip()
50 (str1, str2) = line.split(':')
51 loc = long(str1)
52 rem = long(str2)
53 r2l[rem] = loc
54 l2r[loc] = rem
55 return (r2l, l2r)
56 finally:
57 self.maplock.release()
58
59 def _savemaps(self, dolock = 1):
60 mapfilename = self._getmapfilename()
61 if dolock: self.maplock.acquire()
62 try:
63 file = open(mapfilename + ".tmp", 'wt')
64 for (key, value) in self.diskl2r.iteritems():
65 file.write("%d:%d\n" % (key, value))
66 file.close()
67 os.rename(mapfilename + '.tmp', mapfilename)
68 finally:
69 if dolock: self.maplock.release()
70
71 def _uidlist(self, mapping, items):
72 return [mapping[x] for x in items]
73
74 def cachemessagelist(self):
75 self._mb.cachemessagelist(self)
76 reallist = self._mb.getmessagelist(self)
77
78 self.maplock.acquire()
79 try:
80 # OK. Now we've got a nice list. First, delete things from the
81 # summary that have been deleted from the folder.
82
83 for luid in self.diskl2r.keys():
84 if not reallist.has_key(luid):
85 ruid = self.diskl2r[luid]
86 del self.diskr2l[ruid]
87 del self.diskl2r[luid]
88
89 # Now, assign negative UIDs to local items.
90 self._savemaps(dolock = 0)
91 nextneg = -1
92
93 self.r2l = self.diskr2l.copy()
94 self.l2r = self.diskl2r.copy()
95
96 for luid in reallist.keys():
97 if not self.l2r.has_key(luid):
98 ruid = nextneg
99 nextneg -= 1
100 self.l2r[luid] = ruid
101 self.r2l[ruid] = luid
102 finally:
103 self.maplock.release()
104
105 def getmessagelist(self):
106 """Gets the current message list.
107 You must call cachemessagelist() before calling this function!"""
108
109 retval = {}
110 localhash = self._mb.getmessagelist(self)
111 self.maplock.acquire()
112 try:
113 for key, value in localhash.items():
114 try:
115 key = self.l2r[key]
116 except KeyError:
117 # Sometimes, the IMAP backend may put in a new message,
118 # then this function acquires the lock before the system
119 # has the chance to note it in the mapping. In that case,
120 # just ignore it.
121 continue
122 value = value.copy()
123 value['uid'] = self.l2r[value['uid']]
124 retval[key] = value
125 return retval
126 finally:
127 self.maplock.release()
128
129 def getmessage(self, uid):
130 """Returns the content of the specified message."""
131 return self._mb.getmessage(self, self.r2l[uid])
132
133 def savemessage(self, uid, content, flags):
134 """Writes a new message, with the specified uid.
135 If the uid is < 0, the backend should assign a new uid and return it.
136
137 If the backend cannot assign a new uid, it returns the uid passed in
138 WITHOUT saving the message.
139
140 If the backend CAN assign a new uid, but cannot find out what this UID
141 is (as is the case with many IMAP servers), it returns 0 but DOES save
142 the message.
143
144 IMAP backend should be the only one that can assign a new uid.
145
146 If the uid is > 0, the backend should set the uid to this, if it can.
147 If it cannot set the uid to that, it will save it anyway.
148 It will return the uid assigned in any case.
149 """
150 if uid < 0:
151 # We cannot assign a new uid.
152 return uid
153 if uid in self.r2l:
154 self.savemessageflags(uid, flags)
155 return uid
156 newluid = self._mb.savemessage(self, -1, content, flags)
157 if newluid < 1:
158 raise ValueError, "Backend could not find uid for message"
159 self.maplock.acquire()
160 try:
161 self.diskl2r[newluid] = uid
162 self.diskr2l[uid] = newluid
163 self.l2r[newluid] = uid
164 self.r2l[uid] = newluid
165 self._savemaps(dolock = 0)
166 finally:
167 self.maplock.release()
168
169 def getmessageflags(self, uid):
170 return self._mb.getmessageflags(self, self.r2l[uid])
171
172 def savemessageflags(self, uid, flags):
173 self._mb.savemessageflags(self, self.r2l[uid], flags)
174
175 def addmessageflags(self, uid, flags):
176 self._mb.addmessageflags(self, self.r2l[uid], flags)
177
178 def addmessagesflags(self, uidlist, flags):
179 self._mb.addmessagesflags(self, self._uidlist(self.r2l, uidlist),
180 flags)
181
182 def _mapped_delete(self, uidlist):
183 self.maplock.acquire()
184 try:
185 needssave = 0
186 for ruid in uidlist:
187 luid = self.r2l[ruid]
188 del self.r2l[ruid]
189 del self.l2r[luid]
190 if ruid > 0:
191 del self.diskr2l[ruid]
192 del self.diskl2r[luid]
193 needssave = 1
194 if needssave:
195 self._savemaps(dolock = 0)
196 finally:
197 self.maplock.release()
198
199 def deletemessageflags(self, uid, flags):
200 self._mb.deletemessageflags(self, self.r2l[uid], flags)
201
202 def deletemessagesflags(self, uidlist, flags):
203 self._mb.deletemessagesflags(self, self._uidlist(self.r2l, uidlist),
204 flags)
205
206 def deletemessage(self, uid):
207 self._mb.deletemessage(self, self.r2l[uid])
208 self._mapped_delete([uid])
209
210 def deletemessages(self, uidlist):
211 self._mb.deletemessages(self, self._uidlist(self.r2l, uidlist))
212 self._mapped_delete(uidlist)
213
214 #def syncmessagesto_neguid_msg(self, uid, dest, applyto, register = 1):
215 # does not need changes because it calls functions that make the changes
216 # same goes for all other sync messages types.
217
218
219 # Define a class for local part of IMAP.
220 class MappedIMAPFolder(MappingFolderMixIn, IMAPFolder):
221 def __init__(self, *args, **kwargs):
222 apply(IMAPFolder.__init__, (self,) + args, kwargs)
223 self._initmapping()