1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

perf: use fork() for the export process

This commit is contained in:
2022-02-18 18:16:13 +01:00
parent 4051eff382
commit 748d44977e
9 changed files with 149 additions and 178 deletions

View File

@ -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,79 +173,71 @@ 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)) {
return new Promise((resolve, reject) => {
(async () => {
if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace
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.',
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
});
if (result.response !== 1) {
resolve({
type: 'error',
response: 'Operation aborted'
});
return;
}
}
// 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;
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();
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}` });
});
})();
});
});
@ -262,7 +255,7 @@ export default connections => {
if (result.response === 1) {
willAbort = true;
exporter.cancel();
exporter.send({ type: 'cancel' });
}
}

View File

@ -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) {

View File

@ -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 {
}

View File

@ -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();
});

View File

@ -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;
// // });

View File

@ -110,5 +110,4 @@ export default {
.modal.modal-sm .modal-container {
padding: 0;
}
</style>

View File

@ -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;

View File

@ -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 {

View File

@ -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: {