update: added gui handler for core db exceptions
This commit is contained in:
parent
aecfdbad33
commit
3c67e72d2d
12 changed files with 405 additions and 87 deletions
|
|
@ -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")
|
||||
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 = False
|
||||
logger.info("[DB MANAGEMENT] Starting DB init..")
|
||||
try:
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
89
gui/v2/actions/database_health.py
Executable file
89
gui/v2/actions/database_health.py
Executable 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)
|
||||
|
|
@ -53,16 +53,23 @@ def read_bwrap_enabled():
|
|||
|
||||
|
||||
def prepare(gui_config_file):
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ class ConnectionManager:
|
|||
self.profile_button_objects[profile_id] = profile_button_objects
|
||||
|
||||
def get_available_resolutions(self, 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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
profile = profiles_dict.get(idx)
|
||||
if profile is None:
|
||||
continue
|
||||
|
||||
new_profile = {}
|
||||
|
||||
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")
|
||||
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':
|
||||
browser, browser_version, browser_supported = self._browser_info(profile)
|
||||
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
|
||||
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))
|
||||
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'
|
||||
if profile.connection.code == 'tor' and not profile.application_version.installed and not is_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:
|
||||
try:
|
||||
current_connection = self.update_status.get_current_connection()
|
||||
profile = ProfileController.get(int(self.reverse_id))
|
||||
if current_connection != 'tor' and profile.connection.code == 'tor':
|
||||
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
0
gui/v2/ui/popups/generic_choice.py
Normal file → Executable file
0
gui/v2/ui/popups/terminal_threading.py
Normal file → Executable file
0
gui/v2/ui/popups/terminal_threading.py
Normal file → Executable 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):
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -168,12 +168,19 @@ class WorkerThread(QThread):
|
|||
self.finished.emit(False)
|
||||
|
||||
def list_profiles(self):
|
||||
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):
|
||||
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)
|
||||
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']}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue