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:
		| @@ -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' }); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 { | ||||
|    | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/main/workers/exporter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/main/workers/exporter.js
									
									
									
									
									
										Normal 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(); | ||||
| }); | ||||
| @@ -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; | ||||
| // // }); | ||||
| @@ -110,5 +110,4 @@ export default { | ||||
| .modal.modal-sm .modal-container { | ||||
|   padding: 0; | ||||
| } | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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: { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user