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 <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-01-03 15:56:41 -05:00
parent 2bb9128b5b
commit 5b9f637c2d
1 changed files with 77 additions and 6 deletions

View File

@ -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,6 +709,7 @@ class PackageUpdater:
self.refresh_condition.wait()
return
try:
if fetch_packages:
await self.execute_cmd(f"{APT_CMD} update", timeout=300.)
res = await self.execute_cmd_with_response(
"apt list --upgradable")