add: functions to migrate database to V2 schema

add: doc for database schema
This commit is contained in:
alikia2x (寒寒) 2024-12-08 17:31:17 +08:00
parent 95f982f245
commit 37e0fbfc89
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
12 changed files with 677 additions and 59 deletions

160
.tokeignore Normal file
View File

@ -0,0 +1,160 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
bin
*.yaml
*.json
*.md

202
docs/database-changelog.md Normal file
View File

@ -0,0 +1,202 @@
# Database Schema Documentation
This document outlines the changes made across different versions of
database structure used in the OpenRewind, including tables and fields.
## Version 2 Schema Changes
Cooresponding version: Since 0.4.0
### New Table: `config`
Stores configuration data, including the database version.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|---------------------|-----------------------------------------------------------------------------|
| `key` | TEXT | PRIMARY KEY | Unique key for configuration settings. |
| `value` | TEXT | | Value associated with the key. |
#### Insert Default Version
```sql
INSERT INTO config (key, value) VALUES ('version', '2');
```
### New Table: `encoding_task`
Stores encoding tasks that are queued for processing.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|----------------------------|--------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique ID for the task. |
| `createAt` | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Timestamp when the task was created. |
| `status` | INTEGER | DEFAULT 0 | Indicates the status of the task. |
### Task status
- `0`: Pending
- `1`: In Progress
- `2`: Completed
- Once the task was set to this status, it will be imminently deleted by a trigger mentioned below.
### New Trigger: `delete_encoding_task`
Triggered after updating the `status` of an encoding task to `2` (Completed).
```sql
CREATE TRIGGER delete_encoding_task
AFTER UPDATE OF status
ON encoding_task
BEGIN
DELETE FROM encoding_task_data
WHERE encodingTaskID = OLD.id AND NEW.status = 2;
DELETE FROM encoding_task
WHERE id = OLD.id AND NEW.status = 2;
END;
```
### New Table: `encoding_task_data`
Stores the frames that need to be encoded for the encoding task
| Column Name | Data Type | Constraints/Default | Description |
|------------------|-----------|-------------------------------------|------------------------------------------------------|
| `frame` | INTEGER | PRIMARY KEY, FOREIGN KEY (frame.id) | ID for the frame associated with the encoding task. |
| `encodingTaskID` | TIMESTAMP | FOREIGN KEY (encoding_task.id) | ID for the encoding task associated with this frame. |
### Update `frame` Table
#### Simplify `imgFilename`
The `imgFilename` column was updated to store only the filename without the full path.
```typescript
const rows = db.prepare('SELECT id, imgFilename FROM frame').all() as OldFrame[];
rows.forEach(row => {
const filename = row.imgFilename.match(/[^\\/]+$/)?.[0];
if (filename) {
db.prepare('UPDATE frame SET imgFilename = ? WHERE id = ?').run(filename, row.id);
}
});
```
#### Add `encodeStatus` Column
A new column `encodeStatus` was added to replace the deprecated `encoded` column.
```sql
ALTER TABLE frame ADD encodeStatus INT;
UPDATE frame SET encodeStatus = CASE WHEN encoded THEN 2 ELSE 0 END;
```
### Summary of Changes
- **New Table:** `config` to store configuration data.
- **Update `frame` Table:**
- Simplified `imgFilename` to store only the filename.
- Added `encodeStatus` column to replace the deprecated `encoded` column.
- **Deprecated `encoded` column.**
- The `encoded` column is no longer used and is retained due to SQLite's inability to drop columns.
Creating a new table without this column and copying data to the new table could be time-consuming.
## Version 1 Schema
Cooresponding version: 0.3.x
### Table: `frame`
Stores information about individual frames.
| Column Name | Data Type | Constraints/Default | Description |
|-------------------|-----------|---------------------------------|-------------------------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each frame. |
| `createAt` | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Timestamp when the frame was created. |
| `imgFilename` | TEXT | | Filename of the image associated with the frame. |
| `segmentID` | INTEGER | NULL, FOREIGN KEY (segments.id) | ID of the segment to which the frame belongs. |
| `videoPath` | TEXT | NULL | Path to the video file if the frame is part of a video. |
| `videoFrameIndex` | INTEGER | NULL | Index of the frame within the video. |
| `collectionID` | INTEGER | NULL | ID of the collection to which the frame belongs. |
| `encoded` | BOOLEAN | DEFAULT 0 | Indicates whether the frame has been encoded (0 for false, 1 for true). |
### Table: `recognition_data`
Stores recognition data associated with frames.
| Column Name | Data Type | Constraints/Default | Desc[database-structure.md](database-structure.md)ription |
|-------------|-----------|----------------------------|-----------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each recognition data entry. |
| `frameID` | INTEGER | FOREIGN KEY (frame.id) | ID of the frame to which the recognition data belongs. |
| `data` | TEXT | | Raw recognition data. |
| `text` | TEXT | | Recognized text. |
### Table: `segments`
A segment is a period of time when a user uses an application.
While capturing the screen, OpenRewind retrieves the currently active window.
When it finds that the currently active window has changed to another application, a new segment will start.
| Column Name | Data Type | Constraints/Default | Description |
|---------------|-----------|----------------------------|------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each segment. |
| `startAt` | TIMESTAMP | | Timestamp when the segment starts. |
| `endAt` | TIMESTAMP | | Timestamp when the segment ends. |
| `title` | TEXT | | Title of the segment. |
| `appName` | TEXT | | Name of the application associated with the segment. |
| `appPath` | TEXT | | Path to the application. |
| `text` | TEXT | | Text content of the segment. |
| `type` | TEXT | | Type of the segment. |
| `appBundleID` | TEXT | NULL | Bundle ID of the application. |
| `url` | TEXT | NULL | URL associated with the segment. |
### Virtual Table: `text_search`
Used for full-text search on recognition data.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|---------------------|--------------------------------------------------------|
| `id` | INTEGER | UNINDEXED | ID of the recognition data entry. |
| `frameID` | INTEGER | UNINDEXED | ID of the frame to which the recognition data belongs. |
| `data` | TEXT | | Raw recognition data. |
| `text` | TEXT | | Recognized text. |
### Triggers
#### `recognition_data_after_insert`
Triggered after inserting a new row into `recognition_data`.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_insert AFTER INSERT ON recognition_data
BEGIN
INSERT INTO text_search (id, frameID, data, text)
VALUES (NEW.id, NEW.frameID, NEW.data, NEW.text);
END;
```
#### `recognition_data_after_update`
Triggered after updating a row in `recognition_data`.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_update AFTER UPDATE ON recognition_data
BEGIN
UPDATE text_search
SET frameID = NEW.frameID, data = NEW.data, text = NEW.text
WHERE id = NEW.id;
END;
```
#### `recognition_data_after_delete`
Triggered after deleting a row from `recognition_data`.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_delete AFTER DELETE ON recognition_data
BEGIN
DELETE FROM text_search WHERE id = OLD.id;
END;
```

169
docs/database-structure.md Normal file
View File

@ -0,0 +1,169 @@
# Database Schema Documentation (Version 2)
This document outlines the current structure of the database schema used in the application.
It includes tables, fields, and their descriptions.
## Table: `config`
Stores configuration data.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|---------------------|-----------------------------------------------------------------------------|
| `key` | TEXT | PRIMARY KEY | Unique key for configuration settings. |
| `value` | TEXT | | Value associated with the key. |
### Key: version
The current database schema version, represented as a integer.
Since the `config` table does not exist in V1, the version must be at least 2.
## Table: `frame`
Stores information about individual frames.
| Column Name | Data Type | Constraints/Default | Description |
|-------------------|-----------|---------------------------------|-----------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each frame. |
| `createAt` | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Timestamp when the frame was created. |
| `imgFilename` | TEXT | | Filename of the image associated with the frame. |
| `segmentID` | INTEGER | NULL, FOREIGN KEY (segments.id) | ID of the segment to which the frame belongs. |
| `videoPath` | TEXT | NULL | Relative path to the video file if the frame was encoded. |
| `videoFrameIndex` | INTEGER | NULL | Index of the frame within the encoded video. |
| `collectionID` | INTEGER | NULL | ID of the collection to which the frame belongs. |
| `encodeStatus` | INTEGER | 0 | Indicates the encoding status of the frame. |
### Status Description
- `0`: The frame is not encoded.
- `1`: The frame is scheduled for encoding. It will appear in the `encoding_task` table.
- `2`: The frame is already encoded.
## Table: `recognition_data`
Stores recognition data associated with frames.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|------------------------------|-----------------------------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each recognition data entry. |
| `frameID` | INTEGER | FOREIGN KEY (frame.id) | ID of the frame to which the recognition data belongs. |
| `data` | TEXT | | Raw recognition data. |
| `text` | TEXT | | Recognized text. |
## Table: `segments`
A segment is a period of time when a user uses a particular application.
While capturing the screen, OpenRewind detects the currently active window.
When it finds that the currently active window has changed to another application, a new segment will start.
| Column Name | Data Type | Constraints/Default | Description |
|---------------|-----------|----------------------------|------------------------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier for each segment. |
| `startAt` | TIMESTAMP | | Timestamp when the segment starts. |
| `endAt` | TIMESTAMP | | Timestamp when the segment ends. |
| `title` | TEXT | | Title of the segment. |
| `appName` | TEXT | | Name of the application associated with the segment. |
| `appPath` | TEXT | | Path to the application. |
| `text` | TEXT | | Text content of the segment. |
| `type` | TEXT | | Type of the segment. |
| `appBundleID` | TEXT | NULL | Bundle ID of the application. |
| `url` | TEXT | NULL | URL associated with the segment. |
## Table: `encoding_task`
Stores encoding tasks that are queued for processing.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|----------------------------|--------------------------------------|
| `id` | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique ID for the task. |
| `createAt` | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Timestamp when the task was created. |
| `status` | INTEGER | DEFAULT 0 | Indicates the status of the task. |
### Task status Description
- `0`: Pending
- `1`: In Progress
- `2`: Completed
- Once the task was set to this status, it will be imminently deleted by a trigger mentioned below.
## Table: `encoding_task_data`
Stores the frames that need to be encoded for the encoding task
| Column Name | Data Type | Constraints/Default | Description |
|------------------|-----------|-------------------------------------|------------------------------------------------------|
| `frame` | INTEGER | PRIMARY KEY, FOREIGN KEY (frame.id) | ID for the frame associated with the encoding task. |
| `encodingTaskID` | TIMESTAMP | FOREIGN KEY (encoding_task.id) | ID for the encoding task associated with this frame. |
## Virtual Table: `text_search`
Used for full-text search on recognition data.
| Column Name | Data Type | Constraints/Default | Description |
|-------------|-----------|---------------------|--------------------------------------------------------|
| `id` | INTEGER | UNINDEXED | ID of the recognition data entry. |
| `frameID` | INTEGER | UNINDEXED | ID of the frame to which the recognition data belongs. |
| `data` | TEXT | | Raw recognition data. |
| `text` | TEXT | | Recognized text. |
## Triggers
### `recognition_data_after_insert`
Triggered after inserting a new row into `recognition_data`.
Inserts a new row into `text_search` with the same data.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_insert AFTER INSERT ON recognition_data
BEGIN
INSERT INTO text_search (id, frameID, data, text)
VALUES (NEW.id, NEW.frameID, NEW.data, NEW.text);
END;
```
### `recognition_data_after_update`
Triggered after updating a row in `recognition_data`.
Updates the associated `text_search` row.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_update AFTER UPDATE ON recognition_data
BEGIN
UPDATE text_search
SET frameID = NEW.frameID, data = NEW.data, text = NEW.text
WHERE id = NEW.id;
END;
```
### `recognition_data_after_delete`
Triggered after deleting a row from `recognition_data`.
Deletes the associated `text_search` row.
```sql
CREATE TRIGGER IF NOT EXISTS recognition_data_after_delete AFTER DELETE ON recognition_data
BEGIN
DELETE FROM text_search WHERE id = OLD.id;
END;
```
### `delete_encoding_task`
Triggered after updating the `status` of an encoding task to `2` (Completed).
Deletes the associated `encoding_task_data` and `encoding_task` rows.
```sql
CREATE TRIGGER delete_encoding_task
AFTER UPDATE OF status
ON encoding_task
BEGIN
DELETE FROM encoding_task_data
WHERE encodingTaskID = OLD.id AND NEW.status = 2;
DELETE FROM encoding_task
WHERE id = OLD.id AND NEW.status = 2;
END;
```

View File

@ -1,6 +1,6 @@
{
"name": "openrewind",
"version": "0.3.1",
"version": "0.4.0",
"type": "module",
"description": "Your second brain, superpowered.",
"main": "dist/electron/index.js",

View File

@ -1,41 +1,11 @@
import { useState } from "react";
import "./index.css";
export default function RewindPage() {
const [currentScreenShotBase64, setScreenShotData] = useState<string | null>(null);
window.api.receive("fromMain", (message: string) => {
setScreenShotData(message);
});
return (
<>
<div className="w-screen h-screen relative dark:text-white">
{currentScreenShotBase64 && (
<img
className="absolute top-0 left-0 z-10 w-full h-full"
src={"data:image/png;base64," + currentScreenShotBase64}
alt=""
/>
)}
{currentScreenShotBase64 ? (
<div
className="rounded-xl bottom-12 left-6 fixed z-30 w-auto h-auto px-4 py-3
bg-white bg-opacity-50 backdrop-blur-lg text-gray-800"
>
Here's a screenshot captured just now.
<br />
The relavant features has not been implemented, and you han hit Esc to quit
this window.
<br />
Meow! (=^ω^=)丿
</div>
) : (
<div
className="rounded-xl bottom-12 left-6 fixed z-30 w-auto h-auto px-4 py-3
bg-white bg-opacity-50 backdrop-blur-lg text-gray-800"
>
Now capturing a screenshot for your screen...( ̀_́)
</div>
)}
</div>
</>
);

View File

@ -68,6 +68,9 @@ importers:
react-router-dom:
specifier: ^7.0.1
version: 7.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
screenshot-desktop:
specifier: ^1.15.0
version: 1.15.0
sqlite3:
specifier: ^5.1.7
version: 5.1.7
@ -153,9 +156,6 @@ importers:
postcss:
specifier: ^8.4.38
version: 8.4.49
screenshot-desktop:
specifier: ^1.15.0
version: 1.15.0
tailwindcss:
specifier: ^3.4.15
version: 3.4.16
@ -4927,7 +4927,7 @@ snapshots:
app-builder-bin@5.0.0-alpha.10: {}
app-builder-lib@25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
app-builder-lib@25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/notarize': 2.5.0
@ -4943,7 +4943,7 @@ snapshots:
chromium-pickle-js: 0.2.0
config-file-ts: 0.2.8-rc1
debug: 4.4.0
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8)
dotenv: 16.4.7
dotenv-expand: 11.0.7
ejs: 3.1.10
@ -5566,9 +5566,9 @@ snapshots:
dlv@1.1.3: {}
dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8):
dependencies:
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
builder-util: 25.1.7
builder-util-runtime: 9.2.10
fs-extra: 10.1.0
@ -5646,7 +5646,7 @@ snapshots:
electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8):
dependencies:
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
archiver: 5.3.2
builder-util: 25.1.7
fs-extra: 10.1.0
@ -5657,11 +5657,11 @@ snapshots:
electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
dependencies:
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
builder-util: 25.1.7
builder-util-runtime: 9.2.10
chalk: 4.1.2
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8)
fs-extra: 10.1.0
is-ci: 3.0.1
lazy-val: 1.0.5

View File

@ -1,7 +1,10 @@
import * as path from "path";
import Database from "better-sqlite3";
import { Database } from "better-sqlite3";
import DB from "better-sqlite3";
import { __dirname } from "../dirname.js";
import { getDatabasePath } from "../utils/backend.js";
import { getDatabaseDir } from "../utils/backend.js";
import { migrate } from "./migrate/index.js";
import { initSchemaInV2 } from "./migrate/migrateToV2";
function getLibSimpleExtensionPath() {
switch (process.platform) {
@ -16,13 +19,12 @@ function getLibSimpleExtensionPath() {
}
}
export function initDatabase() {
const dbPath = getDatabasePath();
const db = new Database(dbPath, { verbose: console.log });
const libSimpleExtensionPath = getLibSimpleExtensionPath();
db.loadExtension(libSimpleExtensionPath);
function databaseInitialized(db: Database) {
return db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='frame';`).get()
!== undefined;
}
function init(db: Database) {
db.exec(`
CREATE TABLE IF NOT EXISTS frame (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -95,5 +97,22 @@ export function initDatabase() {
END;
`);
initSchemaInV2(db);
}
export function initDatabase() {
const dbPath = getDatabaseDir();
const db = new DB(dbPath, { verbose: console.log });
const libSimpleExtensionPath = getLibSimpleExtensionPath();
db.loadExtension(libSimpleExtensionPath);
if (!databaseInitialized(db)) {
init(db);
}
else {
migrate(db);
}
return db;
}

