""" 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()