implemented api for local library & global config
This commit is contained in:
3
desktop/package-lock.json
generated
3
desktop/package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.3",
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
@@ -1955,7 +1956,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/assert-plus": {
|
"node_modules/assert-plus": {
|
||||||
@@ -4825,7 +4825,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.3",
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const cron = require("node-cron");
|
|||||||
const { initHeadless } = require("./electron/shared/headless");
|
const { initHeadless } = require("./electron/shared/headless");
|
||||||
const { initDatabase } = require("./electron/shared/database");
|
const { initDatabase } = require("./electron/shared/database");
|
||||||
const { loadExtensions } = require("./electron/shared/extensions");
|
const { loadExtensions } = require("./electron/shared/extensions");
|
||||||
|
const { ensureConfigFile } = require("./electron/shared/config");
|
||||||
const { init } = require("./electron/api/rpc/rpc.controller");
|
const { init } = require("./electron/api/rpc/rpc.controller");
|
||||||
const {refreshTrendingAnime, refreshTopAiringAnime} = require("./electron/api/anime/anime.service");
|
const {refreshTrendingAnime, refreshTopAiringAnime} = require("./electron/api/anime/anime.service");
|
||||||
const {refreshPopularBooks, refreshTrendingBooks} = require("./electron/api/books/books.service");
|
const {refreshPopularBooks, refreshTrendingBooks} = require("./electron/api/books/books.service");
|
||||||
@@ -29,6 +30,7 @@ const rpcRoutes = require("./electron/api/rpc/rpc.routes");
|
|||||||
const userRoutes = require("./electron/api/user/user.routes");
|
const userRoutes = require("./electron/api/user/user.routes");
|
||||||
const listRoutes = require("./electron/api/list/list.routes");
|
const listRoutes = require("./electron/api/list/list.routes");
|
||||||
const anilistRoute = require("./electron/api/anilist/anilist");
|
const anilistRoute = require("./electron/api/anilist/anilist");
|
||||||
|
const localRoutes = require("./electron/api/local/local.routes");
|
||||||
|
|
||||||
fastify.addHook("preHandler", async (request) => {
|
fastify.addHook("preHandler", async (request) => {
|
||||||
const auth = request.headers.authorization;
|
const auth = request.headers.authorization;
|
||||||
@@ -70,15 +72,18 @@ fastify.register(rpcRoutes, { prefix: "/api" });
|
|||||||
fastify.register(userRoutes, { prefix: "/api" });
|
fastify.register(userRoutes, { prefix: "/api" });
|
||||||
fastify.register(anilistRoute, { prefix: "/api" });
|
fastify.register(anilistRoute, { prefix: "/api" });
|
||||||
fastify.register(listRoutes, { prefix: "/api" });
|
fastify.register(listRoutes, { prefix: "/api" });
|
||||||
|
fastify.register(localRoutes, { prefix: "/api" });
|
||||||
|
|
||||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
|
ensureConfigFile()
|
||||||
initDatabase("anilist");
|
initDatabase("anilist");
|
||||||
initDatabase("favorites");
|
initDatabase("favorites");
|
||||||
initDatabase("cache");
|
initDatabase("cache");
|
||||||
initDatabase("userdata");
|
initDatabase("userdata");
|
||||||
|
initDatabase("local_library");
|
||||||
init();
|
init();
|
||||||
|
|
||||||
const refreshAll = async () => {
|
const refreshAll = async () => {
|
||||||
|
|||||||
252
desktop/src/api/local/local.controller.ts
Normal file
252
desktop/src/api/local/local.controller.ts
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { getConfig as loadConfig, setConfig as saveConfig } from '../../shared/config.js';
|
||||||
|
import { queryOne, queryAll, run } from '../../shared/database.js';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import fs from "fs";
|
||||||
|
import { PathLike } from "node:fs";
|
||||||
|
import path from "path";
|
||||||
|
import {getAnimeById, searchAnimeLocal} from "../anime/anime.service";
|
||||||
|
import {getBookById, searchBooksLocal} from "../books/books.service";
|
||||||
|
|
||||||
|
type SetConfigBody = {
|
||||||
|
library?: {
|
||||||
|
anime?: string | null;
|
||||||
|
manga?: string | null;
|
||||||
|
novels?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ScanQuery = {
|
||||||
|
mode?: 'full' | 'incremental';
|
||||||
|
};
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
type: 'anime' | 'manga' | 'novels';
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function resolveEntryMetadata(entry: any, type: string) {
|
||||||
|
let metadata = null;
|
||||||
|
let matchedId = entry.matched_id;
|
||||||
|
|
||||||
|
if (!matchedId) {
|
||||||
|
const query = entry.folder_name;
|
||||||
|
|
||||||
|
const results = type === 'anime'
|
||||||
|
? await searchAnimeLocal(query)
|
||||||
|
: await searchBooksLocal(query);
|
||||||
|
|
||||||
|
const first = results?.[0];
|
||||||
|
|
||||||
|
if (first?.id) {
|
||||||
|
matchedId = first.id;
|
||||||
|
|
||||||
|
await run(
|
||||||
|
`UPDATE local_entries
|
||||||
|
SET matched_id = ?, matched_source = 'anilist'
|
||||||
|
WHERE id = ?`,
|
||||||
|
[matchedId, entry.id],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedId) {
|
||||||
|
metadata = type === 'anime'
|
||||||
|
? await getAnimeById(matchedId)
|
||||||
|
: await getBookById(matchedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: entry.id,
|
||||||
|
type: entry.type,
|
||||||
|
matched: !!matchedId,
|
||||||
|
metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getConfig(_request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
return loadConfig();
|
||||||
|
} catch {
|
||||||
|
return reply.status(500).send({ error: 'FAILED_TO_LOAD_CONFIG' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setConfig(request: FastifyRequest<{ Body: SetConfigBody }>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { body } = request;
|
||||||
|
if (!body || typeof body !== 'object') {
|
||||||
|
return reply.status(400).send({ error: 'INVALID_BODY' });
|
||||||
|
}
|
||||||
|
return saveConfig(body);
|
||||||
|
} catch {
|
||||||
|
return reply.status(500).send({ error: 'FAILED_TO_SAVE_CONFIG' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function scanLibrary(request: FastifyRequest<{ Querystring: ScanQuery }>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const mode = request.query.mode || 'incremental';
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
if (!config.library) {
|
||||||
|
return reply.status(400).send({ error: 'NO_LIBRARY_CONFIGURED' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'full') {
|
||||||
|
await run(`DELETE FROM local_files`, [], 'local_library');
|
||||||
|
await run(`DELETE FROM local_entries`, [], 'local_library');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [type, basePath] of Object.entries(config.library)) {
|
||||||
|
if (!basePath || !fs.existsSync(<PathLike>basePath)) continue;
|
||||||
|
|
||||||
|
const dirs = fs.readdirSync(<string>basePath, { withFileTypes: true }).filter(d => d.isDirectory());
|
||||||
|
|
||||||
|
for (const dir of dirs) {
|
||||||
|
const fullPath = path.join(<string>basePath, dir.name);
|
||||||
|
const id = crypto.createHash('sha1').update(fullPath).digest('hex');
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const existing = await queryOne(`SELECT id FROM local_entries WHERE id = ?`, [id], 'local_library');
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await run(`UPDATE local_entries SET last_scan = ? WHERE id = ?`, [now, id], 'local_library');
|
||||||
|
await run(`DELETE FROM local_files WHERE entry_id = ?`, [id], 'local_library');
|
||||||
|
} else {
|
||||||
|
await run(
|
||||||
|
`INSERT INTO local_entries (id, type, path, folder_name, last_scan) VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
[id, type, fullPath, dir.name, now],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(fullPath, { withFileTypes: true }).filter(f => f.isFile());
|
||||||
|
for (const file of files) {
|
||||||
|
await run(
|
||||||
|
`INSERT INTO local_files (id, entry_id, file_path) VALUES (?, ?, ?)`,
|
||||||
|
[crypto.randomUUID(), id, path.join(fullPath, file.name)],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { status: 'OK' };
|
||||||
|
} catch (err) {
|
||||||
|
return reply.status(500).send({ error: 'FAILED_TO_SCAN_LIBRARY' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listEntries(request: FastifyRequest<{ Params: Params }>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { type } = request.params;
|
||||||
|
const entries = await queryAll(`SELECT * FROM local_entries WHERE type = ?`, [type], 'local_library');
|
||||||
|
|
||||||
|
return await Promise.all(entries.map((entry: any) => resolveEntryMetadata(entry, type)));
|
||||||
|
} catch {
|
||||||
|
return reply.status(500).send({ error: 'FAILED_TO_LIST_ENTRIES' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEntry(request: FastifyRequest<{ Params: Params }>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { type, id } = request.params as { type: string, id: string };
|
||||||
|
|
||||||
|
const entry = await queryOne(
|
||||||
|
`SELECT * FROM local_entries WHERE matched_id = ? AND type = ?`,
|
||||||
|
[Number(id), type],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return reply.status(404).send({ error: 'ENTRY_NOT_FOUND' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [details, files] = await Promise.all([
|
||||||
|
resolveEntryMetadata(entry, type),
|
||||||
|
queryAll(
|
||||||
|
`SELECT id, file_path, unit_number FROM local_files WHERE entry_id = ? ORDER BY unit_number ASC`,
|
||||||
|
[id],
|
||||||
|
'local_library'
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { ...details, files };
|
||||||
|
} catch {
|
||||||
|
return reply.status(500).send({ error: 'FAILED_TO_GET_ENTRY' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function streamUnit(
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
const { id, unit } = request.params as any;
|
||||||
|
|
||||||
|
const file = await queryOne(
|
||||||
|
`SELECT file_path FROM local_files WHERE entry_id = ? AND unit_number = ?`,
|
||||||
|
[id, unit],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!file || !fs.existsSync(file.file_path)) {
|
||||||
|
return reply.status(404).send({ error: 'FILE_NOT_FOUND' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = fs.statSync(file.file_path);
|
||||||
|
const range = request.headers.range;
|
||||||
|
|
||||||
|
if (!range) {
|
||||||
|
reply.header('Content-Length', stat.size);
|
||||||
|
reply.header('Content-Type', 'video/mp4'); // o dinámico
|
||||||
|
return fs.createReadStream(file.file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
|
||||||
|
const start = parseInt(startStr, 10);
|
||||||
|
const end = endStr ? parseInt(endStr, 10) : stat.size - 1;
|
||||||
|
|
||||||
|
reply
|
||||||
|
.status(206)
|
||||||
|
.header('Content-Range', `bytes ${start}-${end}/${stat.size}`)
|
||||||
|
.header('Accept-Ranges', 'bytes')
|
||||||
|
.header('Content-Length', end - start + 1)
|
||||||
|
.header('Content-Type', 'video/mp4');
|
||||||
|
|
||||||
|
return fs.createReadStream(file.file_path, { start, end });
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchBody = {
|
||||||
|
source: 'anilist';
|
||||||
|
matched_id: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function matchEntry(
|
||||||
|
request: FastifyRequest<{ Body: MatchBody }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
const { id, type } = request.params as any;
|
||||||
|
const { source, matched_id } = request.body;
|
||||||
|
|
||||||
|
const entry = await queryOne(
|
||||||
|
`SELECT id FROM local_entries WHERE id = ? AND type = ?`,
|
||||||
|
[id, type],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return reply.status(404).send({ error: 'ENTRY_NOT_FOUND' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await run(
|
||||||
|
`UPDATE local_entries
|
||||||
|
SET matched_source = ?, matched_id = ?
|
||||||
|
WHERE id = ?`,
|
||||||
|
[source, matched_id, id],
|
||||||
|
'local_library'
|
||||||
|
);
|
||||||
|
|
||||||
|
return { status: 'OK', matched: !!matched_id };
|
||||||
|
}
|
||||||
14
desktop/src/api/local/local.routes.ts
Normal file
14
desktop/src/api/local/local.routes.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import * as controller from './local.controller';
|
||||||
|
|
||||||
|
async function localRoutes(fastify: FastifyInstance) {
|
||||||
|
fastify.get('/library/config', controller.getConfig);
|
||||||
|
fastify.post('/library/config', controller.setConfig);
|
||||||
|
fastify.post('/library/scan', controller.scanLibrary);
|
||||||
|
fastify.get('/library/:type', controller.listEntries);
|
||||||
|
fastify.get('/library/:type/:id', controller.getEntry);
|
||||||
|
fastify.get('/library/stream/:type/:id/:unit', controller.streamUnit);
|
||||||
|
fastify.post('/library/:type/:id/match', controller.matchEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default localRoutes;
|
||||||
0
desktop/src/api/local/local.service.ts
Normal file
0
desktop/src/api/local/local.service.ts
Normal file
71
desktop/src/shared/config.js
Normal file
71
desktop/src/shared/config.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
const BASE_DIR = path.join(os.homedir(), 'WaifuBoards');
|
||||||
|
const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml');
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
library: {
|
||||||
|
anime: null,
|
||||||
|
manga: null,
|
||||||
|
novels: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function ensureConfigFile() {
|
||||||
|
if (!fs.existsSync(BASE_DIR)) {
|
||||||
|
fs.mkdirSync(BASE_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
CONFIG_PATH,
|
||||||
|
yaml.dump(DEFAULT_CONFIG),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfig() {
|
||||||
|
ensureConfigFile();
|
||||||
|
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||||
|
return yaml.load(raw) || DEFAULT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setConfig(partialConfig) {
|
||||||
|
ensureConfigFile();
|
||||||
|
|
||||||
|
const current = getConfig();
|
||||||
|
const next = deepMerge(current, partialConfig);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
CONFIG_PATH,
|
||||||
|
yaml.dump(next),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepMerge(target, source) {
|
||||||
|
for (const key in source) {
|
||||||
|
if (
|
||||||
|
source[key] &&
|
||||||
|
typeof source[key] === 'object' &&
|
||||||
|
!Array.isArray(source[key])
|
||||||
|
) {
|
||||||
|
target[key] = deepMerge(target[key] || {}, source[key]);
|
||||||
|
} else {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ensureConfigFile,
|
||||||
|
getConfig,
|
||||||
|
setConfig,
|
||||||
|
};
|
||||||
@@ -2,6 +2,54 @@ const sqlite3 = require('sqlite3').verbose();
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
|
async function ensureLocalLibrarySchema(db) {
|
||||||
|
await run(db, `
|
||||||
|
CREATE TABLE IF NOT EXISTS local_entries (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
folder_name TEXT NOT NULL,
|
||||||
|
matched_id INTEGER,
|
||||||
|
matched_source TEXT,
|
||||||
|
last_scan INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await run(db, `
|
||||||
|
CREATE TABLE IF NOT EXISTS local_files (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
entry_id TEXT NOT NULL,
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
unit_number INTEGER,
|
||||||
|
FOREIGN KEY (entry_id) REFERENCES local_entries(id)
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await run(db, `
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_local_entries_type
|
||||||
|
ON local_entries(type)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await run(db, `
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_local_entries_matched
|
||||||
|
ON local_entries(matched_id)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await run(db, `
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_local_files_entry
|
||||||
|
ON local_files(entry_id)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(db, sql, params = []) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.run(sql, params, err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureUserDataDB(dbPath) {
|
async function ensureUserDataDB(dbPath) {
|
||||||
const dir = path.dirname(dbPath);
|
const dir = path.dirname(dbPath);
|
||||||
|
|
||||||
@@ -230,5 +278,6 @@ module.exports = {
|
|||||||
ensureAnilistSchema,
|
ensureAnilistSchema,
|
||||||
ensureExtensionsTable,
|
ensureExtensionsTable,
|
||||||
ensureCacheTable,
|
ensureCacheTable,
|
||||||
ensureFavoritesDB
|
ensureFavoritesDB,
|
||||||
|
ensureLocalLibrarySchema
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user