Use standard binary fields to store e-mails.

This commit is contained in:
Albert Cervera i Areny 2023-03-19 00:08:52 +01:00
parent 444925970f
commit ef9c7f5403
1 changed files with 9 additions and 120 deletions

View File

@ -254,10 +254,8 @@ class ElectronicMail(ModelSQL, ModelView):
attachments = fields.Function(fields.Text('Attachments'), 'get_mail')
message_id = fields.Char('Message-ID', help='Unique Message Identifier')
in_reply_to = fields.Char('In-Reply-To')
digest = fields.Char('MD5 Digest', size=32)
collision = fields.Integer('Collision')
mail_file = fields.Function(fields.Binary('Mail File',
filename='mail_file_name'), 'get_mail', setter='set_mail')
mail_file = fields.Binary('Mail File', file_id='mail_file_id')
mail_file_id = fields.Char('Mail File ID')
mail_file_name = fields.Function(fields.Char('Mail File Name'), 'get_mail')
flag_send = fields.Boolean('Sent', readonly=True)
flag_received = fields.Boolean('Received', readonly=True)
@ -340,10 +338,6 @@ class ElectronicMail(ModelSQL, ModelView):
(_decode_header(bcc[0]), _decode_header(bcc[1])))
return parse_all_bcc
@staticmethod
def default_collision():
return 0
@staticmethod
def default_flag_seen():
return False
@ -463,39 +457,19 @@ class ElectronicMail(ModelSQL, ModelView):
})
return attachments
@staticmethod
def _get_mail(electronic_mail):
"""
Returns the mail object from reading the FS
:param electronic_mail: Browse Record of the mail
"""
db_name = Transaction().database.name
value = ''
if electronic_mail.digest:
filename = electronic_mail.digest
if electronic_mail.collision:
filename = filename + '-' + str(electronic_mail.collision)
filename = os.path.join(config.get('database', 'path'),
db_name, 'email', filename[0:2], filename)
try:
with open(filename, 'rb') as file_p:
value = file_p.read()
except IOError:
pass
return value
@classmethod
def get_mail(cls, mails, names):
result = {}
for fname in ['body', 'body_plain', 'body_html', 'num_attach',
'mail_file', 'mail_file_name', 'attachments']:
'mail_file_name', 'attachments']:
result[fname] = {}
with Transaction().set_context({'electronic.mail.mail_file': None}):
# Ensure that mail_file returns a binary and not its size
mails = cls.browse(mails)
for mail in mails:
mail_file = cls._get_mail(mail)
if mail_file:
result['mail_file'][mail.id] = fields.Binary.cast(mail_file)
if mail.mail_file:
result['mail_file_name'][mail.id] = '%d.txt' % mail.id
email = message_from_bytes(mail_file)
email = message_from_bytes(mail.mail_file)
body = mail.get_body(email)
html = body.get('body_html').strip()
# TODO: Find a better way to know if there's a real HTML body
@ -510,102 +484,17 @@ class ElectronicMail(ModelSQL, ModelView):
result['attachments'][mail.id] = '\n'.join([x['filename'] for x
in cls.get_attachments(email)])
else:
result['mail_file'][mail.id] = None
result['mail_file_name'][mail.id] = None
result['body'][mail.id] = None
result['body_plain'][mail.id] = None
result['body_html'][mail.id] = None
result['num_attach'][mail.id] = None
result['attachments'][mail.id] = None
for fname in ['body_plain', 'body_html', 'num_attach', 'mail_file']:
for fname in ['body_plain', 'body_html', 'num_attach']:
if fname not in names:
del result[fname]
return result
@classmethod
def set_mail(cls, records, name, data):
"""Saves an mail to the data path
:param data: Mail as string
"""
if data is False or data is None:
return
db_name = Transaction().database.name
# Prepare Directory <DATA PATH>/<DB NAME>/email
directory = os.path.join(config.get('database', 'path'), db_name)
if not os.path.isdir(directory):
os.makedirs(directory, 0o770)
digest = cls.make_digest(data)
directory = os.path.join(directory, 'email', digest[0:2])
if not os.path.isdir(directory):
os.makedirs(directory, 0o770)
# Filename <DIRECTORY>/<DIGEST>
filename = os.path.join(directory, digest)
collision = 0
if not os.path.isfile(filename):
# File doesnt exist already
# When writing output to the stream, if newline is None, any '\n'
# characters written are translated to the system default line separator,
# os.linesep. If newline is '' or '\n', no translation takes place.
# If newline is any of the other legal values, any '\n' characters
# written are translated to the given string.
# https://docs.python.org/3/library/functions.html?highlight=open#open
with open(filename, 'w', newline='\r\n') as file_p:
file_p.write(data)
else:
# File already exists, may be its the same email data
# or maybe different.
# Case 1: If different: we have to write file with updated
# Collission index
# Case 2: Same file: Leave it as such
with open(filename, 'r') as file_p:
data2 = file_p.read()
if data != data2:
cursor = Transaction().connection.cursor()
cursor.execute(
'SELECT DISTINCT(collision) FROM electronic_mail '
'WHERE digest = %s AND collision !=0 '
'ORDER BY collision', (digest,))
collision2 = 0
for row in cursor.fetchall():
collision2 = row[0]
filename = os.path.join(
directory, digest + '-' + str(collision2))
if os.path.isfile(filename):
with open(filename, 'r') as file_p:
data2 = file_p.read()
if data == data2:
collision = collision2
break
if collision == 0:
collision = collision2 + 1
filename = os.path.join(
directory, digest + '-' + str(collision))
with open(filename, 'w') as file_p:
file_p.write(data)
cls.write(records, {'digest': digest, 'collision': collision})
@staticmethod
def make_digest(data):
"""
Returns a digest from the mail
:param data: Data String
:return: Digest
"""
if isinstance(data, str):
data = data.encode('utf-8')
if hashlib:
digest = hashlib.md5(data).hexdigest()
else:
digest = md5.new(data).hexdigest()
return digest
@classmethod
def create_from_mail(cls, mail, mailbox, record=None):
"""