Added libs
This commit is contained in:
135
app/updater.py
Normal file
135
app/updater.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import sys
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import os
|
||||
import json
|
||||
import types
|
||||
import logging
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
|
||||
from libs.app.common.config import Config
|
||||
from libs.app.common.logging import get_logger
|
||||
from libs.app.common.paths import LIBS_DIR
|
||||
|
||||
logger = get_logger("updater", buffer=True)
|
||||
|
||||
class Updater:
|
||||
"""
|
||||
Updater class responsible for managing library updates.
|
||||
"""
|
||||
def __init__(self, config:Config):
|
||||
self.config = config
|
||||
self.repositories = self.config.get("app", "repositories", default=[])
|
||||
self.updated_libs: dict[str, any] = {}
|
||||
|
||||
self._event_listeners: list[callable] = []
|
||||
|
||||
def update_libs(self):
|
||||
"""Check and update all configured libraries in parallel."""
|
||||
logger.debug("Checking libraries updates...")
|
||||
threads: list[Thread] = []
|
||||
|
||||
for lib in self.config.get("app", "libs", default=[]):
|
||||
lib_id = lib.get("id")
|
||||
self._update_lib_status(lib_id, "Reading configuration")
|
||||
if lib.get("update", {"checkUpdates":True}).get("checkUpdates", True):
|
||||
self._update_lib_status(lib_id, "Checking updates")
|
||||
thread = Thread(target=self.check_lib_update, args=(lib,))
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
else:
|
||||
self._update_lib_status(lib_id, "Checking updates disabled")
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
return self.updated_libs
|
||||
|
||||
def check_lib_update(self, lib:dict):
|
||||
"""
|
||||
Check for a library update in repositories and install if newer version is available.
|
||||
"""
|
||||
lib_id = lib.get("id")
|
||||
lib_config = self.config.get(lib_id)
|
||||
lib_repositories = lib.get("update", {"repositories":[]}).get("repositories", [])
|
||||
lib_path = LIBS_DIR / lib_id
|
||||
|
||||
if not lib_config:
|
||||
Path(lib_path).mkdir(parents=True, exist_ok=True)
|
||||
current_version = 0
|
||||
else:
|
||||
current_version = lib_config.get("info").get("version")
|
||||
|
||||
logger.debug(f"Checking updates of lib {lib_id} with current version {current_version}")
|
||||
|
||||
repositories = list(self.repositories)
|
||||
repositories.extend(lib_repositories)
|
||||
latest_version = (current_version, None)
|
||||
|
||||
for repo in repositories:
|
||||
try:
|
||||
info_url = f"{repo}/{lib_id}/info.json"
|
||||
logger.debug(f"Reading remote info of {lib_id} from repository {repo}")
|
||||
|
||||
with urllib.request.urlopen(info_url, timeout=2) as response:
|
||||
remote_info = json.load(response)
|
||||
logger.debug(f"{lib_id} remote info: {remote_info}")
|
||||
|
||||
remote_version = remote_info.get("version")
|
||||
if remote_version > latest_version[0]:
|
||||
latest_version = (remote_version, f"{repo}/{lib_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading remote info of {lib_id} from {repo}: {e}")
|
||||
|
||||
if latest_version[1]:
|
||||
lib_url = f"{latest_version[1]}/{lib_id}.zip"
|
||||
lib_file = LIBS_DIR / f"{lib_id}.zip"
|
||||
|
||||
logger.debug(f"Downloading lib from {lib_url} to {lib_file}")
|
||||
self._update_lib_status(lib_id, f"Downloading lastest lib version {latest_version[0]}")
|
||||
self.download_lib(lib_url, lib_file)
|
||||
|
||||
self._update_lib_status(lib_id, f"Installing lib requirements")
|
||||
self.pip_install_requirements(lib_path)
|
||||
self._update_lib_status(lib_id, f"Success updated")
|
||||
else:
|
||||
self._update_lib_status(lib_id, f"Current version is the latest")
|
||||
|
||||
def download_lib(self, url: str, path: str) -> None:
|
||||
"""Download and extract a lib from repository."""
|
||||
urllib.request.urlretrieve(url, path)
|
||||
logger.info(f"Extracting all from {path}")
|
||||
with zipfile.ZipFile(path, "r") as zip_ref:
|
||||
zip_ref.extractall(Path(path).parent.absolute())
|
||||
|
||||
def pip_install_requirements(self, lib_path: str) -> None:
|
||||
"""Run pip install on requirements.txt if present."""
|
||||
requirements_path = os.path.join(lib_path, "requirements.txt")
|
||||
if os.path.exists(requirements_path):
|
||||
logger.info(f"Installing requirements from {requirements_path}")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", requirements_path])
|
||||
else:
|
||||
logger.info(f"No requirements.txt found for {lib_path}")
|
||||
|
||||
def register_listener(self, callback: callable):
|
||||
"""
|
||||
Register a listener callback that will be called with
|
||||
each new event.
|
||||
"""
|
||||
if callback not in self._event_listeners:
|
||||
self._event_listeners.append(callback)
|
||||
|
||||
def emit_event(self, **kwargs):
|
||||
event = types.SimpleNamespace()
|
||||
for k, v in kwargs.items():
|
||||
setattr(event, k, v)
|
||||
for listener in self._event_listeners:
|
||||
listener(event)
|
||||
|
||||
def _update_lib_status(self, lib, status):
|
||||
self.updated_libs[lib] = status
|
||||
self.emit_event(name="status_lib", lib=lib, status=status)
|
||||
Reference in New Issue
Block a user