update: added gui handler for core db exceptions

This commit is contained in:
JOhn 2026-08-08 10:47:59 -04:00
parent aecfdbad33
commit 3c67e72d2d
12 changed files with 405 additions and 87 deletions

View file

@ -10,6 +10,7 @@ from core.controllers.SyncController import new_sync
from core.controllers.ConfigurationController import ConfigurationController
from essentials.observers.ConnectionObserver import ConnectionObserver
from core.observers.ClientObserver import ClientObserver
from gui.v2.actions.database_health import GuiStorageDatabaseError, validate_storage_database, has_required_sync_data
# generic
import os
@ -121,12 +122,14 @@ if not assets_folder_setup():
# INITIALIZE DATABASE
# ============================================================================
lock_file = Path(f"{Constants.HV_DATA_HOME}/deleted_db.lock")
system_path = get_path()
database_path = system_path / "storage.db"
main_db_exists = False
logger.info("[DB MANAGEMENT] Starting DB init..")
try:
# Does the database exist?
system_path = get_path()
database_path = system_path / "storage.db"
main_db_exists = does_it_exist(database_path)
if main_db_exists:
validate_storage_database(database_path)
# Setup operations on the main DB which create it
init_session() # (engine, Session, _session all initialized from session_management)
@ -134,9 +137,14 @@ try:
# does the version checker table exist?
version_table_exists = does_db_version_table_exist()
except:
logger.error("Critical Error with database initialization!")
sys.exit()
except GuiStorageDatabaseError as error:
logger.error(f"[DB MANAGEMENT] Critical storage database error during initialization: {error}")
close_session()
recovery_dialog(str(error), "Unreadable")
except Exception as error:
logger.error(f"[DB MANAGEMENT] Critical Error with database initialization: {error}")
close_session()
recovery_dialog("HydraVeil could not initialize the local storage database. Please move or reset storage.db, then restart.", "Startup Error")
# ============================================================================
# LEGACY DATABASE CHECKS
@ -248,6 +256,14 @@ did_it_insert = insert_new_version(session, Constants.DB_VERSION_THIS_APP_WANTS)
if not did_it_insert:
logger.error(f"[DB MANAGEMENT] Critical Failure with updating/inserting the new DB version into the database.")
try:
sync_data_missing = not has_required_sync_data(database_path)
except GuiStorageDatabaseError as error:
logger.error(f"[DB MANAGEMENT] Critical storage database error during synced data check: {error}")
close_session()
recovery_dialog(str(error), "Unreadable")
sync_data_missing = True
# Load GUI either way:
from gui.main_ui import start_ui
@ -256,7 +272,10 @@ if migration_happened:
force_sync = True
clear_sync_cache()
else:
force_sync = False
force_sync = sync_data_missing
if sync_data_missing:
logger.info("[DB MANAGEMENT] Required synced data is missing. GUI will prompt for sync.")
# if we deleted their DB, we want to force sync,
if lock_file.exists():
@ -266,5 +285,3 @@ if lock_file.exists():
# Start GUI either way:
logger.info(f"[DB MANAGEMENT] Starting GUI..")
start_ui(force_sync)

View file

