♻️ 合并 TGSql 与 TGSqlite,重构代码作为 Plugin 使用

This commit is contained in:
BTMuli
2023-05-31 17:32:00 +08:00
parent 1a9ca12363
commit 51058c6825
8 changed files with 477 additions and 434 deletions

View File

@@ -1,300 +0,0 @@
/**
* @file utils TGSql.ts
* @description 数据库sql语句
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.5
*/
// tauri
import { app } from "@tauri-apps/api";
// utils
import { getBuildTime } from "./TGBuild";
import minifySql from "./minifySql";
import { AppAchievementsData, AppAchievementSeriesData, AppNameCardsData } from "../data";
/**
* @description 初始化应用数据表
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initAppTable (): string[] {
const sqlRes = [];
// 创建应用数据表
sqlRes.push(minifySql(`
CREATE TABLE IF NOT EXISTS AppData
(
key TEXT PRIMARY KEY,
value TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`));
return sqlRes;
}
/**
* @description 初始化游戏账号数据表
* @since Alpha v0.1.5
* @see TGApp.Sqlite.Account.Game
* @returns {string[]} sql
*/
function initGameAccountTable (): string[] {
const sqlRes = [];
// 创建游戏账号数据表
sqlRes.push(minifySql(`
CREATE TABLE IF NOT EXISTS GameAccount
(
gameBiz TEXT NOT NULL,
gameUid TEXT NOT NULL,
isChosen BOOLEAN DEFAULT 0,
isOfficial BOOLEAN DEFAULT 0,
level INTEGER DEFAULT 0,
nickname TEXT DEFAULT NULL,
region TEXT DEFAULT NULL,
regionName TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL,
PRIMARY KEY (gameBiz, gameUid)
);
`));
return sqlRes;
}
/**
* @description 初始化成就系列数据表
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initAchievementSeriesTable (): string[] {
const sqlRes = [];
// 创建成就系列数据表
sqlRes.push(minifySql(`
CREATE TABLE IF NOT EXISTS AchievementSeries
(
id INTEGER PRIMARY KEY,
"order" INTEGER,
name TEXT DEFAULT NULL,
version TEXT DEFAULT NULL,
totalCount INTEGER DEFAULT 0,
finCount INTEGER DEFAULT 0,
icon TEXT NOT NULL,
nameCard TEXT NOT NULL,
updated TEXT DEFAULT NULL
);
`));
return sqlRes;
}
/**
* @description 初始化成就数据表
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initAchievementTable (): string[] {
const sqlRes = [];
// 创建成就数据表
sqlRes.push(minifySql(`
CREATE TABLE IF NOT EXISTS Achievements
(
id INTEGER PRIMARY KEY,
series INTEGER,
"order" INTEGER,
name TEXT DEFAULT NULL,
description TEXT DEFAULT NULL,
reward INTEGER DEFAULT 0,
isCompleted BOOLEAN DEFAULT 0,
completedTime TEXT DEFAULT NULL,
progress INTEGER DEFAULT 0,
version TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`));
// 创建触发器
sqlRes.push(minifySql(`
CREATE TRIGGER IF NOT EXISTS updateAchievement
AFTER UPDATE ON Achievements
FOR EACH ROW
BEGIN
UPDATE AchievementSeries SET finCount = finCount + 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series AND NEW.isCompleted = 1 AND OLD.isCompleted = 0;
UPDATE AchievementSeries SET finCount = finCount - 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series AND NEW.isCompleted = 0 AND OLD.isCompleted = 1;
END;
`));
sqlRes.push(minifySql(`
CREATE TRIGGER IF NOT EXISTS insertAchievement
AFTER INSERT ON Achievements
FOR EACH ROW
BEGIN
UPDATE AchievementSeries SET totalCount = totalCount + 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series;
UPDATE AchievementSeries SET version = NEW.version, updated = datetime('now', 'localtime')
WHERE id = NEW.series AND NEW.version > version;
END;
`));
return sqlRes;
}
/**
* @description 初始化名片数据表
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initNameCardTable (): string[] {
const sqlRes = [];
// 创建名片数据表
sqlRes.push(minifySql(`
CREATE TABLE IF NOT EXISTS NameCard
(
name TEXT PRIMARY KEY,
"desc" TEXT DEFAULT NULL,
icon TEXT NOT NULL,
bg TEXT NOT NULL,
profile TEXT NOT NULL,
type INTEGER DEFAULT 0,
source TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`));
return sqlRes;
}
/**
* @description 初始化数据库表
* @since Alpha v0.2.0
* @returns {string[]} sql
*/
export function initSQLiteTable (): string[] {
const sqlRes = [];
sqlRes.push(...initAppTable());
sqlRes.push(...initGameAccountTable());
sqlRes.push(...initAchievementSeriesTable());
sqlRes.push(...initAchievementTable());
sqlRes.push(...initNameCardTable());
return sqlRes;
}
/**
* @description 初始化应用数据
* @since Alpha v0.1.5
* @returns {Promise<string[]>} sql
*/
async function initAppData (): Promise<string[]> {
const sqlRes = [];
const appVersion = await app.getVersion();
const buildTime = getBuildTime();
const dataUpdated = buildTime.startsWith("dev") ? buildTime.slice(4) : buildTime;
// 初始化应用版本
sqlRes.push(minifySql(`
INSERT INTO AppData (key, value, updated)
VALUES ('appVersion', '${appVersion}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${appVersion}', updated = datetime('now', 'localtime');
`));
// 初始化应用数据更新时间
sqlRes.push(minifySql(`
INSERT INTO AppData (key, value, updated)
VALUES ('dataUpdated', '${dataUpdated}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${dataUpdated}', updated = datetime('now', 'localtime');
`));
// 初始化 cookie
sqlRes.push(minifySql(`
INSERT INTO AppData (key, value, updated)
VALUES ('cookie', '{}', datetime('now', 'localtime'))
ON CONFLICT(key) DO NOTHING;
`));
return sqlRes;
}
/**
* @description 初始化成就系列数据
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initAchievementSeriesData (): string[] {
const sqlRes: string[] = [];
AppAchievementSeriesData.map((data) => {
const sql = minifySql(`
INSERT OR IGNORE INTO AchievementSeries (id, "order", name, version, icon, nameCard, updated)
VALUES (${data.id}, ${data.order}, '${data.name}', '${data.version}', '${data.icon}', '${data.card}', datetime('now', 'localtime'));
`);
return sqlRes.push(sql);
});
return sqlRes;
}
/**
* @description 初始化成就数据
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initAchievementData (): string[] {
const sqlRes: string[] = [];
AppAchievementsData.map((data) => {
const sql = minifySql(`
INSERT OR IGNORE INTO Achievements (id, series, "order", name, description, reward, version, updated)
VALUES (${data.id}, ${data.series}, ${data.order}, '${data.name}', '${data.description}', ${data.reward}, '${data.version}', datetime('now', 'localtime'));
`);
return sqlRes.push(sql);
});
return sqlRes;
}
/**
* @description 初始化名片数据
* @since Alpha v0.1.5
* @returns {string[]} sql
*/
function initNameCardData (): string[] {
const sqlRes: string[] = [];
AppNameCardsData.map((data) => {
const sql = minifySql(`
INSERT OR IGNORE INTO NameCard (name, "desc", icon, bg, profile, type, source, updated)
VALUES ('${data.name}', '${data.desc}', '${data.icon}', '${data.bg}', '${data.profile}', ${data.type}, '${data.source}', datetime('now', 'localtime'));
`);
return sqlRes.push(sql);
});
return sqlRes;
}
/**
* @description 初始化数据库数据
* @since Alpha v0.1.4
* @returns {Promise<string[]>} sql
*/
export async function initSQLiteData (): Promise<string[]> {
const sqlRes = [];
sqlRes.push(...initAchievementSeriesData());
sqlRes.push(...initAchievementData());
sqlRes.push(...initNameCardData());
sqlRes.push(...await initAppData());
return sqlRes;
}
/**
* @description 导入UIAF数据
* @since Alpha v0.1.5
* @param {TGApp.Plugins.UIAF.Achievement[]} data
* @returns {string[]} sql
*/
export function importUIAFData (data: TGApp.Plugins.UIAF.Achievement[]): string[] {
const sqlRes: string[] = [];
data.map((achievement) => {
let sql;
// 获取完成状态
const isCompleted = achievement.status === 2 || achievement.status === 3;
if (isCompleted) {
const completedTime = new Date(achievement.timestamp * 1000).toISOString().replace("T", " ").slice(0, 19);
sql = minifySql(`
UPDATE Achievements
SET isCompleted = 1, completedTime = '${completedTime}', progress = ${achievement.current}, updated = datetime('now', 'localtime')
WHERE id = ${achievement.id} AND (isCompleted = 0 OR completedTime != '${completedTime}' OR progress != ${achievement.current});
`);
} else {
sql = minifySql(`
UPDATE Achievements
SET progress = ${achievement.current}, updated = datetime('now', 'localtime')
WHERE id = ${achievement.id} AND progress != ${achievement.current};
`);
}
return sqlRes.push(sql);
});
return sqlRes;
}

