添加启动时的数据库表及表结构完整性校验

This commit is contained in:
lyswhut
2023-02-06 17:33:21 +08:00
parent c8eac2690f
commit cf960e3695
5 changed files with 260 additions and 95 deletions
+15 -3
View File
@@ -1,6 +1,6 @@
import { join, dirname } from 'path'
import { existsSync, mkdirSync } from 'fs'
import { app, shell, screen, nativeTheme } from 'electron'
import { existsSync, mkdirSync, renameSync } from 'fs'
import { app, shell, screen, nativeTheme, dialog } from 'electron'
import { URL_SCHEME_RXP } from '@common/constants'
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { navigationUrlWhiteList } from '@common/config'
@@ -10,6 +10,7 @@ import { createAppEvent, createListEvent } from '@main/event'
import { isMac, log } from '@common/utils'
import createWorkers from './worker'
import { migrateDBData } from './utils/migrate'
import { openDirInExplorer } from '@common/utils/electron'
export const initGlobalData = () => {
global.isDev = process.env.NODE_ENV !== 'production'
@@ -237,7 +238,18 @@ export const initAppSetting = async() => {
}
if (!isInitialized) {
const dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
let dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
if (dbFileExists === null) {
const backPath = join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
dialog.showMessageBoxSync({
type: 'warning',
message: 'Database verify failed',
detail: `数据库表结构校验失败,我们将把有问题的数据库备份到:${backPath}\n若此问题导致你的数据丢失,你可以尝试从备份文件找回它们。\n\nThe database table structure verification failed, we will back up the problematic database to: ${backPath}\nIf this problem causes your data to be lost, you can try to retrieve them from the backup file.`,
})
renameSync(join(global.lxDataPath, 'lx.data.db'), backPath)
openDirInExplorer(backPath)
dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
}
global.lx.appSetting = (await initSetting()).setting
if (!dbFileExists) await migrateDBData().catch(err => { log.error(err) })
initTheme()
+10 -92
View File
@@ -1,108 +1,22 @@
import Database from 'better-sqlite3'
import path from 'path'
import tables from './tables'
import verifyDB from './verifyDB'
// import migrateData from './migrate'
let db: Database.Database
const initTables = (db: Database.Database) => {
const sql = `
CREATE TABLE "db_info" (
"id" INTEGER NOT NULL UNIQUE,
"field_name" TEXT,
"field_value" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
);
CREATE TABLE "my_list" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"source" TEXT,
"sourceListId" TEXT,
"position" INTEGER NOT NULL,
"locationUpdateTime" INTEGER,
PRIMARY KEY("id")
);
CREATE TABLE "my_list_music_info" (
"id" TEXT NOT NULL,
"listId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"singer" TEXT NOT NULL,
"source" TEXT NOT NULL,
"interval" TEXT,
"meta" TEXT NOT NULL,
UNIQUE("id","listId")
);
CREATE INDEX "index_my_list_music_info" ON "my_list_music_info" (
"id",
"listId"
);
CREATE TABLE "my_list_music_info_order" (
"listId" TEXT NOT NULL,
"musicInfoId" TEXT NOT NULL,
"order" INTEGER NOT NULL
);
CREATE INDEX "index_my_list_music_info_order" ON "my_list_music_info_order" (
"listId",
"musicInfoId"
);
CREATE TABLE "music_info_other_source" (
"source_id" TEXT NOT NULL,
"id" TEXT NOT NULL,
"source" TEXT NOT NULL,
"name" TEXT NOT NULL,
"singer" TEXT NOT NULL,
"meta" TEXT NOT NULL,
"order" INTEGER NOT NULL,
UNIQUE("source_id","id")
);
CREATE INDEX "index_music_info_other_source" ON "music_info_other_source" (
"source_id",
"id"
);
-- TODO "meta" TEXT NOT NULL,
CREATE TABLE "lyric" (
"id" TEXT NOT NULL,
"source" TEXT NOT NULL,
"type" TEXT NOT NULL,
"text" TEXT NOT NULL
);
CREATE TABLE "music_url" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL
);
CREATE TABLE "download_list" (
"id" TEXT NOT NULL,
"isComplate" INTEGER NOT NULL,
"status" TEXT NOT NULL,
"statusText" TEXT NOT NULL,
"progress_downloaded" INTEGER NOT NULL,
"progress_total" INTEGER NOT NULL,
"url" TEXT,
"quality" TEXT NOT NULL,
"ext" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"filePath" TEXT NOT NULL,
"musicInfo" TEXT NOT NULL,
"position" INTEGER NOT NULL,
PRIMARY KEY("id")
);
db.exec(`
${Array.from(tables.values()).join('\n')}
INSERT INTO "main"."db_info" ("field_name", "field_value") VALUES ('version', '1');
`
db.exec(sql)
`)
}
// 打开、初始化数据库
export const init = (lxDataPath: string): boolean => {
export const init = (lxDataPath: string): boolean | null => {
const databasePath = path.join(lxDataPath, 'lx.data.db')
const nativeBinding = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
let dbFileExists = true
@@ -127,6 +41,10 @@ export const init = (lxDataPath: string): boolean => {
// https://www.sqlite.org/pragma.html#pragma_optimize
if (dbFileExists) db.exec('PRAGMA optimize;')
if (!verifyDB(db)) {
db.close()
return null
}
// https://www.sqlite.org/lang_vacuum.html
// db.exec('VACUUM "main"')
+209
View File
@@ -0,0 +1,209 @@
// export const sql = `
// CREATE TABLE "db_info" (
// "id" INTEGER NOT NULL UNIQUE,
// "field_name" TEXT,
// "field_value" TEXT,
// PRIMARY KEY("id" AUTOINCREMENT)
// );
// CREATE TABLE "my_list" (
// "id" TEXT NOT NULL,
// "name" TEXT NOT NULL,
// "source" TEXT,
// "sourceListId" TEXT,
// "position" INTEGER NOT NULL,
// "locationUpdateTime" INTEGER,
// PRIMARY KEY("id")
// );
// CREATE TABLE "my_list_music_info" (
// "id" TEXT NOT NULL,
// "listId" TEXT NOT NULL,
// "name" TEXT NOT NULL,
// "singer" TEXT NOT NULL,
// "source" TEXT NOT NULL,
// "interval" TEXT,
// "meta" TEXT NOT NULL,
// UNIQUE("id","listId")
// );
// CREATE INDEX "index_my_list_music_info" ON "my_list_music_info" (
// "id",
// "listId"
// );
// CREATE TABLE "my_list_music_info_order" (
// "listId" TEXT NOT NULL,
// "musicInfoId" TEXT NOT NULL,
// "order" INTEGER NOT NULL
// );
// CREATE INDEX "index_my_list_music_info_order" ON "my_list_music_info_order" (
// "listId",
// "musicInfoId"
// );
// CREATE TABLE "music_info_other_source" (
// "source_id" TEXT NOT NULL,
// "id" TEXT NOT NULL,
// "source" TEXT NOT NULL,
// "name" TEXT NOT NULL,
// "singer" TEXT NOT NULL,
// "meta" TEXT NOT NULL,
// "order" INTEGER NOT NULL,
// UNIQUE("source_id","id")
// );
// CREATE INDEX "index_music_info_other_source" ON "music_info_other_source" (
// "source_id",
// "id"
// );
// -- TODO "meta" TEXT NOT NULL,
// CREATE TABLE "lyric" (
// "id" TEXT NOT NULL,
// "source" TEXT NOT NULL,
// "type" TEXT NOT NULL,
// "text" TEXT NOT NULL
// );
// CREATE TABLE "music_url" (
// "id" TEXT NOT NULL,
// "url" TEXT NOT NULL
// );
// CREATE TABLE "download_list" (
// "id" TEXT NOT NULL,
// "isComplate" INTEGER NOT NULL,
// "status" TEXT NOT NULL,
// "statusText" TEXT NOT NULL,
// "progress_downloaded" INTEGER NOT NULL,
// "progress_total" INTEGER NOT NULL,
// "url" TEXT,
// "quality" TEXT NOT NULL,
// "ext" TEXT NOT NULL,
// "fileName" TEXT NOT NULL,
// "filePath" TEXT NOT NULL,
// "musicInfo" TEXT NOT NULL,
// "position" INTEGER NOT NULL,
// PRIMARY KEY("id")
// );
// `
// export const tables = [
// 'table_db_info',
// 'table_my_list',
// 'table_my_list_music_info',
// 'index_index_my_list_music_info',
// 'table_my_list_music_info_order',
// 'index_index_my_list_music_info_order',
// 'table_music_info_other_source',
// 'index_index_music_info_other_source',
// 'table_lyric',
// 'table_music_url',
// 'table_download_list',
// ]
const tables = new Map<string, string>()
tables.set('db_info', `
CREATE TABLE "db_info" (
"id" INTEGER NOT NULL UNIQUE,
"field_name" TEXT,
"field_value" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
);
`)
tables.set('my_list', `
CREATE TABLE "my_list" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"source" TEXT,
"sourceListId" TEXT,
"position" INTEGER NOT NULL,
"locationUpdateTime" INTEGER,
PRIMARY KEY("id")
);
`)
tables.set('my_list_music_info', `
CREATE TABLE "my_list_music_info" (
"id" TEXT NOT NULL,
"listId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"singer" TEXT NOT NULL,
"source" TEXT NOT NULL,
"interval" TEXT,
"meta" TEXT NOT NULL,
UNIQUE("id","listId")
);
`)
tables.set('index_my_list_music_info', `
CREATE INDEX "index_my_list_music_info" ON "my_list_music_info" (
"id",
"listId"
);
`)
tables.set('my_list_music_info_order', `
CREATE TABLE "my_list_music_info_order" (
"listId" TEXT NOT NULL,
"musicInfoId" TEXT NOT NULL,
"order" INTEGER NOT NULL
);
`)
tables.set('index_my_list_music_info_order', `
CREATE INDEX "index_my_list_music_info_order" ON "my_list_music_info_order" (
"listId",
"musicInfoId"
);
`)
tables.set('music_info_other_source', `
CREATE TABLE "music_info_other_source" (
"source_id" TEXT NOT NULL,
"id" TEXT NOT NULL,
"source" TEXT NOT NULL,
"name" TEXT NOT NULL,
"singer" TEXT NOT NULL,
"meta" TEXT NOT NULL,
"order" INTEGER NOT NULL,
UNIQUE("source_id","id")
);
`)
tables.set('index_music_info_other_source', `
CREATE INDEX "index_music_info_other_source" ON "music_info_other_source" (
"source_id",
"id"
);
`)
tables.set('lyric', `
-- TODO "meta" TEXT NOT NULL,
CREATE TABLE "lyric" (
"id" TEXT NOT NULL,
"source" TEXT NOT NULL,
"type" TEXT NOT NULL,
"text" TEXT NOT NULL
);
`)
tables.set('music_url', `
CREATE TABLE "music_url" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL
);
`)
tables.set('download_list', `
CREATE TABLE "download_list" (
"id" TEXT NOT NULL,
"isComplate" INTEGER NOT NULL,
"status" TEXT NOT NULL,
"statusText" TEXT NOT NULL,
"progress_downloaded" INTEGER NOT NULL,
"progress_total" INTEGER NOT NULL,
"url" TEXT,
"quality" TEXT NOT NULL,
"ext" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"filePath" TEXT NOT NULL,
"musicInfo" TEXT NOT NULL,
"position" INTEGER NOT NULL,
PRIMARY KEY("id")
);
`)
export default tables
+25
View File
@@ -0,0 +1,25 @@
import type Database from 'better-sqlite3'
import tables from './tables'
const rxp = /\n|\s|;|--.+/g
export default (db: Database.Database) => {
const result = db.prepare('SELECT type,name,tbl_name,sql FROM "main".sqlite_master WHERE sql NOT NULL;').all()
const dbTableMap = new Map<string, string>()
for (const info of result) dbTableMap.set(info.name, info.sql.replace(rxp, ''))
return Array.from(tables.entries()).every(([name, sql]) => {
const dbSql = dbTableMap.get(name)
return dbSql && dbSql == sql.replace(rxp, '')
})
// console.log(dbTableMap)
// for (const [name, sql] of tables.entries()) {
// const dbSql = dbTableMap.get(name)
// if (dbSql) {
// if (dbSql == sql.replace(rxp, '')) continue
// console.log(dbSql)
// console.log(sql.replace(rxp, ''))
// } else {
// console.log(name)
// }
// }
// if (result.every((info) => { tables.includes() }))
}