From b744ed0a2dbc244474b98243c7e4f48b37b9fbd2 Mon Sep 17 00:00:00 2001 From: codeking Date: Sat, 15 Mar 2025 18:42:10 +0100 Subject: [PATCH] Implement support for incremental client updates --- core/Constants.py | 3 ++ core/Errors.py | 4 ++ core/controllers/ClientController.py | 65 ++++++++++++++++++++++++++-- core/observers/ClientObserver.py | 5 +++ 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/core/Constants.py b/core/Constants.py index 3625724..ad5a85b 100644 --- a/core/Constants.py +++ b/core/Constants.py @@ -8,6 +8,9 @@ class Constants: SP_API_BASE_URL: Final[str] = 'https://api.simplifiedprivacy.is/api/v1' + HV_CLIENT_PATH: Final[str] = os.environ.get('HV_CLIENT_PATH') + HV_CLIENT_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER') + HOME: Final[str] = os.path.expanduser('~') SYSTEM_CONFIG_PATH: Final[str] = '/etc' diff --git a/core/Errors.py b/core/Errors.py index bff83e7..08adbc7 100644 --- a/core/Errors.py +++ b/core/Errors.py @@ -6,6 +6,10 @@ class CommandNotFoundError(OSError): super().__init__(f"Command '{subject}' could not be found.") +class UnknownClientPathError(Exception): + pass + + class UnknownClientVersionError(Exception): pass diff --git a/core/controllers/ClientController.py b/core/controllers/ClientController.py index d9ebc99..202f31a 100644 --- a/core/controllers/ClientController.py +++ b/core/controllers/ClientController.py @@ -1,4 +1,5 @@ -from core.Errors import UnknownClientVersionError +from core.Constants import Constants +from core.Errors import UnknownClientPathError, UnknownClientVersionError, CommandNotFoundError from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationVersionController import ApplicationVersionController from core.controllers.ClientVersionController import ClientVersionController @@ -10,6 +11,9 @@ from core.observers.ConnectionObserver import ConnectionObserver from typing import Optional import os import pathlib +import re +import shutil +import subprocess class ClientController: @@ -21,9 +25,7 @@ class ClientController: @staticmethod def get_version(): - version_number = os.environ.get('HV_VERSION_NUMBER') - - if version_number is None: + if (version_number := Constants.HV_CLIENT_VERSION_NUMBER) is None: raise UnknownClientVersionError('The client version could not be determined.') return ClientVersionController.get_or_new(version_number) @@ -44,6 +46,20 @@ class ClientController: from core.controllers.ConnectionController import ConnectionController ConnectionController.with_preferred_connection(task=ClientController.__sync, client_observer=client_observer, connection_observer=connection_observer) + @staticmethod + def update(client_observer: ClientObserver = None, connection_observer: ConnectionObserver = None): + + from core.controllers.ConnectionController import ConnectionController + ConnectionController.with_preferred_connection(task=ClientController.__update, client_observer=client_observer, connection_observer=connection_observer) + + @staticmethod + def __get_path(): + + if (path := Constants.HV_CLIENT_PATH) is None: + raise UnknownClientPathError('The client path could not be determined.') + + return path + @staticmethod def __sync(client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): @@ -62,3 +78,44 @@ class ClientController: SubscriptionPlanController._sync(proxies=proxies) ConfigurationController.update_last_synced_at() + + if client_observer is not None: + client_observer.notify('synchronized') + + @staticmethod + + def __update(client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): + + if ClientController.can_be_updated(): + + if client_observer is not None: + client_observer.notify('updating') + + client_path = ClientController.__get_path() + update_environment = os.environ.copy() + + if proxies is not None: + update_environment | dict(http_proxy=proxies['http'], https_proxy=proxies['https']) + + if shutil.which('hv-updater') is None: + raise CommandNotFoundError('hv-updater') + + process = subprocess.Popen(('hv-updater', '--overwrite', '--remove-old', client_path), env=update_environment, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + highest_reported_progress = 0 + + for line in iter(process.stdout.readline, ''): + + match = re.search(r'(\d+\.\d+)%', line) + + if match and (progress := float(match.group(1))) > highest_reported_progress: + + highest_reported_progress = progress + + if client_observer is not None: + + client_observer.notify('update_progressing', None, dict( + progress=progress + )) + + if client_observer is not None: + client_observer.notify('updated') diff --git a/core/observers/ClientObserver.py b/core/observers/ClientObserver.py index 1d6cf92..5b06c83 100644 --- a/core/observers/ClientObserver.py +++ b/core/observers/ClientObserver.py @@ -4,4 +4,9 @@ from core.observers.BaseObserver import BaseObserver class ClientObserver(BaseObserver): def __init__(self): + self.on_synchronizing = [] + self.on_synchronized = [] + self.on_updating = [] + self.on_update_progressing = [] + self.on_updated = []