mirror of
https://github.com/0x5t4l1n/AURHub.git
synced 2026-05-26 19:26:35 +00:00
154 lines
5.2 KiB
Python
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()
|