7d53e91a05592811e7bae396b7c40173510a1e66
[imap-mark-seen] / src / mail_iterator.py
1 '''
2 The module contains the MailIterator class.
3
4 Copyright (c) 2012 Intra2net AG
5 Author: Plamen Dimitrov
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 '''
17
18 import sys
19 import imaplib, socket
20 import re
21 import logging
22
23 MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
24
25 #imaplib.Debug = 4
26
27 class MailIterator:
28     """This class communicates with the IMAP server."""
29
30     # class attributes
31     # IMAP4_SSL for connection with an IMAP server
32     mail_con = None
33     # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes
34     mailboxes = None
35     # logged in status
36     logged_in = None
37     # skip shared folders
38     skip_shared_folders = None
39
40     def __init__(self, server, username, password, skip_shared_folders = False):
41         """Creates a connection and a user session."""
42
43         self.skip_shared_folders = skip_shared_folders
44
45         # connect to server
46         try:
47             self.mail_con = imaplib.IMAP4_SSL(server)
48             logging.info("Connected to %s", server)
49         except socket.error as ex:
50             logging.error("Could not connect to host: %s", ex)
51             sys.exit()
52
53         # log in
54         try:
55             self.mail_con.login(username, password)
56             self.logged_in = True
57             logging.info("Logged in as %s.", username)
58         except self.mail_con.error as ex:
59             self.logged_in = False
60             logging.error("Could not log in as user %s: %s", username, ex)
61             sys.exit()
62
63         # list mailboxes
64         try:
65             _result, mailboxes = self.mail_con.list()
66         except self.mail_con.error as ex:
67             logging.warning("Could not retrieve mailboxes for user %s: %s", username, ex)
68         self.mailboxes = []
69         for mailbox in mailboxes:
70             mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups()
71             self.mailboxes.append(mailbox)
72         self.mailboxes = sorted(self.mailboxes, key=lambda box: box[2], reverse=True)
73
74         return
75
76     def __del__(self):
77         """Closes the connection and the user session."""
78
79         if(self.logged_in):
80             self.mail_con.close()
81             self.mail_con.logout()
82
83     def __iter__(self):
84         """Iterates through all mailboxes, returns (children,delimiter,name)."""
85
86         for mailbox in self.mailboxes:
87             logging.debug("Checking mailbox %s", mailbox[2])
88             # detect if mailbox is shared and if skip flag is set iterate further
89             imap_delimiter = mailbox[1]
90             if(self.skip_shared_folders and mailbox[2].split(imap_delimiter)[0] == '"user'):
91                 logging.info("Mailbox %s is shared and therefore skipped.", mailbox[2])
92                 continue
93             # select mailbox if writable
94             try:
95                 self.mail_con.select(mailbox[2])
96                 logging.info("Processing mailbox %s", mailbox[2])
97             except self.mail_con.readonly:
98                 logging.warning("Mailbox %s is not writable and therefore skipped", mailbox[2])
99                 continue
100             yield mailbox
101
102     def fetch_messages(self):
103         """Fetches the messages from the current mailbox, return list of uids."""
104
105         try:
106             # Work around unsolicited server responses in imaplib by clearing them
107             self.mail_con.response('SEARCH')
108             _result, data = self.mail_con.uid('search', None, "ALL")
109         except (self.mail_con.error):
110             raise UserWarning("Could not fetch messages")
111         mailid_list = data[0].split()
112         return mailid_list
113
114     def set_seen_messages(self, mid_range):
115         """Sets the \\Seen flag for all messages with the respective mids."""
116
117         try:
118             # Work around unsolicited server responses in imaplib by clearing them
119             self.mail_con.response('STORE')
120             _result, data = self.mail_con.uid('STORE', mid_range, '+FLAGS', "(\Seen)")
121             logging.debug("New flags for messages %s are %s", mid_range, data)
122         except (self.mail_con.error) as ex:
123             raise UserWarning("Could not set the flags for some messages: %s", ex)
124         self.mail_con.expunge()