Cache version improvements
[imap-fix-internaldate] / fix_imap_internaldate.py
CommitLineData
c9da760a
PD
1'''
2fix_imap_internaldate.py - Fix the INTERNALDATE field on IMAP servers
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
c9da760a 18import csv
8fe4e3ff
PD
19import argparse, configparser
20import logging
8a9d4c89 21from mail_date_parser import MailDateParser
c9da760a 22from mail_iterator import MailIterator
8301e589 23from caching_data import CachingData
c9da760a
PD
24
25def main():
97bd6bea
PD
26 """Interprets command arguments and initializes configuration and logger.
27 Then begins mail synchronization."""
648f0037 28
97bd6bea 29 # parse arguments
648f0037
PD
30 parser = argparse.ArgumentParser(description="Fix the INTERNALDATE field on IMAP servers. "
31 "Small tool to fix the IMAP internaldate "
32 "in case it's too much off compared to the last date "
33 "stored in the received lines.")
34 parser.add_argument('-u', '--update', dest='test_mode', action='store_false',
35 default=True, help='update all e-mails and exit test mode')
c9da760a 36
8fe4e3ff 37 # config and logging setup
c9da760a 38 config = load_configuration()
8fe4e3ff 39 prepare_logger(config)
97bd6bea 40 args = parser.parse_args()
648f0037 41 if(args.test_mode):
f9fd9b29 42 logging.info("Testing mode initiated. No message will be modified on the server.")
648f0037 43 else:
f9fd9b29 44 logging.info("Update mode initiated. Messages will be modified.")
3b81023f 45
97bd6bea
PD
46 # proceed to main functionality
47 synchronize_csv(config, args.test_mode)
48
49 return
50
51def load_configuration():
52 """Loads the script configuration from a file or creates such."""
53 config = configparser.RawConfigParser()
54 success = config.read('confscript.cfg')
55 if(len(success)==0):
56 config.add_section('basic_settings')
57 config.set('basic_settings', 'file_log_level', logging.INFO)
58 config.set('basic_settings', 'console_log_level', logging.INFO)
59 config.set('basic_settings', 'imap_server', 'imap.company.com')
60 config.set('basic_settings', 'tolerance', 30)
61 with open('confscript.cfg', 'w') as configfile:
62 config.write(configfile)
63 return config
64
65def prepare_logger(config):
66 """Sets up the logging functionality"""
67
68 # reset the log
69 with open('fix_imap_internaldate.log', 'w'):
70 pass
71
72 # add basic configuration
73 logging.basicConfig(filename='fix_imap_internaldate.log',
74 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
75 level=config.getint('basic_settings', 'file_log_level'))
76
77 # add a handler for a console output
78 console = logging.StreamHandler()
79 console.setLevel(config.getint('basic_settings', 'console_log_level'))
80 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
81 console.setFormatter(formatter)
82 logging.getLogger('').addHandler(console)
83 return
84
85def synchronize_csv(config, test_mode):
86 """Iterates through csv list of users and synchronizes their messages."""
87
88 # initialize loop permanent data
89 caching_data = CachingData()
90 date_parser = MailDateParser()
c9da760a 91 server = config.get('basic_settings', 'imap_server')
8fe4e3ff 92 tolerance = config.getint('basic_settings', 'tolerance') * 60
c9da760a 93
97bd6bea
PD
94 # iterate through the users in the csv data
95 user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',')
c9da760a
PD
96 for user in user_reader:
97 try:
98 session = MailIterator(server, user['username'], user['password'])
99 except UserWarning as ex:
100 logging.error(ex)
101 continue
102 for mailbox in session:
103 try:
7a1d4c35 104 box = caching_data.retrieve_cached_mailbox(mailbox[0], mailbox[1], user['username'])
c9da760a 105 mail_ids = session.fetch_messages()
3b81023f 106 new_ids = box.synchronize(mail_ids, tolerance)
6f2bc406 107 logging.info("%s new messages out of %s found in %s.", len(new_ids), len(mail_ids), box.name)
c9da760a
PD
108 except UserWarning as ex:
109 logging.error(ex)
110 continue
8301e589 111 for mid in new_ids:
c9da760a
PD
112 try:
113 fetched_internal_date = session.fetch_internal_date(mid)
8a9d4c89 114 internal_date = date_parser.extract_internal_date(fetched_internal_date)
c9da760a 115 fetched_received_date = session.fetch_received_date(mid)
8a9d4c89 116 received_date = date_parser.extract_received_date(fetched_received_date)
c9da760a 117 if(received_date==""):
d5ccfbec 118 logging.debug("No received date could be found in message uid: %s - mailbox: %s - user: %s.",
8a9d4c89 119 mid.decode('iso-8859-1'), box.name, box.owner)
7a1d4c35 120 box.no_received_field += 1
c9da760a
PD
121 continue
122 except UserWarning as ex:
123 logging.error(ex)
124 continue
8a9d4c89 125 if(date_parser.compare_dates(received_date, internal_date, tolerance)):
36f4d196
TJ
126 logging.warning("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.",
127 mid.decode('iso-8859-1'), box.name, box.owner,
128 internal_date.strftime("%d %b %Y %H:%M:%S"),
129 received_date.strftime("%d %b %Y %H:%M:%S"),
130 fetched_received_date.split("Received:")[1])
97bd6bea 131 if(test_mode==0):
c9da760a 132 try:
7a1d4c35 133 session.update_message(mid, box.name, received_date)
c9da760a
PD
134 except UserWarning as ex:
135 logging.error(ex)
136 continue
36f4d196 137
8301e589 138 # count total emails for every user and mailbox
7a1d4c35 139 box.date_conflicts += 1
36f4d196 140
8301e589 141 # if all messages were successfully fixed confirm caching
97bd6bea 142 if(not test_mode):
7a1d4c35 143 box.confirm_change()
97bd6bea 144
8301e589 145 # final report on date conflicts
3b81023f 146 caching_data.report_conflicts()
8fe4e3ff
PD
147 return
148
c9da760a
PD
149if(__name__ == "__main__"):
150 main()