caching system + add extension entries to metadata pool

This commit is contained in:
2025-12-02 18:21:41 +01:00
parent af1e1d8098
commit 47169a5f66
18 changed files with 924 additions and 485 deletions

View File

@@ -7,9 +7,46 @@ const databases = new Map();
const DEFAULT_PATHS = {
anilist: path.join(__dirname, '..', 'metadata', 'anilist_anime.db'),
favorites: path.join(os.homedir(), "WaifuBoards", "favorites.db")
favorites: path.join(os.homedir(), "WaifuBoards", "favorites.db"),
cache: path.join(os.homedir(), "WaifuBoards", "cache.db")
};
async function ensureExtensionsTable(db) {
return new Promise((resolve, reject) => {
db.exec(`
CREATE TABLE IF NOT EXISTS extension (
ext_name TEXT NOT NULL,
id TEXT NOT NULL,
title TEXT NOT NULL,
metadata TEXT NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY(ext_name, id)
);
`, (err) => {
if (err) reject(err);
else resolve(true);
});
});
}
async function ensureCacheTable(db) {
return new Promise((resolve, reject) => {
db.exec(`
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
result TEXT NOT NULL,
created_at INTEGER NOT NULL,
ttl_ms INTEGER NOT NULL
);
`, (err) => {
if (err) reject(err);
else resolve(true);
});
});
}
function ensureFavoritesDB(dbPath) {
const dir = path.dirname(dbPath);
@@ -25,7 +62,6 @@ function ensureFavoritesDB(dbPath) {
);
return new Promise((resolve, reject) => {
if (!exists) {
const schema = `
CREATE TABLE IF NOT EXISTS favorites (
@@ -56,15 +92,11 @@ function ensureFavoritesDB(dbPath) {
const queries = [];
if (!hasHeaders) {
queries.push(
`ALTER TABLE favorites ADD COLUMN headers TEXT NOT NULL DEFAULT ""`
);
queries.push(`ALTER TABLE favorites ADD COLUMN headers TEXT NOT NULL DEFAULT ""`);
}
if (!hasProvider) {
queries.push(
`ALTER TABLE favorites ADD COLUMN provider TEXT NOT NULL DEFAULT ""`
);
queries.push(`ALTER TABLE favorites ADD COLUMN provider TEXT NOT NULL DEFAULT ""`);
}
if (queries.length === 0) {
@@ -91,6 +123,13 @@ function initDatabase(name = 'anilist', dbPath = null, readOnly = false) {
.catch(err => console.error("Error creando favorites:", err));
}
if (name === "cache") {
const dir = path.dirname(finalPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
const mode = readOnly ? sqlite3.OPEN_READONLY : (sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE);
const db = new sqlite3.Database(finalPath, mode, (err) => {
@@ -102,12 +141,25 @@ function initDatabase(name = 'anilist', dbPath = null, readOnly = false) {
});
databases.set(name, db);
if (name === "anilist") {
ensureExtensionsTable(db)
.catch(err => console.error("Error creating extension table:", err));
}
if (name === "cache") {
ensureCacheTable(db)
.catch(err => console.error("Error creating cache table:", err));
}
return db;
}
function getDatabase(name = 'anilist') {
if (!databases.has(name)) {
return initDatabase(name, null, name === 'anilist');
const readOnly = (name === 'anilist');
return initDatabase(name, null, readOnly);
}
return databases.get(name);
}
@@ -156,11 +208,73 @@ function closeDatabase(name = null) {
}
}
async function getCachedExtension(extName, id) {
return queryOne(
"SELECT metadata FROM extension WHERE ext_name = ? AND id = ?",
[extName, id]
);
}
async function cacheExtension(extName, id, title, metadata) {
return run(
`
INSERT INTO extension (ext_name, id, title, metadata, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(ext_name, id)
DO UPDATE SET
title = excluded.title,
metadata = excluded.metadata,
updated_at = ?
`,
[extName, id, title, JSON.stringify(metadata), Date.now(), Date.now()]
);
}
async function getExtensionTitle(extName, id) {
const sql = "SELECT title FROM extension WHERE ext_name = ? AND id = ?";
const row = await queryOne(sql, [extName, id], 'anilist');
return row ? row.title : null;
}
async function deleteExtension(extName) {
return run(
"DELETE FROM extension WHERE ext_name = ?",
[extName]
);
}
async function getCache(key) {
return queryOne("SELECT result, created_at, ttl_ms FROM cache WHERE key = ?", [key], "cache");
}
async function setCache(key, result, ttl_ms) {
return run(
`
INSERT INTO cache (key, result, created_at, ttl_ms)
VALUES (?, ?, ?, ?)
ON CONFLICT(key)
DO UPDATE SET result = excluded.result, created_at = excluded.created_at, ttl_ms = excluded.ttl_ms
`,
[key, JSON.stringify(result), Date.now(), ttl_ms],
"cache"
);
}
module.exports = {
initDatabase,
getDatabase,
queryOne,
queryAll,
run,
closeDatabase
getCachedExtension,
cacheExtension,
getExtensionTitle,
deleteExtension,
closeDatabase,
getCache,
setCache
};

View File

@@ -1,6 +1,7 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { queryAll, run } = require('./database');
const extensions = new Map();
@@ -24,11 +25,16 @@ async function loadExtensions() {
delete require.cache[require.resolve(filePath)];
const ExtensionClass = require(filePath);
const instance = typeof ExtensionClass === 'function'
? new ExtensionClass()
: (ExtensionClass.default ? new ExtensionClass.default() : null);
if (instance && (instance.type === "anime-board" || instance.type === "book-board" || instance.type === "image-board")) {
if (instance &&
(instance.type === "anime-board" ||
instance.type === "book-board" ||
instance.type === "image-board")) {
const name = instance.constructor.name;
extensions.set(name, instance);
console.log(`📦 Loaded Extension: ${name}`);
@@ -40,6 +46,22 @@ async function loadExtensions() {
}
console.log(`✅ Loaded ${extensions.size} extensions`);
try {
const loaded = Array.from(extensions.keys());
const rows = await queryAll("SELECT DISTINCT ext_name FROM extension");
for (const row of rows) {
if (!loaded.includes(row.ext_name)) {
console.log(`🧹 Cleaning cached metadata for removed extension: ${row.ext_name}`);
await run("DELETE FROM extension WHERE ext_name = ?", [row.ext_name]);
}
}
} catch (err) {
console.error("❌ Error cleaning extension cache:", err);
}
} catch (err) {
console.error("❌ Extension Scan Error:", err);
}