添加启动时的数据库表及表结构完整性校验
parent
c8eac2690f
commit
cf960e3695
|
@ -8,6 +8,7 @@
|
||||||
- 新增当前版本更新日志显示弹窗(建议大家阅读更新日志以了解当前版本的变化),在更新版本后将自动弹出
|
- 新增当前版本更新日志显示弹窗(建议大家阅读更新日志以了解当前版本的变化),在更新版本后将自动弹出
|
||||||
- 新增是否在更新版本的首次启动时显示更新日志弹窗设置,默认开启,可以去设置-软件更新更改
|
- 新增是否在更新版本的首次启动时显示更新日志弹窗设置,默认开启,可以去设置-软件更新更改
|
||||||
- 添加wy、tx源逐字歌词的支持
|
- 添加wy、tx源逐字歌词的支持
|
||||||
|
- 添加启动时的数据库表及表结构完整性校验,若未通过校验,则会显示弹窗提示后将该数据库重命名添加`.bak`后缀后重建数据库启动。对于某些人遇到更新到v2.0.0后出现之前收藏的歌曲全部丢失或者歌曲无法添加到列表的问题,可以通过此特性自动重建数据库并重新迁移数据,不再需要手动去数据目录删除数据库
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { join, dirname } from 'path'
|
import { join, dirname } from 'path'
|
||||||
import { existsSync, mkdirSync } from 'fs'
|
import { existsSync, mkdirSync, renameSync } from 'fs'
|
||||||
import { app, shell, screen, nativeTheme } from 'electron'
|
import { app, shell, screen, nativeTheme, dialog } from 'electron'
|
||||||
import { URL_SCHEME_RXP } from '@common/constants'
|
import { URL_SCHEME_RXP } from '@common/constants'
|
||||||
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
||||||
import { navigationUrlWhiteList } from '@common/config'
|
import { navigationUrlWhiteList } from '@common/config'
|
||||||
|
@ -10,6 +10,7 @@ import { createAppEvent, createListEvent } from '@main/event'
|
||||||
import { isMac, log } from '@common/utils'
|
import { isMac, log } from '@common/utils'
|
||||||
import createWorkers from './worker'
|
import createWorkers from './worker'
|
||||||
import { migrateDBData } from './utils/migrate'
|
import { migrateDBData } from './utils/migrate'
|
||||||
|
import { openDirInExplorer } from '@common/utils/electron'
|
||||||
|
|
||||||
export const initGlobalData = () => {
|
export const initGlobalData = () => {
|
||||||
global.isDev = process.env.NODE_ENV !== 'production'
|
global.isDev = process.env.NODE_ENV !== 'production'
|
||||||
|
@ -237,7 +238,18 @@ export const initAppSetting = async() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInitialized) {
|
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
|
global.lx.appSetting = (await initSetting()).setting
|
||||||
if (!dbFileExists) await migrateDBData().catch(err => { log.error(err) })
|
if (!dbFileExists) await migrateDBData().catch(err => { log.error(err) })
|
||||||
initTheme()
|
initTheme()
|
||||||
|
|
|
@ -1,108 +1,22 @@
|
||||||
import Database from 'better-sqlite3'
|
import Database from 'better-sqlite3'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import tables from './tables'
|
||||||
|
import verifyDB from './verifyDB'
|
||||||
// import migrateData from './migrate'
|
// import migrateData from './migrate'
|
||||||
|
|
||||||
let db: Database.Database
|
let db: Database.Database
|
||||||
|
|
||||||
|
|
||||||
const initTables = (db: Database.Database) => {
|
const initTables = (db: Database.Database) => {
|
||||||
const sql = `
|
db.exec(`
|
||||||
CREATE TABLE "db_info" (
|
${Array.from(tables.values()).join('\n')}
|
||||||
"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")
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO "main"."db_info" ("field_name", "field_value") VALUES ('version', '1');
|
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 databasePath = path.join(lxDataPath, 'lx.data.db')
|
||||||
const nativeBinding = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
|
const nativeBinding = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
|
||||||
let dbFileExists = true
|
let dbFileExists = true
|
||||||
|
@ -127,6 +41,10 @@ export const init = (lxDataPath: string): boolean => {
|
||||||
|
|
||||||
// https://www.sqlite.org/pragma.html#pragma_optimize
|
// https://www.sqlite.org/pragma.html#pragma_optimize
|
||||||
if (dbFileExists) db.exec('PRAGMA optimize;')
|
if (dbFileExists) db.exec('PRAGMA optimize;')
|
||||||
|
if (!verifyDB(db)) {
|
||||||
|
db.close()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// https://www.sqlite.org/lang_vacuum.html
|
// https://www.sqlite.org/lang_vacuum.html
|
||||||
// db.exec('VACUUM "main"')
|
// db.exec('VACUUM "main"')
|
||||||
|
|
|
@ -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
|
|
@ -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() }))
|
||||||
|
}
|
Loading…
Reference in New Issue