@ -59,6 +59,7 @@ from gui.v2.actions.profile_data import (
write_profile_data,
clear_profile_data,
)
from gui.v2.actions.database_health import GuiStorageDatabaseError
from gui.v2.actions.ticket_failure import (
save_ticket_verification_failure,
get_ticket_verification_failure,
@ -303,6 +304,18 @@ class CustomWindow(QMainWindow):
if issubclass(identifier, UnknownConnectionTypeError):
self.setup_popup()
os.execv(sys.executable, [sys.executable] + sys.argv)
elif issubclass(identifier, GuiStorageDatabaseError):
core_logger.error(
f"Storage database error:\n"
f"Type: {identifier.__name__}\n"
f"Value: {str(message)}\n"
f"Traceback:\n{''.join(traceback.format_tb(trace))}"
)
QMessageBox.critical(
self,
"Database Error",
"The local storage database could not be read. Restart HydraVeil and use the database recovery options.",
)
else:
config = self._load_gui_config()
if config and config["logging"]["gui_logging_enabled"] == True:

View file

@ -0,0 +1,89 @@
import sqlite3
from pathlib import Path
class GuiStorageDatabaseError(Exception):
def __init__(self, message, detail=None):
self.detail = detail
super().__init__(f"{message} Details: {detail}" if detail else message)
def _connect_readonly(database_path):
path = Path(database_path).resolve()
return sqlite3.connect(f"{path.as_uri()}?mode=ro", uri=True)
def validate_storage_database(database_path):
path = Path(database_path)
if not path.exists():
return
connection = None
try:
connection = _connect_readonly(path)
connection.execute("PRAGMA schema_version").fetchone()
result = connection.execute("PRAGMA quick_check").fetchone()
if result and result[0] != "ok":
raise GuiStorageDatabaseError(
"The local storage database failed SQLite integrity checks.",
str(result[0]),
)
table_rows = connection.execute("SELECT name FROM sqlite_master WHERE type = 'table'").fetchall()
table_names = {row[0] for row in table_rows}
known_tables = {
"applications",
"application_versions",
"cached_sync",
"client_versions",
"database_version",
"encryptedproxies",
"locations",
"operators",
"subscription_plans",
}
if table_names and table_names.isdisjoint(known_tables):
raise GuiStorageDatabaseError(
"The local storage database does not look like a HydraVeil storage database.",
", ".join(sorted(table_names)),
)
except GuiStorageDatabaseError:
raise
except sqlite3.Error as error:
raise GuiStorageDatabaseError(
"The local storage database could not be read. It may be malformed, stale, or incompatible.",
str(error),
) from error
finally:
if connection is not None:
connection.close()
def _table_has_rows(database_path, table_name):
path = Path(database_path)
if not path.exists():
return False
connection = None
try:
connection = _connect_readonly(path)
return connection.execute(f'SELECT 1 FROM "{table_name}" LIMIT 1').fetchone() is not None
except sqlite3.OperationalError as error:
if "no such table" in str(error).lower():
return False
raise GuiStorageDatabaseError(
"The local storage database could not be checked for synced data.",
str(error),
) from error
except sqlite3.Error as error:
raise GuiStorageDatabaseError(
"The local storage database could not be checked for synced data.",
str(error),
) from error
finally:
if connection is not None:
connection.close()
def has_required_sync_data(database_path):
required_tables = ("applications", "application_versions", "locations")
return all(_table_has_rows(database_path, table_name) for table_name in required_tables)

View file

@ -53,16 +53,23 @@ def read_bwrap_enabled():
def prepare(gui_config_file):
profiles = ProfileController.get_all()
try:
profiles = ProfileController.get_all()
except Exception:
profiles = {}
profile_order = normalize_profile_order(gui_config_file, profiles.keys())
try:
endpoint_verification_enabled = ConfigurationController.get_endpoint_verification_enabled()
except Exception:
endpoint_verification_enabled = False
try:
current_connection = ConfigurationController.get_connection()
except Exception:
current_connection = None
return {
"profiles": profiles,
"profile_order": profile_order,
"current_connection": ConfigurationController.get_connection(),
"current_connection": current_connection,
"endpoint_verification_enabled": endpoint_verification_enabled,
"systemwide_enabled": read_systemwide_enabled(),
"bwrap_enabled": read_bwrap_enabled(),
@ -115,8 +122,9 @@ def truncate_key(text, max_length=50):
def build_verification_view(profile):
operator = None
if profile and getattr(profile, 'location', None) and profile.location.operator:
operator = profile.location.operator
location = getattr(profile, 'location', None) if profile else None
if location and not isinstance(location, dict) and getattr(location, 'operator', None):
operator = location.operator
if not operator:
return {
"operator_name": "N/A",

View file

@ -19,7 +19,10 @@ class ConnectionManager:
self.profile_button_objects[profile_id] = profile_button_objects
def get_available_resolutions(self, profile_id):
profile = ProfileController.get(profile_id)
try:
profile = ProfileController.get(profile_id)
except Exception:
profile = None
if profile and hasattr(profile, 'resolution') and profile.resolution:
self.available_resolutions.append(profile.resolution)
return self.available_resolutions

View file

@ -21,10 +21,10 @@ class BrowserPage(Page):
self.title.setText("Pick a Browser")
self.button_back.setVisible(True)
cm = main_window.connection_manager
if cm.is_synced():
self.connection_manager = main_window.connection_manager
if self.connection_manager.is_synced():
from gui.v2.actions.sync import generate_grid_positions
browsers = cm.get_browser_list()
browsers = self.connection_manager.get_browser_list()
positions = generate_grid_positions(len(browsers))
available = [(QPushButton, brw, positions[i]) for i, brw in enumerate(browsers)]
self.create_interface_elements(available)
@ -239,3 +239,21 @@ class BrowserPage(Page):
def gestionar_next(self):
self.custom_window.navigator.navigate("screen")
def gestionar_back(self):
profile_data = self.update_status.read_data()
protocol = profile_data.get("protocol", "")
if not self.connection_manager.is_synced() or not self.connection_manager.get_browser_list():
self.custom_window.navigator.navigate("menu")
return
if protocol == "wireguard":
self.custom_window.navigator.navigate("location")
return
if protocol in ("hidetor", "residential"):
self.custom_window.navigator.navigate("hidetor")
return
self.custom_window.navigator.navigate("menu")

View file

@ -359,14 +359,25 @@ class EditorPage(Page):
if browser_version == '':
browser_version = data_profile.get('browser_version', '')
browser_value = f"{browser_type} {browser_version}"
if connection == 'system-wide' or not browser_value.strip():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
normalized_browser = browser_value.strip().lower()
unknown_browser = (
not normalized_browser
or normalized_browser == "unknown"
or normalized_browser.startswith("unknown browser")
)
if connection == 'system-wide':
base_image = QPixmap()
elif unknown_browser:
base_image = QPixmap(fallback_path)
if base_image.isNull():
base_image = BrowserPage.create_browser_button_image(
"Browser", fallback_path, True)
else:
base_image = BrowserPage.create_browser_button_image(
browser_value, self.btn_path)
if base_image.isNull():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
base_image = BrowserPage.create_browser_button_image(
browser_value, fallback_path, True)
elif key == 'location':
@ -593,8 +604,14 @@ class EditorPage(Page):
self.on_sync_complete_for_edit_profile)
return
previous_index = (index - 1) % len(parameters[key])
previous_value = parameters[key][previous_index]
values = parameters.get(key, [])
if not values:
self.update_status.update_status(
f"No {key} data available. Sync the database and try again.")
return
previous_index = (index - 1) % len(values)
previous_value = values[previous_index]
self.update_temp_value(key, previous_value)
def show_next_value(self, key: str, index: int, parameters: dict) -> None:
@ -606,8 +623,14 @@ class EditorPage(Page):
self.on_sync_complete_for_edit_profile)
return
next_index = (index + 1) % len(parameters[key])
next_value = parameters[key][next_index]
values = parameters.get(key, [])
if not values:
self.update_status.update_status(
f"No {key} data available. Sync the database and try again.")
return
next_index = (index + 1) % len(values)
next_value = values[next_index]
self.update_temp_value(key, next_value)

View file

@ -12,6 +12,7 @@ from PyQt6.QtWidgets import (
from core.controllers.ConfigurationController import ConfigurationController
from core.controllers.ProfileController import ProfileController
from core.errors.logger import logger
from core.controllers.tickets.UseTicketController import (
do_we_use_a_random_ticket,
get_unused_tickets,
@ -20,6 +21,7 @@ from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemProfile import SystemProfile
from gui.v2.infrastructure.setup_observers import ticket_observer
from gui.v2.actions.database_health import GuiStorageDatabaseError
from gui.v2.actions.profile_order import normalize_profile_order
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
@ -205,6 +207,88 @@ class MenuPage(Page):
except RuntimeError:
pass
def _safe_profiles(self):
try:
return ProfileController.get_all()
except GuiStorageDatabaseError as error:
logger.error(f"[MENU] Storage database error while loading profiles: {error}")
self.update_status.update_status("Local storage database could not be read. Restart and recover storage.db.")
except Exception as error:
logger.error(f"[MENU] Error while loading profiles: {error}")
self.update_status.update_status("Could not load profiles. Sync or restart and try again.")
return {}
def _safe_profile(self, profile_id):
try:
return ProfileController.get(int(profile_id))
except GuiStorageDatabaseError as error:
logger.error(f"[MENU] Storage database error while loading profile {profile_id}: {error}")
self.update_status.update_status("Local storage database could not be read. Restart and recover storage.db.")
except Exception as error:
logger.error(f"[MENU] Error while loading profile {profile_id}: {error}")
self.update_status.update_status("Could not load profile data. Sync or restart and try again.")
return None
def _connection_code(self, profile):
connection = getattr(profile, 'connection', None)
return getattr(connection, 'code', None) or 'unknown'
def _location_key(self, profile):
location = getattr(profile, 'location', None)
if isinstance(location, dict):
country_code = location.get("country_code") or "na"
code = location.get("code") or "na"
return f'{country_code}_{code}'
country_code = getattr(location, 'country_code', None)
code = getattr(location, 'code', None)
if country_code and code:
return f'{country_code}_{code}'
return 'na_na'
def _browser_info(self, profile):
if not isinstance(profile, SessionProfile):
return "unknown browser", "", False
application_version = getattr(profile, 'application_version', None)
if isinstance(application_version, dict):
browser = application_version.get('application_code') or "unknown browser"
version = application_version.get('version_number') or ""
return browser, version, False
browser = getattr(application_version, 'application_code', None) or "unknown browser"
version = getattr(application_version, 'version_number', None) or ""
supported = bool(getattr(application_version, 'supported', False))
return browser, version, supported
def _profile_incomplete_reason(self, profile):
if profile is None:
return "Profile data could not be loaded. Sync or restart and try again."
connection = getattr(profile, 'connection', None)
if not getattr(connection, 'code', None):
return "Profile connection data is incomplete. Sync the database and try again."
location = getattr(profile, 'location', None)
if location is None or isinstance(location, dict):
return "Profile location data is incomplete. Sync the database and try again."
if isinstance(profile, SessionProfile):
application_version = getattr(profile, 'application_version', None)
if isinstance(application_version, dict) or application_version is None:
return "Profile browser data is incomplete. Sync the database and try again."
if not getattr(application_version, 'application_code', None) or not getattr(application_version, 'version_number', None):
return "Profile browser data is incomplete. Sync the database and try again."
return None
def _location_operator(self, profile):
location = getattr(profile, 'location', None)
if isinstance(location, dict) or location is None:
return None
return getattr(location, 'operator', None)
def match_core_profiles(self, profiles_dict):
new_dict = {}
@ -213,26 +297,13 @@ class MenuPage(Page):
profiles_dict.keys())
for idx in profile_ids:
try:
profile = profiles_dict[idx]
new_profile = {}
profile = profiles_dict.get(idx)
if profile is None:
continue
protocol = profile.connection.code
# print(f"DEBUG: the type is = {type(profile.location)}, value = {profile.location}")
if isinstance(profile.location, dict):
# Fake/missing data — show placeholders from data,
country_code = profile.location.get("country_code", "na")
code = profile.location.get("code", "na")
location = f'{country_code}_{code}'
else:
# Proper Location object
location = f'{profile.location.country_code}_{profile.location.code}'
except:
import sys
print(f"idx: {idx}")
sys.exit("STOPPING TO READ PRINTS FOR IDX. this failed on getting the location data inside match_core_profiles")
new_profile = {}
protocol = self._connection_code(profile)
location = self._location_key(profile)
new_profile['location'] = location
@ -251,23 +322,17 @@ class MenuPage(Page):
else:
new_profile['connection'] = 'just proxy'
if isinstance(profile, SessionProfile):
browser = profile.application_version.application_code
else:
browser = 'unknown'
if browser != 'unknown':
new_profile['browser'] = browser
new_profile['browser_version'] = profile.application_version.version_number
new_profile['browser_supported'] = profile.application_version.supported
else:
new_profile['browser'] = 'unknown browser'
new_profile['browser_supported'] = False
browser, browser_version, browser_supported = self._browser_info(profile)
new_profile['browser'] = browser
new_profile['browser_version'] = browser_version
new_profile['browser_supported'] = browser_supported
resolution = profile.resolution if hasattr(
profile, 'resolution') else 'None'
new_profile['dimentions'] = resolution
new_profile['name'] = profile.name
new_profile['name'] = getattr(profile, 'name', None) or f'Profile {idx}'
new_profile['incomplete_reason'] = self._profile_incomplete_reason(profile)
new_dict[f'Profile_{idx}'] = new_profile
@ -281,7 +346,7 @@ class MenuPage(Page):
if hasattr(self, 'verification_button'):
self.verification_button.setEnabled(False)
self.profiles_data = self.match_core_profiles(
ProfileController.get_all())
self._safe_profiles())
self.number_of_profiles = len(self.profiles_data)
self.profile_info = dict(self.profiles_data)
@ -296,7 +361,7 @@ class MenuPage(Page):
def refresh_profiles_data(self):
self.profiles_data = self.match_core_profiles(
ProfileController.get_all())
self._safe_profiles())
self.number_of_profiles = len(self.profiles_data)
self.profile_info = dict(self.profiles_data)
for profile_name, profile_value in self.profiles_data.items():
@ -304,12 +369,16 @@ class MenuPage(Page):
self.update_scroll_widget_size()
def refresh_menu_buttons(self):
profiles = ProfileController.get_all()
profiles = self._safe_profiles()
self.button_states.clear()
self.connection_manager._connected_profiles.clear()
self.IsSystem = 0
for profile_id, profile in profiles.items():
if ProfileController.is_enabled(profile):
try:
is_enabled = ProfileController.is_enabled(profile)
except Exception:
is_enabled = False
if is_enabled:
self.connection_manager.add_connected_profile(profile_id)
if isinstance(profile, SessionProfile):
if profile.connection and profile.connection.code == 'tor':
@ -424,7 +493,7 @@ class MenuPage(Page):
def create_profile_name_label(self, parent_label, name):
child_label = QLabel(parent_label)
child_label.setGeometry(0, 65, 175, 30)
child_label.setText(name)
child_label.setText(str(name or "Unnamed Profile"))
child_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
child_label.setAttribute(
Qt.WidgetAttribute.WA_TransparentForMouseEvents)
@ -537,9 +606,7 @@ class MenuPage(Page):
verification_layout.setContentsMargins(20, 20, 20, 20)
verification_layout.setSpacing(15)
operator = None
if profile_obj.location and profile_obj.location.operator:
operator = profile_obj.location.operator
operator = self._location_operator(profile_obj)
info_items = [
("Operator Name", "operator_name"),
@ -696,14 +763,18 @@ class MenuPage(Page):
connection = profile.get("connection", "")
country_garaje = profile.get("country_garaje", "")
profile_obj = ProfileController.get(self.reverse_id)
profile_obj = self._safe_profile(self.reverse_id)
is_profile_enabled = self.connection_manager.is_profile_connected(
self.reverse_id)
show_verification_widget = False
label_principal = None
label_tor = None
text_color = "white"
operator_name = profile_obj.location.operator.name if profile_obj.location and profile_obj.location.operator else ""
profile_location = getattr(profile_obj, 'location', None)
if isinstance(profile_location, dict):
profile_location = None
profile_operator = self._location_operator(profile_obj)
operator_name = getattr(profile_operator, 'name', '') if profile_operator else ""
if protocol.lower() == "wireguard" and is_profile_enabled and profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard':
verification_widget = self.create_verification_widget(
@ -713,7 +784,7 @@ class MenuPage(Page):
label_principal = None
elif operator_name != 'Simplified Privacy' and protocol.lower() == "wireguard":
if profile_obj.is_session_profile():
if isinstance(profile_obj, SessionProfile):
text_color = "black"
label_background = QLabel(self)
label_background.setGeometry(0, 60, 410, 354)
@ -733,9 +804,9 @@ class MenuPage(Page):
l_img.setScaledContents(True)
l_img.show()
self.additional_labels.append(l_img)
l_name = f"{profile_obj.location.country_name}, {profile_obj.location.name}" if profile_obj.location else ""
o_name = profile_obj.location.operator.name if profile_obj.location and profile_obj.location.operator else ""
n_key = profile_obj.location.operator.nostr_public_key if profile_obj.location and profile_obj.location.operator else ""
l_name = f"{profile_location.country_name}, {profile_location.name}" if profile_location else ""
o_name = getattr(profile_operator, 'name', '') if profile_operator else ""
n_key = getattr(profile_operator, 'nostr_public_key', '') if profile_operator else ""
info_txt = QTextEdit(self)
info_txt.setGeometry(130, 110, 260, 250)
@ -884,11 +955,12 @@ class MenuPage(Page):
self.current_profile_location = location
def show_profile_verification(self, profile_id):
profile = ProfileController.get(profile_id)
if not profile or not profile.location:
profile = self._safe_profile(profile_id)
profile_location = getattr(profile, 'location', None)
if not profile or not profile_location or isinstance(profile_location, dict):
return
location_key = f'{profile.location.country_code}_{profile.location.code}'
location_key = f'{profile_location.country_code}_{profile_location.code}'
location_info = self.connection_manager.get_location_info(location_key)
if not location_info:
return
@ -919,9 +991,22 @@ class MenuPage(Page):
self.boton_just.setEnabled(False)
self.boton_just_session.setEnabled(False)
profile = ProfileController.get(int(self.reverse_id))
is_tor = self.update_status.get_current_connection() == 'tor'
if profile.connection.code == 'tor' and not profile.application_version.installed and not is_tor:
profile = self._safe_profile(int(self.reverse_id))
incomplete_reason = self._profile_incomplete_reason(profile)
if incomplete_reason:
self.update_status.update_status(incomplete_reason)
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
return
try:
is_tor = self.update_status.get_current_connection() == 'tor'
except Exception:
is_tor = False
connection = getattr(profile, 'connection', None)
application_version = getattr(profile, 'application_version', None)
if getattr(connection, 'code', None) == 'tor' and not getattr(application_version, 'installed', False) and not is_tor:
message = f'You are using a Tor profile, but the associated browser is not installed. If you want the browser to be downloaded with Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
else:
@ -973,7 +1058,12 @@ class MenuPage(Page):
profile_data = {
'id': int(self.reverse_id)
}
profile = ProfileController.get(int(self.reverse_id))
profile = self._safe_profile(int(self.reverse_id))
if profile is None:
self.update_status.update_status("Could not load profile data. Sync or restart and try again.")
self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True)
return
is_session_profile = isinstance(profile, SessionProfile)
if not is_session_profile:
connected_profiles = self.connection_manager.get_connected_profiles()
@ -1030,7 +1120,7 @@ class MenuPage(Page):
'Sync failed. Please try again later.')
def change_connect_button(self):
profile = ProfileController.get(int(self.reverse_id))
profile = self._safe_profile(int(self.reverse_id))
is_connected = self.connection_manager.is_profile_connected(
int(self.reverse_id))
is_session_profile = isinstance(profile, SessionProfile)
@ -1047,6 +1137,10 @@ class MenuPage(Page):
is_connected and not is_session_profile)
def update_status_message(self, profile, is_connected):
if profile is None:
self.update_status.update_status("Profile data could not be loaded. Sync or restart and try again.")
return
if is_connected:
message = Worker.generate_profile_message(profile, is_enabled=True)
self.update_status.enable_marquee(message)
@ -1214,9 +1308,15 @@ class MenuPage(Page):
def change_app_page(self, text, Is_changed):
self.update_status.update_status(str(text))
if Is_changed:
current_connection = self.update_status.get_current_connection()
profile = ProfileController.get(int(self.reverse_id))
if current_connection != 'tor' and profile.connection.code == 'tor':
try:
current_connection = self.update_status.get_current_connection()
except Exception:
current_connection = None
profile = self._safe_profile(int(self.reverse_id))
incomplete_reason = self._profile_incomplete_reason(profile)
if incomplete_reason:
self.update_status.update_status(incomplete_reason)
elif current_connection != 'tor' and getattr(getattr(profile, 'connection', None), 'code', None) == 'tor':
message = f'You are using a Tor profile, but the profile subscription is missing or expired. If you want the billing to be done thourgh Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
self.popup = self._make_confirmation_popup(message, button_text='Proceed')
self.popup.finished.connect(
@ -1261,7 +1361,7 @@ class MenuPage(Page):
self.boton_edit.setEnabled(False)
if profile_id == self.reverse_id:
profile_obj = ProfileController.get(profile_id)
profile_obj = self._safe_profile(profile_id)
if profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard' and ConfigurationController.get_endpoint_verification_enabled():
self.print_profile_details(f"Profile_{profile_id}")
else:

0
gui/v2/ui/popups/generic_choice.py Normal file → Executable file
View file

0
gui/v2/ui/popups/terminal_threading.py Normal file → Executable file
View file

View file

@ -25,6 +25,7 @@ from core.Errors import (
)
from gui.v2.actions.locations import location_candidates
from gui.v2.actions.database_health import GuiStorageDatabaseError
from gui.v2.infrastructure.setup_observers import (
application_version_observer,
connection_observer,
@ -50,7 +51,22 @@ class Worker(QObject):
self._consumed_ticket = None
def run(self):
self.profile = ProfileController.get(int(self.profile_data['id']))
try:
self.profile = ProfileController.get(int(self.profile_data['id']))
except GuiStorageDatabaseError:
self.update_signal.emit(
"Local storage database could not be read. Restart and recover storage.db.", False, None, None, None)
return
except Exception:
self.update_signal.emit(
"Could not load profile data. Sync or restart and try again.", False, None, None, None)
return
incomplete_reason = self._profile_incomplete_reason()
if incomplete_reason:
self.update_signal.emit(incomplete_reason, False, None, None, None)
return
if 'use_ticket' in self.profile_data:
ticket_billing_code = self._consume_ticket(
self.profile_data['use_ticket'],
@ -131,6 +147,27 @@ class Worker(QObject):
self.update_signal.emit(
f"No profile found with ID: {self.profile_data['id']}", False, None, None, None)
def _profile_incomplete_reason(self):
if self.profile is None:
return None
connection = getattr(self.profile, 'connection', None)
if not getattr(connection, 'code', None):
return "Profile connection data is incomplete. Sync the database and try again."
location = getattr(self.profile, 'location', None)
if location is None or isinstance(location, dict):
return "Profile location data is incomplete. Sync the database and try again."
if isinstance(self.profile, SessionProfile):
application_version = getattr(self.profile, 'application_version', None)
if isinstance(application_version, dict) or application_version is None:
return "Profile browser data is incomplete. Sync the database and try again."
if not getattr(application_version, 'application_code', None) or not getattr(application_version, 'version_number', None):
return "Profile browser data is incomplete. Sync the database and try again."
return None
def _location_candidates(self, preferred=None):
return location_candidates(self.profile, preferred)

View file

@ -168,12 +168,19 @@ class WorkerThread(QThread):
self.finished.emit(False)
def list_profiles(self):
profiles = ProfileController.get_all()
try:
profiles = ProfileController.get_all()
except Exception:
profiles = {}
self.text_output.emit("Could not load profiles. Sync or restart and try again.")
self.profiles_output.emit(profiles)
def create_profile(self, profile_type):
location = LocationController.get(
self.profile_data['country_code'], self.profile_data['code'])
try:
location = LocationController.get(
self.profile_data['country_code'], self.profile_data['code'])
except Exception:
location = None
if location is None:
self.text_output.emit(
f"Invalid location code: {self.profile_data['location_code']}")
@ -188,8 +195,11 @@ class WorkerThread(QThread):
application_details = self.profile_data['application'].split(
':', 1)
application_version = ApplicationVersionController.get(
application_details[0], application_details[1] if len(application_details) > 1 else None)
try:
application_version = ApplicationVersionController.get(
application_details[0], application_details[1] if len(application_details) > 1 else None)
except Exception:
application_version = None
if application_version is None:
self.text_output.emit(
f"Invalid application: {self.profile_data['application']}")