]> code.delx.au - offlineimap/blob - offlineimap/folder/UIDMaps.py
Merge branch 'master' of http://git.complete.org/offlineimap
[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, rtime):
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, rtime)
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 getmessagetime(self, uid):
173 return None
174
175 def savemessageflags(self, uid, flags):
176 self._mb.savemessageflags(self, self.r2l[uid], flags)
177
178 def addmessageflags(self, uid, flags):
179 self._mb.addmessageflags(self, self.r2l[uid], flags)
180
181 def addmessagesflags(self, uidlist, flags):
182 self._mb.addmessagesflags(self, self._uidlist(self.r2l, uidlist),
183 flags)
184
185 def _mapped_delete(self, uidlist):
186 self.maplock.acquire()
187 try:
188 needssave = 0
189 for ruid in uidlist:
190 luid = self.r2l[ruid]
191 del self.r2l[ruid]
192 del self.l2r[luid]
193 if ruid > 0:
194 del self.diskr2l[ruid]
195 del self.diskl2r[luid]
196 needssave = 1
197 if needssave:
198 self._savemaps(dolock = 0)
199 finally:
200 self.maplock.release()
201
202 def deletemessageflags(self, uid, flags):
203 self._mb.deletemessageflags(self, self.r2l[uid], flags)
204
205 def deletemessagesflags(self, uidlist, flags):
206 self._mb.deletemessagesflags(self, self._uidlist(self.r2l, uidlist),
207 flags)
208
209 def deletemessage(self, uid):
210 self._mb.deletemessage(self, self.r2l[uid])
211 self._mapped_delete([uid])
212
213 def deletemessages(self, uidlist):
214 self._mb.deletemessages(self, self._uidlist(self.r2l, uidlist))
215 self._mapped_delete(uidlist)
216
217 #def syncmessagesto_neguid_msg(self, uid, dest, applyto, register = 1):
218 # does not need changes because it calls functions that make the changes
219 # same goes for all other sync messages types.
220
221
222 # Define a class for local part of IMAP.
223 class MappedIMAPFolder(MappingFolderMixIn, IMAPFolder):
224 def __init__(self, *args, **kwargs):
225 apply(IMAPFolder.__init__, (self,) + args, kwargs)
226 self._initmapping()