From e26809f260099ba194bf5d00671cae14d438197b Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Fri, 1 Dec 2023 14:18:40 +0100 Subject: [PATCH] fix(Flatpak): import/export schema not working --- src/main/ipc-handlers/schema.ts | 90 ++++++++++++------- src/main/libs/clients/BaseClient.ts | 2 +- src/main/libs/exporters/sql/MysqlExporter.ts | 7 +- .../libs/exporters/sql/PostgreSQLExporter.ts | 1 - src/main/libs/importers/sql/MySQLlImporter.ts | 4 +- .../libs/importers/sql/PostgreSQLImporter.ts | 4 +- src/main/workers/exporter.ts | 39 ++++---- src/main/workers/importer.ts | 40 ++++----- src/renderer/stores/scratchpad.ts | 11 ++- 9 files changed, 117 insertions(+), 81 deletions(-) diff --git a/src/main/ipc-handlers/schema.ts b/src/main/ipc-handlers/schema.ts index 1a137cde..e4ac55aa 100644 --- a/src/main/ipc-handlers/schema.ts +++ b/src/main/ipc-handlers/schema.ts @@ -1,9 +1,8 @@ -import { ChildProcess, fork } from 'child_process'; +import { ChildProcess, fork, spawn } from 'child_process'; import * as antares from 'common/interfaces/antares'; import * as workers from 'common/interfaces/workers'; import { dialog, ipcMain } from 'electron'; import * as fs from 'fs'; -import * as path from 'path'; import { validateSender } from '../libs/misc/validateSender'; @@ -209,11 +208,6 @@ export default (connections: {[key: string]: antares.Client}) => { return new Promise((resolve/*, reject */) => { (async () => { - if (isFlatpak) { - resolve({ status: 'error', response: 'Temporarily unavailable on Flatpak' }); - return; - } - if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace const result = await dialog.showMessageBox({ type: 'warning', @@ -234,11 +228,20 @@ export default (connections: {[key: string]: antares.Client}) => { } // Init exporter process - exporter = fork(isDevelopment ? './dist/exporter.js' : './exporter.js', [], { - execArgv: isDevelopment ? ['--inspect=9224'] : undefined - }); + if (isFlatpak) { + exporter = spawn('flatpak-spawn', ['--watch-bus', '--host', 'node', './exporter.js'], { + shell: true + }); + } + else { + exporter = fork(isDevelopment ? './dist/exporter.js' : './exporter.js', [], { + execArgv: isDevelopment ? ['--inspect=9224'] : undefined, + silent: true + }); + // exporter = spawn('node', [isDevelopment ? '--inspect=9224' : '', isDevelopment ? './dist/exporter.js' : './exporter.js']); + } - exporter.send({ + exporter.stdin.write(JSON.stringify({ type: 'init', client: { name: type, @@ -246,18 +249,31 @@ export default (connections: {[key: string]: antares.Client}) => { }, tables, options: rest - }); + })); // Exporter message listener - exporter.on('message', ({ type, payload }: workers.WorkerIpcMessage) => { + exporter.stdout.on('data', (buff: Buffer) => { + let message; + try { // Ignore non-JSON data (console.log output) + message = JSON.parse(buff.toString()); + } + catch (_) { + if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', buff.toString()); + return; + } + + const { type, payload } = message as workers.WorkerIpcMessage; + switch (type) { case 'export-progress': event.sender.send('export-progress', payload); break; case 'end': setTimeout(() => { // Ensures that writing process has finished - exporter.kill(); - exporter = null; + if (exporter) { + exporter.kill(); + exporter = null; + } }, 2000); resolve({ status: 'success', response: payload }); break; @@ -274,7 +290,7 @@ export default (connections: {[key: string]: antares.Client}) => { } }); - exporter.on('exit', code => { + exporter.on('close', code => { exporter = null; resolve({ status: 'error', response: `Operation ended with code: ${code}` }); }); @@ -298,7 +314,7 @@ export default (connections: {[key: string]: antares.Client}) => { if (result.response === 1) { willAbort = true; - exporter.send({ type: 'cancel' }); + exporter.stdin.write(JSON.stringify({ type: 'cancel' })); } } @@ -315,26 +331,40 @@ export default (connections: {[key: string]: antares.Client}) => { return new Promise((resolve/*, reject */) => { (async () => { - if (isFlatpak) { - resolve({ status: 'warning', response: 'Temporarily unavailable on Flatpak' }); - return; - } - const dbConfig = await connections[options.uid].getDbConfig(); // Init importer process - importer = fork(isDevelopment ? './dist/importer.js' : path.resolve(__dirname, './importer.js'), [], { - execArgv: isDevelopment ? ['--inspect=9224'] : undefined - }); + if (isFlatpak) { + importer = spawn('flatpak-spawn', ['--watch-bus', 'node', './importer.js'], { + cwd: __dirname + }); + } + else { + importer = fork(isDevelopment ? './dist/importer.js' : './importer.js', [], { + execArgv: isDevelopment ? ['--inspect=9224'] : undefined, + silent: true + }); + } - importer.send({ + importer.stdin.write(JSON.stringify({ type: 'init', dbConfig, options - }); + })); // Importer message listener - importer.on('message', ({ type, payload }: workers.WorkerIpcMessage) => { + importer.stdout.on('data', (buff: Buffer) => { + let message; + try { // Ignore non-JSON data (console.log output) + message = JSON.parse(buff.toString()); + } + catch (_) { + if (process.env.NODE_ENV === 'development') console.log('IMPORTER:', buff.toString()); + return; + } + + const { type, payload } = message as workers.WorkerIpcMessage; + switch (type) { case 'import-progress': event.sender.send('import-progress', payload); @@ -362,7 +392,7 @@ export default (connections: {[key: string]: antares.Client}) => { } }); - importer.on('exit', code => { + importer.on('close', code => { importer = null; resolve({ status: 'error', response: `Operation ended with code: ${code}` }); }); @@ -386,7 +416,7 @@ export default (connections: {[key: string]: antares.Client}) => { if (result.response === 1) { willAbort = true; - importer.send({ type: 'cancel' }); + importer.stdin.write(JSON.stringify({ type: 'cancel' })); } } diff --git a/src/main/libs/clients/BaseClient.ts b/src/main/libs/clients/BaseClient.ts index 773f3277..2e85997a 100644 --- a/src/main/libs/clients/BaseClient.ts +++ b/src/main/libs/clients/BaseClient.ts @@ -10,7 +10,7 @@ const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => { const mainWindow = require('electron').webContents.fromId(1); mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() }); } - if (process.env.NODE_ENV === 'development') console.log(escapedSql); + if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql); }; /** diff --git a/src/main/libs/exporters/sql/MysqlExporter.ts b/src/main/libs/exporters/sql/MysqlExporter.ts index 49c91a34..469f5844 100644 --- a/src/main/libs/exporters/sql/MysqlExporter.ts +++ b/src/main/libs/exporters/sql/MysqlExporter.ts @@ -1,6 +1,5 @@ import * as exporter from 'common/interfaces/exporter'; import { valueToSqlString } from 'common/libs/sqlUtils'; -import * as mysql from 'mysql2/promise'; import { MySQLClient } from '../../clients/MySQLClient'; import { SqlExporter } from './SqlExporter'; @@ -334,12 +333,10 @@ CREATE TABLE \`${view.Name}\`( } async _queryStream (sql: string) { - if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql); - const isPool = 'getConnection' in this._client._connection; - const connection = isPool ? await (this._client._connection as mysql.Pool).getConnection() : this._client._connection; + const connection = await this._client.getConnection(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const stream = (connection as any).connection.query(sql).stream(); - const dispose = () => (connection as mysql.PoolConnection).release(); + const dispose = () => connection.end(); stream.on('end', dispose); stream.on('error', dispose); diff --git a/src/main/libs/exporters/sql/PostgreSQLExporter.ts b/src/main/libs/exporters/sql/PostgreSQLExporter.ts index 82fed408..b54c9a72 100644 --- a/src/main/libs/exporters/sql/PostgreSQLExporter.ts +++ b/src/main/libs/exporters/sql/PostgreSQLExporter.ts @@ -425,7 +425,6 @@ SET row_security = off;\n\n\n`; } async _queryStream (sql: string) { - if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql); const connection = await this._client.getConnection(); const query = new QueryStream(sql, null); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/main/libs/importers/sql/MySQLlImporter.ts b/src/main/libs/importers/sql/MySQLlImporter.ts index 58233672..4c0c0330 100644 --- a/src/main/libs/importers/sql/MySQLlImporter.ts +++ b/src/main/libs/importers/sql/MySQLlImporter.ts @@ -33,8 +33,8 @@ export default class MySQLImporter extends BaseImporter { parser.on('error', reject); parser.on('close', async () => { - console.log('TOTAL QUERIES', queryCount); - console.log('import end'); + // console.log('TOTAL QUERIES', queryCount); + // console.log('import end'); resolve(); }); diff --git a/src/main/libs/importers/sql/PostgreSQLImporter.ts b/src/main/libs/importers/sql/PostgreSQLImporter.ts index 463c149d..8b6eee91 100644 --- a/src/main/libs/importers/sql/PostgreSQLImporter.ts +++ b/src/main/libs/importers/sql/PostgreSQLImporter.ts @@ -33,8 +33,8 @@ export default class PostgreSQLImporter extends BaseImporter { parser.on('error', reject); parser.on('close', async () => { - console.log('TOTAL QUERIES', queryCount); - console.log('import end'); + // console.log('TOTAL QUERIES', queryCount); + // console.log('import end'); resolve(); }); diff --git a/src/main/workers/exporter.ts b/src/main/workers/exporter.ts index 204c6245..64ba8ae3 100644 --- a/src/main/workers/exporter.ts +++ b/src/main/workers/exporter.ts @@ -1,6 +1,7 @@ import * as antares from 'common/interfaces/antares'; import * as log from 'electron-log/main'; import * as fs from 'fs'; +import { nextTick } from 'process'; import { MySQLClient } from '../libs/clients/MySQLClient'; import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient'; @@ -10,10 +11,12 @@ import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter'; let exporter: antares.Exporter; log.transports.file.fileName = 'workers.log'; +log.transports.console = null; log.errorHandler.startCatching(); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -process.on('message', async ({ type, client, tables, options }: any) => { +process.stdin.on('data', async buff => { + const { type, client, tables, options } = JSON.parse(buff.toString()); + if (type === 'init') { try { const connection = await ClientsFactory.getClient({ @@ -32,49 +35,53 @@ process.on('message', async ({ type, client, tables, options }: any) => { exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options); break; default: - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: `"${client.name}" exporter not aviable` - }); + })); return; } exporter.once('error', err => { log.error(err.toString()); - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: err.toString() - }); + })); }); exporter.once('end', () => { - process.send({ - type: 'end', - payload: { cancelled: exporter.isCancelled } + nextTick(() => { + process.stdout.write(JSON.stringify({ + type: 'end', + payload: { cancelled: exporter.isCancelled } + })); + connection.destroy(); }); - connection.destroy(); }); exporter.once('cancel', () => { - fs.unlinkSync(exporter.outputFile); - process.send({ type: 'cancel' }); + nextTick(() => { + fs.unlinkSync(exporter.outputFile); + process.stdout.write(JSON.stringify({ type: 'cancel' })); + }); }); exporter.on('progress', state => { - process.send({ + process.stdout.write(JSON.stringify({ type: 'export-progress', payload: state - }); + })); }); exporter.run(); } catch (err) { log.error(err.toString()); - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: err.toString() - }); + })); } } else if (type === 'cancel') diff --git a/src/main/workers/importer.ts b/src/main/workers/importer.ts index 2ef638f2..c7c5a702 100644 --- a/src/main/workers/importer.ts +++ b/src/main/workers/importer.ts @@ -1,6 +1,4 @@ -import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig'; import * as antares from 'common/interfaces/antares'; -import { ImportOptions } from 'common/interfaces/importer'; import * as log from 'electron-log/main'; import * as mysql from 'mysql2'; import * as pg from 'pg'; @@ -13,16 +11,12 @@ import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter'; let importer: antares.Importer; log.transports.file.fileName = 'workers.log'; +log.transports.console = null; log.errorHandler.startCatching(); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -process.on('message', async ({ type, dbConfig, options }: { - type: string; - dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean } - | pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean } - | { databasePath: string; readonly: boolean }; - options: ImportOptions; -}) => { +process.stdin.on('data', async (buff: Buffer) => { + const { type, dbConfig, options } = JSON.parse(buff.toString()); + if (type === 'init') { try { const connection = await ClientsFactory.getClient({ @@ -45,54 +39,54 @@ process.on('message', async ({ type, dbConfig, options }: { importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options); break; default: - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: `"${options.type}" importer not aviable` - }); + })); return; } importer.once('error', err => { log.error(err.toString()); - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: err.toString() - }); + })); }); importer.once('end', () => { - process.send({ + process.stdout.write(JSON.stringify({ type: 'end', payload: { cancelled: importer.isCancelled } - }); + })); }); importer.once('cancel', () => { - process.send({ type: 'cancel' }); + process.stdout.write(JSON.stringify({ type: 'cancel' })); }); importer.on('progress', state => { - process.send({ + process.stdout.write(JSON.stringify({ type: 'import-progress', payload: state - }); + })); }); importer.on('query-error', state => { - process.send({ + process.stdout.write(JSON.stringify({ type: 'query-error', payload: state - }); + })); }); importer.run(); } catch (err) { log.error(err.toString()); - process.send({ + process.stdout.write(JSON.stringify({ type: 'error', payload: err.toString() - }); + })); } } else if (type === 'cancel') diff --git a/src/renderer/stores/scratchpad.ts b/src/renderer/stores/scratchpad.ts index ac8ba5f9..73f2f802 100644 --- a/src/renderer/stores/scratchpad.ts +++ b/src/renderer/stores/scratchpad.ts @@ -2,9 +2,18 @@ import * as Store from 'electron-store'; import { defineStore } from 'pinia'; const persistentStore = new Store({ name: 'notes' }); +export interface ConnectionNote { + uid: string; + note: string; + date: Date; +} + export const useScratchpadStore = defineStore('scratchpad', { state: () => ({ - notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/antares-sql/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n') as string + /** Global notes */ + notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/antares-sql/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n') as string, + /** Connection specific notes */ + connectionNotes: persistentStore.get('connectionNotes', {}) as {[k: string]: ConnectionNote} }), actions: { changeNotes (notes: string) {