More user friendly log message about new messages
[imap-fix-internaldate] / fix_imap_internaldate.py
1 '''
2 fix_imap_internaldate.py - Fix the INTERNALDATE field on IMAP servers
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 csv
20 import argparse, configparser
21 import logging
22 from mail_date_parser import MailDateParser
23 from mail_iterator import MailIterator
24 from caching_data import CachingData
25
26 def main():
27     """Iterates through csv list of users and their mailboxes"""
28     
29     """parser = argparse.ArgumentParser(description='Fix the INTERNALDATE field on IMAP servers.')
30     parser.add_argument('--h', metavar='N', type=int, nargs='+',
31                        help='an integer for the accumulator')
32     parser.add_argument('--u', dest='accumulate', type=bool,
33                        const=sum, default=max,
34                        help='sum the integers (default: find the max)')
35     args = parser.parse_args()
36     print(args.accumulate(args.integers))"""
37
38     if(len(sys.argv) > 1):
39         if(sys.argv[1]=="--h"):
40             print("The default mode of the script is test mode."
41                   "Add '--u' argument to exit to modify messages."
42                   "For a detailed list of each message with a date conflict change"
43                   "change the 'log_level' in the configuration file from '30' to '20'.")
44             return
45         if(sys.argv[1]=="--u"):
46             test_mode = False
47     else:
48         test_mode = True
49
50     # config and logging setup
51     config = load_configuration()
52     prepare_logger(config)
53
54     date_parser = MailDateParser()
55     caching_data = CachingData()
56     logging.warning("Cache version %s loaded.", caching_data.version)
57     user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',')
58     
59     # server name is stored in the config
60     server = config.get('basic_settings', 'imap_server')
61     # tolerance is now in seconds
62     tolerance = config.getint('basic_settings', 'tolerance') * 60
63
64     for user in user_reader:
65         try:
66             session = MailIterator(server, user['username'], user['password'])
67         except UserWarning as ex:
68             logging.error(ex)
69             continue
70         for mailbox in session:
71             try:
72                 box = caching_data.retrieve_cached_mailbox(mailbox[0], mailbox[1], user['username'])
73                 mail_ids = session.fetch_messages()
74                 new_ids = box.synchronize(mail_ids)
75                 logging.warning("%s new messages out of %s found in %s.", len(new_ids), len(mail_ids), box.name)
76             except UserWarning as ex:
77                 logging.error(ex)
78                 continue
79             for mid in new_ids:
80                 try:
81                     fetched_internal_date = session.fetch_internal_date(mid)
82                     internal_date = date_parser.extract_internal_date(fetched_internal_date)
83                     fetched_received_date = session.fetch_received_date(mid)
84                     received_date = date_parser.extract_received_date(fetched_received_date)
85                     if(received_date==""):
86                         logging.debug("No received date could be found in message uid: %s - mailbox: %s - user: %s.",
87                                         mid.decode('iso-8859-1'), box.name, box.owner)
88                         box.no_received_field += 1
89                         continue
90                 except UserWarning as ex:
91                     logging.error(ex)
92                     continue
93                 if(date_parser.compare_dates(received_date, internal_date, tolerance)):
94                     #print(received_date, internal_date)
95                     if(test_mode==0):
96                         try:
97                             session.update_message(mid, box.name, received_date)
98                         except UserWarning as ex:
99                             logging.error(ex)
100                             continue
101                     else:
102                         logging.info("Date conflict found in message uid: %s - mailbox: %s - user: %s.\nInternal date %s is different from received date %s from RECEIVED header:\n%s.",
103                                         mid.decode('iso-8859-1'), box.name, box.owner,
104                                         internal_date.strftime("%d %b %Y %H:%M:%S"),
105                                         received_date.strftime("%d %b %Y %H:%M:%S"),
106                                         fetched_received_date[0][1].decode('iso-8859-1').split("Received:")[1])
107                     # count total emails for every user and mailbox
108                     box.date_conflicts += 1
109             # if all messages were successfully fixed confirm caching
110             if(not test_mode):
111                 box.confirm_change()
112
113         # final report on date conflicts
114         caching_data.report_date_conflicts()
115
116 def load_configuration():
117     """Loads the script configuration from a file or creates such."""
118     config = configparser.RawConfigParser()    
119     success = config.read('confscript.cfg')
120     if(len(success)==0):
121         config.add_section('basic_settings')
122         config.set('basic_settings', 'file_log_level', logging.INFO)
123         config.set('basic_settings', 'console_log_level', logging.INFO)
124         config.set('basic_settings', 'imap_server', 'imap.company.com')
125         config.set('basic_settings', 'tolerance', 30)
126         #config.set('Basic settings', 'bool', 'true')
127         with open('confscript.cfg', 'w') as configfile:
128             config.write(configfile)
129     return config
130
131 def prepare_logger(config):
132     """Sets up the logging functionality"""
133     
134     # reset the log
135     with open('fix_imap_internaldate.log', 'w'):
136         pass
137     
138     # add basic configuration
139     logging.basicConfig(filename='fix_imap_internaldate.log',
140                         format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
141                         level=config.getint('basic_settings', 'file_log_level'))
142     
143     # add a handler for a console output
144     console = logging.StreamHandler()
145     console.setLevel(config.getint('basic_settings', 'console_log_level'))
146     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
147     console.setFormatter(formatter)
148     logging.getLogger('').addHandler(console)
149     return
150
151 if(__name__ == "__main__"):
152     main()