diff --git a/src/main/ipc-handlers/schema.js b/src/main/ipc-handlers/schema.js index df987c42..b87a0262 100644 --- a/src/main/ipc-handlers/schema.js +++ b/src/main/ipc-handlers/schema.js @@ -1,10 +1,11 @@ -import { ipcMain, dialog, Notification } from 'electron'; -import path from 'path'; import fs from 'fs'; +import path from 'path'; +import { fork } from 'child_process'; +import { ipcMain, dialog, Notification } from 'electron'; // @TODO: need some factories -import MysqlExporter from '../libs/exporters/sql/MysqlExporter'; import MysqlImporter from '../libs/importers/sql/MysqlImporter'; +const isDevelopment = process.env.NODE_ENV !== 'production'; export default connections => { let exporter = null; @@ -172,80 +173,72 @@ export default connections => { } }); - ipcMain.handle('export', async (event, { uid, type, tables, ...rest }) => { + ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => { if (exporter !== null) return; - switch (type) { - case 'mysql': - case 'maria': - exporter = new MysqlExporter(connections[uid], tables, rest); - break; - default: - return { - status: 'error', - response: `${type} exporter not aviable` - }; - } - - const outputFileName = path.basename(rest.outputFile); - - if (fs.existsSync(rest.outputFile)) { - const result = await dialog.showMessageBox({ - type: 'warning', - message: `File ${outputFileName} already exists. Do you want to replace it?`, - detail: - 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.', - buttons: ['Cancel', 'Replace'], - defaultId: 0, - cancelId: 0 - }); - - if (result.response !== 1) { - exporter = null; - return { status: 'error', response: 'Operation aborted' }; - } - } - return new Promise((resolve, reject) => { - exporter.once('error', err => { - reject(err); - }); + (async () => { + if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace + const result = await dialog.showMessageBox({ + type: 'warning', + message: `File ${rest.outputFile} already exists. Do you want to replace it?`, + detail: 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.', + buttons: ['Cancel', 'Replace'], + defaultId: 0, + cancelId: 0 + }); - exporter.once('end', () => { - resolve({ cancelled: exporter.isCancelled }); - }); - - exporter.once('cancel', () => { - fs.unlinkSync(exporter.outputFile); - }); - - exporter.on('progress', state => { - event.sender.send('export-progress', state); - }); - - exporter.run(); - }) - .then(response => { - if (!response.cancelled) { - new Notification({ - title: 'Export finished', - body: `Finished exporting to ${outputFileName}` - }).show(); + if (result.response !== 1) { + resolve({ + type: 'error', + response: 'Operation aborted' + }); + return; + } } - return { status: 'success', response }; - }) - .catch(err => { - new Notification({ - title: 'Export error', - body: err.toString() - }).show(); - return { status: 'error', response: err.toString() }; - }) - .finally(() => { - exporter.removeAllListeners(); - exporter = null; - }); + // Init exporter process + exporter = fork(isDevelopment ? './dist/exporter.js' : path.resolve(__dirname, './exporter.js')); + exporter.send({ + type: 'init', + client: { + name: type, + config: await connections[uid].getDbConfig() + }, + tables, + options: rest + }); + + // Exporter message listener + exporter.on('message', ({ type, payload }) => { + switch (type) { + case 'export-progress': + event.sender.send('export-progress', payload); + break; + case 'end': + exporter.kill(); + exporter = null; + resolve({ status: 'success', response: payload }); + break; + case 'cancel': + exporter.kill(); + exporter = null; + resolve({ status: 'error', response: 'Operation cancelled' }); + break; + case 'error': + exporter.kill(); + exporter = null; + resolve({ status: 'error', response: payload }); + break; + } + }); + + exporter.on('exit', code => { + exporter = null; + resolve({ status: 'error', response: `Operation ended with code: ${code}` }); + }); + })(); + }); }); ipcMain.handle('abort-export', async event => { @@ -262,7 +255,7 @@ export default connections => { if (result.response === 1) { willAbort = true; - exporter.cancel(); + exporter.send({ type: 'cancel' }); } } diff --git a/src/main/libs/exporters/sql/MysqlExporter.js b/src/main/libs/exporters/sql/MysqlExporter.js index 467e9b79..1af55c49 100644 --- a/src/main/libs/exporters/sql/MysqlExporter.js +++ b/src/main/libs/exporters/sql/MysqlExporter.js @@ -53,9 +53,7 @@ ${footer} let rowCount = 0; let sqlStr = ''; - const countResults = await this._client.raw( - `SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\`` - ); + const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``); if (countResults.rows.length === 1) rowCount = countResults.rows[0].count; if (rowCount > 0) { diff --git a/src/main/workers/ExportService.js b/src/main/workers/ExportService.js deleted file mode 100644 index e82524a5..00000000 --- a/src/main/workers/ExportService.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Worker, isMainThread, workerData, parentPort } from 'worker_threads'; -import - -if (isMainThread) { - module.exports = function run (workerData) { - return new Promise((resolve, reject) => { - const worker = new Worker(__filename, { workerData }); - worker.on('message', resolve); - worker.on('error', reject); - worker.on('exit', (code) => { - if (code !== 0) - reject(new Error(`Worker stopped with exit code ${code}`)); - }); - }); - }; -} -else { - -} diff --git a/src/main/workers/exporter.js b/src/main/workers/exporter.js new file mode 100644 index 00000000..19dea14c --- /dev/null +++ b/src/main/workers/exporter.js @@ -0,0 +1,61 @@ +import { ClientsFactory } from '../libs/ClientsFactory'; +// TODO: exporter factory class +import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js'; +import fs from 'fs'; +let exporter; + +process.on('message', async ({ type, client, tables, options }) => { + if (type === 'init') { + const connection = await ClientsFactory.getConnection({ + client: client.name, + params: client.config, + poolSize: 5 + }); + await connection.connect(); + + switch (client.name) { + case 'mysql': + case 'maria': + exporter = new MysqlExporter(connection, tables, options); + break; + default: + process.send({ + type: 'error', + payload: `"${client.name}" exporter not aviable` + }); + return; + } + + exporter.once('error', err => { + console.error(err); + process.send({ + type: 'error', + payload: err.toString() + }); + }); + + exporter.once('end', () => { + process.send({ + type: 'end', + payload: { cancelled: exporter.isCancelled } + }); + connection.destroy(); + }); + + exporter.once('cancel', () => { + fs.unlinkSync(exporter.outputFile); + process.send({ type: 'cancel' }); + }); + + exporter.on('progress', state => { + process.send({ + type: 'export-progress', + payload: state + }); + }); + + exporter.run(); + } + else if (type === 'cancel') + exporter.cancel(); +}); diff --git a/src/main/workers/exporterProcess.js b/src/main/workers/exporterProcess.js deleted file mode 100644 index 31481b40..00000000 --- a/src/main/workers/exporterProcess.js +++ /dev/null @@ -1,75 +0,0 @@ -// import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js'; -// import path from 'path'; -// import fs from 'fs'; -// let exporter; - -// switch (type) { -// case 'mysql': -// case 'maria': -// exporter = new MysqlExporter(connections[uid], tables, rest); -// break; -// default: -// // return { -// // status: 'error', -// // response: `${type} exporter not aviable` -// // }; -// } - -// const outputFileName = path.basename(rest.outputFile); - -// if (fs.existsSync(rest.outputFile)) { -// const result = await dialog.showMessageBox({ -// type: 'warning', -// message: `File ${outputFileName} already exists. Do you want to replace it?`, -// detail: -// 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.', -// buttons: ['Cancel', 'Replace'], -// defaultId: 0, -// cancelId: 0 -// }); - -// if (result.response !== 1) -// exporter = null; -// // return { status: 'error', response: 'Operation aborted' }; -// } - -// // return new Promise((resolve, reject) => { -// exporter.once('error', err => { -// reject(err); -// }); - -// exporter.once('end', () => { -// resolve({ cancelled: exporter.isCancelled }); -// }); - -// exporter.once('cancel', () => { -// fs.unlinkSync(exporter.outputFile); -// }); - -// exporter.on('progress', state => { -// event.sender.send('export-progress', state); -// }); - -// exporter.run(); -// // }) -// // .then(response => { -// // if (!response.cancelled) { -// // new Notification({ -// // title: 'Export finished', -// // body: `Finished exporting to ${outputFileName}` -// // }).show(); -// // } -// // return { status: 'success', response }; -// // }) -// // .catch(err => { -// // new Notification({ -// // title: 'Export error', -// // body: err.toString() -// // }).show(); - -// // return { status: 'error', response: err.toString() }; -// // }) -// // .finally(() => { -// // exporter.removeAllListeners(); -// // exporter = null; -// // }); diff --git a/src/renderer/components/BaseConfirmModal.vue b/src/renderer/components/BaseConfirmModal.vue index e717a7ea..e562d9fa 100644 --- a/src/renderer/components/BaseConfirmModal.vue +++ b/src/renderer/components/BaseConfirmModal.vue @@ -110,5 +110,4 @@ export default { .modal.modal-sm .modal-container { padding: 0; } - diff --git a/src/renderer/components/ModalExportSchema.vue b/src/renderer/components/ModalExportSchema.vue index ac96a12e..ffa8a5e4 100644 --- a/src/renderer/components/ModalExportSchema.vue +++ b/src/renderer/components/ModalExportSchema.vue @@ -292,6 +292,7 @@ export default { }, methods: { ...mapActions({ + addNotification: 'notifications/addNotification', refreshSchema: 'workspaces/refreshSchema' }), async startExport () { @@ -306,13 +307,17 @@ export default { ...this.options }; - const result = await Schema.export(params); - if (result) { - if (result.status === 'success') - this.progressStatus = result.response.cancelled ? this.$t('word.aborted') : this.$t('word.completed'); - - else - this.progressStatus = result.response; + try { + const { status, response } = await Schema.export(params); + if (status === 'success') + this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed'); + else { + this.progressStatus = response; + this.addNotification({ status: 'error', message: response }); + } + } + catch (err) { + this.addNotification({ status: 'error', message: err.stack }); } this.isExporting = false; diff --git a/src/renderer/scss/main.scss b/src/renderer/scss/main.scss index c28ca7ad..5c38527b 100644 --- a/src/renderer/scss/main.scss +++ b/src/renderer/scss/main.scss @@ -165,6 +165,15 @@ option:checked { } } } + + .modal-overlay{ + background: rgba( 255, 255, 255, 0.1 ); + box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); + backdrop-filter: blur( 4px ); + -webkit-backdrop-filter: blur( 4px ); + border-radius: 10px; + border: 1px solid rgba( 255, 255, 255, 0.18 ); + } } .tab { diff --git a/webpack.workers.config.js b/webpack.workers.config.js index 69794339..9e457e75 100644 --- a/webpack.workers.config.js +++ b/webpack.workers.config.js @@ -13,7 +13,7 @@ const config = { mode: process.env.NODE_ENV, devtool: isDevMode ? 'eval-source-map' : false, entry: { - exporterProcess: path.join(__dirname, './src/main/workers/exporterProcess.js') + exporter: path.join(__dirname, './src/main/workers/exporter.js') }, target: 'node', output: {