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