]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/folder/IMAP.py
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 Base
import BaseFolder
20 from offlineimap
import imaputil
, imaplib
21 from offlineimap
.ui
import UIBase
22 import rfc822
, time
, string
23 from StringIO
import StringIO
27 class IMAPFolder(BaseFolder
):
28 def __init__(self
, imapserver
, name
, visiblename
, accountname
, repository
):
29 self
.config
= imapserver
.config
31 if self
.config
.has_option(accountname
, 'expunge'):
32 self
.expunge
= self
.config
.getboolean(accountname
, 'expunge')
33 self
.name
= imaputil
.dequote(name
)
34 self
.root
= None # imapserver.root
35 self
.sep
= imapserver
.delim
36 self
.imapserver
= imapserver
37 self
.messagelist
= None
38 self
.visiblename
= visiblename
39 self
.accountname
= accountname
40 self
.repository
= repository
42 def getaccountname(self
):
43 return self
.accountname
45 def suggeststhreads(self
):
48 def waitforthread(self
):
49 self
.imapserver
.connectionwait()
51 def getcopyinstancelimit(self
):
52 return 'MSGCOPY_' + self
.accountname
54 def getvisiblename(self
):
55 return self
.visiblename
57 def getuidvalidity(self
):
58 imapobj
= self
.imapserver
.acquireconnection()
60 # Primes untagged_responses
61 imapobj
.select(self
.getfullname(), readonly
= 1)
62 return long(imapobj
.untagged_responses
['UIDVALIDITY'][0])
64 self
.imapserver
.releaseconnection(imapobj
)
66 def cachemessagelist(self
):
67 imapobj
= self
.imapserver
.acquireconnection()
71 # Primes untagged_responses
72 imapobj
.select(self
.getfullname(), readonly
= 1)
73 maxmsgid
= long(imapobj
.untagged_responses
['EXISTS'][0])
78 # Now, get the flags and UIDs for these.
79 # We could conceivably get rid of maxmsgid and just say
81 response
= imapobj
.fetch('1:%d' % maxmsgid
, '(FLAGS UID)')[1]
83 self
.imapserver
.releaseconnection(imapobj
)
84 for messagestr
in response
:
85 # Discard the message number.
86 messagestr
= string
.split(messagestr
, maxsplit
= 1)[1]
87 options
= imaputil
.flags2hash(messagestr
)
88 if not options
.has_key('UID'):
89 UIBase
.getglobalui().warn('No UID in message with options %s' %\
93 uid
= long(options
['UID'])
94 flags
= imaputil
.flagsimap2maildir(options
['FLAGS'])
95 self
.messagelist
[uid
] = {'uid': uid
, 'flags': flags
}
97 def getmessagelist(self
):
98 return self
.messagelist
100 def getmessage(self
, uid
):
101 imapobj
= self
.imapserver
.acquireconnection()
103 imapobj
.select(self
.getfullname(), readonly
= 1)
104 return imapobj
.uid('fetch', '%d' % uid
, '(BODY.PEEK[])')[1][0][1].replace("\r\n", "\n")
106 self
.imapserver
.releaseconnection(imapobj
)
108 def getmessageflags(self
, uid
):
109 return self
.messagelist
[uid
]['flags']
111 def savemessage(self
, uid
, content
, flags
):
112 imapobj
= self
.imapserver
.acquireconnection()
115 imapobj
.select(self
.getfullname()) # Needed for search
116 except imapobj
.readonly
:
117 UIBase
.getglobalui().msgtoreadonly(self
, uid
, content
, flags
)
118 # Return indicating message taken, but no UID assigned.
122 # This backend always assigns a new uid, so the uid arg is ignored.
123 # In order to get the new uid, we need to save off the message ID.
125 message
= rfc822
.Message(StringIO(content
))
126 mid
= message
.getheader('Message-Id')
128 mid
= imapobj
._quote
(mid
)
129 datetuple
= rfc822
.parsedate(message
.getheader('Date'))
130 # Will be None if missing or not in a valid format.
131 if datetuple
== None:
132 datetuple
= time
.localtime()
134 if datetuple
[0] < 1981:
136 # This could raise a value error if it's not a valid format.
137 date
= imaplib
.Time2Internaldate(datetuple
)
139 # Argh, sometimes it's a valid format but year is 0102
140 # or something. Argh. It seems that Time2Internaldate
141 # will rause a ValueError if the year is 0102 but not 1902,
142 # but some IMAP servers nonetheless choke on 1902.
143 date
= imaplib
.Time2Internaldate(time
.localtime())
145 if content
.find("\r\n") == -1: # Convert line endings if not already
146 content
= content
.replace("\n", "\r\n")
148 assert(imapobj
.append(self
.getfullname(),
149 imaputil
.flagsmaildir2imap(flags
),
150 date
, content
)[0] == 'OK')
151 # Checkpoint. Let it write out the messages, etc.
152 assert(imapobj
.check()[0] == 'OK')
154 # No message ID in original message -- no sense trying to
157 # Now find the UID it got.
159 matchinguids
= imapobj
.uid('search', None,
160 '(HEADER Message-Id %s)' % mid
)[1][0]
161 except imapobj
.error
:
162 # IMAP server doesn't implement search or had a problem.
164 matchinguids
= matchinguids
.split(' ')
165 if len(matchinguids
) != 1 or matchinguids
[0] == None:
169 uid
= long(matchinguids
[-1])
172 self
.messagelist
[uid
] = {'uid': uid
, 'flags': flags
}
175 self
.imapserver
.releaseconnection(imapobj
)
177 def savemessageflags(self
, uid
, flags
):
178 imapobj
= self
.imapserver
.acquireconnection()
181 imapobj
.select(self
.getfullname())
182 except imapobj
.readonly
:
183 UIBase
.getglobalui().flagstoreadonly(self
, [uid
], flags
)
185 result
= imapobj
.uid('store', '%d' % uid
, 'FLAGS',
186 imaputil
.flagsmaildir2imap(flags
))
187 assert result
[0] == 'OK', 'Error with store: ' + r
[1]
189 self
.imapserver
.releaseconnection(imapobj
)
190 result
= result
[1][0]
192 self
.messagelist
[uid
]['flags'] = flags
194 flags
= imaputil
.flags2hash(imaputil
.imapsplit(result
)[1])['FLAGS']
195 self
.messagelist
[uid
]['flags'] = imaputil
.flagsimap2maildir(flags
)
197 def addmessageflags(self
, uid
, flags
):
198 self
.addmessagesflags([uid
], flags
)
200 def addmessagesflags(self
, uidlist
, flags
):
201 imapobj
= self
.imapserver
.acquireconnection()
204 imapobj
.select(self
.getfullname())
205 except imapobj
.readonly
:
206 UIBase
.getglobalui().flagstoreadonly(self
, uidlist
, flags
)
208 r
= imapobj
.uid('store',
209 imaputil
.listjoin(uidlist
),
211 imaputil
.flagsmaildir2imap(flags
))
212 assert r
[0] == 'OK', 'Error with store: ' + r
[1]
215 self
.imapserver
.releaseconnection(imapobj
)
216 # Some IMAP servers do not always return a result. Therefore,
217 # only update the ones that it talks about, and manually fix
219 needupdate
= copy(uidlist
)
222 # Compensate for servers that don't return anything from
225 attributehash
= imaputil
.flags2hash(imaputil
.imapsplit(result
)[1])
226 if not ('UID' in attributehash
and 'FLAGS' in attributehash
):
227 # Compensate for servers that don't return a UID attribute.
229 flags
= attributehash
['FLAGS']
230 uid
= long(attributehash
['UID'])
231 self
.messagelist
[uid
]['flags'] = imaputil
.flagsimap2maildir(flags
)
233 needupdate
.remove(uid
)
234 except ValueError: # Let it slide if it's not in the list
236 for uid
in needupdate
:
238 if not flag
in self
.messagelist
[uid
]['flags']:
239 self
.messagelist
[uid
]['flags'].append(flag
)
240 self
.messagelist
[uid
]['flags'].sort()
242 def deletemessage(self
, uid
):
243 self
.deletemessages([uid
])
245 def deletemessages(self
, uidlist
):
246 # Weed out ones not in self.messagelist
247 uidlist
= [uid
for uid
in uidlist
if uid
in self
.messagelist
]
251 self
.addmessagesflags(uidlist
, ['T'])
252 imapobj
= self
.imapserver
.acquireconnection()
255 imapobj
.select(self
.getfullname())
256 except imapobj
.readonly
:
257 UIBase
.getglobalui().deletereadonly(self
, uidlist
)
260 assert(imapobj
.expunge()[0] == 'OK')
262 self
.imapserver
.releaseconnection(imapobj
)
264 del self
.messagelist
[uid
]