From a8e681b9735f1f8711423357ae19a159ee0ece18 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 30 Aug 2022 18:13:42 +0530 Subject: [PATCH] fix: use unlinkIfExists - handle failed migration copy issue separately --- backend/database/manager.ts | 62 +++++++++++++------- backend/helpers.ts | 29 +++++++++ main/registerAutoUpdaterListeners.ts | 2 +- main/registerProcessListeners.ts | 19 +++++- src/errorHandling.ts | 6 +- src/renderer/registerIpcRendererListeners.ts | 2 +- utils/messages.ts | 7 ++- 7 files changed, 100 insertions(+), 27 deletions(-) diff --git a/backend/database/manager.ts b/backend/database/manager.ts index 5744207e..f97f69c2 100644 --- a/backend/database/manager.ts +++ b/backend/database/manager.ts @@ -1,9 +1,12 @@ -import { constants } from 'fs'; import fs from 'fs/promises'; import path from 'path'; import { DatabaseDemuxBase, DatabaseMethod } from 'utils/db/types'; import { getSchemas } from '../../schemas'; -import { databaseMethodSet } from '../helpers'; +import { + databaseMethodSet, + emitMainProcessError, + unlinkIfExists +} from '../helpers'; import patches from '../patches'; import { BespokeQueries } from './bespoke'; import DatabaseCore from './core'; @@ -22,7 +25,7 @@ export class DatabaseManager extends DatabaseDemuxBase { } async createNewDatabase(dbPath: string, countryCode: string) { - await this.#unlinkIfExists(dbPath); + await unlinkIfExists(dbPath); return await this.connectToDatabase(dbPath, countryCode); } @@ -61,12 +64,35 @@ export class DatabaseManager extends DatabaseDemuxBase { try { await this.#runPatchesAndMigrate(); } catch (err) { - console.error(err); - await this.db!.close(); - copyPath && (await fs.copyFile(copyPath, dbPath)); - throw err; + this.#handleFailedMigration(err, dbPath, copyPath); } finally { - copyPath && (await fs.unlink(copyPath)); + await unlinkIfExists(copyPath); + } + } + + async #handleFailedMigration( + error: unknown, + dbPath: string, + copyPath: string | null + ) { + await this.db!.close(); + + if (copyPath) { + await this.#restoreDbCopy(dbPath, copyPath); + } + + if (error instanceof Error) { + error.message = `failed migration\n${error.message}`; + } + + throw error; + } + + async #restoreDbCopy(dbPath: string, copyPath: string) { + try { + await fs.copyFile(copyPath!, dbPath); + } catch (err) { + emitMainProcessError(err); } } @@ -130,17 +156,6 @@ export class DatabaseManager extends DatabaseDemuxBase { return await queryFunction(this.db!, ...args); } - async #unlinkIfExists(dbPath: string) { - const exists = await fs - .access(dbPath, constants.W_OK) - .then(() => true) - .catch(() => false); - - if (exists) { - fs.unlink(dbPath); - } - } - async #getIsFirstRun(): Promise { if (!this.#isInitialized) { return true; @@ -160,7 +175,14 @@ export class DatabaseManager extends DatabaseDemuxBase { const dir = path.parse(src).dir; const dest = path.join(dir, '__premigratory_temp.db'); - await fs.copyFile(src, dest); + + try { + await fs.copyFile(src, dest); + } catch (err) { + emitMainProcessError(err); + return null; + } + return dest; } } diff --git a/backend/helpers.ts b/backend/helpers.ts index 4e480160..ae2f0df1 100644 --- a/backend/helpers.ts +++ b/backend/helpers.ts @@ -1,4 +1,7 @@ +import { constants } from 'fs'; +import fs from 'fs/promises'; import { DatabaseMethod } from 'utils/db/types'; +import { CUSTOM_EVENTS } from 'utils/messages'; import { KnexColumnType } from './database/types'; export const sqliteTypeMap: Record = { @@ -47,3 +50,29 @@ export const databaseMethodSet: Set = new Set([ 'close', 'exists', ]); + +export function emitMainProcessError(error: unknown) { + (process.emit as Function)(CUSTOM_EVENTS.MAIN_PROCESS_ERROR, error); +} + +export async function checkFileAccess(filePath: string, mode?: number) { + mode ??= constants.W_OK; + return await fs + .access(filePath, mode) + .then(() => true) + .catch(() => false); +} + +export async function unlinkIfExists(filePath: unknown) { + if (!filePath || typeof filePath !== 'string') { + return false; + } + + const exists = await checkFileAccess(filePath); + if (exists) { + await fs.unlink(filePath); + return true; + } + + return false; +} diff --git a/main/registerAutoUpdaterListeners.ts b/main/registerAutoUpdaterListeners.ts index a6d4996f..811c117c 100644 --- a/main/registerAutoUpdaterListeners.ts +++ b/main/registerAutoUpdaterListeners.ts @@ -13,7 +13,7 @@ export default function registerAutoUpdaterListeners(main: Main) { return; } - main.mainWindow!.webContents.send(IPC_CHANNELS.MAIN_PROCESS_ERROR, error); + main.mainWindow!.webContents.send(IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, error); dialog.showErrorBox( 'Update Error: ', error == null ? 'unknown' : (error.stack || error).toString() diff --git a/main/registerProcessListeners.ts b/main/registerProcessListeners.ts index 8bb9394e..0622717a 100644 --- a/main/registerProcessListeners.ts +++ b/main/registerProcessListeners.ts @@ -1,5 +1,5 @@ import { app } from 'electron'; -import { IPC_CHANNELS } from 'utils/messages'; +import { CUSTOM_EVENTS, IPC_CHANNELS } from 'utils/messages'; import { Main } from '../main'; export default function registerProcessListeners(main: Main) { @@ -17,12 +17,25 @@ export default function registerProcessListeners(main: Main) { } } + process.on(CUSTOM_EVENTS.MAIN_PROCESS_ERROR, (error) => { + main.mainWindow!.webContents.send( + IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, + error + ); + }); + process.on('unhandledRejection', (error) => { - main.mainWindow!.webContents.send(IPC_CHANNELS.MAIN_PROCESS_ERROR, error); + main.mainWindow!.webContents.send( + IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, + error + ); }); process.on('uncaughtException', (error) => { - main.mainWindow!.webContents.send(IPC_CHANNELS.MAIN_PROCESS_ERROR, error); + main.mainWindow!.webContents.send( + IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, + error + ); setTimeout(() => process.exit(1), 10000); }); } diff --git a/src/errorHandling.ts b/src/errorHandling.ts index bb2a7357..35a7382b 100644 --- a/src/errorHandling.ts +++ b/src/errorHandling.ts @@ -57,7 +57,11 @@ export function getErrorLogObject( error: Error, more: Record ): ErrorLog { - const { name, stack, message } = error; + const { name, stack, message, cause } = error; + if (cause) { + more.cause = cause; + } + const errorLogObj = { name, stack, message, more }; fyo.errorLog.push(errorLogObj); diff --git a/src/renderer/registerIpcRendererListeners.ts b/src/renderer/registerIpcRendererListeners.ts index 8f8a18d4..45b865fb 100644 --- a/src/renderer/registerIpcRendererListeners.ts +++ b/src/renderer/registerIpcRendererListeners.ts @@ -4,7 +4,7 @@ import { fyo } from 'src/initFyo'; import { IPC_CHANNELS } from 'utils/messages'; export default function registerIpcRendererListeners() { - ipcRenderer.on(IPC_CHANNELS.MAIN_PROCESS_ERROR, async (_, error) => { + ipcRenderer.on(IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, async (_, error) => { if (fyo.store.isDevelopment) { console.error(error); } diff --git a/utils/messages.ts b/utils/messages.ts index c1364683..b904d57c 100644 --- a/utils/messages.ts +++ b/utils/messages.ts @@ -33,7 +33,7 @@ export enum IPC_ACTIONS { // ipcMain.send(...) export enum IPC_CHANNELS { - MAIN_PROCESS_ERROR = 'main-process-error', + LOG_MAIN_PROCESS_ERROR = 'main-process-error', CONSOLE_LOG = 'console-log', } @@ -42,3 +42,8 @@ export enum DB_CONN_FAILURE { CANT_OPEN = 'cant-open', CANT_CONNECT = 'cant-connect', } + +// events +export enum CUSTOM_EVENTS { + MAIN_PROCESS_ERROR = 'main-process-error', +}