from Base import BaseFolder
from offlineimap import imaputil, imaplib
-import rfc822, time, string
+from offlineimap.ui import UIBase
+import rfc822, time, string, random, binascii
from StringIO import StringIO
from copy import copy
-import __main__
class IMAPFolder(BaseFolder):
- def __init__(self, imapserver, name, visiblename, accountname):
+ def __init__(self, imapserver, name, visiblename, accountname, repository):
+ self.config = imapserver.config
+ self.expunge = repository.getexpunge()
self.name = imaputil.dequote(name)
self.root = None # imapserver.root
self.sep = imapserver.delim
self.messagelist = None
self.visiblename = visiblename
self.accountname = accountname
+ self.repository = repository
+ self.randomgenerator = random.Random()
+ BaseFolder.__init__(self)
+
+ def getaccountname(self):
+ return self.accountname
def suggeststhreads(self):
return 1
self.imapserver.connectionwait()
def getcopyinstancelimit(self):
- return 'MSGCOPY_' + self.accountname
+ return 'MSGCOPY_' + self.repository.getname()
def getvisiblename(self):
return self.visiblename
try:
# Primes untagged_responses
- imapobj.select(self.getfullname(), readonly = 1)
- maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
+ assert(imapobj.select(self.getfullname(), readonly = 1)[0] == 'OK')
+ try:
+ # Some mail servers do not return an EXISTS response if
+ # the folder is empty.
+ maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
+ except KeyError:
+ return
if maxmsgid < 1:
# No messages; return
return
# Discard the message number.
messagestr = string.split(messagestr, maxsplit = 1)[1]
options = imaputil.flags2hash(messagestr)
- uid = long(options['UID'])
- flags = imaputil.flagsimap2maildir(options['FLAGS'])
- self.messagelist[uid] = {'uid': uid, 'flags': flags}
+ if not options.has_key('UID'):
+ UIBase.getglobalui().warn('No UID in message with options %s' %\
+ str(options),
+ minor = 1)
+ else:
+ uid = long(options['UID'])
+ flags = imaputil.flagsimap2maildir(options['FLAGS'])
+ self.messagelist[uid] = {'uid': uid, 'flags': flags}
def getmessagelist(self):
return self.messagelist
def getmessageflags(self, uid):
return self.messagelist[uid]['flags']
-
+
+ def savemessage_getnewheader(self, content):
+ headername = 'X-OfflineIMAP-%s-' % str(binascii.crc32(content)).replace('-', 'x')
+ headername += binascii.hexlify(self.repository.getname()) + '-'
+ headername += binascii.hexlify(self.getname())
+ headervalue= '%d-' % long(time.time())
+ headervalue += str(self.randomgenerator.random()).replace('.', '')
+ return (headername, headervalue)
+
+ def savemessage_addheader(self, content, headername, headervalue):
+ insertionpoint = content.find("\r\n")
+ leader = content[0:insertionpoint]
+ newline = "\r\n%s: %s" % (headername, headervalue)
+ trailer = content[insertionpoint:]
+ return leader + newline + trailer
+
+ def savemessage_searchforheader(self, imapobj, headername, headervalue):
+ # Now find the UID it got.
+ headervalue = imapobj._quote(headervalue)
+ try:
+ matchinguids = imapobj.uid('search', None,
+ '(HEADER %s %s)' % (headername, headervalue))[1][0]
+ except imapobj.error:
+ # IMAP server doesn't implement search or had a problem.
+ return 0
+ matchinguids = matchinguids.split(' ')
+ if len(matchinguids) != 1 or matchinguids[0] == None:
+ raise ValueError, "While attempting to find UID for message with header %s, got wrong-sized matchinguids of %s" % (headername, str(matchinguids))
+ matchinguids.sort()
+ return long(matchinguids[0])
+
def savemessage(self, uid, content, flags):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname()) # Needed for search
except imapobj.readonly:
- __main__.ui.msgtoreadonly(self, uid, content, flags)
+ UIBase.getglobalui().msgtoreadonly(self, uid, content, flags)
# Return indicating message taken, but no UID assigned.
# Fudge it.
return 0
# In order to get the new uid, we need to save off the message ID.
message = rfc822.Message(StringIO(content))
- mid = message.getheader('Message-Id')
- if mid != None:
- mid = imapobj._quote(mid)
datetuple = rfc822.parsedate(message.getheader('Date'))
# Will be None if missing or not in a valid format.
if datetuple == None:
datetuple = time.localtime()
try:
- date = imaplib.Time2Internaldate(datetuple)
+ if datetuple[0] < 1981:
+ raise ValueError
+ # This could raise a value error if it's not a valid format.
+ date = imaplib.Time2Internaldate(datetuple)
except ValueError:
# Argh, sometimes it's a valid format but year is 0102
- # or something. Argh.
+ # or something. Argh. It seems that Time2Internaldate
+ # will rause a ValueError if the year is 0102 but not 1902,
+ # but some IMAP servers nonetheless choke on 1902.
date = imaplib.Time2Internaldate(time.localtime())
- if content.find("\r\n") == -1: # Convert line endings if not already
- content = content.replace("\n", "\r\n")
+ content = re.sub("[^\r]\n", "\r\n", content)
+
+ (headername, headervalue) = self.savemessage_getnewheader(content)
+ content = self.savemessage_addheader(content, headername,
+ headervalue)
assert(imapobj.append(self.getfullname(),
imaputil.flagsmaildir2imap(flags),
date, content)[0] == 'OK')
+
# Checkpoint. Let it write out the messages, etc.
assert(imapobj.check()[0] == 'OK')
- if mid == None:
- # No message ID in original message -- no sense trying to
- # search for it.
- return 0
- # Now find the UID it got.
- try:
- matchinguids = imapobj.uid('search', None,
- '(HEADER Message-Id %s)' % mid)[1][0]
- except imapobj.error:
- # IMAP server doesn't implement search or had a problem.
- return 0
- matchinguids = matchinguids.split(' ')
- if len(matchinguids) != 1 or matchinguids[0] == None:
- return 0
- matchinguids.sort()
+
+ # Keep trying until we get the UID.
try:
- uid = long(matchinguids[-1])
+ uid = self.savemessage_searchforheader(imapobj, headername,
+ headervalue)
except ValueError:
- return 0
- self.messagelist[uid] = {'uid': uid, 'flags': flags}
- return uid
+ assert(imapobj.noop()[0] == 'OK')
+ uid = self.savemessage_searchforheader(imapobj, headername,
+ headervalue)
finally:
self.imapserver.releaseconnection(imapobj)
+ self.messagelist[uid] = {'uid': uid, 'flags': flags}
+ return uid
+
def savemessageflags(self, uid, flags):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
- __main__.ui.flagstoreadonly(self, [uid], flags)
+ UIBase.getglobalui().flagstoreadonly(self, [uid], flags)
return
result = imapobj.uid('store', '%d' % uid, 'FLAGS',
imaputil.flagsmaildir2imap(flags))
def addmessageflags(self, uid, flags):
self.addmessagesflags([uid], flags)
+ def addmessagesflags_noconvert(self, uidlist, flags):
+ self.processmessagesflags('+', uidlist, flags)
+
def addmessagesflags(self, uidlist, flags):
+ """This is here for the sake of UIDMaps.py -- deletemessages must
+ add flags and get a converted UID, and if we don't have noconvert,
+ then UIDMaps will try to convert it twice."""
+ self.addmessagesflags_noconvert(uidlist, flags)
+
+ def deletemessageflags(self, uid, flags):
+ self.deletemessagesflags([uid], flags)
+
+ def deletemessagesflags(self, uidlist, flags):
+ self.processmessagesflags('-', uidlist, flags)
+
+ def processmessagesflags(self, operation, uidlist, flags):
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
- __main__.ui.flagstoreadonly(self, uidlist, flags)
+ UIBase.getglobalui().flagstoreadonly(self, uidlist, flags)
return
r = imapobj.uid('store',
imaputil.listjoin(uidlist),
- '+FLAGS',
+ operation + 'FLAGS',
imaputil.flagsmaildir2imap(flags))
assert r[0] == 'OK', 'Error with store: ' + r[1]
r = r[1]
except ValueError: # Let it slide if it's not in the list
pass
for uid in needupdate:
- for flag in flags:
- if not flag in self.messagelist[uid]['flags']:
- self.messagelist[uid]['flags'].append(flag)
- self.messagelist[uid]['flags'].sort()
+ if operation == '+':
+ for flag in flags:
+ if not flag in self.messagelist[uid]['flags']:
+ self.messagelist[uid]['flags'].append(flag)
+ self.messagelist[uid]['flags'].sort()
+ elif operation == '-':
+ for flag in flags:
+ if flag in self.messagelist[uid]['flags']:
+ self.messagelist[uid]['flags'].remove(flag)
def deletemessage(self, uid):
- self.deletemessages([uid])
+ self.deletemessages_noconvert([uid])
def deletemessages(self, uidlist):
+ self.deletemessages_noconvert(uidlist)
+
+ def deletemessages_noconvert(self, uidlist):
# Weed out ones not in self.messagelist
uidlist = [uid for uid in uidlist if uid in self.messagelist]
if not len(uidlist):
return
- self.addmessagesflags(uidlist, ['T'])
+ self.addmessagesflags_noconvert(uidlist, ['T'])
imapobj = self.imapserver.acquireconnection()
try:
try:
imapobj.select(self.getfullname())
except imapobj.readonly:
- __main__.ui.deletereadonly(self, uidlist)
+ UIBase.getglobalui().deletereadonly(self, uidlist)
return
- assert(imapobj.expunge()[0] == 'OK')
+ if self.expunge:
+ assert(imapobj.expunge()[0] == 'OK')
finally:
self.imapserver.releaseconnection(imapobj)
for uid in uidlist: