add: functions to migrate database to V2 schema
add: doc for database schema
This commit is contained in:
parent
95f982f245
commit
37e0fbfc89
160
.tokeignore
Normal file
160
.tokeignore
Normal 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
202
docs/database-changelog.md
Normal 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
169
docs/database-structure.md
Normal 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;
|
||||||
|
|
||||||
|
```
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openrewind",
|
"name": "openrewind",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Your second brain, superpowered.",
|
"description": "Your second brain, superpowered.",
|
||||||
"main": "dist/electron/index.js",
|
"main": "dist/electron/index.js",
|
||||||
|
@ -1,41 +1,11 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
export default function RewindPage() {
|
export default function RewindPage() {
|
||||||
const [currentScreenShotBase64, setScreenShotData] = useState<string | null>(null);
|
|
||||||
window.api.receive("fromMain", (message: string) => {
|
|
||||||
setScreenShotData(message);
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-screen h-screen relative dark:text-white">
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -68,6 +68,9 @@ importers:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.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:
|
sqlite3:
|
||||||
specifier: ^5.1.7
|
specifier: ^5.1.7
|
||||||
version: 5.1.7
|
version: 5.1.7
|
||||||
@ -153,9 +156,6 @@ importers:
|
|||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.38
|
specifier: ^8.4.38
|
||||||
version: 8.4.49
|
version: 8.4.49
|
||||||
screenshot-desktop:
|
|
||||||
specifier: ^1.15.0
|
|
||||||
version: 1.15.0
|
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.15
|
specifier: ^3.4.15
|
||||||
version: 3.4.16
|
version: 3.4.16
|
||||||
@ -4927,7 +4927,7 @@ snapshots:
|
|||||||
|
|
||||||
app-builder-bin@5.0.0-alpha.10: {}
|
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:
|
dependencies:
|
||||||
'@develar/schema-utils': 2.6.5
|
'@develar/schema-utils': 2.6.5
|
||||||
'@electron/notarize': 2.5.0
|
'@electron/notarize': 2.5.0
|
||||||
@ -4943,7 +4943,7 @@ snapshots:
|
|||||||
chromium-pickle-js: 0.2.0
|
chromium-pickle-js: 0.2.0
|
||||||
config-file-ts: 0.2.8-rc1
|
config-file-ts: 0.2.8-rc1
|
||||||
debug: 4.4.0
|
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: 16.4.7
|
||||||
dotenv-expand: 11.0.7
|
dotenv-expand: 11.0.7
|
||||||
ejs: 3.1.10
|
ejs: 3.1.10
|
||||||
@ -5566,9 +5566,9 @@ snapshots:
|
|||||||
|
|
||||||
dlv@1.1.3: {}
|
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:
|
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: 25.1.7
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
@ -5646,7 +5646,7 @@ snapshots:
|
|||||||
|
|
||||||
electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8):
|
electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8):
|
||||||
dependencies:
|
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
|
archiver: 5.3.2
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7
|
||||||
fs-extra: 10.1.0
|
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)):
|
electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
|
||||||
dependencies:
|
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: 25.1.7
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10
|
||||||
chalk: 4.1.2
|
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
|
fs-extra: 10.1.0
|
||||||
is-ci: 3.0.1
|
is-ci: 3.0.1
|
||||||
lazy-val: 1.0.5
|
lazy-val: 1.0.5
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import * as path from "path";
|
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 { __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() {
|
function getLibSimpleExtensionPath() {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
@ -16,13 +19,12 @@ function getLibSimpleExtensionPath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initDatabase() {
|
function databaseInitialized(db: Database) {
|
||||||
const dbPath = getDatabasePath();
|
return db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='frame';`).get()
|
||||||
const db = new Database(dbPath, { verbose: console.log });
|
!== undefined;
|
||||||
const libSimpleExtensionPath = getLibSimpleExtensionPath();
|
}
|
||||||
|
|
||||||
db.loadExtension(libSimpleExtensionPath);
|
|
||||||
|
|
||||||
|
function init(db: Database) {
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS frame (
|
CREATE TABLE IF NOT EXISTS frame (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -95,5 +97,22 @@ export function initDatabase() {
|
|||||||
END;
|
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;
|
return db;
|
||||||
}
|
}
|
||||||
|
11
src/electron/backend/migrate/index.ts
Normal file
11
src/electron/backend/migrate/index.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
81
src/electron/backend/migrate/migrateToV2.ts
Normal file
81
src/electron/backend/migrate/migrateToV2.ts
Normal 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;
|
||||||
|
`);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import screenshot from "screenshot-desktop";
|
import screenshot from "screenshot-desktop";
|
||||||
import { getScreenshotsPath } from "../utils/backend.js";
|
import { getScreenshotsDir } from "../utils/backend.js";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { Database }from "better-sqlite3";
|
import { Database }from "better-sqlite3";
|
||||||
import SqlString from "sqlstring";
|
import SqlString from "sqlstring";
|
||||||
@ -7,12 +7,13 @@ import SqlString from "sqlstring";
|
|||||||
export function startScreenshotLoop(db: Database) {
|
export function startScreenshotLoop(db: Database) {
|
||||||
return setInterval(() => {
|
return setInterval(() => {
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const screenshotPath = getScreenshotsPath();
|
const screenshotDir = getScreenshotsDir();
|
||||||
const filename = join(screenshotPath, `${timestamp}.png`);
|
const filename = `${timestamp}.png`;
|
||||||
screenshot({filename: filename}).then((absolutePath) => {
|
const screenshotPath = join(screenshotDir, filename);
|
||||||
|
screenshot({filename: screenshotPath, format: "png"}).then((absolutePath) => {
|
||||||
const SQL = SqlString.format(
|
const SQL = SqlString.format(
|
||||||
"INSERT INTO frame (imgFilename) VALUES (?)",
|
"INSERT INTO frame (imgFilename) VALUES (?)",
|
||||||
[absolutePath]
|
[filename]
|
||||||
);
|
);
|
||||||
db.exec(SQL);
|
db.exec(SQL);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -82,6 +82,11 @@ app.on("ready", () => {
|
|||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.on("will-quit", ()=> {
|
||||||
|
dbConnection?.close();
|
||||||
|
});
|
||||||
|
|
||||||
// app.on("window-all-closed", () => {
|
// app.on("window-all-closed", () => {
|
||||||
// if (process.platform !== "darwin") app.quit();
|
// if (process.platform !== "darwin") app.quit();
|
||||||
// });
|
// });
|
||||||
|
@ -31,12 +31,12 @@ export function createTempDir() {
|
|||||||
return tempDir;
|
return tempDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatabasePath() {
|
export function getDatabaseDir() {
|
||||||
const dataDir = createDataDir();
|
const dataDir = createDataDir();
|
||||||
return path.join(dataDir, "main.db");
|
return path.join(dataDir, "main.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScreenshotsPath() {
|
export function getScreenshotsDir() {
|
||||||
const tempDir = createTempDir();
|
const tempDir = createTempDir();
|
||||||
const screenshotsDir = path.join(tempDir, "screenshots");
|
const screenshotsDir = path.join(tempDir, "screenshots");
|
||||||
if (!fs.existsSync(screenshotsDir)) {
|
if (!fs.existsSync(screenshotsDir)) {
|
||||||
@ -45,7 +45,7 @@ export function getScreenshotsPath() {
|
|||||||
return screenshotsDir;
|
return screenshotsDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRecordingsPath() {
|
export function getRecordingsDir() {
|
||||||
const dataDir = createDataDir();
|
const dataDir = createDataDir();
|
||||||
return path.join(dataDir, "recordings");
|
return path.join(dataDir, "recordings");
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user