Compare commits

...

2 commits

Author SHA1 Message Date
JOhn
fac64de65d update: updated gui status handling 2026-07-09 18:26:26 -04:00
JOhn
43d47145a7 update: updated tor sync 2026-07-08 15:38:23 -04:00
8 changed files with 153 additions and 70 deletions

0
gui/__main__.py Normal file → Executable file
View file

10
gui/v2/actions/sync_result.py Executable file
View file

@ -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

View file

@ -5,6 +5,22 @@ from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver from core.observers.ProfileObserver import ProfileObserver
from core.observers.TicketObserver import TicketObserver from core.observers.TicketObserver import TicketObserver
from core.controllers.ApplicationController import ApplicationController 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() application_version_observer = ApplicationVersionObserver()
client_observer = ClientObserver() client_observer = ClientObserver()
@ -14,71 +30,64 @@ profile_observer = ProfileObserver()
ticket_observer = TicketObserver() ticket_observer = TicketObserver()
def observer_message(topic, event):
subject = getattr(event, 'subject', None)
meta = getattr(event, 'meta', None)
if subject is not None:
return str(subject)
if meta:
return str(meta)
return str(topic)
def subscribe_observer_messages(observer, post_status):
for attr in dir(observer):
if not attr.startswith('on_'):
continue
callbacks = getattr(observer, attr, None)
if not isinstance(callbacks, list):
continue
topic = attr[3:]
observer.subscribe(
topic,
lambda event, topic=topic: post_status(observer_message(topic, event)))
def setup_observers(update_status): def setup_observers(update_status):
relay = StatusRelay(update_status)
post_status = relay.post
profile_observer.subscribe( profile_observer.subscribe(
'created', lambda event: update_status('Profile Created')) 'created', lambda event: post_status('Profile Created'))
profile_observer.subscribe( profile_observer.subscribe(
'destroyed', lambda event: update_status('Profile destroyed')) 'destroyed', lambda event: post_status('Profile destroyed'))
# client_observer.subscribe( subscribe_observer_messages(client_observer, post_status)
# 'synchronizing', lambda event: update_status('Sync in progress...'))
client_observer.subscribe( application_version_observer.subscribe('downloading', lambda event: post_status(
'synchronizing', lambda event: update_status(f'{event.subject if event.subject else "Sync in progress..."}'))
client_observer.subscribe(
'synchronized', lambda event: update_status('Sync complete'))
client_observer.subscribe(
'updating', lambda event: update_status('Updating client...'))
client_observer.subscribe('update_progressing', lambda event: update_status(
f'Current progress: {event.meta.get('progress'):.2f}%'))
client_observer.subscribe('updated', lambda event: update_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"}'))
application_version_observer.subscribe('downloading', lambda event: update_status(
f'Downloading {ApplicationController.get(event.subject.application_code).name}')) 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}%')) 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}')) f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
connection_observer.subscribe('connecting', lambda event: update_status( subscribe_observer_messages(connection_observer, 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( ticket_observer.subscribe('connecting', lambda event: post_status('Connecting to ticket server...'))
'Establishing Tor connection...')) ticket_observer.subscribe('sync_done', lambda event: post_status('Ticket prices synced.'))
ticket_observer.subscribe('waiting', lambda event: post_status('Waiting for payment...'))
connection_observer.subscribe( ticket_observer.subscribe('paid', lambda event: post_status('Payment received.'))
'tor_bootstrap_progressing', lambda event: update_status(f'{event.subject if event.subject else "Tor Bootstrapping.."}')) ticket_observer.subscribe('ticket_ready', lambda event: post_status('Ticket ready.'))
ticket_observer.subscribe('used', lambda event: post_status('Ticket used.'))
# original replaced version: ticket_observer.subscribe('connection_error', lambda event: post_status('Ticket server connection error.'))
# connection_observer.subscribe('tor_bootstrap_progressing', lambda event: update_status( ticket_observer.subscribe('failed_output', lambda event: post_status(f'{event.subject if event.subject else ""}'))
# f'Bootstrapping Tor {event.meta.get('progress'):.2f}%')) 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.'))
connection_observer.subscribe( ticket_observer.subscribe('error', lambda event: post_status(f'Ticket error: {event.subject if event.subject else ""}'))
'tor_bootstrapped', lambda event: update_status('Tor connection established.'))
client_observer.subscribe(
'custom_message', lambda event: update_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 ""}'))
return { return {
'status_relay': relay,
'application_version_observer': application_version_observer, 'application_version_observer': application_version_observer,
'client_observer': client_observer, 'client_observer': client_observer,
'connection_observer': connection_observer, 'connection_observer': connection_observer,

View file

@ -576,11 +576,9 @@ class EditorPage(Page):
def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
if status: if status:
self.update_status.update_status('Sync complete.')
self.extraccion() self.extraccion()
else: else:
self.update_status.update_status( self.connection_manager.set_synced(False)
'Sync failed. Please try again later.')
def show_previous_value(self, key: str, index: int, parameters: dict) -> None: def show_previous_value(self, key: str, index: int, parameters: dict) -> None:
if key == 'browser' or key == 'location': if key == 'browser' or key == 'location':

View file

@ -21,6 +21,7 @@ from core.models.system.SystemProfile import SystemProfile
from gui.v2.infrastructure.setup_observers import ticket_observer from gui.v2.infrastructure.setup_observers import ticket_observer
from gui.v2.actions.profile_order import normalize_profile_order 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.Page import Page
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
@ -1008,9 +1009,14 @@ class MenuPage(Page):
if fast_mode: if fast_mode:
if not self.connection_manager.is_synced(): if not self.connection_manager.is_synced():
self.update_status.update_status('Syncing in progress..') self.update_status.update_status('Syncing in progress..')
self.update_status.sync() self.fast_registration_sync_worker = self._build_sync_worker()
self.update_status.worker_thread.sync_output.connect( 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.on_sync_complete_for_fast_registration)
self.fast_registration_sync_worker.start()
else: else:
self.custom_window.navigator.navigate("fast_registration") self.custom_window.navigator.navigate("fast_registration")
return return
@ -1021,11 +1027,15 @@ class MenuPage(Page):
self.custom_window.navigator.navigate("protocol") self.custom_window.navigator.navigate("protocol")
def on_sync_complete_for_fast_registration(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): 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") self.custom_window.navigator.navigate("fast_registration")
else: else:
self.update_status.update_status( self.connection_manager.set_synced(False)
'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): def change_connect_button(self):
profile = ProfileController.get(int(self.reverse_id)) profile = ProfileController.get(int(self.reverse_id))
@ -1067,8 +1077,7 @@ class MenuPage(Page):
if status: if status:
self.custom_window.navigator.navigate("editor") self.custom_window.navigator.navigate("editor")
else: else:
self.update_status.update_status( self.connection_manager.set_synced(False)
'Sync failed. Please try again later.')
def settings_gui(self): def settings_gui(self):
self.custom_window.navigator.navigate("settings") self.custom_window.navigator.navigate("settings")
@ -1163,9 +1172,11 @@ class MenuPage(Page):
self.update_status.update_status("Syncing...") self.update_status.update_status("Syncing...")
self.worker_thread = WorkerThread('SYNC') self.worker_thread = WorkerThread('SYNC')
self.worker_thread.finished.connect( 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 None)
self.worker_thread.sync_output.connect( self.worker_thread.sync_output.connect(
self.update_status.update_values) self.update_status.update_values)
self.worker_thread.text_output.connect(
self.update_status.update_status)
self.worker_thread.start() self.worker_thread.start()
else: else:
self.update_status.update_status("Profile enable aborted") self.update_status.update_status("Profile enable aborted")

View file

@ -5,6 +5,7 @@ from PyQt6.QtGui import QPixmap
from core.controllers.ClientController import ClientController 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.ui.pages.Page import Page
from gui.v2.workers.worker_thread import WorkerThread from gui.v2.workers.worker_thread import WorkerThread
@ -82,10 +83,11 @@ class SyncScreen(Page):
self.worker_thread = WorkerThread('SYNC') self.worker_thread = WorkerThread('SYNC')
self.worker_thread.sync_output.connect(self.update_output) self.worker_thread.sync_output.connect(self.update_output)
self.worker_thread.text_output.connect(self.update_status.update_status)
self.worker_thread.start() self.worker_thread.start()
def update_output(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): 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") self.custom_window.navigator.navigate("install_system_package")
install_page = self.custom_window.navigator.get_cached("install_system_package") install_page = self.custom_window.navigator.get_cached("install_system_package")
if install_page is not None: if install_page is not None:
@ -95,14 +97,12 @@ class SyncScreen(Page):
self.button_back.setEnabled(True) self.button_back.setEnabled(True)
return 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_go.setEnabled(True)
self.button_back.setEnabled(True) self.button_back.setEnabled(True)
self.update_status.update_status('An error occurred during sync')
return return
self.update_status.update_status('Sync complete')
update_available = ClientController.can_be_updated() update_available = ClientController.can_be_updated()
if update_available: if update_available:

View file

@ -15,6 +15,8 @@ from core.models.session.SessionConnection import SessionConnection
from core.models.session.SessionProfile import SessionProfile from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemConnection import SystemConnection from core.models.system.SystemConnection import SystemConnection
from core.models.system.SystemProfile import SystemProfile 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 ( from gui.v2.infrastructure.setup_observers import (
client_observer, client_observer,
@ -222,28 +224,81 @@ class WorkerThread(QThread):
finally: finally:
self.finished.emit(True) self.finished.emit(True)
def _emit_observer_message(self, topic, event):
subject = getattr(event, 'subject', None)
meta = getattr(event, 'meta', None)
if subject is not None:
self.text_output.emit(str(subject))
elif meta:
self.text_output.emit(str(meta))
else:
self.text_output.emit(str(topic))
def _subscribe_observer_messages(self, observer, skip_topics=None):
skip_topics = skip_topics or set()
for attr in dir(observer):
if not attr.startswith('on_'):
continue
callbacks = getattr(observer, attr, None)
if not isinstance(callbacks, list):
continue
topic = attr[3:]
if topic in skip_topics:
continue
observer.subscribe(
topic,
lambda event, topic=topic: self._emit_observer_message(topic, event))
def sync(self): def sync(self):
try: try:
if self.action == 'SYNC_TOR': if self.action == 'SYNC_TOR':
ConfigurationController.set_connection('tor') ConfigurationController.set_connection('tor')
else: else:
ConfigurationController.set_connection('system') ConfigurationController.set_connection('system')
self.check_for_update() sync_succeeded = False
def sync_complete(event):
nonlocal sync_succeeded
sync_succeeded = True
self._emit_observer_message('synchronized', event)
sync_client_observer = ClientObserver()
sync_connection_observer = ConnectionObserver()
self._subscribe_observer_messages(
sync_client_observer, {'synchronized'})
self._subscribe_observer_messages(sync_connection_observer)
sync_client_observer.subscribe('synchronized', sync_complete)
ClientController.sync(
client_observer=sync_client_observer,
connection_observer=sync_connection_observer)
if not sync_succeeded:
self.sync_output.emit([], [], False, False, [], [])
self.finished.emit(False)
return
locations = LocationController.get_all() locations = LocationController.get_all()
browser = ApplicationVersionController.get_all() browser = ApplicationVersionController.get_all()
# print('the browser is: ', browser)
all_browser_versions = [ all_browser_versions = [
f"{browser.application_code}:{browser.version_number}" for browser in browser if browser.supported] f"{browser.application_code}:{browser.version_number}" for browser in browser if browser.supported]
all_location_codes = [ all_location_codes = [
f"{location.country_code}_{location.code}" for location in locations] 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( self.sync_output.emit(
all_location_codes, all_browser_versions, True, False, locations, browser) all_location_codes, all_browser_versions, True, False, locations, browser)
self.finished.emit(True)
except Exception as e: except Exception as e:
print(f'the error is: {e}') print(f'the error is: {e}')
self.sync_output.emit([], [], False, False, [], []) self.sync_output.emit([], [], False, False, [], [])
self.finished.emit(False)
def get_connection(self): def get_connection(self):
connection = ConfigurationController.get_connection() connection = ConfigurationController.get_connection()