diff --git a/gui/__main__.py b/gui/__main__.py old mode 100644 new mode 100755 diff --git a/gui/__pycache__/__main__.cpython-312.pyc b/gui/__pycache__/__main__.cpython-312.pyc index f1cb84d..0a1cf29 100644 Binary files a/gui/__pycache__/__main__.cpython-312.pyc and b/gui/__pycache__/__main__.cpython-312.pyc differ diff --git a/gui/v2/actions/sync_result.py b/gui/v2/actions/sync_result.py new file mode 100755 index 0000000..76c0ca2 --- /dev/null +++ b/gui/v2/actions/sync_result.py @@ -0,0 +1,10 @@ +def is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers): + if status is not True: + return False + if isinstance(all_browsers, bool): + return False + if not available_locations or not available_browsers: + return False + if not locations or not all_browsers: + return False + return True diff --git a/gui/v2/infrastructure/setup_observers.py b/gui/v2/infrastructure/setup_observers.py index 3811843..a166fd9 100755 --- a/gui/v2/infrastructure/setup_observers.py +++ b/gui/v2/infrastructure/setup_observers.py @@ -5,6 +5,22 @@ from core.observers.InvoiceObserver import InvoiceObserver from core.observers.ProfileObserver import ProfileObserver from core.observers.TicketObserver import TicketObserver from core.controllers.ApplicationController import ApplicationController +from PyQt6.QtCore import QObject, pyqtSignal + + +class StatusRelay(QObject): + status_requested = pyqtSignal(object, bool) + + def __init__(self, update_status): + super().__init__() + self.update_status = update_status + self.status_requested.connect(self.apply_status) + + def post(self, text, clear=False): + self.status_requested.emit(text, clear) + + def apply_status(self, text, clear=False): + self.update_status(text, clear=clear) application_version_observer = ApplicationVersionObserver() client_observer = ClientObserver() @@ -15,70 +31,74 @@ ticket_observer = TicketObserver() def setup_observers(update_status): + relay = StatusRelay(update_status) + post_status = relay.post + profile_observer.subscribe( - 'created', lambda event: update_status('Profile Created')) + 'created', lambda event: post_status('Profile Created')) profile_observer.subscribe( - 'destroyed', lambda event: update_status('Profile destroyed')) + 'destroyed', lambda event: post_status('Profile destroyed')) # client_observer.subscribe( # 'synchronizing', lambda event: update_status('Sync in progress...')) client_observer.subscribe( - 'synchronizing', lambda event: update_status(f'{event.subject if event.subject else "Sync in progress..."}')) + 'synchronizing', lambda event: post_status(f'{event.subject if event.subject else "Sync in progress..."}')) client_observer.subscribe( - 'synchronized', lambda event: update_status('Sync complete')) + 'synchronized', lambda event: post_status('Sync complete')) client_observer.subscribe( - 'updating', lambda event: update_status('Updating client...')) - client_observer.subscribe('update_progressing', lambda event: update_status( + 'updating', lambda event: post_status('Updating client...')) + client_observer.subscribe('update_progressing', lambda event: post_status( f'Current progress: {event.meta.get('progress'):.2f}%')) - client_observer.subscribe('updated', lambda event: update_status( + client_observer.subscribe('updated', lambda event: post_status( 'Restart client to apply update.')) client_observer.subscribe( - 'custom_message', lambda event: update_status(f'{event.subject if event.subject else "Error, check logs"}')) + 'custom_message', lambda event: post_status(f'{event.subject if event.subject else "Error, check logs"}')) - application_version_observer.subscribe('downloading', lambda event: update_status( + application_version_observer.subscribe('downloading', lambda event: post_status( f'Downloading {ApplicationController.get(event.subject.application_code).name}')) - application_version_observer.subscribe('download_progressing', lambda event: update_status( + application_version_observer.subscribe('download_progressing', lambda event: post_status( f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%')) - application_version_observer.subscribe('downloaded', lambda event: update_status( + application_version_observer.subscribe('downloaded', lambda event: post_status( f'Downloaded {ApplicationController.get(event.subject.application_code).name}')) - connection_observer.subscribe('connecting', lambda event: update_status( + connection_observer.subscribe('connecting', lambda event: post_status( f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...')) - connection_observer.subscribe('tor_bootstrapping', lambda event: update_status( + connection_observer.subscribe('tor_bootstrapping', lambda event: post_status( 'Establishing Tor connection...')) connection_observer.subscribe( - 'tor_bootstrap_progressing', lambda event: update_status(f'{event.subject if event.subject else "Tor Bootstrapping.."}')) + 'tor_bootstrap_progressing', lambda event: post_status(f'{event.subject if event.subject else "Tor Bootstrapping.."}')) # original replaced version: # connection_observer.subscribe('tor_bootstrap_progressing', lambda event: update_status( # f'Bootstrapping Tor {event.meta.get('progress'):.2f}%')) connection_observer.subscribe( - 'tor_bootstrapped', lambda event: update_status('Tor connection established.')) + 'tor_bootstrapped', lambda event: post_status('Tor connection established.')) client_observer.subscribe( - 'custom_message', lambda event: update_status(f'{event.subject if event.subject else ""}')) + 'custom_message', lambda event: post_status(f'{event.subject if event.subject else ""}')) - ticket_observer.subscribe('connecting', lambda event: update_status('Connecting to ticket server...')) - ticket_observer.subscribe('sync_done', lambda event: update_status('Ticket prices synced.')) - ticket_observer.subscribe('waiting', lambda event: update_status('Waiting for payment...')) - ticket_observer.subscribe('paid', lambda event: update_status('Payment received.')) - ticket_observer.subscribe('ticket_ready', lambda event: update_status('Ticket ready.')) - ticket_observer.subscribe('used', lambda event: update_status('Ticket used.')) - ticket_observer.subscribe('connection_error', lambda event: update_status('Ticket server connection error.')) - ticket_observer.subscribe('failed_output', lambda event: update_status(f'{event.subject if event.subject else ""}')) - ticket_observer.subscribe('failed_input', lambda event: update_status(f'{event.subject if event.subject else ""}')) - ticket_observer.subscribe('unknown_error', lambda event: update_status('Unknown ticket error.')) - ticket_observer.subscribe('error', lambda event: update_status(f'Ticket error: {event.subject if event.subject else ""}')) + ticket_observer.subscribe('connecting', lambda event: post_status('Connecting to ticket server...')) + ticket_observer.subscribe('sync_done', lambda event: post_status('Ticket prices synced.')) + ticket_observer.subscribe('waiting', lambda event: post_status('Waiting for payment...')) + ticket_observer.subscribe('paid', lambda event: post_status('Payment received.')) + ticket_observer.subscribe('ticket_ready', lambda event: post_status('Ticket ready.')) + ticket_observer.subscribe('used', lambda event: post_status('Ticket used.')) + ticket_observer.subscribe('connection_error', lambda event: post_status('Ticket server connection error.')) + ticket_observer.subscribe('failed_output', lambda event: post_status(f'{event.subject if event.subject else ""}')) + ticket_observer.subscribe('failed_input', lambda event: post_status(f'{event.subject if event.subject else ""}')) + ticket_observer.subscribe('unknown_error', lambda event: post_status('Unknown ticket error.')) + ticket_observer.subscribe('error', lambda event: post_status(f'Ticket error: {event.subject if event.subject else ""}')) return { + 'status_relay': relay, 'application_version_observer': application_version_observer, 'client_observer': client_observer, 'connection_observer': connection_observer, diff --git a/gui/v2/ui/pages/menu_page.py b/gui/v2/ui/pages/menu_page.py index 5904cea..4cdb41a 100755 --- a/gui/v2/ui/pages/menu_page.py +++ b/gui/v2/ui/pages/menu_page.py @@ -21,6 +21,7 @@ from core.models.system.SystemProfile import SystemProfile from gui.v2.infrastructure.setup_observers import ticket_observer from gui.v2.actions.profile_order import normalize_profile_order +from gui.v2.actions.sync_result import is_valid_sync_payload from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.location_verification_page import LocationVerificationPage from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS @@ -1008,9 +1009,14 @@ class MenuPage(Page): if fast_mode: if not self.connection_manager.is_synced(): self.update_status.update_status('Syncing in progress..') - self.update_status.sync() - self.update_status.worker_thread.sync_output.connect( + self.fast_registration_sync_worker = self._build_sync_worker() + self.fast_registration_sync_worker.text_output.connect( + self.update_status.update_status) + self.fast_registration_sync_worker.sync_output.connect( + self.update_status.update_values) + self.fast_registration_sync_worker.sync_output.connect( self.on_sync_complete_for_fast_registration) + self.fast_registration_sync_worker.start() else: self.custom_window.navigator.navigate("fast_registration") return @@ -1021,12 +1027,18 @@ class MenuPage(Page): self.custom_window.navigator.navigate("protocol") def on_sync_complete_for_fast_registration(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): - if status: + if is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers): self.custom_window.navigator.navigate("fast_registration") else: + self.connection_manager.set_synced(False) self.update_status.update_status( 'Sync failed. Please try again later.') + def _build_sync_worker(self): + if self.update_status.get_current_connection() == 'tor': + return WorkerThread('SYNC_TOR') + return WorkerThread('SYNC') + def change_connect_button(self): profile = ProfileController.get(int(self.reverse_id)) is_connected = self.connection_manager.is_profile_connected( @@ -1163,9 +1175,11 @@ class MenuPage(Page): self.update_status.update_status("Syncing...") self.worker_thread = WorkerThread('SYNC') self.worker_thread.finished.connect( - lambda: self.handle_sync_after_verification(profile_id)) + lambda ok: self.handle_sync_after_verification(profile_id) if ok else self.update_status.update_status("Sync failed. Please try again later.")) self.worker_thread.sync_output.connect( self.update_status.update_values) + self.worker_thread.text_output.connect( + self.update_status.update_status) self.worker_thread.start() else: self.update_status.update_status("Profile enable aborted") diff --git a/gui/v2/ui/pages/sync_screen.py b/gui/v2/ui/pages/sync_screen.py index 5dcd9c6..f4d2557 100755 --- a/gui/v2/ui/pages/sync_screen.py +++ b/gui/v2/ui/pages/sync_screen.py @@ -5,6 +5,7 @@ from PyQt6.QtGui import QPixmap from core.controllers.ClientController import ClientController +from gui.v2.actions.sync_result import is_valid_sync_payload from gui.v2.ui.pages.Page import Page from gui.v2.workers.worker_thread import WorkerThread @@ -82,10 +83,11 @@ class SyncScreen(Page): self.worker_thread = WorkerThread('SYNC') self.worker_thread.sync_output.connect(self.update_output) + self.worker_thread.text_output.connect(self.update_status.update_status) self.worker_thread.start() def update_output(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): - if isinstance(all_browsers, bool) and not all_browsers: + if status is True and isinstance(all_browsers, bool) and not all_browsers: self.custom_window.navigator.navigate("install_system_package") install_page = self.custom_window.navigator.get_cached("install_system_package") if install_page is not None: @@ -95,7 +97,8 @@ class SyncScreen(Page): self.button_back.setEnabled(True) return - if status is False: + if not is_valid_sync_payload(available_locations, available_browsers, status, locations, all_browsers): + self.connection_manager.set_synced(False) self.button_go.setEnabled(True) self.button_back.setEnabled(True) self.update_status.update_status('An error occurred during sync') diff --git a/gui/v2/workers/worker_thread.py b/gui/v2/workers/worker_thread.py index a9a499e..5fc76ab 100755 --- a/gui/v2/workers/worker_thread.py +++ b/gui/v2/workers/worker_thread.py @@ -15,6 +15,8 @@ from core.models.session.SessionConnection import SessionConnection from core.models.session.SessionProfile import SessionProfile from core.models.system.SystemConnection import SystemConnection from core.models.system.SystemProfile import SystemProfile +from core.observers.ClientObserver import ClientObserver +from core.observers.ConnectionObserver import ConnectionObserver from gui.v2.infrastructure.setup_observers import ( client_observer, @@ -228,22 +230,72 @@ class WorkerThread(QThread): ConfigurationController.set_connection('tor') else: ConfigurationController.set_connection('system') - self.check_for_update() + sync_failed = False + + def sync_status(event): + nonlocal sync_failed + message = event.subject if event.subject else "Sync in progress..." + message_text = str(message) + lowered = message_text.lower() + if "error" in lowered or "failed" in lowered or "failure" in lowered: + sync_failed = True + self.text_output.emit(message_text) + + def sync_complete(event): + self.text_output.emit("Sync complete") + + def connection_status(event): + subject = event.subject + if isinstance(subject, dict): + attempt = subject.get("attempt_count") + maximum = subject.get("maximum_number_of_attempts") + self.text_output.emit( + f"[{attempt}/{maximum}] Performing connection attempt...") + else: + self.text_output.emit("Performing connection attempt...") + + sync_client_observer = ClientObserver() + sync_connection_observer = ConnectionObserver() + sync_client_observer.subscribe('synchronizing', sync_status) + sync_client_observer.subscribe('synchronized', sync_complete) + sync_connection_observer.subscribe('connecting', connection_status) + sync_connection_observer.subscribe( + 'tor_bootstrapping', lambda event: self.text_output.emit('Establishing Tor connection...')) + sync_connection_observer.subscribe( + 'tor_bootstrap_progressing', lambda event: self.text_output.emit(f'{event.subject if event.subject else "Tor Bootstrapping.."}')) + sync_connection_observer.subscribe( + 'tor_bootstrapped', lambda event: self.text_output.emit('Tor connection established.')) + + ClientController.sync( + client_observer=sync_client_observer, + connection_observer=sync_connection_observer) + + if sync_failed: + self.sync_output.emit([], [], False, False, [], []) + self.finished.emit(False) + return locations = LocationController.get_all() browser = ApplicationVersionController.get_all() - # print('the browser is: ', browser) all_browser_versions = [ f"{browser.application_code}:{browser.version_number}" for browser in browser if browser.supported] all_location_codes = [ f"{location.country_code}_{location.code}" for location in locations] + + if not all_location_codes or not all_browser_versions or not locations or not browser: + self.sync_output.emit([], [], False, False, [], []) + self.finished.emit(False) + return + self.sync_output.emit( all_location_codes, all_browser_versions, True, False, locations, browser) + self.finished.emit(True) except Exception as e: print(f'the error is: {e}') self.sync_output.emit([], [], False, False, [], []) + self.finished.emit(False) def get_connection(self): connection = ConfigurationController.get_connection()