@@ -78,6 +78,9 @@
# 30 days.
VANISHED_AGE = (60 * 60 * 24 * 30)
+# Regex used to remove problematic characters from oldmail filenames
+STRIP_CHAR_RE = r'[/\:;<>|]+'
+
# Kerberos authentication state constants
(GSS_STATE_STEP, GSS_STATE_WRAP) = (0, 1)
@@ -338,13 +341,14 @@
__init__(self, **args)
__del__(self)
- initialize(self)
+ initialize(self, options)
checkconf(self)
'''
def __init__(self, **args):
self.msgnum_by_msgid = {}
self.msgid_by_msgnum = {}
+ self.sorted_msgnum_msgid = {}
self.msgsizes = {}
self.headercache = {}
self.oldmail = {}
@@ -387,29 +391,53 @@
self.log.trace('i == %d' % i)
if not self.__initialized:
raise getmailOperationError('not initialized')
- return sorted(self.msgid_by_msgnum.items())[i][1]
+ return self.sorted_msgnum_msgid[i][1]
def _read_oldmailfile(self):
'''Read contents of oldmail file.'''
self.log.trace()
- try:
- for line in open(self.oldmail_filename, 'rb'):
- line = line.strip()
- if not line or not '\0' in line:
- # malformed
- continue
- try:
- (msgid, timestamp) = line.split('\0', 1)
- self.oldmail[msgid] = int(timestamp)
- except ValueError:
- # malformed
- self.log.info('skipped malformed line "%r" for %s'
- % (line, self)
- + os.linesep)
- self.log.moreinfo('read %i uids for %s' % (len(self.oldmail), self)
- + os.linesep)
- except IOError:
- self.log.moreinfo('no oldmail file for %s' % self + os.linesep)
+ # Read separate oldmail file for each folder (IMAP).
+ mailboxes = self.conf.get('mailboxes', (None, ))
+ for mailbox in mailboxes:
+ filename = self.oldmail_filename
+ mailbox_logstr = ''
+ if mailbox:
+ mailbox_logstr = 'mailbox %s in ' % mailbox
+ filename += '-' + re.sub(STRIP_CHAR_RE, '.', mailbox)
+ if not os.path.isfile(filename):
+ # use existing oldmail file, to enable smooth migration to
+ # oldmail filenames with appended mailbox name
+ self.log.info(
+ 'oldmail file %s not found, reverting to %s%s'
+ % (filename, self.oldmail_filename, os.linesep)
+ )
+ filename = self.oldmail_filename
+ try:
+ oldlength = len(self.oldmail)
+ for line in open(filename, 'rb'):
+ line = line.strip()
+ if not line or not '\0' in line:
+ # malformed
+ continue
+ try:
+ (msgid, timestamp) = line.split('\0', 1)
+ self.oldmail[msgid] = int(timestamp)
+ except ValueError:
+ # malformed
+ self.log.info(
+ 'skipped malformed line "%r" for %s%s'
+ % (line, self, os.linesep)
+ )
+ self.log.moreinfo(
+ 'read %i uids for %s%s%s'
+ % (len(self.oldmail) - oldlength, mailbox_logstr, self,
+ os.linesep)
+ )
+ except IOError:
+ self.log.moreinfo('no oldmail file for %s%s%s'
+ % (mailbox_logstr, self, os.linesep))
+ self.log.moreinfo('read %i uids in total for %s%s'
+ % (len(self.oldmail), self, os.linesep))
def write_oldmailfile(self, forget_deleted=True):
'''Write oldmail info to oldmail file.'''
@@ -417,9 +445,18 @@
if (self.__oldmail_written or not self.__initialized
or not self.gotmsglist):
return
- wrote = 0
- try:
- f = updatefile(self.oldmail_filename)
+ mailboxes = self.conf.get('mailboxes', (None,))
+ wrote = {}
+ for mailbox in mailboxes:
+ wrote[mailbox] = 0
+ f = {}
+ try:
+ # Write each folders msgids/data to a separate oldmail file.
+ for mailbox in mailboxes:
+ filename = self.oldmail_filename
+ if mailbox:
+ filename += '-' + re.sub(STRIP_CHAR_RE, '.', mailbox)
+ f[mailbox] = updatefile(filename)
msgids = frozenset(
self.__delivered.keys()
).union(frozenset(self.oldmail.keys()))
@@ -433,18 +470,34 @@
# not deleted, remember this one's time stamp
t = self.oldmail.get(msgid, self.timestamp)
self.log.debug(' timestamp %s' % t + os.linesep)
- f.write('%s\0%i%s' % (msgid, t, os.linesep))
- wrote += 1
- f.close()
- self.log.moreinfo('wrote %i uids for %s' % (wrote, self)
- + os.linesep)
+ mailbox = None
+ match = re.match('^\d+/(.+?)/\d+$', msgid)
+ if match:
+ mailbox = match.group(1)
+ if mailbox in mailboxes:
+ f[mailbox].write('%s\0%i%s' % (msgid, t, os.linesep))
+ wrote[mailbox] += 1
+ else:
+ self.log.info('mailbox %s unknown for msgid %s%s'
+ % (mailbox, msgid, os.linesep))
+ for mailbox in mailboxes:
+ f[mailbox].close()
+ mailbox_logstr = ''
+ if mailbox != None:
+ mailbox_logstr = 'mailbox %s in ' % mailbox
+ self.log.moreinfo('wrote %i uids for %s%s%s'
+ % (wrote[mailbox], mailbox_logstr, self,
+ os.linesep))
except IOError, o:
self.log.error('failed writing oldmail file for %s (%s)'
% (self, o) + os.linesep)
- f.abort()
+ for file in f:
+ file.abort()
self.__oldmail_written = True
- def initialize(self):
+ def initialize(self, options):
+ # Options - dict of application-wide settings, including ones that
+ # aren't used in initializing the retriever.
self.log.trace()
self.checkconf()
# socket.ssl() and socket timeouts are incompatible in Python 2.3
@@ -456,13 +509,16 @@
# strip problematic characters from oldmail filename. Mostly for
# non-Unix systems; only / is illegal in a Unix path component
oldmail_filename = re.sub(
- r'[/\:;<>|]+', '-',
+ STRIP_CHAR_RE, '-',
'oldmail-%(server)s-%(port)i-%(username)s' % self.conf
)
self.oldmail_filename = os.path.join(self.conf['getmaildir'],
oldmail_filename)
self._read_oldmailfile()
self.received_from = None
+
+ self.app_options = options
+
self.__initialized = True
def delivered(self, msgid):
@@ -549,6 +605,7 @@
self.msgid_by_msgnum[msgnum] = msgid
self.log.debug('Message IDs: %s'
% sorted(self.msgnum_by_msgid.keys()) + os.linesep)
+ self.sorted_msgnum_msgid = sorted(self.msgid_by_msgnum.items())
response, msglist, octets = self.conn.list()
for line in msglist:
msgnum = int(line.split()[0])
@@ -590,14 +647,14 @@
parser = email.Parser.HeaderParser()
return parser.parsestr(os.linesep.join(headerlist))
- def initialize(self):
+ def initialize(self, options):
self.log.trace()
# Handle password
if self.conf.get('password', None) is None:
self.conf['password'] = getpass.getpass(
'Enter password for %s: ' % self
)
- RetrieverSkeleton.initialize(self)
+ RetrieverSkeleton.initialize(self, options)
try:
self._connect()
if self.conf['use_apop']:
@@ -653,9 +710,9 @@
be specified as "delivered-to:2".
'''
- def initialize(self):
+ def initialize(self, options):
self.log.trace()
- POP3RetrieverBase.initialize(self)
+ POP3RetrieverBase.initialize(self, options)
self.envrecipname = (
self.conf['envelope_recipient'].split(':')[0].lower()
)
@@ -827,8 +884,14 @@
self.conn.close()
self.log.debug('selecting mailbox "%s"' % mailbox + os.linesep)
try:
- response = self._parse_imapcmdresponse('SELECT', mailbox)
- count = int(response[-1]) # use *last* EXISTS returned
+ #response = self._parse_imapcmdresponse('SELECT', mailbox)
+ #count = int(response[-1]) # use *last* EXISTS returned
+ if self.app_options['delete'] or self.app_options['delete_after']:
+ read_only = False
+ else:
+ read_only = True
+ status, count = self.conn.select(mailbox, read_only)
+ count = int(count[-1])
uidvalidity = self.conn.response('UIDVALIDITY')[1][0]
except imaplib.IMAP4.error, o:
raise getmailOperationError('IMAP error (%s)' % o)
@@ -950,7 +1013,7 @@
self.log.trace()
return self._getmsgpartbyid(msgid, '(RFC822[header])')
- def initialize(self):
+ def initialize(self, options):
self.log.trace()
# Handle password
if (self.conf.get('password', None) is None
@@ -958,7 +1021,7 @@
self.conf['password'] = getpass.getpass(
'Enter password for %s: ' % self
)
- RetrieverSkeleton.initialize(self)
+ RetrieverSkeleton.initialize(self, options)
try:
self.log.trace('trying self._connect()' + os.linesep)
self._connect()
@@ -1025,9 +1088,9 @@
be specified as "delivered-to:2".
'''
- def initialize(self):
+ def initialize(self, options):
self.log.trace()
- IMAPRetrieverBase.initialize(self)
+ IMAPRetrieverBase.initialize(self, options)
self.envrecipname = (self.conf['envelope_recipient'].split(':')
[0].lower())
if self.envrecipname in NOT_ENVELOPE_RECIPIENT_HEADERS:
|