ccd91e2d2c9571f6a3f9a67d966cee91d07eb840
[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
38     def __init__(self, server, username, password):
39         """Creates a connection and a user session."""
40
41         # connect to server
42         try:
43             self.mail_con = imaplib.IMAP4_SSL(server)
44             logging.info("Connected to %s", server)
45         except socket.error as ex:
46             logging.error("Could not connect to host: %s", ex)
47             sys.exit()
48
49         # log in
50         try:
51             self.mail_con.login(username, password)
52             self.logged_in = True
53             logging.info("Logged in as %s.", username)
54         except self.mail_con.error as ex:
55             self.logged_in = False
56             logging.error("Could not log in as user %s: %s", username, ex)
57             sys.exit()
58
59         # list mailboxes
60         try:
61             _result, mailboxes = self.mail_con.list()
62         except self.mail_con.error as ex:
63             logging.warning("Could not retrieve mailboxes for user %s: %s", username, ex)
64         self.mailboxes = []
65         for mailbox in mailboxes:
66             mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups()
67             self.mailboxes.append(mailbox)
68         self.mailboxes = sorted(self.mailboxes, key=lambda box: box[2], reverse=True)
69
70         return
71
72     def __del__(self):
73         """Closes the connection and the user session."""
74
75         if(self.logged_in):
76             self.mail_con.close()
77             self.mail_con.logout()
78
79     def __iter__(self):
80         """Iterates through all mailboxes, returns (children,delimiter,name)."""
81
82         for mailbox in self.mailboxes:
83             logging.debug("Checking mailbox %s", mailbox[2])
84             # select mailbox if writable
85             try:
86                 self.mail_con.select(mailbox[2])
87                 logging.info("Processing mailbox %s", mailbox[2])
88             except self.mail_con.readonly:
89                 logging.warning("Mailbox %s is not writable and therefore skipped", mailbox[2])
90                 continue
91             yield mailbox
92
93     def fetch_messages(self):
94         """Fetches the messages from the current mailbox, return list of uids."""
95
96         try:
97             # Work around unsolicited server responses in imaplib by clearing them
98             self.mail_con.response('SEARCH')
99             _result, data = self.mail_con.uid('search', None, "ALL")
100         except (self.mail_con.error):
101             raise UserWarning("Could not fetch messages")
102         mailid_list = data[0].split()
103         return mailid_list
104
105     def set_seen_messages(self, mid_range):
106         """Sets the \\Seen flag for all messages with the respective mids."""
107
108         try:
109             # Work around unsolicited server responses in imaplib by clearing them
110             self.mail_con.response('STORE')
111             _result, data = self.mail_con.uid('STORE', mid_range, '+FLAGS', "(\Seen)")
112             logging.info("New flags for messages %s are %s", mid_range, data)
113         except (self.mail_con.error) as ex:
114             raise UserWarning("Could not set the flags for some messages: %s", ex)
115         self.mail_con.expunge()