update_manager: compare file hashes rather than modified times

The metadata won't always depict an actual change to the content of a file.  Compare a hash of the file content rather than retrieve modified times to determine if it is necessary to install dependencies.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-05-21 08:58:40 -04:00
parent f1ba8e3d9b
commit 68b9a073af
1 changed files with 26 additions and 20 deletions

View File

@ -16,6 +16,7 @@ import zipfile
import io import io
import time import time
import tempfile import tempfile
import hashlib
import tornado.gen import tornado.gen
from tornado.ioloop import IOLoop, PeriodicCallback from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
@ -687,12 +688,12 @@ class GitUpdater(BaseUpdater):
if self.repo.is_current(): if self.repo.is_current():
# No need to update # No need to update
return return
inst_mtime = self._get_file_mtime(self.install_script) inst_hash = self._get_file_hash(self.install_script)
pyreqs_mtime = self._get_file_mtime(self.python_reqs) pyreqs_hash = self._get_file_hash(self.python_reqs)
npm_mtime = self._get_file_mtime(self.npm_pkg_json) npm_hash = self._get_file_hash(self.npm_pkg_json)
await self._pull_repo() await self._pull_repo()
# Check Semantic Versions # Check Semantic Versions
await self._update_dependencies(inst_mtime, pyreqs_mtime, npm_mtime) await self._update_dependencies(inst_hash, pyreqs_hash, npm_hash)
# Refresh local repo state # Refresh local repo state
await self._update_repo_state(need_fetch=False) await self._update_repo_state(need_fetch=False)
if self.name == "moonraker": if self.name == "moonraker":
@ -718,19 +719,19 @@ class GitUpdater(BaseUpdater):
raise self._log_exc("Error running 'git pull'") raise self._log_exc("Error running 'git pull'")
async def _update_dependencies(self, async def _update_dependencies(self,
inst_mtime: Optional[float], inst_hash: Optional[str],
pyreqs_mtime: Optional[float], pyreqs_hash: Optional[str],
npm_mtime: Optional[float], npm_hash: Optional[str],
force: bool = False force: bool = False
) -> None: ) -> None:
vinfo = self._get_version_info() vinfo = self._get_version_info()
cur_version: Tuple = vinfo.get('version', ()) cur_version: Tuple = vinfo.get('version', ())
need_env_rebuild = cur_version < vinfo.get('env_version', ()) need_env_rebuild = cur_version < vinfo.get('env_version', ())
if force or self._check_need_update(inst_mtime, self.install_script): if force or self._check_need_update(inst_hash, self.install_script):
await self._install_packages() await self._install_packages()
if force or self._check_need_update(pyreqs_mtime, self.python_reqs): if force or self._check_need_update(pyreqs_hash, self.python_reqs):
await self._update_virtualenv(need_env_rebuild) await self._update_virtualenv(need_env_rebuild)
if force or self._check_need_update(npm_mtime, self.npm_pkg_json): if force or self._check_need_update(npm_hash, self.npm_pkg_json):
if self.npm_pkg_json is not None: if self.npm_pkg_json is not None:
self._notify_status("Updating Node Packages...") self._notify_status("Updating Node Packages...")
try: try:
@ -740,19 +741,24 @@ class GitUpdater(BaseUpdater):
except Exception: except Exception:
self._notify_status("Node Package Update failed") self._notify_status("Node Package Update failed")
def _get_file_mtime(self, filename: Optional[str]) -> Optional[float]: def _get_file_hash(self, filename: Optional[str]) -> Optional[str]:
if filename is None or not os.path.isfile(filename): if filename is None or not os.path.isfile(filename):
return None return None
return os.path.getmtime(filename) try:
with open(filename, "rb") as f:
data = f.read()
except Exception:
return None
return hashlib.sha256(data).hexdigest()
def _check_need_update(self, def _check_need_update(self,
prev_mtime: Optional[float], prev_hash: Optional[str],
filename: Optional[str] filename: Optional[str]
) -> bool: ) -> bool:
cur_mtime = self._get_file_mtime(filename) cur_hash = self._get_file_hash(filename)
if prev_mtime is None or cur_mtime is None: if prev_hash is None or cur_hash is None:
return False return False
return cur_mtime != prev_mtime return prev_hash != cur_hash
async def _install_packages(self) -> None: async def _install_packages(self) -> None:
if self.install_script is None: if self.install_script is None:
@ -856,9 +862,9 @@ class GitUpdater(BaseUpdater):
force_dep_update: bool = False force_dep_update: bool = False
) -> None: ) -> None:
self._notify_status("Attempting Repo Recovery...") self._notify_status("Attempting Repo Recovery...")
inst_mtime = self._get_file_mtime(self.install_script) inst_hash = self._get_file_hash(self.install_script)
pyreqs_mtime = self._get_file_mtime(self.python_reqs) pyreqs_hash = self._get_file_hash(self.python_reqs)
npm_mtime = self._get_file_mtime(self.npm_pkg_json) npm_hash = self._get_file_hash(self.npm_pkg_json)
if hard: if hard:
await self.repo.clone() await self.repo.clone()
@ -871,7 +877,7 @@ class GitUpdater(BaseUpdater):
if self.repo.is_dirty() or not self.is_valid: if self.repo.is_dirty() or not self.is_valid:
raise self.server.error( raise self.server.error(
"Recovery attempt failed, repo state not pristine", 500) "Recovery attempt failed, repo state not pristine", 500)
await self._update_dependencies(inst_mtime, pyreqs_mtime, npm_mtime, await self._update_dependencies(inst_hash, pyreqs_hash, npm_hash,
force=force_dep_update) force=force_dep_update)
if self.name == "moonraker": if self.name == "moonraker":
IOLoop.current().call_later( IOLoop.current().call_later(