Refactor HistoryManager to use persistent DB connection
Replaces per-operation SQLite connections with a persistent connection for improved efficiency and thread safety. Adds additional indexes for faster search on title, channel, and URL. Refactors all database access methods to use the persistent connection and simplifies transaction handling.
This commit is contained in:
@@ -50,6 +50,7 @@ class HistoryManager:
|
|||||||
_lock = threading.RLock()
|
_lock = threading.RLock()
|
||||||
# Define DB file next to the old JSON file
|
# Define DB file next to the old JSON file
|
||||||
_db_file = APP_DATA_DIR / "ytsage_history.db"
|
_db_file = APP_DATA_DIR / "ytsage_history.db"
|
||||||
|
_connection = None
|
||||||
_initialized = False
|
_initialized = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -67,8 +68,12 @@ class HistoryManager:
|
|||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
cls._db_file.parent.mkdir(parents=True, exist_ok=True)
|
cls._db_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with sqlite3.connect(cls._db_file, check_same_thread=False) as conn:
|
# We use a persistent connection to avoid churn
|
||||||
cursor = conn.cursor()
|
if cls._connection is None:
|
||||||
|
cls._connection = sqlite3.connect(cls._db_file, check_same_thread=False)
|
||||||
|
cls._connection.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
cursor = cls._connection.cursor()
|
||||||
|
|
||||||
# Create table
|
# Create table
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
@@ -96,7 +101,13 @@ class HistoryManager:
|
|||||||
ON history (timestamp DESC)
|
ON history (timestamp DESC)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
conn.commit()
|
# Indexes for faster search (title, channel, url)
|
||||||
|
# This prevents full table scans during search
|
||||||
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_title ON history (title)")
|
||||||
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_channel ON history (channel)")
|
||||||
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_url ON history (url)")
|
||||||
|
|
||||||
|
cls._connection.commit()
|
||||||
|
|
||||||
# If we just created the DB and have a JSON file, migrate
|
# If we just created the DB and have a JSON file, migrate
|
||||||
if not db_exists and legacy_json_exists:
|
if not db_exists and legacy_json_exists:
|
||||||
@@ -117,7 +128,10 @@ class HistoryManager:
|
|||||||
|
|
||||||
if isinstance(data, list):
|
if isinstance(data, list):
|
||||||
count = 0
|
count = 0
|
||||||
with sqlite3.connect(cls._db_file, check_same_thread=False) as conn:
|
# Use the persistent connection
|
||||||
|
conn = cls._get_connection()
|
||||||
|
try:
|
||||||
|
with conn: # Transaction
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
for entry in data:
|
for entry in data:
|
||||||
try:
|
try:
|
||||||
@@ -159,8 +173,6 @@ class HistoryManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Skipped invalid entry during migration: {e}")
|
logger.error(f"Skipped invalid entry during migration: {e}")
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
logger.info(f"Successfully migrated {count} history entries.")
|
logger.info(f"Successfully migrated {count} history entries.")
|
||||||
|
|
||||||
# Rename old JSON to .bak to avoid re-migration, or keep as backup
|
# Rename old JSON to .bak to avoid re-migration, or keep as backup
|
||||||
@@ -169,14 +181,21 @@ class HistoryManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Could not rename legacy history file: {e}")
|
logger.warning(f"Could not rename legacy history file: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Migration transaction failed: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Migration failed: {e}")
|
logger.error(f"Migration failed: {e}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_connection(cls):
|
def _get_connection(cls):
|
||||||
"""Get a database connection."""
|
"""Get the persistent database connection."""
|
||||||
cls._init_db()
|
cls._init_db()
|
||||||
return sqlite3.connect(cls._db_file, check_same_thread=False)
|
if cls._connection is None:
|
||||||
|
# Should be created in _init_db, but just in case
|
||||||
|
cls._connection = sqlite3.connect(cls._db_file, check_same_thread=False)
|
||||||
|
cls._connection.row_factory = sqlite3.Row
|
||||||
|
return cls._connection
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_entries(cls, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
def get_all_entries(cls, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||||
@@ -192,9 +211,10 @@ class HistoryManager:
|
|||||||
entries = []
|
entries = []
|
||||||
try:
|
try:
|
||||||
with cls._lock: # Lock for simple concurrency safety
|
with cls._lock: # Lock for simple concurrency safety
|
||||||
with cls._get_connection() as conn:
|
# Use persistent connection
|
||||||
# Return dict-like rows
|
conn = cls._get_connection()
|
||||||
conn.row_factory = sqlite3.Row
|
# conn.row_factory is already set in _init_db/_get_connection
|
||||||
|
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
query = "SELECT * FROM history ORDER BY timestamp DESC"
|
query = "SELECT * FROM history ORDER BY timestamp DESC"
|
||||||
@@ -237,8 +257,7 @@ class HistoryManager:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
with cls._get_connection() as conn:
|
conn = cls._get_connection()
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("SELECT * FROM history WHERE id = ?", (entry_id,))
|
cursor.execute("SELECT * FROM history WHERE id = ?", (entry_id,))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
@@ -299,7 +318,7 @@ class HistoryManager:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
with cls._get_connection() as conn:
|
conn = cls._get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
@@ -338,7 +357,7 @@ class HistoryManager:
|
|||||||
"""Remove an entry from history by ID."""
|
"""Remove an entry from history by ID."""
|
||||||
try:
|
try:
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
with cls._get_connection() as conn:
|
conn = cls._get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("DELETE FROM history WHERE id = ?", (entry_id,))
|
cursor.execute("DELETE FROM history WHERE id = ?", (entry_id,))
|
||||||
if cursor.rowcount > 0:
|
if cursor.rowcount > 0:
|
||||||
@@ -355,7 +374,7 @@ class HistoryManager:
|
|||||||
"""Clear all history entries."""
|
"""Clear all history entries."""
|
||||||
try:
|
try:
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
with cls._get_connection() as conn:
|
conn = cls._get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("DELETE FROM history")
|
cursor.execute("DELETE FROM history")
|
||||||
count = cursor.rowcount
|
count = cursor.rowcount
|
||||||
@@ -384,8 +403,7 @@ class HistoryManager:
|
|||||||
try:
|
try:
|
||||||
search_pattern = f"%{query}%"
|
search_pattern = f"%{query}%"
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
with cls._get_connection() as conn:
|
conn = cls._get_connection()
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT * FROM history
|
SELECT * FROM history
|
||||||
|
|||||||
Reference in New Issue
Block a user