| 1 | ''' |
| 2 | caching_data.py - The module contains the CachingData 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 | import os, platform, tempfile |
| 18 | import pickle |
| 19 | import logging |
| 20 | from mailbox_state import MailboxState |
| 21 | |
| 22 | CACHE_FILENAME = "message_cache.dat" |
| 23 | CACHE_VERSION = "1" |
| 24 | STATISTICS_FILENAME = "statistics.txt" |
| 25 | |
| 26 | class CachingData: |
| 27 | """This class is responsible for the caching of data.""" |
| 28 | |
| 29 | # class attributes |
| 30 | # integer for version of the cache |
| 31 | version = None |
| 32 | # boolean flag which indicates fallback mode of the cache |
| 33 | fallback_to_date_header = None |
| 34 | # dictionary of usernames as keys and dictionaries as values |
| 35 | # the second dictionaries have unique mailbox keys and mailboxes as values |
| 36 | data = None |
| 37 | |
| 38 | def __init__(self, fallback_mode): |
| 39 | # open data file or create one and initialize date if not found |
| 40 | try: |
| 41 | cachefile = open(CACHE_FILENAME, 'rb') |
| 42 | cache_info, self.data = pickle.load(cachefile) |
| 43 | cache_info = cache_info.split(' ') |
| 44 | self.version = cache_info[0] |
| 45 | self.fallback_to_date_header = cache_info[1] |
| 46 | if(self.version != CACHE_VERSION): |
| 47 | raise IOError("Cache file has version %s and the script version is %s" % (self.version, CACHE_VERSION)) |
| 48 | if(self.fallback_to_date_header != str(fallback_mode)): |
| 49 | raise IOError("Cache file date fallback mode setting is different than current settings") |
| 50 | logging.info("Cache file %s loaded", CACHE_FILENAME) |
| 51 | logging.info("%s users found.", len(self.data)) |
| 52 | except (IOError, ValueError) as ex: |
| 53 | logging.warning("Couldn't load cache file %s: %s", CACHE_FILENAME, ex) |
| 54 | logging.warning("DELETING CACHE") |
| 55 | self.version = CACHE_VERSION |
| 56 | self.fallback_to_date_header = str(fallback_mode) |
| 57 | stored_cache_info = self.version + ' ' + self.fallback_to_date_header |
| 58 | self.data = {} |
| 59 | with open(CACHE_FILENAME, 'wb') as cachefile: |
| 60 | pickle.dump((stored_cache_info, self.data), cachefile) |
| 61 | |
| 62 | def __del__(self): |
| 63 | # create temporary file first |
| 64 | location = os.path.dirname(CACHE_FILENAME) |
| 65 | file_descriptor, tmpname = tempfile.mkstemp(dir=location) |
| 66 | try: |
| 67 | cachefile = os.fdopen(file_descriptor, 'wb') |
| 68 | |
| 69 | # prepare data based on a save flag |
| 70 | saved_data = {} |
| 71 | for user in self.data: |
| 72 | saved_data[user] = {} |
| 73 | for box_key in self.data[user]: |
| 74 | if(self.data[user][box_key].needs_save): |
| 75 | saved_data[user][box_key] = self.data[user][box_key] |
| 76 | logging.debug("The mailbox %s will be saved.", saved_data[user][box_key].name) |
| 77 | if(len(saved_data[user])==0): |
| 78 | del saved_data[user] |
| 79 | logging.debug("The user %s will not be saved.", user) |
| 80 | self.data = saved_data |
| 81 | # avoid test mode or cases where nothing needs saving |
| 82 | if(len(saved_data)==0): |
| 83 | cachefile.close() |
| 84 | os.unlink(tmpname) |
| 85 | return |
| 86 | |
| 87 | # serialize in file |
| 88 | stored_cache_info = self.version + ' ' + self.fallback_to_date_header |
| 89 | pickle.dump((stored_cache_info, self.data), cachefile) |
| 90 | logging.debug("%s users stored.", len(self.data)) |
| 91 | |
| 92 | # handle windows non-atomic rename |
| 93 | if(platform.system()=='Windows'): |
| 94 | if(os.path.exists(CACHE_FILENAME)): |
| 95 | cachefile.close() |
| 96 | os.unlink(CACHE_FILENAME) |
| 97 | |
| 98 | os.rename(tmpname, CACHE_FILENAME) |
| 99 | except: |
| 100 | # clean up temporary file |
| 101 | os.unlink(tmpname) |
| 102 | raise |
| 103 | |
| 104 | logging.info("Wrote cache file %s", CACHE_FILENAME) |
| 105 | |
| 106 | def retrieve_cached_mailbox(self, name, uidvalidity, user): |
| 107 | """Retrieve a cached mailbox or create it.""" |
| 108 | box_key = name.strip('"') + uidvalidity |
| 109 | if(user not in self.data): |
| 110 | self.data[user] = {} |
| 111 | logging.debug("New user %s cached.", user) |
| 112 | if(box_key not in self.data[user]): |
| 113 | self.data[user][box_key] = MailboxState(name, uidvalidity, user) |
| 114 | logging.debug("New mailbox %s cached.", box_key) |
| 115 | return self.data[user][box_key] |
| 116 | |
| 117 | def report_conflicts(self): |
| 118 | """Write a date conflicts report in a file.""" |
| 119 | with open(STATISTICS_FILENAME, 'w') as statsfile: |
| 120 | owner_total_conflicts = {} |
| 121 | owner_total_missing = {} |
| 122 | for user in self.data: |
| 123 | statsfile.write("user: %s\n" % user) |
| 124 | owner_total_conflicts[user] = 0 |
| 125 | owner_total_missing[user] = 0 |
| 126 | for box_key in self.data[user]: |
| 127 | owner_total_conflicts[user] += self.data[user][box_key].date_conflicts |
| 128 | owner_total_missing[user] += self.data[user][box_key].no_received_field |
| 129 | statsfile.write("date conflicts: %-15.15s missing header: %-15.15s mailbox: %s\n"\ |
| 130 | % (self.data[user][box_key].date_conflicts, |
| 131 | self.data[user][box_key].no_received_field, |
| 132 | self.data[user][box_key].name)) |
| 133 | statsfile.write("date conflicts: %-15.15s missing header: %-15.15s TOTAL \n\n"\ |
| 134 | % (owner_total_conflicts[user], owner_total_missing[user])) |
| 135 | return |