You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
6.1 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 LasLabs Inc.
  3. # Copyright 2018 ACSONE SA/NV.
  4. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
  5. import json
  6. import logging
  7. import os
  8. from odoo import api, exceptions, models, tools
  9. from odoo.modules.module import get_module_path
  10. from ..addon_hash import addon_hash
  11. PARAM_INSTALLED_CHECKSUMS = \
  12. 'module_auto_update.installed_checksums'
  13. PARAM_EXCLUDE_PATTERNS = \
  14. 'module_auto_update.exclude_patterns'
  15. DEFAULT_EXCLUDE_PATTERNS = \
  16. '*.pyc,*.pyo,i18n/*.pot,i18n_extra/*.pot,static/*'
  17. _logger = logging.getLogger(__name__)
  18. class FailedUpgradeError(exceptions.UserError):
  19. pass
  20. class IncompleteUpgradeError(exceptions.UserError):
  21. pass
  22. def ensure_module_state(env, modules, state):
  23. # read module states, bypassing any Odoo cache
  24. if not modules:
  25. return
  26. env.cr.execute(
  27. "SELECT name FROM ir_module_module "
  28. "WHERE id IN %s AND state != %s",
  29. (tuple(modules.ids), state),
  30. )
  31. names = [r[0] for r in env.cr.fetchall()]
  32. if names:
  33. raise FailedUpgradeError(
  34. "The following modules should be in state '%s' "
  35. "at this stage: %s. Bailing out for safety." %
  36. (state, ','.join(names), ),
  37. )
  38. class Module(models.Model):
  39. _inherit = 'ir.module.module'
  40. @api.multi
  41. def _get_checksum_dir(self):
  42. self.ensure_one()
  43. exclude_patterns = self.env["ir.config_parameter"].get_param(
  44. PARAM_EXCLUDE_PATTERNS,
  45. DEFAULT_EXCLUDE_PATTERNS,
  46. )
  47. exclude_patterns = [p.strip() for p in exclude_patterns.split(',')]
  48. keep_langs = self.env['res.lang'].search([]).mapped('code')
  49. module_path = get_module_path(self.name)
  50. if module_path and os.path.isdir(module_path):
  51. checksum_dir = addon_hash(
  52. module_path,
  53. exclude_patterns,
  54. keep_langs,
  55. )
  56. else:
  57. checksum_dir = False
  58. return checksum_dir
  59. @api.model
  60. def _get_saved_checksums(self):
  61. Icp = self.env['ir.config_parameter']
  62. return json.loads(Icp.get_param(PARAM_INSTALLED_CHECKSUMS, '{}'))
  63. @api.model
  64. def _save_checksums(self, checksums):
  65. Icp = self.env['ir.config_parameter']
  66. Icp.set_param(PARAM_INSTALLED_CHECKSUMS, json.dumps(checksums))
  67. @api.model
  68. def _save_installed_checksums(self):
  69. checksums = {}
  70. installed_modules = self.search([('state', '=', 'installed')])
  71. for module in installed_modules:
  72. checksums[module.name] = module._get_checksum_dir()
  73. self._save_checksums(checksums)
  74. @api.model
  75. def _get_modules_partially_installed(self):
  76. return self.search([
  77. ('state', 'in', ('to install', 'to remove', 'to upgrade')),
  78. ])
  79. @api.model
  80. def _get_modules_with_changed_checksum(self):
  81. saved_checksums = self._get_saved_checksums()
  82. installed_modules = self.search([('state', '=', 'installed')])
  83. return installed_modules.filtered(
  84. lambda r: r._get_checksum_dir() != saved_checksums.get(r.name),
  85. )
  86. @api.model
  87. def upgrade_changed_checksum(self, overwrite_existing_translations=False):
  88. """Run an upgrade of the database, upgrading only changed modules.
  89. Installed modules for which the checksum has changed since the
  90. last successful run of this method are marked "to upgrade",
  91. then the normal Odoo scheduled upgrade process
  92. is launched.
  93. If there is no module with a changed checksum, and no module in state
  94. other than installed, uninstalled, uninstallable, this method does
  95. nothing, otherwise the normal Odoo upgrade process is launched.
  96. After a successful upgrade, the checksums of installed modules are
  97. saved.
  98. In case of error during the upgrade, an exception is raised.
  99. If any module remains to upgrade or to uninstall after the upgrade
  100. process, an exception is raised as well.
  101. Note: this method commits the current transaction at each important
  102. step, it is therefore not intended to be run as part of a
  103. larger transaction.
  104. """
  105. _logger.info(
  106. "Checksum upgrade starting (i18n-overwrite=%s)...",
  107. overwrite_existing_translations
  108. )
  109. tools.config['overwrite_existing_translations'] = \
  110. overwrite_existing_translations
  111. _logger.info("Updating modules list...")
  112. self.update_list()
  113. changed_modules = self._get_modules_with_changed_checksum()
  114. if not changed_modules and not self._get_modules_partially_installed():
  115. _logger.info("No checksum change detected in installed modules "
  116. "and all modules installed, nothing to do.")
  117. return
  118. _logger.info("Marking the following modules to upgrade, "
  119. "for their checksums changed: %s...",
  120. ','.join(changed_modules.mapped('name')))
  121. changed_modules.button_upgrade()
  122. self.env.cr.commit() # pylint: disable=invalid-commit
  123. # in rare situations, button_upgrade may fail without
  124. # exception, this would lead to corruption because
  125. # no upgrade would be performed and save_installed_checksums
  126. # would update cheksums for modules that have not been upgraded
  127. ensure_module_state(self.env, changed_modules, 'to upgrade')
  128. _logger.info("Upgrading...")
  129. self.env['base.module.upgrade'].upgrade_module()
  130. self.env.cr.commit() # pylint: disable=invalid-commit
  131. _logger.info("Upgrade successful, updating checksums...")
  132. self._save_installed_checksums()
  133. self.env.cr.commit() # pylint: disable=invalid-commit
  134. partial_modules = self._get_modules_partially_installed()
  135. if partial_modules:
  136. raise IncompleteUpgradeError(
  137. "Checksum upgrade successful "
  138. "but incomplete for the following modules: %s" %
  139. ','.join(partial_modules.mapped('name'))
  140. )
  141. _logger.info("Checksum upgrade complete.")