Files
AURHub/backend/database/db.py
T
2026-05-21 13:50:34 +05:30

154 lines
5.2 KiB
Python

"""
SQLite database manager for ArchStore.
Handles connection lifecycle, schema creation, and cache operations.
"""
import aiosqlite
import os
import time
import json
from typing import Optional
# Determine writable database path based on install directory type
is_system_install = os.path.dirname(os.path.dirname(__file__)).startswith("/usr")
if is_system_install:
xdg_data_home = os.environ.get("XDG_DATA_HOME")
if xdg_data_home:
data_dir = os.path.join(xdg_data_home, "archstore")
else:
data_dir = os.path.join(os.path.expanduser("~"), ".local", "share", "archstore")
os.makedirs(data_dir, exist_ok=True)
DB_PATH = os.path.join(data_dir, "archstore.db")
else:
DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "archstore.db")
CACHE_TTL = 900 # 15 minutes in seconds
class Database:
"""Async SQLite database manager."""
def __init__(self, db_path: str = DB_PATH):
self.db_path = db_path
self._db: Optional[aiosqlite.Connection] = None
async def connect(self):
"""Open database connection and create tables."""
self._db = await aiosqlite.connect(self.db_path)
self._db.row_factory = aiosqlite.Row
await self._db.execute("PRAGMA journal_mode=WAL")
await self._create_tables()
async def close(self):
"""Close database connection."""
if self._db:
await self._db.close()
self._db = None
async def _create_tables(self):
"""Create required tables if they don't exist."""
await self._db.executescript("""
CREATE TABLE IF NOT EXISTS search_cache (
query TEXT NOT NULL,
source TEXT NOT NULL,
results TEXT NOT NULL,
created_at REAL NOT NULL,
PRIMARY KEY (query, source)
);
CREATE TABLE IF NOT EXISTS package_cache (
name TEXT PRIMARY KEY,
data TEXT NOT NULL,
created_at REAL NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_search_cache_time
ON search_cache(created_at);
CREATE INDEX IF NOT EXISTS idx_package_cache_time
ON package_cache(created_at);
""")
await self._db.commit()
async def get_cached_search(self, query: str, source: str) -> Optional[list]:
"""Get cached search results if still fresh."""
cursor = await self._db.execute(
"SELECT results, created_at FROM search_cache WHERE query = ? AND source = ?",
(query, source)
)
row = await cursor.fetchone()
if row and (time.time() - row["created_at"]) < CACHE_TTL:
return json.loads(row["results"])
return None
async def set_cached_search(self, query: str, source: str, results: list):
"""Cache search results."""
await self._db.execute(
"""INSERT OR REPLACE INTO search_cache (query, source, results, created_at)
VALUES (?, ?, ?, ?)""",
(query, source, json.dumps(results), time.time())
)
await self._db.commit()
async def get_cached_package(self, name: str) -> Optional[dict]:
"""Get cached package info if still fresh."""
cursor = await self._db.execute(
"SELECT data, created_at FROM package_cache WHERE name = ?",
(name,)
)
row = await cursor.fetchone()
if row and (time.time() - row["created_at"]) < CACHE_TTL:
return json.loads(row["data"])
return None
async def set_cached_package(self, name: str, data: dict):
"""Cache package info."""
await self._db.execute(
"""INSERT OR REPLACE INTO package_cache (name, data, created_at)
VALUES (?, ?, ?)""",
(name, json.dumps(data), time.time())
)
await self._db.commit()
async def get_setting(self, key: str, default: str = "") -> str:
"""Get a setting value."""
cursor = await self._db.execute(
"SELECT value FROM settings WHERE key = ?", (key,)
)
row = await cursor.fetchone()
return row["value"] if row else default
async def set_setting(self, key: str, value: str):
"""Set a setting value."""
await self._db.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
(key, value)
)
await self._db.commit()
async def clear_cache(self):
"""Clear all cached data."""
await self._db.execute("DELETE FROM search_cache")
await self._db.execute("DELETE FROM package_cache")
await self._db.commit()
async def cleanup_expired(self):
"""Remove expired cache entries."""
cutoff = time.time() - CACHE_TTL
await self._db.execute(
"DELETE FROM search_cache WHERE created_at < ?", (cutoff,)
)
await self._db.execute(
"DELETE FROM package_cache WHERE created_at < ?", (cutoff,)
)
await self._db.commit()
# Global database instance
db = Database()