View File

@@ -1,344 +0,0 @@
/**
* @file utils TGSqlite.ts
* @description 数据库操作类
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.6
*/
// tauri
import Database from "tauri-plugin-sql-api";
// utils
import { importUIAFData, initSQLiteData, initSQLiteTable } from "./TGSql";
import minifySql from "./minifySql";
import { getUiafStatus } from "./UIAF";
class TGSqlite {
/**
* @description 数据库地址
* @private
* @type {string}
* @memberOf TGSqlite
* @since Alpha v0.1.4
*/
private readonly dbPath: string = "sqlite:tauri-genshin.db";
/**
* @description 数据库包含的表
* @private
* @type {string[]}
* @memberOf TGSqlite
* @since Alpha v0.1.4
*/
private readonly tables: string[] = [
"AppData",
"Achievements",
"AchievementSeries",
"GameAccount",
"NameCard",
];
/**
* @description 初始化数据库
* @memberOf TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
* @memberOf TGSqlite
*/
public async init (): Promise<void> {
const db = await Database.load(this.dbPath);
const sqlT = initSQLiteTable();
for (const item of sqlT) {
await db.execute(item);
}
const sqlD = await initSQLiteData();
for (const item of sqlD) {
await db.execute(item);
}
await db.close();
}
/**
* @description 获取数据库信息
* @memberOf TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<TGApp.Sqlite.AppData.Item[]>}
*/
public async getAppData (): Promise<TGApp.Sqlite.AppData.Item[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM AppData;";
const res: TGApp.Sqlite.AppData.Item[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取 cookie
* @memberOf TGSqlite
* @since Alpha v0.2.0
* @returns {Promise<Record<string, string>>}
*/
public async getCookie (): Promise<Record<string, string>> {
const db = await Database.load(this.dbPath);
const sql = "SELECT value FROM AppData WHERE key='cookie';";
const res: Array<{ value: string }> = await db.select(sql);
await db.close();
return JSON.parse(res[0].value);
}
/**
* @description 插入 Account 数据
* @memberOf TGSqlite
* @since Alpha v0.1.5
* @param {TGApp.User.Account.Game[]} accounts
* @returns {Promise<void>}
*/
public async insertAccount (accounts: TGApp.User.Account.Game[]): Promise<void> {
const db = await Database.load(this.dbPath);
for (const a of accounts) {
const isChosen = a.is_chosen ? 1 : 0;
const isOfficial = a.is_official ? 1 : 0;
const sql = minifySql(`
INSERT INTO GameAccount (gameBiz, gameUid, isChosen, isOfficial, level, nickname, region, regionName, updated)
VALUES ('${a.game_biz}', '${a.game_uid}', ${isChosen}, ${isOfficial}, '${a.level}', '${a.nickname}',
'${a.region}', '${a.region_name}', datetime('now', 'localtime'))
ON CONFLICT(gameBiz, gameUid) DO UPDATE SET
isChosen = ${isChosen},
isOfficial = ${isOfficial},
level = ${a.level},
nickname = '${a.nickname}',
region = '${a.region}',
regionName = '${a.region_name}',
updated = datetime('now', 'localtime');
`);
await db.execute(sql);
}
await db.close();
}
/**
* @description 获取当前选择的游戏账号
* @memberOf TGSqlite
* @since Alpha v0.2.0
* @returns {Promise<TGApp.Sqlite.Account.Game|false>}
*/
public async getCurAccount (): Promise<TGApp.Sqlite.Account.Game | false> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM GameAccount WHERE isChosen=1;";
const res: TGApp.Sqlite.Account.Game[] = await db.select(sql);
await db.close();
return res.length === 0 ? false : res[0];
}
/**
* @description 保存 appData
* @memberOf TGSqlite
* @since Alpha v0.1.5
* @param {string} key
* @param {string} value
* @returns {Promise<void>}
*/
public async saveAppData (key: string, value: string): Promise<void> {
const db = await Database.load(this.dbPath);
const sql = minifySql(`
INSERT INTO AppData (key, value, updated)
VALUES ('${key}', '${value}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${value}',updated = datetime('now', 'localtime');
`);
await db.execute(sql);
await db.close();
}
/**
* @description 已有数据表跟触发器不变的情况下,更新数据库数据
* @memberOf TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
*/
public async update (): Promise<void> {
const db = await Database.load(this.dbPath);
const sqlD = await initSQLiteData();
for (const item of sqlD) {
await db.execute(item);
}
await db.close();
}
/**
* @description 检测数据库完整性
* @memberOf TGSqlite
* @since Alpha v0.2.0
* @returns {Promise<boolean>}
*/
public async check (): Promise<boolean> {
const db = await Database.load(this.dbPath);
let isVerified = false;
// 检测数据表是否都存在
const sqlT = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;";
const res: Array<{ name: string }> = await db.select(sqlT);
// 考虑到 sqlite_sequence 表,所以需要 +1
if (res.length === this.tables.length) {
if (this.tables.every((item) => res.map((i) => i.name).includes(item))) {
isVerified = true;
}
}
await db.close();
return isVerified;
}
/**
* @description 重置数据库
* @memberOf TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
*/
public async reset (): Promise<void> {
const db = await Database.load(this.dbPath);
this.tables.map(async (item) => {
const sql = `DROP TABLE IF EXISTS ${item};`;
await db.execute(sql);
});
await db.close();
await this.init();
}
/**
* @description 获取成就系列列表
* @memberOf TGSqlite
* @since Alpha v0.1.5
* @returns {Promise<TGApp.Sqlite.Achievement.SeriesTable[]>}
*/
public async getAchievementSeries (): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
const res: TGApp.Sqlite.Achievement.SeriesTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取成就系列对应的名片
* @memberOf TGSqlite
* @since Alpha v0.1.5
* @param {number} seriesId 系列 ID
* @returns {Promise<TGApp.Sqlite.NameCard.Item>}
*/
public async getNameCard (seriesId: number): Promise<TGApp.Sqlite.NameCard.Item> {
const db = await Database.load(this.dbPath);
const sql = `SELECT * FROM NameCard WHERE name = (SELECT nameCard FROM AchievementSeries WHERE id = ${seriesId});`;
const res: TGApp.Sqlite.NameCard.Item[] = await db.select(sql);
await db.close();
return res[0];
}
/**
* @description 获取成就列表
* @memberOf TGSqlite
* @param {number} [seriesId] 系列 ID
* @since Alpha v0.1.5
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async getAchievements (seriesId?: number): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await Database.load(this.dbPath);
let sql;
if (seriesId) {
sql = `SELECT * FROM Achievements WHERE series=${seriesId} ORDER BY isCompleted, \`order\`;`;
} else {
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
}
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取成就概况
* @since Alpha v0.1.4
* @memberOf TGSqlite
* @returns {Promise<{total:number,fin:number}>}
*/
public async getAchievementsOverview (): Promise<{ total: number, fin: number }> {
const db = await Database.load(this.dbPath);
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
const res: Array<{ total: number, fin: number }> = await db.select(sql);
await db.close();
return res[0];
}
/**
* @description 获取最新成就版本
* @since Alpha v0.1.6
* @memberOf TGSqlite
* @returns {Promise<string>}
*/
public async getLatestAchievementVersion (): Promise<string> {
const db = await Database.load(this.dbPath);
const sql = "SELECT version FROM AchievementSeries ORDER BY version DESC LIMIT 1;";
const res: Array<{ version: string }> = await db.select(sql);
await db.close();
return res[0].version;
}
/**
* @description 查询成就
* @memberOf TGSqlite
* @param {string} keyword 关键词
* @since Alpha v0.1.5
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async searchAchievements (keyword: string): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await Database.load(this.dbPath);
let sql;
if (keyword.startsWith("v")) {
const version = keyword.replace("v", "");
sql = `SELECT * FROM Achievements WHERE version LIKE '%${version}%' ORDER BY isCompleted, \`order\`;`;
} else {
sql = `SELECT * FROM Achievements WHERE name LIKE '%${keyword}%' OR description LIKE '%${keyword}%'
ORDER BY isCompleted, \`order\`;`;
}
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 合并 UIAF 数据
* @memberOf TGSqlite
* @param {TGApp.Plugins.UIAF.Achievement[]} achievements UIAF 数据
* @since Alpha v0.1.4
* @returns {Promise<void>}
*/
public async mergeUIAF (achievements: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
const db = await Database.load(this.dbPath);
const sql = importUIAFData(achievements);
for (const item of sql) {
await db.execute(item);
}
await db.close();
}
/**
* @description 获取 UIAF 数据
* @memberOf TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
*/
public async getUIAF (): Promise<TGApp.Plugins.UIAF.Achievement[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
const achievements: TGApp.Plugins.UIAF.Achievement[] = [];
for (const item of res) {
const completed = item.isCompleted === 1;
const status = getUiafStatus(completed, item.progress);
achievements.push({
id: item.id,
status,
timestamp: completed && item.completedTime ? new Date(item.completedTime).getTime() / 1000 : 0,
current: item.progress,
});
}
return achievements;
}
}
export default new TGSqlite();