229 lines
9.1 KiB
Python
229 lines
9.1 KiB
Python
# 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.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
|
|
import time
|
|
|
|
def evaluate_errors(final_result: dict, client_observer: Optional[ClientObserver] = None) -> str:
|
|
if not final_result["success"] and client_observer:
|
|
category = final_result["category"]
|
|
client_observer.notify('synchronizing', f'{category} Error! Check error logs')
|
|
time.sleep(2) # so they can see it.
|
|
|
|
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)
|
|
|
|
# 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")
|
|
evaluate_errors(final_result)
|
|
|
|
|
|
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")
|
|
evaluate_errors(final_result_two)
|
|
|
|
# =================== 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')
|