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)