Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 #This file is part of Tryton. The COPYRIGHT file at the top level of | 1 #This file is part of Tryton. The COPYRIGHT file at the top level of |
2 #this repository contains the full copyright notices and license terms. | 2 #this repository contains the full copyright notices and license terms. |
3 "Electronic Mail" | 3 "Electronic Mail" |
4 from __future__ import with_statement | 4 from __future__ import with_statement |
5 | 5 |
6 import os | 6 import os |
7 import base64 | 7 import base64 |
8 from sys import getsizeof | 8 from sys import getsizeof |
ced
2011/03/23 10:42:16
only in 2.6
| |
9 try: | 9 try: |
10 import hashlib | 10 import hashlib |
11 except ImportError: | 11 except ImportError: |
12 hashlib = None | 12 hashlib = None |
13 import md5 | 13 import md5 |
14 from datetime import datetime | 14 from datetime import datetime |
15 from time import mktime | 15 from time import mktime |
16 from email.utils import parsedate | 16 from email.utils import parsedate |
17 | 17 |
18 from trytond.model import ModelView, ModelSQL, fields | 18 from trytond.model import ModelView, ModelSQL, fields |
19 from trytond.config import CONFIG | 19 from trytond.config import CONFIG |
20 from trytond.transaction import Transaction | 20 from trytond.transaction import Transaction |
21 | 21 |
22 | 22 |
23 class Mailbox(ModelSQL, ModelView): | 23 class Mailbox(ModelSQL, ModelView): |
24 "Mailbox" | 24 "Mailbox" |
25 _name = "electronic_mail.mailbox" | 25 _name = "electronic_mail.mailbox" |
26 _description = __doc__ | 26 _description = __doc__ |
27 | 27 |
28 name = fields.Char('Name', required=True) | 28 name = fields.Char('Name', required=True) |
29 user = fields.Many2One('res.user', 'Owner') | 29 user = fields.Many2One('res.user', 'Owner') |
30 parents = fields.Many2Many( | 30 parents = fields.Many2Many( |
31 'electronic_mail.mailbox-parent-mailbox', | 31 'electronic_mail.mailbox-mailbox', |
32 'mailbox_parent', 'mailbox_child' ,'Parents') | 32 'parent', 'child' ,'Parents') |
33 subscribed = fields.Boolean('Subscribed') | 33 subscribed = fields.Boolean('Subscribed') |
ced
2011/03/23 10:42:16
Could add a default method
| |
34 read_users = fields.Many2Many('electronic_mail.mailbox-read-res.user', | 34 read_users = fields.Many2Many('electronic_mail.mailbox-read-res.user', |
35 'mailbox', 'user', 'Read Users') | 35 'mailbox', 'user', 'Read Users') |
36 write_users = fields.Many2Many('electronic_mail.mailbox-write-res.user', | 36 write_users = fields.Many2Many('electronic_mail.mailbox-write-res.user', |
37 'mailbox', 'user', 'Write Users') | 37 'mailbox', 'user', 'Write Users') |
38 | 38 |
39 def __init__(self): | |
ced
2010/11/22 16:57:22
not used
| |
40 super(Mailbox, self).__init__() | |
pheller
2010/11/22 16:49:31
Do you plan to override the superclass __init__()
| |
41 | |
42 Mailbox() | 39 Mailbox() |
43 | 40 |
44 | 41 |
45 class MailboxParent(ModelSQL): | 42 class MailboxParent(ModelSQL): |
46 'Mailbox - parent - Mailbox' | 43 'Mailbox - parent - Mailbox' |
47 _description = __doc__ | 44 _description = __doc__ |
48 _name = 'electronic_mail.mailbox-parent-mailbox' | 45 _name = 'electronic_mail.mailbox-mailbox' |
ced
2010/11/22 16:57:22
mailbox-mailbox
| |
49 | 46 |
50 mailbox_parent = fields.Many2One('electronic_mail.mailbox', 'Parent', | 47 parent = fields.Many2One('electronic_mail.mailbox', 'Parent', |
ced
2010/11/22 16:57:22
simply parent
| |
51 ondelete='CASCADE', required=True, select=1) | 48 ondelete='CASCADE', required=True, select=1) |
52 mailbox_child = fields.Many2One('electronic_mail.mailbox', 'Child', | 49 child = fields.Many2One('electronic_mail.mailbox', 'Child', |
ced
2010/11/22 16:57:22
simply child
| |
53 ondelete='CASCADE', required=True, select=1) | 50 ondelete='CASCADE', required=True, select=1) |
54 | 51 |
55 MailboxParent() | 52 MailboxParent() |
56 | 53 |
57 | 54 |
58 class ReadUser(ModelSQL): | 55 class ReadUser(ModelSQL): |
59 'Electronic Mail - read - User' | 56 'Electronic Mail - read - User' |
60 _description = __doc__ | 57 _description = __doc__ |
61 _name = 'electronic_mail.mailbox-read-res.user' | 58 _name = 'electronic_mail.mailbox-read-res.user' |
62 | 59 |
(...skipping 27 matching lines...) Expand all Loading... | |
90 'electronic_mail.mailbox', 'Mailbox', required=True) | 87 'electronic_mail.mailbox', 'Mailbox', required=True) |
91 from_ = fields.Char('From') | 88 from_ = fields.Char('From') |
92 sender = fields.Char('Sender') | 89 sender = fields.Char('Sender') |
93 to = fields.Char('To') | 90 to = fields.Char('To') |
94 cc = fields.Char('CC') | 91 cc = fields.Char('CC') |
95 bcc = fields.Char('BCC') | 92 bcc = fields.Char('BCC') |
96 subject = fields.Char('Subject') | 93 subject = fields.Char('Subject') |
97 date = fields.DateTime('Date') | 94 date = fields.DateTime('Date') |
98 message_id = fields.Char('Message-ID', help='Unique Message Identifier') | 95 message_id = fields.Char('Message-ID', help='Unique Message Identifier') |
99 in_reply_to = fields.Char('In-Reply-To') | 96 in_reply_to = fields.Char('In-Reply-To') |
100 headers = fields.One2Many('electronic_mail.header', | 97 headers = fields.One2Many( |
101 'electronic_mail', 'Headers') | 98 'electronic_mail.header', 'electronic_mail', 'Headers') |
ced
2010/11/22 16:57:22
Not Tryton indent
| |
102 digest = fields.Char('MD5 Digest') | 99 digest = fields.Char('MD5 Digest', size=32) |
ced
2010/11/22 16:57:22
size=32
| |
103 collision = fields.Integer('Collision') | 100 collision = fields.Integer('Collision') |
104 email = fields.Function(fields.Binary('Email'), 'get_email', 'set_email') | 101 email = fields.Function(fields.Binary('Email'), 'get_email', 'set_email') |
105 flag_seen = fields.Boolean('Seen') | 102 flag_seen = fields.Boolean('Seen') |
106 flag_answered = fields.Boolean('Answered') | 103 flag_answered = fields.Boolean('Answered') |
107 flag_flagged = fields.Boolean('Flagged') | 104 flag_flagged = fields.Boolean('Flagged') |
108 flag_draft = fields.Boolean('Draft') | 105 flag_draft = fields.Boolean('Draft') |
109 flag_recent = fields.Boolean('Recent') | 106 flag_recent = fields.Boolean('Recent') |
110 size = fields.Integer('Size') | 107 size = fields.Integer('Size') |
111 mailbox_owner = fields.Function( | 108 mailbox_owner = fields.Function( |
112 fields.Many2One('res.user', 'Owner'), | 109 fields.Many2One('res.user', 'Owner'), |
113 'get_mailbox_owner', searcher='search_mailbox_owner') | 110 'get_mailbox_owner', searcher='search_mailbox_owner') |
114 mailbox_read_users = fields.Function( | 111 mailbox_read_users = fields.Function( |
115 fields.One2Many('res.user', None, 'Read Users'), | 112 fields.One2Many('res.user', None, 'Read Users'), |
116 'get_mailbox_users', searcher='search_mailbox_users') | 113 'get_mailbox_users', searcher='search_mailbox_users') |
117 mailbox_write_users = fields.Function( | 114 mailbox_write_users = fields.Function( |
118 fields.One2Many('res.user', None, 'Write Users'), | 115 fields.One2Many('res.user', None, 'Write Users'), |
119 'get_mailbox_users', searcher='search_mailbox_users') | 116 'get_mailbox_users', searcher='search_mailbox_users') |
120 | 117 |
121 def __init__ (self): | |
ced
2010/11/22 16:57:22
Not used
| |
122 super(ElectronicMail, self).__init__() | |
pheller
2010/11/22 16:49:31
Do you plan to override the superclass __init__()
| |
123 | |
124 def default_collision(self): | 118 def default_collision(self): |
125 return 0 | 119 return 0 |
126 | 120 |
127 def default_flag_seen(self): | 121 def default_flag_seen(self): |
128 return False | 122 return False |
129 | 123 |
130 def default_flag_answered(self): | 124 def default_flag_answered(self): |
131 return False | 125 return False |
132 | 126 |
133 def default_flag_flagged(self): | 127 def default_flag_flagged(self): |
134 return False | 128 return False |
135 | 129 |
136 def default_flag_recent(self): | 130 def default_flag_recent(self): |
137 return False | 131 return False |
138 | 132 |
139 def get_mailbox_owner(self, ids, name): | 133 def get_mailbox_owner(self, ids, name): |
134 "Returns owner of mailbox" | |
140 mails = self.browse(ids) | 135 mails = self.browse(ids) |
141 return dict([(mail.id, mail.mailbox.user.id) for mail in mails]) | 136 return dict([(mail.id, mail.mailbox.user.id) for mail in mails]) |
142 | 137 |
143 def get_mailbox_users(self, ids, name): | 138 def get_mailbox_users(self, ids, name): |
144 assert name in ('mailbox_read_users', 'mailbox_write_users') | 139 assert name in ('mailbox_read_users', 'mailbox_write_users') |
145 res = {} | 140 res = {} |
146 for mail in self.browse(ids): | 141 for mail in self.browse(ids): |
147 if name == 'mailbox_read_users': | 142 if name == 'mailbox_read_users': |
148 res[mail.id] = [x.id for x in mail.mailbox['read_users']] | 143 res[mail.id] = [x.id for x in mail.mailbox['read_users']] |
149 else: | 144 else: |
150 res[mail.id] = [x.id for x in mail.mailbox['write_users']] | 145 res[mail.id] = [x.id for x in mail.mailbox['write_users']] |
151 return res | 146 return res |
152 | 147 |
153 def search_mailbox_owner(self, name, clause): | 148 def search_mailbox_owner(self, name, clause): |
154 return [('mailbox.user',) + clause[1:]] | 149 return [('mailbox.user',) + clause[1:]] |
155 | 150 |
156 def search_mailbox_users(self, name, clause): | 151 def search_mailbox_users(self, name, clause): |
157 return [('mailbox.' + name[8:],) + clause[1:]] | 152 return [('mailbox.' + name[8:],) + clause[1:]] |
158 | 153 |
159 def _get_email(self, electronic_mail): | 154 def _get_email(self, electronic_mail): |
160 """ | 155 """ |
161 Returns the email object from reading the FS | 156 Returns the email object from reading the FS |
162 :param electronic_mail: Browse Record of the mail | 157 :param electronic_mail: Browse Record of the mail |
163 """ | 158 """ |
164 db_name = Transaction().cursor.dbname | 159 db_name = Transaction().cursor.dbname |
165 value = u'' | 160 value = u'' |
ced
2011/03/23 10:42:16
Should be a str not a unicode
| |
166 if electronic_mail.digest: | 161 if electronic_mail.digest: |
167 filename = electronic_mail.digest | 162 filename = electronic_mail.digest |
168 if electronic_mail.collision: | 163 if electronic_mail.collision: |
169 filename = filename + '-' + str(electronic_mail.collision) | 164 filename = filename + '-' + str(electronic_mail.collision) |
170 filename = os.path.join( | 165 filename = os.path.join( |
171 CONFIG['data_path'], db_name,· | 166 CONFIG['data_path'], db_name,· |
172 'email', filename[0:2], filename) | 167 'email', filename[0:2], filename) |
ced
2011/03/23 10:42:16
You should also use a 2 folders depth to store ema
| |
173 try: | 168 try: |
174 with open(filename, 'r') as file_p: | 169 with open(filename, 'r') as file_p: |
ced
2011/03/23 10:42:16
You should read in binary mode
| |
175 value = file_p.read() | 170 value = file_p.read() |
176 except IOError: | 171 except IOError: |
177 pass | 172 pass |
178 return value | 173 return value |
179 | 174 |
180 def get_email(self, ids, name): | 175 def get_email(self, ids, name): |
181 """Fetches email from the data_path as email object | 176 """Fetches email from the data_path as email object |
182 """ | 177 """ |
183 result = { } | 178 result = { } |
184 for electronic_mail in self.browse(ids): | 179 for electronic_mail in self.browse(ids): |
185 result[electronic_mail.id] = base64.encodestring( | 180 result[electronic_mail.id] = base64.encodestring( |
ced
2010/11/22 16:57:22
Should be False if there is nothing
| |
186 self._get_email(electronic_mail) | 181 self._get_email(electronic_mail) |
187 ) | 182 ) or False |
188 return result | 183 return result |
189 | 184 |
190 def set_email(self, ids, name, data): | 185 def set_email(self, ids, name, data): |
191 """Saves an email to the data path | 186 """Saves an email to the data path |
192 | 187 |
193 :param data: Email as string | 188 :param data: Email as string |
194 """ | 189 """ |
195 if data is False or data is None: | 190 if data is False or data is None: |
ced
2010/11/22 16:57:22
Should merge it with the attachment one.
| |
196 return | 191 return |
197 db_name = Transaction().cursor.dbname | 192 db_name = Transaction().cursor.dbname |
198 # Prepare Directory <DATA PATH>/<DB NAME>/email | 193 # Prepare Directory <DATA PATH>/<DB NAME>/email |
199 directory = os.path.join(CONFIG['data_path'], db_name) | 194 directory = os.path.join(CONFIG['data_path'], db_name) |
200 if not os.path.isdir(directory): | 195 if not os.path.isdir(directory): |
201 os.makedirs(directory, 0770) | 196 os.makedirs(directory, 0770) |
202 digest = self.make_digest(data) | 197 digest = self.make_digest(data) |
203 directory = os.path.join(directory, 'email', digest[0:2]) | 198 directory = os.path.join(directory, 'email', digest[0:2]) |
204 if not os.path.isdir(directory): | 199 if not os.path.isdir(directory): |
205 os.makedirs(directory, 0770) | 200 os.makedirs(directory, 0770) |
206 # Filename <DIRECTORY>/<DIGEST> | 201 # Filename <DIRECTORY>/<DIGEST> |
ced
2011/03/23 10:42:16
2 digest levels
| |
207 filename = os.path.join(directory, digest) | 202 filename = os.path.join(directory, digest) |
208 collision = 0 | 203 collision = 0 |
209 | 204 |
210 if not os.path.isfile(filename): | 205 if not os.path.isfile(filename): |
211 # File doesnt exist already | 206 # File doesnt exist already |
212 with open(filename, 'w') as file_p: | 207 with open(filename, 'w') as file_p: |
ced
2011/03/23 10:42:16
Must write in binary mode
| |
213 file_p.write(data) | 208 file_p.write(data) |
214 else: | 209 else: |
215 # File already exists, may be its the same email data | 210 # File already exists, may be its the same email data |
216 # or maybe different.· | 211 # or maybe different.· |
217 | 212 |
218 # Case 1: If different: we have to write file with updated | 213 # Case 1: If different: we have to write file with updated |
219 # Collission index | 214 # Collission index |
220 | 215 |
221 # Case 2: Same file: Leave it as such | 216 # Case 2: Same file: Leave it as such |
222 with open(filename, 'r') as file_p: | 217 with open(filename, 'r') as file_p: |
223 data2 = file_p.read() | 218 data2 = file_p.read() |
224 if data != data2: | 219 if data != data2: |
225 cursor = Transaction().cursor | 220 cursor = Transaction().cursor |
226 cursor.execute( | 221 cursor.execute( |
227 'SELECT DISTINCT(collision) FROM electronic_mail ' | 222 'SELECT DISTINCT(collision) FROM electronic_mail ' |
228 'WHERE digest = %s AND collision !=0 ' | 223 'WHERE digest = %s AND collision !=0 ' |
ced
2011/03/23 10:42:16
space after !=
| |
229 'ORDER BY collision', (digest,)) | 224 'ORDER BY collision', (digest,)) |
230 collision2 = 0 | 225 collision2 = 0 |
231 for row in cursor.fetchall(): | 226 for row in cursor.fetchall(): |
232 collision2 = row[0] | 227 collision2 = row[0] |
233 filename = os.path.join( | 228 filename = os.path.join( |
234 directory, digest + '-' + str(collision2)) | 229 directory, digest + '-' + str(collision2)) |
235 if os.path.isfile(filename): | 230 if os.path.isfile(filename): |
236 with open(filename, 'r') as file_p: | 231 with open(filename, 'r') as file_p: |
237 data2 = file_p.read() | 232 data2 = file_p.read() |
238 if data == data2: | 233 if data == data2: |
239 collision = collision2 | 234 collision = collision2 |
240 break | 235 break |
241 if collision == 0: | 236 if collision == 0: |
242 collision = collision2 + 1 | 237 collision = collision2 + 1 |
243 filename = os.path.join( | 238 filename = os.path.join( |
244 directory, digest + '-' + str(collision)) | 239 directory, digest + '-' + str(collision)) |
245 with open(filename, 'w') as file_p: | 240 with open(filename, 'w') as file_p: |
ced
2011/03/23 10:42:16
binary mode
| |
246 file_p.write(data) | 241 file_p.write(data) |
247 self.write(ids, {'digest': digest, 'collision': collision}) | 242 self.write(ids, {'digest': digest, 'collision': collision}) |
248 | 243 |
249 def make_digest(self, data): | 244 def make_digest(self, data): |
250 """ | 245 """ |
251 Returns a digest from the mail | 246 Returns a digest from the mail |
252 | 247 |
253 :param data: Data String | 248 :param data: Data String |
254 :return: Digest | 249 :return: Digest |
255 """ | 250 """ |
256 if hashlib: | 251 if hashlib: |
257 digest = hashlib.md5(data).hexdigest() | 252 digest = hashlib.md5(data).hexdigest() |
258 else: | 253 else: |
259 digest = md5.new(data).hexdigest() | 254 digest = md5.new(data).hexdigest() |
260 return digest | 255 return digest |
261 | 256 |
262 def create_from_email(self, mail, mailbox): | 257 def create_from_email(self, mail, mailbox): |
263 """ | 258 """ |
264 Creates a mail record from a given mail | 259 Creates a mail record from a given mail |
265 :param mail: email object | 260 :param mail: email object |
ced
2010/11/22 16:57:22
Why not passing the email data?
| |
266 :param mailbox: ID of the mailbox | 261 :param mailbox: ID of the mailbox |
267 """ | 262 """ |
268 header_obj = self.pool.get('electronic_mail.header') | 263 header_obj = self.pool.get('electronic_mail.header') |
264 email_date = mail.get('date') and datetime.fromtimestamp( | |
265 mktime(parsedate(mail.get('date')))) | |
ced
2011/03/23 10:42:16
Will it be more readable with an if statement
| |
269 values = { | 266 values = { |
270 'mailbox': mailbox, | 267 'mailbox': mailbox, |
271 'from_': mail.get('from'), | 268 'from_': mail.get('from'), |
272 'sender': mail.get('sender'), | 269 'sender': mail.get('sender'), |
273 'to': mail.get('to'), | 270 'to': mail.get('to'), |
274 'cc': mail.get('cc'), | 271 'cc': mail.get('cc'), |
275 'bcc': mail.get('bcc'), | 272 'bcc': mail.get('bcc'), |
276 'subject': mail.get('subject'), | 273 'subject': mail.get('subject'), |
277 'date': datetime.fromtimestamp( | 274 'date': email_date, |
278 mktime(parsedate(mail.get('date'))) | |
279 ), | |
280 'message_id': mail.get('message-id'), | 275 'message_id': mail.get('message-id'), |
281 'in_reply_to': mail.get('in-reply-to'), | 276 'in_reply_to': mail.get('in-reply-to'), |
282 'email': mail.as_string(), | 277 'email': mail.as_string(), |
283 'size': getsizeof(mail.as_string()), | 278 'size': getsizeof(mail.as_string()), |
284 } | 279 } |
285 create_id = self.create(values) | 280 create_id = self.create(values) |
286 header_obj.create_from_email(mail, create_id) | 281 header_obj.create_from_email(mail, create_id) |
287 return create_id | 282 return create_id |
288 | 283 |
ced
2010/11/22 16:57:22
We also require to have a method to retrieve origi
| |
289 ElectronicMail() | 284 ElectronicMail() |
290 | 285 |
291 | 286 |
292 class Header(ModelSQL, ModelView): | 287 class Header(ModelSQL, ModelView): |
293 "Header fields" | 288 "Header fields" |
294 _name = 'electronic_mail.header' | 289 _name = 'electronic_mail.header' |
295 _description = __doc__ | 290 _description = __doc__ |
296 | 291 |
297 name = fields.Char('Name', help='Name of Header Field') | 292 name = fields.Char('Name', help='Name of Header Field') |
298 value = fields.Char('Value', help='Value of Header Field') | 293 value = fields.Char('Value', help='Value of Header Field') |
299 electronic_mail = fields.Many2One('electronic_mail', 'e-mail') | 294 electronic_mail = fields.Many2One('electronic_mail', 'e-mail') |
300 | 295 |
301 def create_from_email(self, mail, mail_id): | 296 def create_from_email(self, mail, mail_id): |
302 """ | 297 """ |
303 :param mail: Email object | 298 :param mail: Email object |
304 :param mail_id: ID of the email from electronic_mail | 299 :param mail_id: ID of the email from electronic_mail |
305 """ | 300 """ |
306 for name, value in mail.items(): | 301 for name, value in mail.items(): |
307 values = { | 302 values = { |
308 'electronic_mail':mail_id, | 303 'electronic_mail':mail_id, |
ced
2011/03/23 10:42:16
space after :
| |
309 'name':name, | 304 'name':name, |
310 'value':value, | 305 'value':value, |
311 } | 306 } |
312 self.create(values) | 307 self.create(values) |
313 return True | 308 return True |
314 | 309 |
315 Header() | 310 Header() |
LEFT | RIGHT |