This commit is contained in:
vrenaville
2022-05-17 13:31:17 +02:00
parent 43ed7178af
commit 18d07353d9
@@ -20,21 +20,19 @@ _logger = logging.getLogger(__name__)
def clean_fs(files):
_logger.info('cleaning old files from filestore')
_logger.info("cleaning old files from filestore")
for full_path in files:
if os.path.exists(full_path):
try:
os.unlink(full_path)
except OSError:
_logger.info(
"_file_delete could not unlink %s",
full_path, exc_info=True
"_file_delete could not unlink %s", full_path, exc_info=True
)
except IOError:
# Harmless and needed for race conditions
_logger.info(
"_file_delete could not unlink %s",
full_path, exc_info=True
"_file_delete could not unlink %s", full_path, exc_info=True
)
@@ -52,34 +50,32 @@ def savepoint(cursor):
class IrAttachment(osv.osv):
_inherit = 'ir.attachment'
_inherit = "ir.attachment"
@staticmethod
def _compute_checksum(bin_data):
""" compute the checksum for the given datas
"""compute the checksum for the given datas
:param bin_data : datas in its binary form
"""
# an empty file has a checksum too (for caching)
return hashlib.sha1(bin_data or '').hexdigest()
return hashlib.sha1(bin_data or "").hexdigest()
def _is_user_admin(self, cr, uid):
if uid == SUPERUSER_ID:
return True
else:
return self.pool.get('res.users').has_group(
cr, uid, 'base.group_erp_manager'
return self.pool.get("res.users").has_group(
cr, uid, "base.group_erp_manager"
)
def _storage(self, cr, uid, context=None):
return self.pool['ir.config_parameter'].get_param(
cr, SUPERUSER_ID, 'ir_attachment.location', 'file'
return self.pool["ir.config_parameter"].get_param(
cr, SUPERUSER_ID, "ir_attachment.location", "file"
)
def _full_path(self, cr, uid, location, path):
# Hack to allow filestore migration from local filesystem to any remote
return super(IrAttachment, self)._full_path(
cr, uid, 'file://filestore', path
)
return super(IrAttachment, self)._full_path(cr, uid, "file://filestore", path)
def _register_hook(self, cr):
super(IrAttachment, self)._register_hook(cr)
@@ -101,7 +97,7 @@ class IrAttachment(osv.osv):
# done during the initialization. We need to move the attachments that
# could have been created or updated in other addons before this addon
# was loaded
update_module = load_modules_frame.f_locals.get('update_module')
update_module = load_modules_frame.f_locals.get("update_module")
# We need to call the migration on the loading of the model because
# when we are upgrading addons, some of them might add attachments.
@@ -110,12 +106,12 @@ class IrAttachment(osv.osv):
# Typical example is images of ir.ui.menu which are updated in
# ir.attachment at every upgrade of the addons
if update_module:
self.pool.get('ir.attachment')._force_storage_to_object_storage(
self.pool.get("ir.attachment")._force_storage_to_object_storage(
cr, SUPERUSER_ID
)
def _save_in_db_anyway(self, cr, uid, ids, context=None):
""" Return whether an attachment must be stored in db
"""Return whether an attachment must be stored in db
When we are using an Object Store. This is sometimes required
because the object storage is slower than the database/filesystem.
@@ -130,12 +126,11 @@ class IrAttachment(osv.osv):
an old database with attachments pointing to deleted assets.
"""
assert (isinstance(ids, int) or
len(ids) == 1), 'Expecting only one record'
assert isinstance(ids, int) or len(ids) == 1, "Expecting only one record"
rec = self.browse(cr, uid, ids, context=context)
# assets
if rec.res_model == 'ir.ui.view':
if rec.res_model == "ir.ui.view":
# assets are stored in 'ir.ui.view'
return True
@@ -146,58 +141,50 @@ class IrAttachment(osv.osv):
# we keep them in the database instead of the object storage
location = self._storage(cr, uid)
for attach in self.browse(cr, uid, id, context):
if (location in self._get_stores() and
self._save_in_db_anyway(cr, uid, [id], context)):
if location in self._get_stores() and self._save_in_db_anyway(
cr, uid, [id], context
):
# compute the fields that depend on datas
bin_data = value and value.decode('base64') or ''
bin_data = value and value.decode("base64") or ""
vals = {
'file_size': len(bin_data),
'checksum': self._compute_checksum(bin_data),
'db_datas': value,
"file_size": len(bin_data),
"checksum": self._compute_checksum(bin_data),
"db_datas": value,
# we seriously don't need index content on those fields
'index_content': False,
'store_fname': False,
"index_content": False,
"store_fname": False,
}
fname = attach.store_fname
# write as superuser, as user probably does not
# have write access
super(IrAttachment, self).write(
cr, SUPERUSER_ID, id, vals, context
)
super(IrAttachment, self).write(cr, SUPERUSER_ID, id, vals, context)
if fname:
self._file_delete(cr, uid, fname)
continue
self._data_set(cr, uid, id, 'datas', value, None, context)
self._data_set(cr, uid, id, "datas", value, None, context)
def _store_file_read(self, fname, bin_size=False):
storage = fname.partition('://')[0]
raise NotImplementedError(
'No implementation for %s' % (storage,)
)
storage = fname.partition("://")[0]
raise NotImplementedError("No implementation for %s" % (storage,))
def _store_file_write(self, storage, key, bin_data):
raise NotImplementedError(
'No implementation for %s' % (storage,)
)
raise NotImplementedError("No implementation for %s" % (storage,))
def _store_file_delete(self, fname):
storage = fname.partition('://')[0]
raise NotImplementedError(
'No implementation for %s' % (storage,)
)
storage = fname.partition("://")[0]
raise NotImplementedError("No implementation for %s" % (storage,))
def _file_read(self, cr, uid, location, fname, bin_size=False):
if self._is_file_from_a_store(fname):
return self._store_file_read(fname, bin_size=bin_size)
else:
_super = super(IrAttachment, self)
return _super._file_read(cr, uid, location,
fname, bin_size=bin_size)
return _super._file_read(cr, uid, location, fname, bin_size=bin_size)
def _file_write(self, cr, uid, location, value):
storage = self._storage(cr, uid)
if storage in self._get_stores():
bin_data = value.decode('base64')
bin_data = value.decode("base64")
key = self._compute_checksum(bin_data)
filename = self._store_file_write(storage, key, bin_data)
else:
@@ -209,8 +196,9 @@ class IrAttachment(osv.osv):
if self._is_file_from_a_store(fname):
# using SQL to include files hidden through unlink or due to record
# rules
cr.execute("SELECT COUNT(*) FROM ir_attachment "
"WHERE store_fname = %s", (fname,))
cr.execute(
"SELECT COUNT(*) FROM ir_attachment " "WHERE store_fname = %s", (fname,)
)
count = cr.fetchone()[0]
if int(count) == 1:
self._store_file_delete(fname)
@@ -219,33 +207,31 @@ class IrAttachment(osv.osv):
def _is_file_from_a_store(self, fname):
for store_name in self._get_stores():
uri = '{}://'.format(store_name)
uri = "{}://".format(store_name)
if fname.startswith(uri):
return True
return False
def _move_attachment_to_store(self, cr, uid, ids, context=None):
assert (isinstance(ids, int) or
len(ids) == 1), 'Expecting only one record'
assert isinstance(ids, int) or len(ids) == 1, "Expecting only one record"
rec = self.browse(cr, uid, ids, context)
_logger.info('inspecting attachment %s (%d)', rec.name, rec.id)
_logger.info("inspecting attachment %s (%d)", rec.name, rec.id)
fname = rec.store_fname
if fname:
# migrating from filesystem filestore
# or from the old 'store_fname' without the bucket name
_logger.info('moving %s on the object storage', fname)
self.write(cr, uid, ids, {'datas': rec.datas}, context)
_logger.info('moved %s on the object storage', fname)
_logger.info("moving %s on the object storage", fname)
self.write(cr, uid, ids, {"datas": rec.datas}, context)
_logger.info("moved %s on the object storage", fname)
return self._full_path(cr, uid, None, fname)
elif rec.db_datas:
_logger.info('moving on the object storage from database')
self.write(cr, uid, ids, {'datas': rec.datas}, context)
_logger.info("moving on the object storage from database")
self.write(cr, uid, ids, {"datas": rec.datas}, context)
def force_storage(self, cr, uid, context=None):
if not self._is_user_admin(cr, uid):
raise except_orm(
_('Error'),
_('Only administrators can execute this action.')
_("Error"), _("Only administrators can execute this action.")
)
storage = self._storage(cr, uid)
if storage not in self._get_stores():
@@ -253,10 +239,10 @@ class IrAttachment(osv.osv):
self._force_storage_to_object_storage(cr, uid, context)
def _force_storage_to_object_storage(self, cr, uid, context=None):
_logger.info('migrating files to the object storage')
_logger.info("migrating files to the object storage")
storage = self._storage(cr, uid)
domain = [('store_fname', 'not like', '{}://%'.format(storage))]
domain = [("store_fname", "not like", "{}://%".format(storage))]
ids = self.search(cr, uid, domain, context=context)
files_to_clean = []
@@ -273,7 +259,7 @@ class IrAttachment(osv.osv):
"WHERE id = %s "
"FOR UPDATE NOWAIT",
(attachment_id,),
log_exceptions=False
log_exceptions=False,
)
path = self._move_attachment_to_store(
@@ -282,8 +268,9 @@ class IrAttachment(osv.osv):
if path:
files_to_clean.append(path)
except psycopg2.OperationalError:
_logger.error('Could not migrate attachment %s to %s' %
(attachment_id, storage))
_logger.error(
"Could not migrate attachment %s to %s" % (attachment_id, storage)
)
def clean():
clean_fs(files_to_clean)
@@ -291,8 +278,8 @@ class IrAttachment(osv.osv):
# delete the files from the filesystem once we know the changes
# have been committed in ir.attachment
if files_to_clean:
cr.after('commit', clean)
cr.commit()
def _get_stores(self):
""" To get the list of stores activated in the system """
"""To get the list of stores activated in the system"""
return []