From d406eb5ed1cf8a74575cc7ac7f04513a274019fa Mon Sep 17 00:00:00 2001 From: Erin Cold Date: Wed, 17 Mar 2021 01:11:24 -0400 Subject: [PATCH] refactor: Migrate from remote and fix Datalogger Remote calls are removed in preperation for Electron 10, and showOpenDialog calls in Datalogger are fixed. showOpenDialog was promisified in Electron 7 and file import/export has been broken ever since. feat (File Import) - Allows multiple files to be selected refactor (File Import): Removed remote usage Main process now handles the dialog window creation and file copying. refactor (Logged Data): Removed remote usage Main process now handles the dataPath directory creation, directory reading, Export File dialog, and deleting files. dataPath is now pulled from App props instead of redefining it in LoggedData. refactor: Set enableRemoteModule to false fix: Export and Import snackbox messages --- public/electron.js | 100 +++++++++++++++++++++++++++- src/App.js | 4 +- src/components/Appshell/Appshell.js | 30 ++------- src/screen/LoggedData/LoggedData.js | 91 +++++++------------------ src/utils/fileNameProcessor.js | 4 +- 5 files changed, 133 insertions(+), 96 deletions(-) diff --git a/public/electron.js b/public/electron.js index 8b438b1d..e22f42cb 100644 --- a/public/electron.js +++ b/public/electron.js @@ -1,13 +1,16 @@ const electron = require('electron'); const path = require('path'); const url = require('url'); +const fs = require('fs'); const { ipcMain } = require('electron'); const loadBalancer = require('electron-load-balancer'); const { app } = electron; -const { BrowserWindow } = electron; +const { BrowserWindow, dialog } = electron; const nativeImage = electron.nativeImage; +const { extractFileName } = require('../src/utils/fileNameProcessor'); + if (process.env.DEV) { const { default: installExtension, @@ -41,7 +44,7 @@ function createWindow() { icon, webPreferences: { nodeIntegration: true, - enableRemoteModule: true, + enableRemoteModule: false, }, minWidth: 500, minHeight: 300, @@ -149,3 +152,96 @@ ipcMain.on('FETCH_LA', (event, args) => { ipcMain.on('SENSORS_SCAN', (event, args) => { mainWindow.webContents.send('SENSORS_SCAN', args); }); + +ipcMain.handle('OPEN_IMPORT_WINDOW', async (event, dataPath) => { + return dialog + .showOpenDialog(null, { + title: 'Select file(s) to import', + filters: [{ name: 'Data File', extensions: ['csv'] }], + properties: ['openFile', 'multiSelections'], + }) + .then(result => { + if (result.filePaths) { + var message = 'Import successful'; + result.filePaths.forEach(filePath => { + const fileName = extractFileName(filePath); + fs.copyFile(filePath, `${dataPath}/${fileName}`, err => { + if (err) { + console.log(err); + message = 'Import failed'; + } + }); + }); + } + return message; + }) + .catch(err => { + console.log(err); + }); +}); + +ipcMain.handle('OPEN_EXPORT_WINDOW', async (event, filePath) => { + return dialog + .showOpenDialog(null, { + title: 'Select export location', + properties: ['openDirectory'], + }) + .then(result => { + const dirPath = result.filePaths[0]; + if (dirPath) { + var message = 'Export successful'; + const fileName = extractFileName(filePath); + fs.copyFile(filePath, `${dirPath}/${fileName}`, err => { + if (err) { + console.log(err); + message = 'Export failed'; + } + }); + return message; + } + }) + .catch(err => { + console.log(err); + }); +}); + +ipcMain.handle('MAKE_DIRECTORY', (event, dirPath) => { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +}); + +ipcMain.handle('DELETE_FILE', (event, path) => { + fs.unlink(path, err => { + if (err) { + console.log(err); + } + }); +}); + +ipcMain.handle('LIST_FILES', (event, dirPath, extension) => { + const processedFiles = fs + .readdirSync(dirPath) + .filter(files => { + return path.extname(files).toLowerCase() === extension; + }) + .map(file => { + const filepath = path.join(dirPath, file); + return { + name: file, + filepath, + metaData: getMetaData(filepath), + }; + }); + return processedFiles; +}); + +getMetaData = path => { + const content = fs.readFileSync(path, 'utf8'); + const data = content.split(/\r?\n/)[0].split(','); + return { + device: data[0], + date: data[1], + time: data[2], + }; +}; diff --git a/src/App.js b/src/App.js index ca49054c..ee3bf47c 100644 --- a/src/App.js +++ b/src/App.js @@ -252,7 +252,9 @@ class App extends Component { /> } + render={props => ( + + )} /> } /> diff --git a/src/components/Appshell/Appshell.js b/src/components/Appshell/Appshell.js index 71e8d8ee..ab667537 100644 --- a/src/components/Appshell/Appshell.js +++ b/src/components/Appshell/Appshell.js @@ -31,7 +31,6 @@ import { Stop as StopRecordIcon, ShoppingCart as CartIcon, } from '@material-ui/icons'; -import { extractFileName } from '../../utils/fileNameProcessor'; import { Save as SaveIcon } from '@material-ui/icons'; import AppIcon from '../../resources/app_icon.png'; import { @@ -50,10 +49,7 @@ import ConnectedIcon from '../../resources/device_connected.svg'; import { openSnackbar } from '../../redux/actions/app'; const electron = window.require('electron'); -const { remote } = window.require('electron'); const { ipcRenderer } = electron; -const fs = remote.require('fs'); -const { dialog } = remote; const loadBalancer = window.require('electron-load-balancer'); const styles = theme => ({ @@ -84,26 +80,12 @@ const Appshell = ({ const [drawerOpen, toggleDrawer] = useState(false); const [menuOpen, toggleMenu] = useState(null); - const openImportWindow = () => { - dialog.showOpenDialog( - null, - { - title: 'Select file to import', - properties: ['openFile'], - filters: [{ name: 'Data File', extensions: ['csv'] }], - }, - filePath => { - if (filePath) { - const fileName = extractFileName(filePath[0]); - fs.copyFile(filePath[0], `${dataPath}/${fileName}`, err => { - if (err) { - openSnackbar({ message: 'Import failed' }); - } - openSnackbar({ message: 'Import successful' }); - }); - } - }, - ); + const openImportWindow = async () => { + ipcRenderer.invoke('OPEN_IMPORT_WINDOW', dataPath).then(message => { + if (message) { + openSnackbar({ message }); + } + }); }; const layoutButtonRenderer = location => { diff --git a/src/screen/LoggedData/LoggedData.js b/src/screen/LoggedData/LoggedData.js index 08952bbb..28332476 100644 --- a/src/screen/LoggedData/LoggedData.js +++ b/src/screen/LoggedData/LoggedData.js @@ -28,16 +28,11 @@ import { InstrumentWrapper, EmptyWrapper, } from './LoggedData.styles.js'; -import { - fileNameTrimmer, - extractFileName, -} from '../../utils/fileNameProcessor'; +import { fileNameTrimmer } from '../../utils/fileNameProcessor'; import { openSnackbar } from '../../redux/actions/app'; -const { remote } = window.require('electron'); -const fs = remote.require('fs'); -const os = remote.require('os'); -const path = remote.require('path'); -const { dialog } = remote; + +const electron = window.require('electron'); +const { ipcRenderer } = electron; const chokidar = window.require('chokidar'); class LoggedData extends Component { @@ -49,84 +44,46 @@ class LoggedData extends Component { }; } - componentDidMount() { - this.destDir = path.join(os.homedir(), 'Documents', 'PSLab'); - if (!fs.existsSync(this.destDir)) { - fs.mkdirSync(this.destDir, { recursive: true }); - } - this.watcher = chokidar.watch(this.destDir); + componentDidMount = async () => { + const { dataPath } = this.props; + await ipcRenderer.invoke('MAKE_DIRECTORY', dataPath); + this.watcher = chokidar.watch(dataPath); this.setState({ loading: true, }); - this.listFiles('.csv'); this.watcher.on('all', (event, path) => { this.listFiles('.csv'); }); - } + }; componentWillUnmount() { this.watcher.unwatch(this.destDir); } - openExportWindow(filePath) { + openExportWindow = async filePath => { const { openSnackbar } = this.props; - const fileName = extractFileName(filePath); - dialog.showOpenDialog( - null, - { - title: 'Select export location', - properties: ['openDirectory'], - }, - dirPath => { - dirPath && - fs.copyFile(filePath, `${dirPath}/${fileName}`, err => { - if (err) { - openSnackbar({ message: 'Export failed' }); - } - openSnackbar({ message: 'Export successful' }); - }); - }, - ); - } + ipcRenderer.invoke('OPEN_EXPORT_WINDOW', filePath).then(message => { + if (message) { + openSnackbar({ message: message }); + } + }); + }; listFiles = extension => { - fs.readdir(this.destDir, (err, files) => { - const processedFiles = files - .filter(files => { - return path.extname(files).toLowerCase() === extension; - }) - .map(file => { - const filepath = path.join(this.destDir, file); - return { - name: file, - filepath, - metaData: this.getMetaData(filepath), - }; + const { dataPath } = this.props; + ipcRenderer + .invoke('LIST_FILES', dataPath, extension) + .then(processedFiles => { + this.setState({ + fileList: processedFiles, + loading: false, }); - this.setState({ - fileList: processedFiles, - loading: false, }); - }); }; deleteFile = path => { - fs.unlink(path, err => { - if (err) { - console.log(err); - } - }); - }; - - getMetaData = path => { - const content = fs.readFileSync(path, 'utf8'); - const data = content.split(/\r?\n/)[0].split(','); - return { - device: data[0], - date: data[1], - time: data[2], - }; + ipcRenderer.invoke('DELETE_FILE', path); }; iconRenderer = device => { diff --git a/src/utils/fileNameProcessor.js b/src/utils/fileNameProcessor.js index 6d5aa0e3..ce2208b8 100644 --- a/src/utils/fileNameProcessor.js +++ b/src/utils/fileNameProcessor.js @@ -1,4 +1,4 @@ -export const fileNameTrimmer = (n, len) => { +exports.fileNameTrimmer = (n, len) => { const ext = n.substring(n.lastIndexOf('.') + 1, n.length).toLowerCase(); let filename = n.replace('.' + ext, ''); if (filename.length <= len) { @@ -8,7 +8,7 @@ export const fileNameTrimmer = (n, len) => { return filename + '.' + ext; }; -export const extractFileName = filePath => { +exports.extractFileName = filePath => { const fileArray = filePath.split('/'); return fileArray[fileArray.length - 1]; };