sp-hydra-veil-gui/gui/v2/actions/database_health.py

89 lines
3 KiB
Python
Executable file

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)