refactor db

This commit is contained in:
an-lee
2024-09-08 09:52:43 +08:00
parent d96f6adad8
commit be0693da72

View File

@@ -43,166 +43,172 @@ import path from "path";
import url from "url"; import url from "url";
import { i18n } from "@main/i18n"; import { i18n } from "@main/i18n";
import { UserSettingKeyEnum } from "@/types/enums"; import { UserSettingKeyEnum } from "@/types/enums";
import log from "@main/logger";
const __filename = url.fileURLToPath(import.meta.url); const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const logger = log.scope("DB");
const db = { const db = {
connection: null as Sequelize | null, connection: null as Sequelize | null,
connect: async () => {}, connect: async () => {},
disconnect: async () => {}, disconnect: async () => {},
registerIpcHandlers: () => {}, registerIpcHandlers: () => {},
isConnecting: false,
}; };
const handlers = [
audiosHandler,
cacheObjectsHandler,
chatAgentsHandler,
chatMembersHandler,
chatMessagesHandler,
chatsHandler,
conversationsHandler,
messagesHandler,
notesHandler,
pronunciationAssessmentsHandler,
recordingsHandler,
segmentsHandler,
speechesHandler,
transcriptionsHandler,
userSettingsHandler,
videosHandler,
];
db.connect = async () => { db.connect = async () => {
if (db.connection) { // Use a lock to prevent concurrent connections
return; if (db.isConnecting) {
} throw new Error("Database connection is already in progress");
const dbPath = settings.dbPath();
if (!dbPath) {
throw new Error("Db path is not ready");
} }
const sequelize = new Sequelize({ db.isConnecting = true;
dialect: "sqlite",
storage: dbPath,
models: [
Audio,
CacheObject,
Chat,
ChatAgent,
ChatMember,
ChatMessage,
Conversation,
Message,
Note,
PronunciationAssessment,
Recording,
Segment,
Speech,
Transcription,
UserSetting,
Video,
],
});
db.connection = sequelize; try {
if (db.connection) {
const migrationResolver: Resolver<unknown> = ({ return;
name, }
path: filepath, const dbPath = settings.dbPath();
context, if (!dbPath) {
}) => { throw new Error("Db path is not ready");
if (!filepath) {
throw new Error(
`Can't use default resolver for non-filesystem migrations`
);
} }
const loadModule: () => Promise<RunnableMigration<unknown>> = async () => { const sequelize = new Sequelize({
if (os.platform() === "win32") { dialect: "sqlite",
return import(`file://${filepath}`) as Promise< storage: dbPath,
RunnableMigration<unknown> models: [
>; Audio,
} else { CacheObject,
return import(filepath) as Promise<RunnableMigration<unknown>>; Chat,
} ChatAgent,
}; ChatMember,
ChatMessage,
Conversation,
Message,
Note,
PronunciationAssessment,
Recording,
Segment,
Speech,
Transcription,
UserSetting,
Video,
],
});
const getModule = async () => { const migrationResolver: Resolver<unknown> = ({
return await loadModule();
};
return {
name, name,
path: filepath, path: filepath,
up: async () => (await getModule()).up({ path: filepath, name, context }), context,
down: async () => }) => {
(await getModule()).down?.({ path: filepath, name, context }), if (!filepath) {
}; throw new Error(
}; `Can't use default resolver for non-filesystem migrations`
);
const umzug = new Umzug({
migrations: {
glob: ["migrations/*.js", { cwd: __dirname }],
resolve: migrationResolver,
},
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: console,
});
// migrate up to the latest state
await umzug.up();
await sequelize.query("PRAGMA foreign_keys = false;");
await sequelize.sync();
await sequelize.authenticate();
// kill the zombie transcribe processes
Transcription.findAll({
where: {
state: "processing",
},
}).then((transcriptions) => {
transcriptions.forEach((transcription) => {
if (transcription.result) {
transcription.update({ state: "finished" });
} else {
transcription.update({ state: "pending" });
} }
const loadModule: () => Promise<
RunnableMigration<unknown>
> = async () => {
if (os.platform() === "win32") {
return import(`file://${filepath}`) as Promise<
RunnableMigration<unknown>
>;
} else {
return import(filepath) as Promise<RunnableMigration<unknown>>;
}
};
const getModule = async () => {
return await loadModule();
};
return {
name,
path: filepath,
up: async () =>
(await getModule()).up({ path: filepath, name, context }),
down: async () =>
(await getModule()).down?.({ path: filepath, name, context }),
};
};
const umzug = new Umzug({
migrations: {
glob: ["migrations/*.js", { cwd: __dirname }],
resolve: migrationResolver,
},
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: logger,
}); });
});
// migrate settings try {
await UserSetting.migrateFromSettings(); // migrate up to the latest state
await umzug.up();
// initialize i18n await sequelize.query("PRAGMA foreign_keys = false;");
const language = (await UserSetting.get( await sequelize.sync();
UserSettingKeyEnum.LANGUAGE await sequelize.authenticate();
)) as string; } catch (err) {
i18n(language); logger.error(err);
await sequelize.close();
throw err;
}
// vacuum the database // migrate settings
await sequelize.query("VACUUM"); await UserSetting.migrateFromSettings();
// register handlers // initialize i18n
audiosHandler.register(); const language = (await UserSetting.get(
cacheObjectsHandler.register(); UserSettingKeyEnum.LANGUAGE
chatAgentsHandler.register(); )) as string;
chatMembersHandler.register(); i18n(language);
chatMessagesHandler.register();
chatsHandler.register(); // vacuum the database
conversationsHandler.register(); await sequelize.query("VACUUM");
messagesHandler.register();
notesHandler.register(); // register handlers
pronunciationAssessmentsHandler.register();
recordingsHandler.register(); for (const handler of handlers) {
segmentsHandler.register(); handler.register();
speechesHandler.register(); }
transcriptionsHandler.register();
userSettingsHandler.register(); db.connection = sequelize;
videosHandler.register(); logger.info("Database connection established");
} catch (err) {
logger.error(err);
throw err;
} finally {
db.isConnecting = false;
}
}; };
db.disconnect = async () => { db.disconnect = async () => {
// unregister handlers // unregister handlers
audiosHandler.unregister(); for (const handler of handlers) {
cacheObjectsHandler.unregister(); handler.unregister();
chatAgentsHandler.unregister(); }
chatMembersHandler.unregister();
chatMessagesHandler.unregister();
chatsHandler.unregister();
conversationsHandler.unregister();
messagesHandler.unregister();
notesHandler.unregister();
pronunciationAssessmentsHandler.unregister();
recordingsHandler.unregister();
segmentsHandler.unregister();
speechesHandler.unregister();
transcriptionsHandler.unregister();
userSettingsHandler.unregister();
videosHandler.unregister();
await db.connection?.close(); await db.connection?.close();
db.connection = null; db.connection = null;