# new sync refactor: from core.models.manage.session_management import init_session, close_session from core.services.sync.sync_service import coordinate_cache_sync, save_metadata from core.services.sync.orm_methods.sync_one_orm import sync_one_orm_model from core.errors.logger import logger # ORM models that can be sync'ed: from core.models.orm_models.Location import Location from core.models.orm_models.Operator import Operator # prior versions: 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 from core.controllers.ConfigurationController import ConfigurationController # from core.controllers.LocationController import LocationController # from core.controllers.OperatorController import OperatorController from core.controllers.SubscriptionPlanController import SubscriptionPlanController from core.observers.ClientObserver import ClientObserver from core.observers.ConnectionObserver import ConnectionObserver from typing import Optional import os import pathlib import re import shutil import subprocess class ClientController: @staticmethod def get_working_directory(): return str(pathlib.Path(__file__).resolve().parent.parent.parent) @staticmethod def get_version(): 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) @staticmethod def can_be_updated(): try: version = ClientController.get_version() except UnknownClientVersionError: return False return not ClientVersionController.is_latest(version) @staticmethod def sync(client_observer: ClientObserver = None, connection_observer: ConnectionObserver = None): if client_observer is not None: client_observer.notify('synchronizing', "Fetching list of new data ..") result = coordinate_cache_sync(client_observer, connection_observer) # logger.info(f"We got a Result from the API of {result}") # Outright Error: if not result["success"]: error_msg = result["error"] if client_observer is not None: client_observer.notify('synchronizing', f'Error! {error_msg}') return # Same: changed_tables = result["changed_tables"] if not changed_tables: if client_observer is not None: client_observer.notify('synchronized') return # We only make it past this point if there's New Data # flag for after the save, data_was_saved = False # Fetch and update the real data (no longer metadata)... # =================== ORM BASED MODELS ================== """ Note: for the new ORM based models, it does the Tor/system check in the API call itself. """ if "locations" in changed_tables: logger.info("Sync of Locations") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Locations List..') final_result = sync_one_orm_model(Location, "locations") if "operators" in changed_tables: logger.info("Sync of Operators") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Operators List..') final_result_two = sync_one_orm_model(Operator, "operators") # =================== MANUAL-SQL BASED MODELS ================== try: from core.controllers.ConnectionController import ConnectionController ConnectionController.with_preferred_connection(task=ClientController.__sync, changed_tables=changed_tables, client_observer=client_observer, connection_observer=connection_observer) # We set the flag to true, # the reason we use a flag, and don't just save it right here, # is because we want to isolate the success (or failure) of the real data, # from the potential failure of the ORM session metadata. data_was_saved = True except: # sync failed here, if client_observer is not None: client_observer.notify('synchronizing', 'Fetch Failed, but you can use old data.') finally: if data_was_saved: filtered_metadata = result["filtered_metadata"] # from the top of the function save_successful = save_metadata(filtered_metadata) # the "save_data" function is inside sync_service if client_observer is None: logger.error("Error: No client_observer to update the UI, the final part of the sync function skipped") return # can't update their UI if save_successful: logger.info("Metadata Saved Successfully") client_observer.notify('synchronized', "Fetch & Save Complete!") else: client_observer.notify('synchronizing', "Saving List of Metadata Failed.") @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(changed_tables: list, client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): if "applications" in changed_tables: logger.info("Sync applications..") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Browser List..') # noinspection PyProtectedMember ApplicationController._sync(proxies=proxies) if "application_versions" in changed_tables: logger.info("Sync Application Versions..") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Browser Version List..') # noinspection PyProtectedMember ApplicationVersionController._sync(proxies=proxies) if "client_version" in changed_tables: logger.info("Sync of client version") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Client Version List..') # noinspection PyProtectedMember ClientVersionController._sync(proxies=proxies) if "subscriptions" in changed_tables: logger.info("Sync of Subscriptions") if client_observer is not None: client_observer.notify('synchronizing', 'Fetching Subscription List..') # noinspection PyProtectedMember SubscriptionPlanController._sync(proxies=proxies) ConfigurationController.update_last_synced_at() logger.info("Real Data Fetch Completed Successfully") @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')