hydraveil-gui/gui/__main__.py

287 lines
12 KiB
Python
Executable file

from core.errors.logger import logger
from core.models.manage.session_management import init_session, get_session, close_session, create_ALL_tables, create_ONLY_db_version_table, get_path, does_it_exist, does_db_version_table_exist
from core.models.manage.version_check import check_database_compatibility, insert_new_version, get_custom_message
from core.services.helpers.manage_assets import assets_folder_setup
from core.Constants import Constants
from core.models.DatabaseOperation import DatabaseOperation, DBErrorType
from core.models.manage.migrations import migrate_sql
from core.models.manage.clear_sql_model import clear_sql_model
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
import sys
from pathlib import Path
from functools import partial
# ============================================================================
# UTIL FUNCTIONS AND RECOVERY UI
# ============================================================================
def clear_sync_cache():
"""
Purpose:
Clear CachedSync
If that fails, prompt for a database wipe.
Why:
If they migrated, they have new schemas, but old data.
The problem with that, is the newest CachedSync entry will fool the sync data functions into thinking they don't need to sync new data,
Even though they do need to sync with the new schema.
"""
from core.models.orm_models.CachedSync import CachedSync
result = clear_sql_model(CachedSync)
if result:
logger.info(f"[DB MANAGEMENT] After Migration cleared the CachedSync table: {result}.")
else:
custom_error = "There were issues with clearing the Sync Cache in your database. Please delete the old version and fetch the new public data (what locations, browsers, ect) This will NOT affect your profiles or browser sessions."
recovery_dialog(custom_error, "CachedSync Issue")
def failed_migration(db_version_you_have):
close_session() # shut down db connection
custom_error = "There were issues with migrating your database. We transitioned to a new Database format for new features! Please delete the old version and fetch the new public data (what locations, browsers, ect) This will NOT affect your profiles or browser sessions."
recovery_dialog(custom_error, db_version_you_have)
def recovery_dialog(custom_error, db_version_you_have):
"""This ends the app by forcing them to delete the database, move it, or just quit."""
from gui.v2.ui.popups.Database_version import show_recovery_dialog
choice = show_recovery_dialog({"message": custom_error, "db_version": db_version_you_have, "status": "version_mismatch"})
logger.info(f"[DB MANAGEMENT] From the database error options, the user picked {choice}")
if choice == 1:
if os.path.exists(database_path):
os.remove(database_path)
logger.info(f"[DB MANAGEMENT] Deleted the DB file at {database_path}. Now putting lock on for forced sync..")
lock_file.touch()
logger.info(f"[DB MANAGEMENT] Closing the app gracefully, for them to reboot..")
sys.exit()
elif choice == 2:
logger.info(f"[DB MANAGEMENT] User opted to move the DB file.")
from gui.v2.ui.popups.pick_folder_to_move import launch_file_picker
launch_file_picker(database_path)
sys.exit()
else:
logger.error(f"[DB MANAGEMENT] User opted to close the app WITHOUT wiping the database, even though they need to.")
sys.exit()
def emergency_sync():
from gui.v2.ui.popups.generic_choice import show_generic_options
from gui.v2.ui.popups.terminal_threading import show_terminal
option = show_generic_options(
title="Sync the Database",
option_one="Clearweb (fastest)",
option_two="Tor",
option_three="No, Exit."
)
if option == 3:
sys.exit()
client_observer = ClientObserver()
connection_observer = ConnectionObserver()
if option == 1:
ConfigurationController.set_connection("system")
elif option == 2:
ConfigurationController.set_connection("tor")
# setup the function to be able to have the UI background thread run it.
do_this_function = partial(new_sync, client_observer, connection_observer)
return show_terminal(
do_this_function=do_this_function,
connection_observer=connection_observer,
client_observer=client_observer
)
# ============================================================================
# BEGIN PROGRAM
# ============================================================================
# ============================================================================
# ASSETS FOLDER
# ============================================================================
logger.info("Welcome, checking assets..")
if not assets_folder_setup():
from gui.v2.ui.popups.generic_error_popup import show_error
custom_error = "Unable to Setup your Assets Folder. Please create an assets directory at ~/.local/share/hydra-veil/assets"
show_error(custom_error, "Critical Error")
# ============================================================================
# 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:
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)
session = get_session()
# does the version checker table exist?
version_table_exists = does_db_version_table_exist()
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
# ============================================================================
migration_happened = False
# ============================================================================
# ZERO DATABASE = SYNC NOW
# ============================================================================
if not main_db_exists:
made_tables = create_ALL_tables()
if made_tables:
# force sync:
emergency_sync()
# we don't have to worry about migrations, they just got a new DB
create_ONLY_db_version_table()
from gui.main_ui import start_ui
try:
start_ui(force_sync=False)
except Exception as e:
error_msg = f"Critical Error with loading UI: {str(e)}"
logger.error(error_msg)
recovery_dialog(error_msg, "Sync Error")
sys.exit()
# ============================================================================
# LEGACY VERSION. MIGRATE
# ============================================================================
# are they upgrading from a legacy version? that would mean the table existed, but not the version table,
if not version_table_exists:
logger.info(f"[DB MANAGEMENT] We are dealing with a legacy database, we need to transition the user.")
migration = migrate_sql()
logger.info(f"[DB MANAGEMENT] Migration result is {migration.valid}.")
if migration.valid:
migration_happened = True
force_sync = True
clear_sync_cache()
else:
# THEN DISPLAY CHOICES AND RECOVERY UI
db_version_you_have = "Old System"
failed_migration(db_version_you_have)
# If they're still here, then if it's NOT a legacy version,
# and the new version table doesn't exist, then create it:
if not version_table_exists:
create_ONLY_db_version_table()
# ============================================================================
# COMPARE DB VERISONS
# ============================================================================
compatability_dict = check_database_compatibility(session)
reason = compatability_dict.get("reason", "error")
is_compatable = compatability_dict.get("result", False)
logger.info(f"[DB MANAGEMENT] The result of the check is {is_compatable} and the reason is {reason}")
# ============================================================================
# IF the versions do NOT match
# ============================================================================
if not is_compatable:
logger.error(f"[DB MANAGEMENT] The App is expecting a different DB version than the real database. The reason is {reason}")
db_version_you_have = compatability_dict.get("old_db_version", "Error getting the Version")
# WHAT IS THE REASON?
custom_error = get_custom_message(reason, compatability_dict)
# shut down db connection
close_session()
# THEN DISPLAY CHOICES AND RECOVERY UI
recovery_dialog(custom_error, db_version_you_have)
# goodbye. exits.
# ============================================================================
# THE VERSIONS MATCH IF THEY ARE STILL HERE
# ============================================================================
try:
made_tables = create_ALL_tables()
# SCREEN FAILED TABLES
if not made_tables:
custom_error = "Critical Failure with starting the models of the database."
logger.error(f"[DB MANAGEMENT] {custom_error}")
close_session()
from gui.v2.ui.popups.Database_version import show_recovery_dialog
choice = show_recovery_dialog({"message": custom_error, "db_version": 0, "status": "cant_make"})
logger.info(f"[DB MANAGEMENT] From the database error options, the user picked {choice}")
sys.exit()
except:
logger.info(f"[DB MANAGEMENT] create ALL tables failed. Running migrations...")
migration = migrate_sql()
logger.info(f"[DB MANAGEMENT] Results of migration is {migration.valid}")
if not migration.valid:
db_version_you_have = 0
failed_migration(db_version_you_have)
# ASSUME TABLES CREATED
logger.info("[DB MANAGEMENT] Tables successfully made or initialized if pre-existing")
# UPDATE DATA
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
logger.info(f"[DB MANAGEMENT] Launcher is now passing it off to launch the main GUI window WITHOUT force sync..")
if migration_happened:
force_sync = True
clear_sync_cache()
else:
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():
force_sync = True
lock_file.unlink() # unlock for next time
# Start GUI either way:
logger.info(f"[DB MANAGEMENT] Starting GUI..")
start_ui(force_sync)