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 fs from 'fs';
import path from 'path';
import { fork } from 'child_process';
import { ipcMain, dialog, Notification } from 'electron';
// @TODO: need some factories // @TODO: need some factories
import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
import MysqlImporter from '../libs/importers/sql/MysqlImporter'; import MysqlImporter from '../libs/importers/sql/MysqlImporter';
const isDevelopment = process.env.NODE_ENV !== 'production';
export default connections => { export default connections => {
let exporter = null; 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; if (exporter !== null) return;
switch (type) { return new Promise((resolve, reject) => {
case 'mysql': (async () => {
case 'maria': if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace
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({ const result = await dialog.showMessageBox({
type: 'warning', type: 'warning',
message: `File ${outputFileName} already exists. Do you want to replace it?`, message: `File ${rest.outputFile} already exists. Do you want to replace it?`,
detail: detail: 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.',
'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.',
buttons: ['Cancel', 'Replace'], buttons: ['Cancel', 'Replace'],
defaultId: 0, defaultId: 0,
cancelId: 0 cancelId: 0
}); });
if (result.response !== 1) { 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; exporter = null;
return { status: 'error', response: 'Operation aborted' }; resolve({ status: 'success', response: payload });
} break;
} case 'cancel':
exporter.kill();
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; 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) { if (result.response === 1) {
willAbort = true; willAbort = true;
exporter.cancel(); exporter.send({ type: 'cancel' });
} }
} }

View File

@ -53,9 +53,7 @@ ${footer}
let rowCount = 0; let rowCount = 0;
let sqlStr = ''; let sqlStr = '';
const countResults = await this._client.raw( const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``);
`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``
);
if (countResults.rows.length === 1) rowCount = countResults.rows[0].count; if (countResults.rows.length === 1) rowCount = countResults.rows[0].count;
if (rowCount > 0) { 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 { .modal.modal-sm .modal-container {
padding: 0; padding: 0;
} }
</style> </style>

View File

@ -292,6 +292,7 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification',
refreshSchema: 'workspaces/refreshSchema' refreshSchema: 'workspaces/refreshSchema'
}), }),
async startExport () { async startExport () {
@ -306,13 +307,17 @@ export default {
...this.options ...this.options
}; };
const result = await Schema.export(params); try {
if (result) { const { status, response } = await Schema.export(params);
if (result.status === 'success') if (status === 'success')
this.progressStatus = result.response.cancelled ? this.$t('word.aborted') : this.$t('word.completed'); this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
else {
else this.progressStatus = response;
this.progressStatus = result.response; this.addNotification({ status: 'error', message: response });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
} }
this.isExporting = false; 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 { .tab {

View File

@ -13,7 +13,7 @@ const config = {
mode: process.env.NODE_ENV, mode: process.env.NODE_ENV,
devtool: isDevMode ? 'eval-source-map' : false, devtool: isDevMode ? 'eval-source-map' : false,
entry: { entry: {
exporterProcess: path.join(__dirname, './src/main/workers/exporterProcess.js') exporter: path.join(__dirname, './src/main/workers/exporter.js')
}, },
target: 'node', target: 'node',
output: { output: {