From f53616f34548ac1a0410355ea645529adc1fcf18 Mon Sep 17 00:00:00 2001 From: alikia2x Date: Sun, 29 Dec 2024 21:37:36 +0800 Subject: [PATCH] add: endpoint /timeline & /frame/:id in backend server improve: simplify the migration code --- src/electron/backend/migrate/index.ts | 27 +++++------ src/electron/index.ts | 3 ++ src/electron/server/index.ts | 68 +++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/electron/backend/migrate/index.ts b/src/electron/backend/migrate/index.ts index c33da6c..8b82389 100644 --- a/src/electron/backend/migrate/index.ts +++ b/src/electron/backend/migrate/index.ts @@ -12,26 +12,23 @@ function migrateTo(version: number, db: Database) { } } +function getVersion(db: Database): number { + const stmt = db.prepare(`SELECT value FROM config WHERE key = 'version';`); + const data = stmt.get() as { value: string }; + const version = data.value; + return parseInt(version); +} + export function migrate(db: Database) { const configTableExists = - db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='config';`).get() - !== undefined; + db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='config';`).get() !== + undefined; if (!configTableExists) { migrateToV2(db); } - let databaseVersion = parseInt( - ( - db.prepare(`SELECT value FROM config WHERE key = 'version';`).get() as - { value: any } - ).value - ); + let databaseVersion = getVersion(db); while (databaseVersion < CURRENT_VERSION) { migrateTo(databaseVersion, db); - databaseVersion = parseInt( - ( - db.prepare(`SELECT value FROM config WHERE key = 'version';`).get() as - { value: any } - ).value - ); + databaseVersion = getVersion(db); } -} \ No newline at end of file +} diff --git a/src/electron/index.ts b/src/electron/index.ts index dec657b..74c365a 100644 --- a/src/electron/index.ts +++ b/src/electron/index.ts @@ -96,6 +96,7 @@ app.on("ready", () => { setInterval(processEncodingTasks, 10000, db); setInterval(deleteEncodedScreenshots, 5000, db); dbConnection = db; + cache.put("server:dbConnection", dbConnection); }); mainWindow = createMainWindow(port, () => (mainWindow = null)); settingsWindow = createSettingsWindow(port, () => (settingsWindow = null)); @@ -107,6 +108,8 @@ app.on("ready", () => { app.on("will-quit", () => { dbConnection?.close(); + if (screenshotInterval) + clearInterval(screenshotInterval); }); // app.on("window-all-closed", () => { diff --git a/src/electron/server/index.ts b/src/electron/server/index.ts index 5dc1557..5ef9763 100644 --- a/src/electron/server/index.ts +++ b/src/electron/server/index.ts @@ -1,17 +1,75 @@ -import { Hono } from 'hono' +import { Hono } from "hono"; import cache from "memory-cache"; +import { join } from "path"; +import fs from "fs"; +import { Database } from "better-sqlite3"; +import type { Frame } from "../backend/schema"; +import { getScreenshotsDir } from "../utils/backend.js"; const app = new Hono(); app.use(async (c, next) => { const key = cache.get("server:APIKey"); if (key && c.req.header("x-api-key") !== key) { - c.res = undefined + c.res = undefined; c.res = c.json({ error: "Invalid API key" }, 401); } await next(); -}) +}); -app.get('/ping', (c) => c.text('pong')) +app.get("/ping", (c) => c.text("pong")); -export default app; \ No newline at end of file +app.get("/timeline", async (c) => { + const { offset = 0, limit = 50 } = c.req.query(); + const db = cache.get("server:dbConnection"); + + const frames = db + .prepare( + ` + SELECT id, createdAt, imgFilename, videoPath, videoFrameIndex + FROM frame + ORDER BY createdAt DESC + LIMIT ? OFFSET ? + ` + ) + .all(limit, offset); + + return c.json(frames); +}); + +app.get("/frame/:id", async (c) => { + const { id } = c.req.param(); + const db: Database = cache.get("server:dbConnection"); + + const frame = db + .prepare( + ` + SELECT imgFilename, videoPath, videoFrameIndex + FROM frame + WHERE id = ? + ` + ) + .get(id) as Frame; + + if (!frame) { + return c.json({ error: "Frame not found" }, 404); + } + + // If frame is from video, decode and return frame + if (frame.videoPath) { + // TODO: Implement video frame extraction + return c.json({ error: "Video frame extraction not implemented" }, 501); + } + + // Return image file + const imagePath = join(getScreenshotsDir(), frame.imgFilename); + const imageBuffer = fs.readFileSync(imagePath); + return new Response(imageBuffer, { + status: 200, + headers: { + "Content-Type": "image/png" + } + }); +}); + +export default app;