View File

@ -0,0 +1,11 @@
import { Database } from "better-sqlite3";
import { migrateToV2 } from "./migrateToV2.js";
export function migrate(db: Database) {
const configTableExists =
db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='config';`).get()
!== undefined;
if (!configTableExists) {
migrateToV2(db);
}
}

View File

@ -0,0 +1,81 @@
import { Database } from "better-sqlite3";
interface OldFrame {
id: number;
createAt: string;
imgFilename: string;
segmentID: number | null;
videoPath: string | null;
videoFrameIndex: number | null;
collectionID: number | null;
encoded: number;
}
export function initSchemaInV2(db: Database) {
db.exec(`
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT
);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS encoding_task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 0
);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS encoding_task_data (
encodingTaskID INTEGER,
frame ID INTEGER PRIMARY KEY,
FOREIGN KEY (encodingTaskID) REFERENCES encoding_task(id),
FOREIGN KEY (frame) REFERENCES frame(id)
);
`);
db.exec(`
CREATE TRIGGER IF NOT EXISTS delete_encoding_task
AFTER UPDATE OF status
ON encoding_task
BEGIN
DELETE FROM encoding_task_data
WHERE encodingTaskID = OLD.id AND NEW.status = 2;
DELETE FROM encoding_task
WHERE id = OLD.id AND NEW.status = 2;
END;
`);
db.exec(`
INSERT INTO config (key, value) VALUES ('version', '2');
`);
}
/*
* This function assumes that the database does not contain the "config" table,
* and thus needs to be migrated to Version 2.
* */
export function migrateToV2(db: Database) {
initSchemaInV2(db);
// Oh we saved tens of millions of bytes for user!
// Before: /Users/username/Library/Application Support/OpenRewind/Record Data/temp/screenshots/1733568609960.jpg
// After: 1733568609960.jpg
const rows = db.prepare("SELECT id, imgFilename FROM frame").all() as OldFrame[];
rows.forEach(row => {
const filename = row.imgFilename.match(/[^\\/]+$/)?.[0];
if (filename) {
db.prepare("UPDATE frame SET imgFilename = ? WHERE id = ?")
.run(filename, row.id);
}
});
db.exec(`
ALTER TABLE frame ADD encodeStatus INT DEFAULT 0;
UPDATE frame SET encodeStatus = CASE WHEN encoded THEN 2 ELSE 0 END;
`);
}

View File

@ -1,5 +1,5 @@
import screenshot from "screenshot-desktop";
import { getScreenshotsPath } from "../utils/backend.js";
import { getScreenshotsDir } from "../utils/backend.js";
import { join } from "path";
import { Database }from "better-sqlite3";
import SqlString from "sqlstring";
@ -7,12 +7,13 @@ import SqlString from "sqlstring";
export function startScreenshotLoop(db: Database) {
return setInterval(() => {
const timestamp = new Date().getTime();
const screenshotPath = getScreenshotsPath();
const filename = join(screenshotPath, `${timestamp}.png`);
screenshot({filename: filename}).then((absolutePath) => {
const screenshotDir = getScreenshotsDir();
const filename = `${timestamp}.png`;
const screenshotPath = join(screenshotDir, filename);
screenshot({filename: screenshotPath, format: "png"}).then((absolutePath) => {
const SQL = SqlString.format(
"INSERT INTO frame (imgFilename) VALUES (?)",
[absolutePath]
[filename]
);
db.exec(SQL);
}).catch((err) => {

View File

@ -82,6 +82,11 @@ app.on("ready", () => {
mainWindow.hide();
});
});
app.on("will-quit", ()=> {
dbConnection?.close();
});
// app.on("window-all-closed", () => {
// if (process.platform !== "darwin") app.quit();
// });

View File

@ -31,12 +31,12 @@ export function createTempDir() {
return tempDir;
}
export function getDatabasePath() {
export function getDatabaseDir() {
const dataDir = createDataDir();
return path.join(dataDir, "main.db");
}
export function getScreenshotsPath() {
export function getScreenshotsDir() {
const tempDir = createTempDir();
const screenshotsDir = path.join(tempDir, "screenshots");
if (!fs.existsSync(screenshotsDir)) {
@ -45,7 +45,7 @@ export function getScreenshotsPath() {
return screenshotsDir;
}
export function getRecordingsPath() {
export function getRecordingsDir() {
const dataDir = createDataDir();
return path.join(dataDir, "recordings");
}