From 97bd6bea10d88dd1123f1b2cfc5d833f0b817fed Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Wed, 27 Jun 2012 16:47:36 +0200 Subject: [PATCH] Main function refactored and connection failure exception handling added --- README | 16 +++++-- fix_imap_internaldate.py | 103 ++++++++++++++++++++++++---------------------- mail_iterator.py | 19 ++++++--- 3 files changed, 79 insertions(+), 59 deletions(-) diff --git a/README b/README index 5dd4f23..347da3b 100644 --- a/README +++ b/README @@ -20,10 +20,18 @@ An alternative approach would be to interpret the "Date:" header. Prerequisites: Python version 3 or later +Necessary files: +- userdata.csv: Contains information necessary for the user session with format: + username,password + testuser1,testpass1 + testuser2,testpass2 -*** TO DOCUMENT *** -- How to invoke -necessary files -- Invocation on Windows +Produced files: +- confscript.cfg: Configuration of the script - created on first run. +- message_cache.dat: Cache file used for performance improvements +- fix_imap_internaldate.log: Log file that resets with the script. +- conflict_stats.txt: Final report on statistics about detected date conflicts and missing dates. + +Invocation on Windows requires and results in the same files. For more information please contact opensource@intra2net.com diff --git a/fix_imap_internaldate.py b/fix_imap_internaldate.py index 06f39e0..bcf6dc2 100644 --- a/fix_imap_internaldate.py +++ b/fix_imap_internaldate.py @@ -15,7 +15,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ''' -import sys import csv import argparse, configparser import logging @@ -24,34 +23,76 @@ from mail_iterator import MailIterator from caching_data import CachingData def main(): - """Iterates through csv list of users and synchronize their messages""" + """Interprets command arguments and initializes configuration and logger. + Then begins mail synchronization.""" + # parse arguments parser = argparse.ArgumentParser(description="Fix the INTERNALDATE field on IMAP servers. " "Small tool to fix the IMAP internaldate " "in case it's too much off compared to the last date " "stored in the received lines.") parser.add_argument('-u', '--update', dest='test_mode', action='store_false', default=True, help='update all e-mails and exit test mode') - args = parser.parse_args() # config and logging setup config = load_configuration() prepare_logger(config) - - date_parser = MailDateParser() - caching_data = CachingData() - logging.warning("Cache version %s loaded.", caching_data.version) + args = parser.parse_args() if(args.test_mode): logging.info("Testing mode initiated. No message will be modified on the server.") else: logging.info("Update mode initiated. Messages will be modified.") - user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',') - # server name is stored in the config + # proceed to main functionality + synchronize_csv(config, args.test_mode) + + return + +def load_configuration(): + """Loads the script configuration from a file or creates such.""" + config = configparser.RawConfigParser() + success = config.read('confscript.cfg') + if(len(success)==0): + config.add_section('basic_settings') + config.set('basic_settings', 'file_log_level', logging.INFO) + config.set('basic_settings', 'console_log_level', logging.INFO) + config.set('basic_settings', 'imap_server', 'imap.company.com') + config.set('basic_settings', 'tolerance', 30) + with open('confscript.cfg', 'w') as configfile: + config.write(configfile) + return config + +def prepare_logger(config): + """Sets up the logging functionality""" + + # reset the log + with open('fix_imap_internaldate.log', 'w'): + pass + + # add basic configuration + logging.basicConfig(filename='fix_imap_internaldate.log', + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=config.getint('basic_settings', 'file_log_level')) + + # add a handler for a console output + console = logging.StreamHandler() + console.setLevel(config.getint('basic_settings', 'console_log_level')) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + console.setFormatter(formatter) + logging.getLogger('').addHandler(console) + return + +def synchronize_csv(config, test_mode): + """Iterates through csv list of users and synchronizes their messages.""" + + # initialize loop permanent data + caching_data = CachingData() + date_parser = MailDateParser() server = config.get('basic_settings', 'imap_server') - # tolerance is now in seconds tolerance = config.getint('basic_settings', 'tolerance') * 60 + # iterate through the users in the csv data + user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',') for user in user_reader: try: session = MailIterator(server, user['username'], user['password']) @@ -87,7 +128,7 @@ def main(): internal_date.strftime("%d %b %Y %H:%M:%S"), received_date.strftime("%d %b %Y %H:%M:%S"), fetched_received_date.split("Received:")[1]) - if(args.test_mode==0): + if(test_mode==0): try: session.update_message(mid, box.name, received_date) except UserWarning as ex: @@ -98,48 +139,12 @@ def main(): box.date_conflicts += 1 # if all messages were successfully fixed confirm caching - if(not args.test_mode): + if(not test_mode): box.confirm_change() - + # final report on date conflicts caching_data.report_conflicts() - -def load_configuration(): - """Loads the script configuration from a file or creates such.""" - config = configparser.RawConfigParser() - success = config.read('confscript.cfg') - if(len(success)==0): - config.add_section('basic_settings') - config.set('basic_settings', 'file_log_level', logging.INFO) - config.set('basic_settings', 'console_log_level', logging.INFO) - config.set('basic_settings', 'imap_server', 'imap.company.com') - config.set('basic_settings', 'tolerance', 30) - #config.set('Basic settings', 'bool', 'true') - with open('confscript.cfg', 'w') as configfile: - config.write(configfile) - return config - -def prepare_logger(config): - """Sets up the logging functionality""" - - # reset the log - with open('fix_imap_internaldate.log', 'w'): - pass - - # add basic configuration - logging.basicConfig(filename='fix_imap_internaldate.log', - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=config.getint('basic_settings', 'file_log_level')) - - # add a handler for a console output - console = logging.StreamHandler() - console.setLevel(config.getint('basic_settings', 'console_log_level')) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logging.getLogger('').addHandler(console) return - - if(__name__ == "__main__"): main() diff --git a/mail_iterator.py b/mail_iterator.py index e4da7d4..a17ab6c 100644 --- a/mail_iterator.py +++ b/mail_iterator.py @@ -31,21 +31,28 @@ class MailIterator: mail_con = None # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes mailboxes = None + # logged in status + logged_in = None def __init__(self, server, username, password): """Creates a connection and a user session.""" - self.mail_con = imaplib.IMAP4_SSL(server) - result, data = self.mail_con.login(username, password) - if(result!="OK"): - raise UserWarning("Could not log in as user " + username + ". " + data) + try: + self.mail_con = imaplib.IMAP4_SSL(server) + self.mail_con.login(username, password) + logging.info("Logged in as %s.", username) + except: + self.logged_in = False + raise UserWarning("Could not log in as user " + username + ".") + self.logged_in = True result, self.mailboxes = self.mail_con.list() if(result!="OK"): raise UserWarning("Could not retrieve mailboxes for user " + username + ".") def __del__(self): """Closes the connection and the user session.""" - self.mail_con.close() - self.mail_con.logout() + if(self.logged_in): + self.mail_con.close() + self.mail_con.logout() def __iter__(self): """Iterates through all mailboxes, returns (uidval,name).""" -- 1.7.1