Commit | Line | Data |
---|---|---|
8301e589 PD |
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 | ''' | |
5a4bc5d8 | 17 | import os, platform, tempfile |
8301e589 | 18 | import pickle |
8fe4e3ff | 19 | import logging |
7a1d4c35 PD |
20 | from mailbox_state import MailboxState |
21 | ||
8fe4e3ff | 22 | CACHE_FILENAME = "message_cache.dat" |
db3f09a6 | 23 | CACHE_VERSION = "1" |
31f90b0a | 24 | STATISTICS_FILENAME = "statistics.txt" |
8301e589 PD |
25 | |
26 | class CachingData: | |
27 | """This class is responsible for the caching of data.""" | |
7a1d4c35 PD |
28 | |
29 | # class attributes | |
30 | # integer for version of the cache | |
31 | version = None | |
db3f09a6 PD |
32 | # boolean flag which indicates fallback mode of the cache |
33 | fallback_to_date_header = None | |
7a1d4c35 PD |
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 | |
8301e589 | 37 | |
db3f09a6 | 38 | def __init__(self, fallback_mode): |
8fe4e3ff | 39 | # open data file or create one and initialize date if not found |
8301e589 | 40 | try: |
8fe4e3ff | 41 | cachefile = open(CACHE_FILENAME, 'rb') |
db3f09a6 PD |
42 | cache_info, self.data = pickle.load(cachefile) |
43 | cache_info = cache_info.split(' ') | |
44 | self.version = cache_info[0] | |
b31b906b | 45 | self.fallback_to_date_header = cache_info[1] |
8a9d4c89 | 46 | if(self.version != CACHE_VERSION): |
04d3d4de | 47 | raise IOError("Cache file has version %s and the script version is %s" % (self.version, CACHE_VERSION)) |
db3f09a6 | 48 | if(self.fallback_to_date_header != str(fallback_mode)): |
04d3d4de | 49 | raise IOError("Cache file date fallback mode setting is different than current settings") |
28d8aa17 | 50 | logging.info("Cache file %s loaded", CACHE_FILENAME) |
04d3d4de TJ |
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") | |
8a9d4c89 | 55 | self.version = CACHE_VERSION |
b31b906b TJ |
56 | self.fallback_to_date_header = str(fallback_mode) |
57 | stored_cache_info = self.version + ' ' + self.fallback_to_date_header | |
8301e589 | 58 | self.data = {} |
8fe4e3ff | 59 | with open(CACHE_FILENAME, 'wb') as cachefile: |
db3f09a6 | 60 | pickle.dump((stored_cache_info, self.data), cachefile) |
8301e589 PD |
61 | |
62 | def __del__(self): | |
7a1d4c35 | 63 | # create temporary file first |
8fe4e3ff | 64 | location = os.path.dirname(CACHE_FILENAME) |
7a1d4c35 | 65 | file_descriptor, tmpname = tempfile.mkstemp(dir=location) |
8fe4e3ff PD |
66 | try: |
67 | cachefile = os.fdopen(file_descriptor, 'wb') | |
28d8aa17 | 68 | |
8fe4e3ff PD |
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 | |
8fe4e3ff PD |
81 | # avoid test mode or cases where nothing needs saving |
82 | if(len(saved_data)==0): | |
5a4bc5d8 | 83 | cachefile.close() |
8fe4e3ff PD |
84 | os.unlink(tmpname) |
85 | return | |
28d8aa17 | 86 | |
8fe4e3ff | 87 | # serialize in file |
db3f09a6 PD |
88 | stored_cache_info = self.version + ' ' + self.fallback_to_date_header |
89 | pickle.dump((stored_cache_info, self.data), cachefile) | |
8fe4e3ff | 90 | logging.debug("%s users stored.", len(self.data)) |
5a4bc5d8 PD |
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 | ||
8fe4e3ff PD |
98 | os.rename(tmpname, CACHE_FILENAME) |
99 | except: | |
d061b671 | 100 | # clean up temporary file |
28d8aa17 | 101 | os.unlink(tmpname) |
d061b671 | 102 | raise |
28d8aa17 TJ |
103 | |
104 | logging.info("Wrote cache file %s", CACHE_FILENAME) | |
8301e589 | 105 | |
7a1d4c35 PD |
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] = {} | |
8fe4e3ff | 111 | logging.debug("New user %s cached.", user) |
7a1d4c35 PD |
112 | if(box_key not in self.data[user]): |
113 | self.data[user][box_key] = MailboxState(name, uidvalidity, user) | |
8fe4e3ff | 114 | logging.debug("New mailbox %s cached.", box_key) |
7a1d4c35 | 115 | return self.data[user][box_key] |
31f90b0a | 116 | |
3b81023f | 117 | def report_conflicts(self): |
7a1d4c35 | 118 | """Write a date conflicts report in a file.""" |
31f90b0a | 119 | with open(STATISTICS_FILENAME, 'w') as statsfile: |
7a1d4c35 PD |
120 | owner_total_conflicts = {} |
121 | owner_total_missing = {} | |
122 | for user in self.data: | |
d3beb6da | 123 | statsfile.write("user: %s\n" % user) |
7a1d4c35 PD |
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 | |
d3beb6da PD |
129 | statsfile.write("date conflicts: %-15.15s missing header: %-15.15s mailbox: %s\n"\ |
130 | % (self.data[user][box_key].date_conflicts, | |
08ba5736 PD |
131 | self.data[user][box_key].no_received_field, |
132 | self.data[user][box_key].name)) | |
d3beb6da PD |
133 | statsfile.write("date conflicts: %-15.15s missing header: %-15.15s TOTAL \n\n"\ |
134 | % (owner_total_conflicts[user], owner_total_missing[user])) | |
8301e589 | 135 | return |