Style improvements and cache from settings validation added
[imap-fix-internaldate] / src / mail_iterator.py
CommitLineData
c9da760a
PD
1'''
2mail_iterator.py - The module contains the MailIterator class.
3
4Copyright (c) 2012 Intra2net AG
5Author: Plamen Dimitrov
6
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
c9da760a
PD
16'''
17
18import imaplib
19import re
20import time
8fe4e3ff 21import logging
c9da760a 22
8301e589
PD
23MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
24UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)')
c9da760a 25
a05fef0a
PD
26#imaplib.Debug = 4
27
c9da760a
PD
28class MailIterator:
29 """This class communicates with the e-mail server."""
c9da760a 30
7a1d4c35
PD
31 # class attributes
32 # IMAP4_SSL for connection with an IMAP server
33 mail_con = None
34 # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes
35 mailboxes = None
97bd6bea
PD
36 # logged in status
37 logged_in = None
95467f63
PD
38 # skip shared folders
39 skip_shared_folders = None
7a1d4c35 40
95467f63 41 def __init__(self, server, username, password, skip_shared_folders = False):
c9da760a 42 """Creates a connection and a user session."""
97bd6bea
PD
43 try:
44 self.mail_con = imaplib.IMAP4_SSL(server)
45 self.mail_con.login(username, password)
46 logging.info("Logged in as %s.", username)
47 except:
48 self.logged_in = False
49 raise UserWarning("Could not log in as user " + username + ".")
50 self.logged_in = True
3103ebb0 51 try:
db3f09a6 52 _result, self.mailboxes = self.mail_con.list()
3103ebb0 53 except:
c9da760a 54 raise UserWarning("Could not retrieve mailboxes for user " + username + ".")
95467f63 55 self.skip_shared_folders = skip_shared_folders
c9da760a
PD
56
57 def __del__(self):
58 """Closes the connection and the user session."""
97bd6bea 59 if(self.logged_in):
db3f09a6
PD
60 self.mail_con.close()
61 self.mail_con.logout()
c9da760a
PD
62
63 def __iter__(self):
8301e589 64 """Iterates through all mailboxes, returns (uidval,name)."""
c9da760a 65 for mailbox in self.mailboxes:
8fe4e3ff 66 logging.debug("Checking mailbox %s.", mailbox)
8a9d4c89 67 mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups()
95467f63
PD
68 # detect if mailbox is shared and if skip flag is set iterate further
69 if(self.skip_shared_folders and mailbox[2].split(mailbox[1])[0] == '"user'):
70 logging.info("Mailbox %s is shared and therefore skipped.", mailbox[2])
71 continue
72 # retrieve uidvalidity
3103ebb0 73 try:
db3f09a6 74 _result, data = self.mail_con.status(mailbox[2], '(UIDVALIDITY)')
3103ebb0 75 except:
8301e589 76 raise UserWarning("Could not retrieve mailbox uidvalidity.")
8a9d4c89 77 uidval = UIDVAL_RESP.match(data[0].decode('iso-8859-1')).groups()
8fe4e3ff 78 logging.debug("Extracted mailbox info is %s %s.", data[0], uidval)
95467f63
PD
79 # select mailbox if writable
80 try:
81 self.mail_con.select(mailbox[2])
82 except self.mail_con.readonly:
83 logging.warning("Mailbox %s is not writable and therefore skipped.", mailbox[2])
84 continue
8301e589 85 yield (mailbox[2], uidval[1])
c9da760a
PD
86
87 def fetch_messages(self):
88 """Fetches the messages from the current mailbox, return list of uids."""
3103ebb0 89 try:
db3f09a6 90 _result, data = self.mail_con.uid('search', None, "ALL")
3103ebb0 91 except:
c9da760a 92 raise UserWarning("Could not fetch messages.")
c9da760a
PD
93 mailid_list = data[0].split()
94 return mailid_list
95
96 def fetch_internal_date(self, mid):
97 """Fetches the internal date of a message, returns a time tuple."""
3103ebb0 98 try:
db3f09a6 99 _result, data = self.mail_con.uid('fetch', mid, '(INTERNALDATE)')
3103ebb0 100 except:
89ca139f 101 raise UserWarning("Could not fetch the internal date of message " + mid.decode('iso-8859-1') + ".")
c9da760a
PD
102 internal_date = imaplib.Internaldate2tuple(data[0])
103 return internal_date
104
105 def fetch_received_date(self, mid):
106 """Fetches the received date of a message, returns bytes reponse."""
3103ebb0 107 try:
db3f09a6 108 _result, data = self.mail_con.uid('fetch', mid, '(BODY.PEEK[HEADER.FIELDS (RECEIVED)])')
3103ebb0 109 except:
89ca139f 110 raise UserWarning("Could not fetch the received header of message " + mid.decode('iso-8859-1') + ".")
8a9d4c89 111 return data[0][1].decode('iso-8859-1')
c9da760a 112
87cde111
PD
113 def fetch_basic_date(self, mid):
114 """Fetches the basic date of a message, returns bytes reponse."""
115 try:
db3f09a6 116 _result, data = self.mail_con.uid('fetch', mid, '(BODY.PEEK[HEADER.FIELDS (DATE)])')
87cde111 117 except:
89ca139f
TJ
118 raise UserWarning("Could not fetch the date header of message " + mid.decode('iso-8859-1') + ".")
119 return data[0][1].decode('iso-8859-1')
87cde111 120
c9da760a
PD
121 def update_message(self, mid, mailbox, internal_date):
122 """Replaces a message with one with correct internal date."""
44662883 123 internal_date_sec = time.mktime(internal_date.timetuple())
3103ebb0
PD
124 try:
125 result, data = self.mail_con.uid('fetch', mid, '(RFC822)')
126 #logging.debug("Entire e-mail is: %s", data[0][1])
c9da760a 127
44662883 128 # retrieve and select flags to upload
3103ebb0
PD
129 fetched_flags = self.mail_con.uid('fetch', mid, '(FLAGS)')[1][0]
130 parsed_flags = imaplib.ParseFlags(fetched_flags)
db3f09a6
PD
131 selected_flags = ()
132 for flag in parsed_flags:
133 if(flag != b'\\Recent'):
134 selected_flags += (flag,)
135 logging.debug("Selected flags %s from parsed flags %s.", selected_flags, parsed_flags)
85fec87e 136 flags_str = " ".join(flag.decode('iso-8859-1') for flag in selected_flags)
b5525864
TJ
137
138 # upload message copy and delete old one
3103ebb0 139 result, data = self.mail_con.append(mailbox, flags_str,
44662883 140 internal_date_sec, data[0][1])
3103ebb0
PD
141 logging.debug("Adding corrected copy of the message reponse: %s %s", result, data)
142 except:
89ca139f 143 raise UserWarning("Could not replace the e-mail " + mid.decode('iso-8859-1') + ".")
3103ebb0 144 try:
c9da760a 145 result, data = self.mail_con.uid('STORE', mid, '+FLAGS', r'(\Deleted)')
8fe4e3ff 146 logging.debug("Removing old copy of the message reponse: %s %s", result, data)
3103ebb0 147 except:
89ca139f 148 raise UserWarning("Could not delete the e-mail " + mid.decode('iso-8859-1') + ".")
3103ebb0 149 self.mail_con.expunge()
c9da760a 150 return