From 5b9f637c2d39f23e596c08d1383fe8c2f4e25d10 Mon Sep 17 00:00:00 2001 From: Arksine Date: Sun, 3 Jan 2021 15:56:41 -0500 Subject: [PATCH] update_manager: Implement auto refresh The slower system package update will now only executed by the auto-refresh routine. Moonraker will check for updates roughly every 2 hours, however system packages updates will only occur between 1am and 4am local time. If a print is in progress any attempt to refresh or update will be aborted. Signed-off-by: Eric Callahan --- moonraker/plugins/update_manager.py | 83 ++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/moonraker/plugins/update_manager.py b/moonraker/plugins/update_manager.py index 0fe6ccc..2b4b422 100644 --- a/moonraker/plugins/update_manager.py +++ b/moonraker/plugins/update_manager.py @@ -14,7 +14,7 @@ import io import asyncio import time import tornado.gen -from tornado.ioloop import IOLoop +from tornado.ioloop import IOLoop, PeriodicCallback from tornado.httpclient import AsyncHTTPClient from tornado.locks import Event, Condition, Lock @@ -25,6 +25,13 @@ SUPPLEMENTAL_CFG_PATH = os.path.join( APT_CMD = "sudo DEBIAN_FRONTEND=noninteractive apt-get" SUPPORTED_DISTROS = ["debian"] +# Check For Updates Every 2 Hours +UPDATE_REFRESH_TIME = 7200000 +# Refresh APT Repo no sooner than 12 hours +MIN_PKG_UPDATE_INTERVAL = 43200 +# Refresh APT Repo no later than 5am +MAX_PKG_UPDATE_HOUR = 5 + class UpdateManager: def __init__(self, config): self.server = config.get_server() @@ -61,6 +68,12 @@ class UpdateManager: self.cmd_request_lock = Lock() self.is_refreshing = False + # Auto Status Refresh + self.last_package_refresh_time = 0 + self.refresh_cb = PeriodicCallback( + self._handle_auto_refresh, UPDATE_REFRESH_TIME) + self.refresh_cb.start() + self.server.register_endpoint( "/machine/update/moonraker", ["POST"], self._handle_update_request) @@ -97,7 +110,60 @@ class UpdateManager: return self.updaters['klipper'] = GitUpdater(self, "klipper", kpath, env) + async def _check_klippy_printing(self): + klippy_apis = self.server.lookup_plugin('klippy_apis') + result = await klippy_apis.query_objects( + {'print_stats': None}, default={}) + pstate = result.get('print_stats', {}).get('state', "") + return pstate.lower() == "printing" + + async def _handle_auto_refresh(self): + if await self._check_klippy_printing(): + # Don't Refresh during a print + logging.info("Klippy is printing, auto refresh aborted") + return + vinfo = {} + need_refresh_all = not self.is_refreshing + async with self.cmd_request_lock: + self.is_refreshing = True + cur_time = time.time() + cur_hour = time.localtime(cur_time).tm_hour + time_diff = cur_time - self.last_package_refresh_time + try: + # Update packages if it has been more than 12 hours + # and the local time is between 12AM and 5AM + if time_diff > MIN_PKG_UPDATE_INTERVAL and \ + cur_hour <= MAX_PKG_UPDATE_HOUR: + self.last_package_refresh_time = cur_time + sys_updater = self.updaters['system'] + await sys_updater.refresh(True) + vinfo['system'] = sys_updater.get_update_status() + for name, updater in list(self.updaters.items()): + if name in vinfo: + # System was refreshed and added to version info + continue + if need_refresh_all: + ret = updater.refresh() + if asyncio.iscoroutine(ret): + await ret + if hasattr(updater, "get_update_status"): + vinfo[name] = updater.get_update_status() + except Exception: + logging.exception("Unable to Refresh Status") + return + finally: + self.is_refreshing = False + uinfo = { + 'version_info': vinfo, + 'github_rate_limit': self.gh_rate_limit, + 'github_requests_remaining': self.gh_limit_remaining, + 'github_limit_reset_time': self.gh_limit_reset_time, + 'busy': self.current_update is not None} + self.server.send_event("update_manager:update_refreshed", uinfo) + async def _handle_update_request(self, web_request): + if await self._check_klippy_printing(): + raise self.server.error("Update Refused: Klippy is printing") app = web_request.get_endpoint().split("/")[-1] inc_deps = web_request.get_boolean('include_deps', False) if self.current_update is not None and \ @@ -118,9 +184,12 @@ class UpdateManager: async def _handle_status_request(self, web_request): check_refresh = web_request.get_boolean('refresh', False) - # Don't refresh if an update is currently in progress, - # just return current state - check_refresh &= self.current_update is None + # Don't refresh if a print is currently in progress or + # if an update is in progress. Just return the current + # state + if self.current_update is not None or \ + await self._check_klippy_printing(): + check_refresh = False need_refresh = False if check_refresh: # If there is an outstanding request processing a @@ -294,6 +363,7 @@ class UpdateManager: def close(self): self.http_client.close() + self.refresh_cb.stop() class GitUpdater: @@ -631,7 +701,7 @@ class PackageUpdater: self.refresh_condition = None IOLoop.current().spawn_callback(self.refresh) - async def refresh(self): + async def refresh(self, fetch_packages=False): # TODO: Use python-apt python lib rather than command line for updates if self.refresh_condition is None: self.refresh_condition = Condition() @@ -639,7 +709,8 @@ class PackageUpdater: self.refresh_condition.wait() return try: - await self.execute_cmd(f"{APT_CMD} update", timeout=300.) + if fetch_packages: + await self.execute_cmd(f"{APT_CMD} update", timeout=300.) res = await self.execute_cmd_with_response( "apt list --upgradable") pkg_list = [p.strip() for p in res.split("\n") if p.strip()]