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:
parent
2bb9128b5b
commit
5b9f637c2d
|
@ -14,7 +14,7 @@ import io
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import tornado.gen
|
import tornado.gen
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop, PeriodicCallback
|
||||||
from tornado.httpclient import AsyncHTTPClient
|
from tornado.httpclient import AsyncHTTPClient
|
||||||
from tornado.locks import Event, Condition, Lock
|
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"
|
APT_CMD = "sudo DEBIAN_FRONTEND=noninteractive apt-get"
|
||||||
SUPPORTED_DISTROS = ["debian"]
|
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:
|
class UpdateManager:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
|
@ -61,6 +68,12 @@ class UpdateManager:
|
||||||
self.cmd_request_lock = Lock()
|
self.cmd_request_lock = Lock()
|
||||||
self.is_refreshing = False
|
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(
|
self.server.register_endpoint(
|
||||||
"/machine/update/moonraker", ["POST"],
|
"/machine/update/moonraker", ["POST"],
|
||||||
self._handle_update_request)
|
self._handle_update_request)
|
||||||
|
@ -97,7 +110,60 @@ class UpdateManager:
|
||||||
return
|
return
|
||||||
self.updaters['klipper'] = GitUpdater(self, "klipper", kpath, env)
|
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):
|
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]
|
app = web_request.get_endpoint().split("/")[-1]
|
||||||
inc_deps = web_request.get_boolean('include_deps', False)
|
inc_deps = web_request.get_boolean('include_deps', False)
|
||||||
if self.current_update is not None and \
|
if self.current_update is not None and \
|
||||||
|
@ -118,9 +184,12 @@ class UpdateManager:
|
||||||
|
|
||||||
async def _handle_status_request(self, web_request):
|
async def _handle_status_request(self, web_request):
|
||||||
check_refresh = web_request.get_boolean('refresh', False)
|
check_refresh = web_request.get_boolean('refresh', False)
|
||||||
# Don't refresh if an update is currently in progress,
|
# Don't refresh if a print is currently in progress or
|
||||||
# just return current state
|
# if an update is in progress. Just return the current
|
||||||
check_refresh &= self.current_update is None
|
# state
|
||||||
|
if self.current_update is not None or \
|
||||||
|
await self._check_klippy_printing():
|
||||||
|
check_refresh = False
|
||||||
need_refresh = False
|
need_refresh = False
|
||||||
if check_refresh:
|
if check_refresh:
|
||||||
# If there is an outstanding request processing a
|
# If there is an outstanding request processing a
|
||||||
|
@ -294,6 +363,7 @@ class UpdateManager:
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.http_client.close()
|
self.http_client.close()
|
||||||
|
self.refresh_cb.stop()
|
||||||
|
|
||||||
|
|
||||||
class GitUpdater:
|
class GitUpdater:
|
||||||
|
@ -631,7 +701,7 @@ class PackageUpdater:
|
||||||
self.refresh_condition = None
|
self.refresh_condition = None
|
||||||
IOLoop.current().spawn_callback(self.refresh)
|
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
|
# TODO: Use python-apt python lib rather than command line for updates
|
||||||
if self.refresh_condition is None:
|
if self.refresh_condition is None:
|
||||||
self.refresh_condition = Condition()
|
self.refresh_condition = Condition()
|
||||||
|
@ -639,7 +709,8 @@ class PackageUpdater:
|
||||||
self.refresh_condition.wait()
|
self.refresh_condition.wait()
|
||||||
return
|
return
|
||||||
try:
|
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(
|
res = await self.execute_cmd_with_response(
|
||||||
"apt list --upgradable")
|
"apt list --upgradable")
|
||||||
pkg_list = [p.strip() for p in res.split("\n") if p.strip()]
|
pkg_list = [p.strip() for p in res.split("\n") if p.strip()]
|
||||||
|
|
Loading…
Reference in New Issue