From 5aaf7adbdea3f84e600ed5bc0943df0c9ba649b4 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 15:28:06 +0200 Subject: [PATCH 01/14] Look if parity already exists --- electron/operations/doesParityExist.js | 44 ++++++++++++++++++++++++-- electron/operations/fetchParity.js | 10 +++--- electron/operations/runParity.js | 8 ++--- electron/utils/parityPath.js | 12 ------- electron/utils/promiseAny.js | 27 ++++++++++++++++ package.json | 1 + yarn.lock | 4 +++ 7 files changed, 82 insertions(+), 24 deletions(-) delete mode 100644 electron/utils/parityPath.js create mode 100644 electron/utils/promiseAny.js diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index a8f4e064..b9398bfe 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -3,11 +3,51 @@ // // SPDX-License-Identifier: MIT +const { app } = require('electron'); +const commandExists = require('command-exists'); const fs = require('fs'); const util = require('util'); -const parityPath = require('../utils/parityPath'); +const promiseAny = require('../utils/promiseAny'); const fsExists = util.promisify(fs.stat); -module.exports = () => fsExists(parityPath()); +// Locations to test if parity binary exists +const locations = { + linux: ['/bin/parity', '/usr/bin/parity', '/usr/local/bin/parity'], + darwin: ['/Applications/Parity Ethereum.app/Contents/MacOS/parity'], + win32: ['C:\\Program Files\\Parity Technologies\\Parity\\parity.exe'] +}; + +/** + * The default path to install parity, in case there's no other instance found + * on the machine. + */ +const defaultParityPath = () => + `${app.getPath('userData')}/parity${process.platform === 'win32' + ? '.exe' + : ''}`; + +/** + * This function checks if parity has been installed on the local machine: + * - first check if the program is in $PATH, using `command-exists` + * - then check the OS default installation dir if a parity folder exists + * - finally check parity-ui's own userData folder + * This function should run in node env. + * Returns a string which is the command to run parity. + */ +const doesParityExist = () => + commandExists('parity') // First test if `parity` command exists + .then(() => 'parity') // If yes, return `parity` as command to launch parity + .catch(() => + // Then test if OS-specific locations contain parity + promiseAny( + locations[process.platform].map(location => + fsExists(location).then(() => location) + ) + ) + ) + .catch(() => fsExists(defaultParityPath())) // Finally test userData folder + .catch(() => null); // Return null if no parity is found + +module.exports = { defaultParityPath, doesParityExist }; diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index 9e726c70..a737e01b 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -9,11 +9,9 @@ const { download } = require('electron-dl'); const fs = require('fs'); const util = require('util'); +const { defaultParityPath } = require('./doesParityExist'); const handleError = require('./handleError'); -const { - parity: { channel } -} = require('../../package.json'); -const parityPath = require('../utils/parityPath'); +const { parity: { channel } } = require('../../package.json'); const fsChmod = util.promisify(fs.chmod); @@ -60,13 +58,15 @@ module.exports = mainWindow => ) ) .then(({ downloadUrl }) => + // This will install parity into defaultParityPath() download(mainWindow, downloadUrl, { directory: app.getPath('userData'), onProgress: progress => mainWindow.webContents.send('parity-download-progress', progress) // Notify the renderers }) ) - .then(() => fsChmod(parityPath(), '755')) + .then(() => fsChmod(defaultParityPath(), '755')) + .then(() => defaultParityPath()) // Return the install path .catch(err => { handleError(err, 'An error occured while fetching parity.'); }); diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index d0f709b8..6621ebb9 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -11,9 +11,8 @@ const util = require('util'); const { cli, parityArgv } = require('../cli'); const handleError = require('./handleError'); -const parityPath = require('../utils/parityPath'); +const { parityPath } = require('./doesParityExist'); -const fsChmod = util.promisify(fs.chmod); const fsExists = util.promisify(fs.stat); const fsUnlink = util.promisify(fs.unlink); @@ -28,19 +27,18 @@ const catchableErrors = [ ]; module.exports = { - runParity (mainWindow) { + async runParity (mainWindow) { // Do not run parity with --no-run-parity if (cli.runParity === false) { return; } // Create a logStream to save logs - const logFile = `${parityPath()}.log`; + const logFile = `${app.getPath('userData')}/parity.log`; fsExists(logFile) .then(() => fsUnlink(logFile)) // Delete logFile and create a fresh one on each launch .catch(noop) - .then(() => fsChmod(parityPath(), '755')) // Should already be 755 after download, just to be sure .then(() => { const logStream = fs.createWriteStream(logFile, { flags: 'a' }); let logLastLine; // Always contains last line of the logFile diff --git a/electron/utils/parityPath.js b/electron/utils/parityPath.js deleted file mode 100644 index 89f4b99c..00000000 --- a/electron/utils/parityPath.js +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2015-2018 Parity Technologies (UK) Ltd. -// This file is part of Parity. -// -// SPDX-License-Identifier: MIT - -const { app } = require('electron'); - -const parityPath = `${app.getPath('userData')}/parity${ - process.platform === 'win32' ? '.exe' : '' -}`; - -module.exports = () => parityPath; diff --git a/electron/utils/promiseAny.js b/electron/utils/promiseAny.js new file mode 100644 index 00000000..dfea829a --- /dev/null +++ b/electron/utils/promiseAny.js @@ -0,0 +1,27 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +/** + * Resolves when the 1st promise in an array of promises resolves + * @see https://stackoverflow.com/questions/37234191/resolve-es6-promise-with-first-success + * @param {Array} promises + */ +const promiseAny = promises => { + return Promise.all( + promises.map(p => { + // If a request fails, count that as a resolution so it will keep + // waiting for other possible successes. If a request succeeds, + // treat it as a rejection so Promise.all immediately bails out. + return p.then(val => Promise.reject(val), err => Promise.resolve(err)); + }) + ).then( + // If '.all' resolved, we've just got an array of errors. + errors => Promise.reject(errors), + // If '.all' rejected, we've got the result we wanted. + val => Promise.resolve(val) + ); +}; + +module.exports = promiseAny; diff --git a/package.json b/package.json index 4493707d..eba40838 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@parity/api": "^2.1.22", "@parity/light.js": "https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e", "axios": "^0.18.0", + "command-exists": "^1.2.6", "commander": "^2.15.1", "electron": "^2.0.0", "electron-dl": "^1.11.0", diff --git a/yarn.lock b/yarn.lock index d7a121da..de3d019e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2057,6 +2057,10 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" +command-exists@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.6.tgz#577f8e5feb0cb0f159cd557a51a9be1bdd76e09e" + commander@2.15.x, commander@^2.11.0, commander@^2.15.1, commander@^2.9.0, commander@~2.15.0: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" -- GitLab From 8227da7cfc44ca55dac5bcf50d87796ea0904cc6 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 17:23:11 +0200 Subject: [PATCH 02/14] Clean code --- electron/index.js | 2 +- electron/operations/doesParityExist.js | 81 +++++++----- electron/operations/fetchParity.js | 4 +- electron/operations/handleError.js | 9 +- electron/operations/isParityRunning.js | 9 ++ electron/operations/runParity.js | 164 ++++++++++++++----------- electron/operations/signerNewToken.js | 2 +- package.json | 4 + yarn.lock | 16 +++ 9 files changed, 174 insertions(+), 117 deletions(-) create mode 100644 electron/operations/isParityRunning.js diff --git a/electron/index.js b/electron/index.js index 8342dde7..a083c966 100644 --- a/electron/index.js +++ b/electron/index.js @@ -9,7 +9,7 @@ const url = require('url'); const addMenu = require('./menu'); const { cli } = require('./cli'); -const doesParityExist = require('./operations/doesParityExist'); +const { doesParityExist } = require('./operations/doesParityExist'); const fetchParity = require('./operations/fetchParity'); const handleError = require('./operations/handleError'); const messages = require('./messages'); diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index b9398bfe..9b61ab8a 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -6,48 +6,63 @@ const { app } = require('electron'); const commandExists = require('command-exists'); const fs = require('fs'); -const util = require('util'); +const { promisify } = require('util'); const promiseAny = require('../utils/promiseAny'); -const fsExists = util.promisify(fs.stat); +const fsExists = promisify(fs.stat); -// Locations to test if parity binary exists +// The default path to install parity, in case there's no other instance found +// on the machine. +const defaultParityPath = `${app.getPath( + 'userData' +)}/parity${process.platform === 'win32' ? '.exe' : ''}`; + +let parityPath; // The real parity path, will be populatef after doesParityExist Promise resolves + +// OS locations to test if parity binary exists const locations = { linux: ['/bin/parity', '/usr/bin/parity', '/usr/local/bin/parity'], darwin: ['/Applications/Parity Ethereum.app/Contents/MacOS/parity'], win32: ['C:\\Program Files\\Parity Technologies\\Parity\\parity.exe'] }; -/** - * The default path to install parity, in case there's no other instance found - * on the machine. - */ -const defaultParityPath = () => - `${app.getPath('userData')}/parity${process.platform === 'win32' - ? '.exe' - : ''}`; - -/** - * This function checks if parity has been installed on the local machine: - * - first check if the program is in $PATH, using `command-exists` - * - then check the OS default installation dir if a parity folder exists - * - finally check parity-ui's own userData folder - * This function should run in node env. - * Returns a string which is the command to run parity. - */ -const doesParityExist = () => - commandExists('parity') // First test if `parity` command exists - .then(() => 'parity') // If yes, return `parity` as command to launch parity - .catch(() => - // Then test if OS-specific locations contain parity - promiseAny( - locations[process.platform].map(location => - fsExists(location).then(() => location) +module.exports = { + defaultParityPath () { + return defaultParityPath; + }, + /** + * This function checks if parity has been installed on the local machine: + * - first check if the program is in $PATH, using `command-exists` + * - then check the OS default installation dir if a parity folder exists + * - finally check parity-ui's own userData folder + * This function should run in node env. + * + * @return Promise - Resolves to a string which is the command to run parity. + */ + doesParityExist () { + return commandExists('parity') // First test if `parity` command exists + .then(() => 'parity') // If yes, return `parity` as command to launch parity + .catch(() => + // Then test if OS-specific locations contain parity + promiseAny( + locations[process.platform].map(location => + fsExists(location).then(() => location) + ) ) ) - ) - .catch(() => fsExists(defaultParityPath())) // Finally test userData folder - .catch(() => null); // Return null if no parity is found - -module.exports = { defaultParityPath, doesParityExist }; + .catch(() => + // Finally test userData folder + fsExists(defaultParityPath()).then(() => defaultParityPath()) + ) + .then(path => { + parityPath = path; // Save the final result in module variable + console.log(`Parity found on machine, can be run with "${path}"`); + return path; + }) + .catch(() => null); // Return null if no parity is found + }, + parityPath () { + return parityPath; + } +}; diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index a737e01b..afb41196 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -7,13 +7,13 @@ const { app } = require('electron'); const axios = require('axios'); const { download } = require('electron-dl'); const fs = require('fs'); -const util = require('util'); +const { promisify } = require('util'); const { defaultParityPath } = require('./doesParityExist'); const handleError = require('./handleError'); const { parity: { channel } } = require('../../package.json'); -const fsChmod = util.promisify(fs.chmod); +const fsChmod = promisify(fs.chmod); const getArch = () => { switch (process.platform) { diff --git a/electron/operations/handleError.js b/electron/operations/handleError.js index e5bcc553..1fbe7594 100644 --- a/electron/operations/handleError.js +++ b/electron/operations/handleError.js @@ -5,10 +5,7 @@ const { app, dialog } = require('electron'); -const { - parity: { channel } -} = require('../../package.json'); -const parityPath = require('../utils/parityPath'); +const { bugs: { url }, parity: { channel } } = require('../../package.json'); module.exports = (err, message = 'An error occurred.') => { console.error(err); @@ -22,8 +19,8 @@ Channel: ${channel} Error: ${err.message} Please also attach the contents of the following file: -${parityPath()}.log`, - message: `${message} Please file an issue at https://github.com/parity-js/shell/issues.`, +${app.getPath('userData')}/parity.log`, + message: `${message} Please file an issue at ${url}.`, title: 'Parity Error', type: 'error' }, diff --git a/electron/operations/isParityRunning.js b/electron/operations/isParityRunning.js new file mode 100644 index 00000000..141734fc --- /dev/null +++ b/electron/operations/isParityRunning.js @@ -0,0 +1,9 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +const { promisify } = require('util'); +const ps = require('ps-node'); + +const lookup = promisify(ps.lookup); diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index 6621ebb9..2ecac555 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -5,16 +5,15 @@ const { app } = require('electron'); const fs = require('fs'); -const noop = require('lodash/noop'); const { spawn } = require('child_process'); -const util = require('util'); +const { promisify } = require('util'); const { cli, parityArgv } = require('../cli'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); -const fsExists = util.promisify(fs.stat); -const fsUnlink = util.promisify(fs.unlink); +const fsExists = promisify(fs.stat); +const fsUnlink = promisify(fs.unlink); let parity = null; // Will hold the running parity instance @@ -28,79 +27,96 @@ const catchableErrors = [ module.exports = { async runParity (mainWindow) { - // Do not run parity with --no-run-parity - if (cli.runParity === false) { - return; - } + try { + // Do not run parity with --no-run-parity + if (cli.runParity === false) { + return; + } + + // Do not run parity if parityPath has not been calculated. Shouldn't + // happen as we always run runParity after doesParityExist resolves. + if (!parityPath) { + throw new Error('Attempting to run Parity before parityPath is set.'); + } + + // Create a logStream to save logs + const logFile = `${app.getPath('userData')}/parity.log`; + + const logFileExists = await fsExists(logFile); + if (logFileExists) { + try { + await fsUnlink(logFile); + } catch (e) {} // Do nothing if fsUnlink returns error, just in case + } + + // Create variables to hold logs + const logStream = fs.createWriteStream(logFile, { flags: 'a' }); + let logLastLine; // Always contains last line of the logFile + + // Run an instance of parity with the correct args + parity = spawn(parityPath(), parityArgv); + console.log( + `Running command "${parityPath().replace(' ', '\\ ')} ${parityArgv.join( + ' ' + )}"...` + ); + console.log(`See "${logFile}" for logs.`); + + // Pipe all parity command output into the logFile + parity.stdout.pipe(logStream); + parity.stderr.pipe(logStream); - // Create a logStream to save logs - const logFile = `${app.getPath('userData')}/parity.log`; - - fsExists(logFile) - .then(() => fsUnlink(logFile)) // Delete logFile and create a fresh one on each launch - .catch(noop) - .then(() => { - const logStream = fs.createWriteStream(logFile, { flags: 'a' }); - let logLastLine; // Always contains last line of the logFile - - // Run an instance of parity with the correct args - parity = spawn(parityPath(), parityArgv); - - // Pipe all parity command output into the logFile - parity.stdout.pipe(logStream); - parity.stderr.pipe(logStream); - - // Save in memory the last line of the log file, for handling error - const callback = data => { - if (data && data.length) { - logLastLine = data.toString(); - } - }; - parity.stdout.on('data', callback); - parity.stderr.on('data', callback); - - parity.on('error', err => { - handleError(err, 'An error occured while running parity.'); - }); - parity.on('close', (exitCode, signal) => { - if (exitCode === 0) { - return; - } - - // When there's already an instance of parity running, then the log - // is logging a particular line, see below. In this case, we just - // silently ignore our local instance, and let the 1st parity - // instance be the main one. - if (catchableErrors.some(error => logLastLine.includes(error))) { - console.log( - 'Another instance of parity is running, closing local instance.' - ); - return; - } - - // If the exit code is not 0, then we show some error message - if (Object.keys(parityArgv).length > 0) { - // If parity has been launched with some args, then most likely the - // args are wrong, so we show the output of parity. - const log = fs.readFileSync(logFile); - console.log(log.toString()); - app.exit(1); - } else { - handleError( - new Error(`Exit code ${exitCode}, with signal ${signal}.`), - 'An error occured while running parity.' - ); - } - }); - }) - .then(() => { - // Notify the renderers - mainWindow.webContents.send('parity-running', true); - global.isParityRunning = true; // Send this variable to renderes via IPC - }) - .catch(err => { + // Save in memory the last line of the log file, for handling error + const callback = data => { + if (data && data.length) { + logLastLine = data.toString(); + } + }; + parity.stdout.on('data', callback); + parity.stderr.on('data', callback); + + parity.on('error', err => { handleError(err, 'An error occured while running parity.'); }); + parity.on('close', (exitCode, signal) => { + if (exitCode === 0) { + return; + } + + // When there's already an instance of parity running, then the log + // is logging a particular line, see below. In this case, we just + // silently ignore our local instance, and let the 1st parity + // instance be the main one. + if (catchableErrors.some(error => logLastLine.includes(error))) { + console.log( + 'Another instance of parity is running, closing local instance.' + ); + return; + } + + // If the exit code is not 0, then we show some error message + if (Object.keys(parityArgv).length > 0) { + // If parity has been launched with some args, then most likely the + // args are wrong, so we show the output of parity. + const log = fs.readFileSync(logFile); + console.log(log.toString()); + app.exit(1); + } else { + handleError( + new Error(`Exit code ${exitCode}, with signal ${signal}.`), + 'An error occured while running parity.' + ); + } + }); + + // Notify the renderers + mainWindow.webContents.send('parity-running', true); + global.isParityRunning = true; // Send this variable to renderes via IPC + + return Promise.resolve(); + } catch (err) { + handleError(err, 'An error occured while running parity.'); + } }, killParity () { if (parity) { diff --git a/electron/operations/signerNewToken.js b/electron/operations/signerNewToken.js index 5471fdce..594e501c 100644 --- a/electron/operations/signerNewToken.js +++ b/electron/operations/signerNewToken.js @@ -5,7 +5,7 @@ const { spawn } = require('child_process'); -const parityPath = require('../utils/parityPath'); +const { parityPath } = require('./doesParityExist'); module.exports = event => { // Generate a new token diff --git a/package.json b/package.json index eba40838..490d543b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "type": "git", "url": "git+https://github.com/parity-js/light.git" }, + "bugs": { + "url": "https://github.com/parity-js/light/issues" + }, "keywords": [ "Ethereum", "API", @@ -51,6 +54,7 @@ "lodash": "^4.17.10", "mobx": "^4.2.0", "mobx-react": "^5.1.2", + "ps-node": "^0.1.6", "react": "^16.3.2", "react-blockies": "^1.3.0", "react-dom": "^16.3.2", diff --git a/yarn.lock b/yarn.lock index de3d019e..03aad511 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2135,6 +2135,10 @@ connect-history-api-fallback@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" +connected-domain@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93" + console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -7217,6 +7221,12 @@ prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" +ps-node@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3" + dependencies: + table-parser "^0.1.3" + ps-tree@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" @@ -8745,6 +8755,12 @@ symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" +table-parser@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0" + dependencies: + connected-domain "^1.0.0" + table@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" -- GitLab From 0c98038d44789238d2a8102c01788b4bcb11019c Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 17:36:17 +0200 Subject: [PATCH 03/14] Detect if parity is already running --- electron/operations/doesParityExist.js | 2 +- electron/operations/isParityRunning.js | 27 ++++++++++++++++++++++++++ electron/operations/runParity.js | 10 ++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index 9b61ab8a..f40557cf 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -57,7 +57,7 @@ module.exports = { ) .then(path => { parityPath = path; // Save the final result in module variable - console.log(`Parity found on machine, can be run with "${path}"`); + console.log(`Parity found on machine, can be run with "${path}".`); return path; }) .catch(() => null); // Return null if no parity is found diff --git a/electron/operations/isParityRunning.js b/electron/operations/isParityRunning.js index 141734fc..743ac3ac 100644 --- a/electron/operations/isParityRunning.js +++ b/electron/operations/isParityRunning.js @@ -7,3 +7,30 @@ const { promisify } = require('util'); const ps = require('ps-node'); const lookup = promisify(ps.lookup); + +/** + * Detect if another instance of parity is already running or not. + * + * @return [Object | Boolean] - If there is another instance, return the + * instance object. If not return false. + * @example Here is what's returned when there is an instance running + * { + * pid: '14885', + * command: '/Users/amaurymartiny/Workspace/parity/target/release/parity', + * arguments: [ + * '--testnet', + * '--no-periodic-snapshot', + * '--ws-origins', + * 'all', + * '--light' + * ], + * ppid: '14879' + * } + */ + +const isParityRunning = async () => { + const results = await lookup({ command: 'parity' }); + return results && results.length ? results[0] : false; +}; + +module.exports = isParityRunning; diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index 2ecac555..78da93b1 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -9,6 +9,7 @@ const { spawn } = require('child_process'); const { promisify } = require('util'); const { cli, parityArgv } = require('../cli'); +const isParityRunning = require('./isParityRunning'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); @@ -33,6 +34,15 @@ module.exports = { return; } + // Do not run parity if there is already another instance running + const isRunning = await isParityRunning(); + if (isRunning) { + console.log( + `Another instance of parity is already running with pid ${isRunning.pid}, skip running local instance.` + ); + return; + } + // Do not run parity if parityPath has not been calculated. Shouldn't // happen as we always run runParity after doesParityExist resolves. if (!parityPath) { -- GitLab From feae55b100a6525bcaafe4a5fca2c86cd2773d27 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 18:17:18 +0200 Subject: [PATCH 04/14] Fix bug download parity --- electron/operations/doesParityExist.js | 10 +++------- electron/operations/fetchParity.js | 6 +++--- electron/operations/runParity.js | 10 +++++++++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index f40557cf..2ca22257 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -18,7 +18,7 @@ const defaultParityPath = `${app.getPath( 'userData' )}/parity${process.platform === 'win32' ? '.exe' : ''}`; -let parityPath; // The real parity path, will be populatef after doesParityExist Promise resolves +let parityPath; // The real parity path, will be populated after doesParityExist Promise resolves // OS locations to test if parity binary exists const locations = { @@ -28,9 +28,6 @@ const locations = { }; module.exports = { - defaultParityPath () { - return defaultParityPath; - }, /** * This function checks if parity has been installed on the local machine: * - first check if the program is in $PATH, using `command-exists` @@ -53,14 +50,13 @@ module.exports = { ) .catch(() => // Finally test userData folder - fsExists(defaultParityPath()).then(() => defaultParityPath()) + fsExists(defaultParityPath).then(() => defaultParityPath) ) .then(path => { parityPath = path; // Save the final result in module variable console.log(`Parity found on machine, can be run with "${path}".`); return path; - }) - .catch(() => null); // Return null if no parity is found + }); }, parityPath () { return parityPath; diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index afb41196..4aa4d1cf 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -9,7 +9,7 @@ const { download } = require('electron-dl'); const fs = require('fs'); const { promisify } = require('util'); -const { defaultParityPath } = require('./doesParityExist'); +const { doesParityExist } = require('./doesParityExist'); const handleError = require('./handleError'); const { parity: { channel } } = require('../../package.json'); @@ -65,8 +65,8 @@ module.exports = mainWindow => mainWindow.webContents.send('parity-download-progress', progress) // Notify the renderers }) ) - .then(() => fsChmod(defaultParityPath(), '755')) - .then(() => defaultParityPath()) // Return the install path + .then(downloadItem => fsChmod(downloadItem.getSavePath(), '755')) + .then(doesParityExist) // Make sure parity exists now .catch(err => { handleError(err, 'An error occured while fetching parity.'); }); diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index 78da93b1..a62120ca 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -13,6 +13,7 @@ const isParityRunning = require('./isParityRunning'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); +const fsChmod = promisify(fs.chmod); const fsExists = promisify(fs.stat); const fsUnlink = promisify(fs.unlink); @@ -49,6 +50,13 @@ module.exports = { throw new Error('Attempting to run Parity before parityPath is set.'); } + // Some users somehow had no +x on the parity binary after downloading + // it. We try to set it here (no guarantee it will work, we might not + // have rights to do it). + try { + await fsChmod(parityPath(), '755'); + } catch (e) {} + // Create a logStream to save logs const logFile = `${app.getPath('userData')}/parity.log`; @@ -68,7 +76,7 @@ module.exports = { console.log( `Running command "${parityPath().replace(' ', '\\ ')} ${parityArgv.join( ' ' - )}"...` + )}".` ); console.log(`See "${logFile}" for logs.`); -- GitLab From d9be5350e0007e4b86bb0a1dd54a97b9ed3c42b7 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 18:31:35 +0200 Subject: [PATCH 05/14] Use debug package for debugging --- electron/operations/doesParityExist.js | 3 ++- electron/operations/runParity.js | 16 +++++++--------- package.json | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index 2ca22257..ac8f4200 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -5,6 +5,7 @@ const { app } = require('electron'); const commandExists = require('command-exists'); +const debug = require('debug')('electron'); const fs = require('fs'); const { promisify } = require('util'); @@ -54,7 +55,7 @@ module.exports = { ) .then(path => { parityPath = path; // Save the final result in module variable - console.log(`Parity found on machine, can be run with "${path}".`); + debug(`Parity found on machine, can be run with "${path}".`); return path; }); }, diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index a62120ca..91a89421 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -4,6 +4,8 @@ // SPDX-License-Identifier: MIT const { app } = require('electron'); +const debug = require('debug')('electron'); +const debugParity = require('debug')('parity'); const fs = require('fs'); const { spawn } = require('child_process'); const { promisify } = require('util'); @@ -38,7 +40,7 @@ module.exports = { // Do not run parity if there is already another instance running const isRunning = await isParityRunning(); if (isRunning) { - console.log( + debug( `Another instance of parity is already running with pid ${isRunning.pid}, skip running local instance.` ); return; @@ -73,12 +75,11 @@ module.exports = { // Run an instance of parity with the correct args parity = spawn(parityPath(), parityArgv); - console.log( + debug( `Running command "${parityPath().replace(' ', '\\ ')} ${parityArgv.join( ' ' )}".` ); - console.log(`See "${logFile}" for logs.`); // Pipe all parity command output into the logFile parity.stdout.pipe(logStream); @@ -89,6 +90,7 @@ module.exports = { if (data && data.length) { logLastLine = data.toString(); } + debugParity(data.toString()); }; parity.stdout.on('data', callback); parity.stderr.on('data', callback); @@ -106,7 +108,7 @@ module.exports = { // silently ignore our local instance, and let the 1st parity // instance be the main one. if (catchableErrors.some(error => logLastLine.includes(error))) { - console.log( + debug( 'Another instance of parity is running, closing local instance.' ); return; @@ -114,10 +116,6 @@ module.exports = { // If the exit code is not 0, then we show some error message if (Object.keys(parityArgv).length > 0) { - // If parity has been launched with some args, then most likely the - // args are wrong, so we show the output of parity. - const log = fs.readFileSync(logFile); - console.log(log.toString()); app.exit(1); } else { handleError( @@ -138,7 +136,7 @@ module.exports = { }, killParity () { if (parity) { - console.log('Stopping parity.'); + debug('Stopping parity.'); parity.kill(); parity = null; } diff --git a/package.json b/package.json index 490d543b..d8fe8200 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "axios": "^0.18.0", "command-exists": "^1.2.6", "commander": "^2.15.1", + "debug": "^3.1.0", "electron": "^2.0.0", "electron-dl": "^1.11.0", "is-electron": "^2.1.0", -- GitLab From 5c6bc329d4ed57e8b47d463971ca93264117db46 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 19:14:05 +0200 Subject: [PATCH 06/14] Retry multiple times to fetch from vanity --- electron/operations/fetchParity.js | 70 ++++++++++++++++++++---------- electron/operations/handleError.js | 3 +- package.json | 5 ++- yarn.lock | 10 +++++ 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index 4aa4d1cf..11972b24 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -6,8 +6,10 @@ const { app } = require('electron'); const axios = require('axios'); const { download } = require('electron-dl'); +const debug = require('debug')('electron'); const fs = require('fs'); const { promisify } = require('util'); +const retry = require('async-retry'); const { doesParityExist } = require('./doesParityExist'); const handleError = require('./handleError'); @@ -15,6 +17,8 @@ const { parity: { channel } } = require('../../package.json'); const fsChmod = promisify(fs.chmod); +const VANITY_URL = 'https://vanity-service.parity.io/parity-binaries'; + const getArch = () => { switch (process.platform) { case 'darwin': @@ -47,26 +51,46 @@ const getOs = () => { }; // Fetch parity from https://vanity-service.parity.io/parity-binaries -module.exports = mainWindow => - axios - .get( - `https://vanity-service.parity.io/parity-binaries?version=${channel}&os=${getOs()}&architecture=${getArch()}` - ) - .then(response => - response.data[0].files.find( - ({ name }) => name === 'parity' || name === 'parity.exe' - ) - ) - .then(({ downloadUrl }) => - // This will install parity into defaultParityPath() - download(mainWindow, downloadUrl, { - directory: app.getPath('userData'), - onProgress: progress => - mainWindow.webContents.send('parity-download-progress', progress) // Notify the renderers - }) - ) - .then(downloadItem => fsChmod(downloadItem.getSavePath(), '755')) - .then(doesParityExist) // Make sure parity exists now - .catch(err => { - handleError(err, 'An error occured while fetching parity.'); - }); +module.exports = mainWindow => { + try { + return retry( + async (_, attempt) => { + if (attempt > 1) { + debug(`Retrying.`); + } + + // Fetch the metadata of the correct version of parity + debug( + `Downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` + ); + const { data } = await axios.get( + `${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` + ); + + // Get the binary's url + const { downloadUrl } = data[0].files.find( + ({ name }) => name === 'parity' || name === 'parity.exe' + ); + + // Start downloading. This will install parity into defaultParityPath(). + const downloadItem = await download(mainWindow, downloadUrl, { + directory: app.getPath('userData'), + onProgress: progress => + // Notify the renderers + mainWindow.webContents.send('parity-download-progress', progress) + }); + + // Set a+x permissions on the downloaded binary + await fsChmod(downloadItem.getSavePath(), '755'); + + // Double-check that Parity exists now. + return doesParityExist(); + }, + { + retries: 3 + } + ); + } catch (err) { + handleError(err, 'An error occured while fetching parity.'); + } +}; diff --git a/electron/operations/handleError.js b/electron/operations/handleError.js index 1fbe7594..9b856643 100644 --- a/electron/operations/handleError.js +++ b/electron/operations/handleError.js @@ -4,11 +4,12 @@ // SPDX-License-Identifier: MIT const { app, dialog } = require('electron'); +const debug = require('debug')('electron'); const { bugs: { url }, parity: { channel } } = require('../../package.json'); module.exports = (err, message = 'An error occurred.') => { - console.error(err); + debug(err); dialog.showMessageBox( { buttons: ['OK'], diff --git a/package.json b/package.json index d8fe8200..e0214faf 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "channel": "beta" }, "scripts": { - "build": "cross-env NODE_ENV=production && npm run build-css && npm run build-js && npm run build-electron", + "build": "cross-env NODE_ENV=production npm run build-css && npm run build-js && npm run build-electron", "build-css": "node-sass-chokidar src/ -o src/", "build-electron": "webpack --config electron/webpack.config.js", "build-js": "react-app-rewired build", @@ -37,7 +37,7 @@ "lint": "semistandard 'src/**/*.js' 'electron/**/*.js' --parser babel-eslint", "prebuild": "rimraf build/", "start": "npm-run-all -p watch-css start-js", - "start-electron": "electron electron/ --ui-dev --light --ws-origins all", + "start-electron": "cross-env DEBUG=parity,electron electron electron/ --ui-dev --light --ws-origins all", "start-js": "react-app-rewired start", "test": "echo Skipped.", "watch-css": "npm run build-css -- --watch --recursive" @@ -45,6 +45,7 @@ "dependencies": { "@parity/api": "^2.1.22", "@parity/light.js": "https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e", + "async-retry": "^1.2.1", "axios": "^0.18.0", "command-exists": "^1.2.6", "commander": "^2.15.1", diff --git a/yarn.lock b/yarn.lock index 03aad511..6cb910a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -490,6 +490,12 @@ async-foreach@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" +async-retry@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.1.tgz#308c6c4e1d91e63397a4676290334ae9bda7bcb1" + dependencies: + retry "0.10.1" + async@^1.4.0, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -7938,6 +7944,10 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" +retry@0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" -- GitLab From 294a02ec10c0460da854ecb399609202a771ed94 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 22:41:44 +0200 Subject: [PATCH 07/14] Use pino intead of debug --- electron/operations/doesParityExist.js | 4 +- electron/operations/fetchParity.js | 6 +-- electron/operations/handleError.js | 4 +- electron/operations/isParityRunning.js | 10 ++++- electron/operations/runParity.js | 40 +++++------------- electron/utils/pino.js | 31 ++++++++++++++ package.json | 5 ++- yarn.lock | 56 +++++++++++++++++++++++++- 8 files changed, 116 insertions(+), 40 deletions(-) create mode 100644 electron/utils/pino.js diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index ac8f4200..f8363bb8 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -5,10 +5,10 @@ const { app } = require('electron'); const commandExists = require('command-exists'); -const debug = require('debug')('electron'); const fs = require('fs'); const { promisify } = require('util'); +const pino = require('../utils/pino')({ name: 'electron' }); const promiseAny = require('../utils/promiseAny'); const fsExists = promisify(fs.stat); @@ -55,7 +55,7 @@ module.exports = { ) .then(path => { parityPath = path; // Save the final result in module variable - debug(`Parity found on machine, can be run with "${path}".`); + pino.info(`Parity found on machine, can be run with "${path}".`); return path; }); }, diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index 11972b24..40201d3d 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -6,7 +6,6 @@ const { app } = require('electron'); const axios = require('axios'); const { download } = require('electron-dl'); -const debug = require('debug')('electron'); const fs = require('fs'); const { promisify } = require('util'); const retry = require('async-retry'); @@ -14,6 +13,7 @@ const retry = require('async-retry'); const { doesParityExist } = require('./doesParityExist'); const handleError = require('./handleError'); const { parity: { channel } } = require('../../package.json'); +const pino = require('../utils/pino')({ name: 'electron' }); const fsChmod = promisify(fs.chmod); @@ -56,11 +56,11 @@ module.exports = mainWindow => { return retry( async (_, attempt) => { if (attempt > 1) { - debug(`Retrying.`); + pino.warn(`Retrying.`); } // Fetch the metadata of the correct version of parity - debug( + pino.info( `Downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` ); const { data } = await axios.get( diff --git a/electron/operations/handleError.js b/electron/operations/handleError.js index 9b856643..80d25e20 100644 --- a/electron/operations/handleError.js +++ b/electron/operations/handleError.js @@ -4,12 +4,12 @@ // SPDX-License-Identifier: MIT const { app, dialog } = require('electron'); -const debug = require('debug')('electron'); const { bugs: { url }, parity: { channel } } = require('../../package.json'); +const pino = require('../utils/pino')({ name: 'electron' }); module.exports = (err, message = 'An error occurred.') => { - debug(err); + pino.error(err); dialog.showMessageBox( { buttons: ['OK'], diff --git a/electron/operations/isParityRunning.js b/electron/operations/isParityRunning.js index 743ac3ac..d388042d 100644 --- a/electron/operations/isParityRunning.js +++ b/electron/operations/isParityRunning.js @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT const { promisify } = require('util'); +const pino = require('../utils/pino')({ name: 'electron' }); const ps = require('ps-node'); const lookup = promisify(ps.lookup); @@ -30,7 +31,14 @@ const lookup = promisify(ps.lookup); const isParityRunning = async () => { const results = await lookup({ command: 'parity' }); - return results && results.length ? results[0] : false; + if (results && results.length) { + pino.info( + `Another instance of parity is already running with pid ${results[0] + .pid}, skip running local instance.` + ); + return results[0]; + } + return false; }; module.exports = isParityRunning; diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index 91a89421..9c05b2ac 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -4,8 +4,6 @@ // SPDX-License-Identifier: MIT const { app } = require('electron'); -const debug = require('debug')('electron'); -const debugParity = require('debug')('parity'); const fs = require('fs'); const { spawn } = require('child_process'); const { promisify } = require('util'); @@ -14,10 +12,10 @@ const { cli, parityArgv } = require('../cli'); const isParityRunning = require('./isParityRunning'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); +const pino = require('../utils/pino')({ name: 'electron' }); +const pinoParity = require('../utils/pino')({ level: 'info', name: 'parity' }); const fsChmod = promisify(fs.chmod); -const fsExists = promisify(fs.stat); -const fsUnlink = promisify(fs.unlink); let parity = null; // Will hold the running parity instance @@ -40,9 +38,6 @@ module.exports = { // Do not run parity if there is already another instance running const isRunning = await isParityRunning(); if (isRunning) { - debug( - `Another instance of parity is already running with pid ${isRunning.pid}, skip running local instance.` - ); return; } @@ -59,38 +54,22 @@ module.exports = { await fsChmod(parityPath(), '755'); } catch (e) {} - // Create a logStream to save logs - const logFile = `${app.getPath('userData')}/parity.log`; - - const logFileExists = await fsExists(logFile); - if (logFileExists) { - try { - await fsUnlink(logFile); - } catch (e) {} // Do nothing if fsUnlink returns error, just in case - } - - // Create variables to hold logs - const logStream = fs.createWriteStream(logFile, { flags: 'a' }); - let logLastLine; // Always contains last line of the logFile + let logLastLine; // Always contains last line of the Parity logs // Run an instance of parity with the correct args parity = spawn(parityPath(), parityArgv); - debug( + pino.info( `Running command "${parityPath().replace(' ', '\\ ')} ${parityArgv.join( ' ' )}".` ); - // Pipe all parity command output into the logFile - parity.stdout.pipe(logStream); - parity.stderr.pipe(logStream); - // Save in memory the last line of the log file, for handling error const callback = data => { if (data && data.length) { logLastLine = data.toString(); } - debugParity(data.toString()); + pinoParity.info(data.toString()); }; parity.stdout.on('data', callback); parity.stderr.on('data', callback); @@ -107,8 +86,11 @@ module.exports = { // is logging a particular line, see below. In this case, we just // silently ignore our local instance, and let the 1st parity // instance be the main one. - if (catchableErrors.some(error => logLastLine.includes(error))) { - debug( + if ( + logLastLine && + catchableErrors.some(error => logLastLine.includes(error)) + ) { + pino.warn( 'Another instance of parity is running, closing local instance.' ); return; @@ -136,7 +118,7 @@ module.exports = { }, killParity () { if (parity) { - debug('Stopping parity.'); + pino.info('Stopping parity.'); parity.kill(); parity = null; } diff --git a/electron/utils/pino.js b/electron/utils/pino.js new file mode 100644 index 00000000..b44721f2 --- /dev/null +++ b/electron/utils/pino.js @@ -0,0 +1,31 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +const { app } = require('electron'); +const fs = require('fs'); +const { multistream } = require('pino-multi-stream'); +const pino = require('pino'); + +// Pino by default outputs JSON. We prettify that. +const pretty = pino.pretty(); +pretty.pipe(process.stdout); + +// Create 2 output streams: +// - parity.log file (raw JSON) +// - stdout (prettified output) +const streams = [ + { + level: 'info', + stream: fs.createWriteStream(`${app.getPath('userData')}/parity.log`) + }, + { level: 'info', stream: pretty } +]; + +/** + * Usage: const pino = require('../path/to/pino')({ name: 'electron' }); + * + * @param {Object} opts - Options to pass to pino. + */ +module.exports = opts => pino(opts, multistream(streams)); diff --git a/package.json b/package.json index e0214faf..b238f068 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "lint": "semistandard 'src/**/*.js' 'electron/**/*.js' --parser babel-eslint", "prebuild": "rimraf build/", "start": "npm-run-all -p watch-css start-js", - "start-electron": "cross-env DEBUG=parity,electron electron electron/ --ui-dev --light --ws-origins all", + "start-electron": "electron electron/ --ui-dev --light --ws-origins all", "start-js": "react-app-rewired start", "test": "echo Skipped.", "watch-css": "npm run build-css -- --watch --recursive" @@ -49,13 +49,14 @@ "axios": "^0.18.0", "command-exists": "^1.2.6", "commander": "^2.15.1", - "debug": "^3.1.0", "electron": "^2.0.0", "electron-dl": "^1.11.0", "is-electron": "^2.1.0", "lodash": "^4.17.10", "mobx": "^4.2.0", "mobx-react": "^5.1.2", + "pino": "^4.16.1", + "pino-multi-stream": "^3.1.2", "ps-node": "^0.1.6", "react": "^16.3.2", "react-blockies": "^1.3.0", diff --git a/yarn.lock b/yarn.lock index 6cb910a7..f47218ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3521,6 +3521,10 @@ fast-glob@^2.0.2: merge2 "^1.2.1" micromatch "^3.1.10" +fast-json-parse@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -3529,6 +3533,10 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fast-safe-stringify@^1.0.8, fast-safe-stringify@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz#9fe22c37fb2f7f86f06b8f004377dbf8f1ee7bc1" + fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -3696,6 +3704,10 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flatstr@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.5.tgz#5b451b08cbd48e2eac54a2bbe0bf46165aa14be3" + flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -6797,6 +6809,29 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pino-multi-stream@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pino-multi-stream/-/pino-multi-stream-3.1.2.tgz#261af1b66caf208abe9c97f1bad1990d5c25d303" + dependencies: + pino "^4.7.1" + +pino-std-serializers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-2.0.0.tgz#63cbacf34bed8c95dd2c67eec824e48b8dd3445f" + +pino@^4.16.1, pino@^4.7.1: + version "4.16.1" + resolved "https://registry.yarnpkg.com/pino/-/pino-4.16.1.tgz#8bcb6b685ee4d4e26adfe79a05f12f6b46962d40" + dependencies: + chalk "^2.3.2" + fast-json-parse "^1.0.3" + fast-safe-stringify "^1.2.3" + flatstr "^1.0.5" + pino-std-serializers "^2.0.0" + pump "^3.0.0" + quick-format-unescaped "^1.1.2" + split2 "^2.2.0" + pkg-conf@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" @@ -7260,6 +7295,13 @@ pump@^2.0.0, pump@^2.0.1: end-of-stream "^1.1.0" once "^1.3.1" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pumpify@^1.3.3: version "1.5.0" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.0.tgz#30c905a26c88fa0074927af07256672b474b1c15" @@ -7331,6 +7373,12 @@ querystringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" +quick-format-unescaped@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz#0ca581de3174becef25ac3c2e8956342381db698" + dependencies: + fast-safe-stringify "^1.0.8" + raf@3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" @@ -8463,6 +8511,12 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +split2@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" + dependencies: + through2 "^2.0.2" + split@0.3: version "0.3.3" resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" @@ -8860,7 +8914,7 @@ throttleit@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" -through2@^2.0.0: +through2@^2.0.0, through2@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" dependencies: -- GitLab From 422d3801843bc5307426647709e4d7d603b58aff Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 22:45:00 +0200 Subject: [PATCH 08/14] Add initial logging --- electron/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/electron/index.js b/electron/index.js index a083c966..d61a7aa1 100644 --- a/electron/index.js +++ b/electron/index.js @@ -13,12 +13,15 @@ const { doesParityExist } = require('./operations/doesParityExist'); const fetchParity = require('./operations/fetchParity'); const handleError = require('./operations/handleError'); const messages = require('./messages'); +const { productName } = require('./config.json'); +const pino = require('./utils/pino')({ name: 'electron' }); const { runParity, killParity } = require('./operations/runParity'); const { app, BrowserWindow, ipcMain, session } = electron; let mainWindow; function createWindow () { + pino.info(`Starting ${productName}...`); mainWindow = new BrowserWindow({ height: 800, width: 1200 -- GitLab From dc0eb6204a8ff47a5429f5da3038da80447df37e Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 18 May 2018 22:54:17 +0200 Subject: [PATCH 09/14] Use promise any package --- electron/operations/doesParityExist.js | 2 +- electron/utils/promiseAny.js | 27 -------------------------- package.json | 1 + yarn.lock | 4 ++++ 4 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 electron/utils/promiseAny.js diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index f8363bb8..4c0ff53f 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -6,10 +6,10 @@ const { app } = require('electron'); const commandExists = require('command-exists'); const fs = require('fs'); +const promiseAny = require('promise-any'); const { promisify } = require('util'); const pino = require('../utils/pino')({ name: 'electron' }); -const promiseAny = require('../utils/promiseAny'); const fsExists = promisify(fs.stat); diff --git a/electron/utils/promiseAny.js b/electron/utils/promiseAny.js deleted file mode 100644 index dfea829a..00000000 --- a/electron/utils/promiseAny.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2018 Parity Technologies (UK) Ltd. -// This file is part of Parity. -// -// SPDX-License-Identifier: MIT - -/** - * Resolves when the 1st promise in an array of promises resolves - * @see https://stackoverflow.com/questions/37234191/resolve-es6-promise-with-first-success - * @param {Array} promises - */ -const promiseAny = promises => { - return Promise.all( - promises.map(p => { - // If a request fails, count that as a resolution so it will keep - // waiting for other possible successes. If a request succeeds, - // treat it as a rejection so Promise.all immediately bails out. - return p.then(val => Promise.reject(val), err => Promise.resolve(err)); - }) - ).then( - // If '.all' resolved, we've just got an array of errors. - errors => Promise.reject(errors), - // If '.all' rejected, we've got the result we wanted. - val => Promise.resolve(val) - ); -}; - -module.exports = promiseAny; diff --git a/package.json b/package.json index b238f068..2fede85a 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "mobx-react": "^5.1.2", "pino": "^4.16.1", "pino-multi-stream": "^3.1.2", + "promise-any": "^0.2.0", "ps-node": "^0.1.6", "react": "^16.3.2", "react-blockies": "^1.3.0", diff --git a/yarn.lock b/yarn.lock index f47218ef..1b5ac7aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7227,6 +7227,10 @@ progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" +promise-any@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/promise-any/-/promise-any-0.2.0.tgz#7aeaafd6297698d8874cb7d3bc7c0faf89fffe8a" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" -- GitLab From abd10af6d9e47e4b2e5060a00c0d7943ac032110 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Thu, 24 May 2018 16:53:55 +0200 Subject: [PATCH 10/14] Update yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 1b5ac7aa..a470ee11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8396,7 +8396,7 @@ sockjs@0.3.18: faye-websocket "^0.10.0" uuid "^2.0.2" -"solc@github:ngotchac/solc-js": +solc@ngotchac/solc-js: version "0.4.4" resolved "https://codeload.github.com/ngotchac/solc-js/tar.gz/04eb38cc3003fba8cb3656653a7941ed36408818" dependencies: -- GitLab From e1b110a4e8338966a6aa0f0a407b1ea98b0ac224 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Thu, 24 May 2018 18:00:32 +0200 Subject: [PATCH 11/14] Verify checksum after download --- electron/operations/doesParityExist.js | 1 + electron/operations/fetchParity.js | 46 +++++++++++++++++++++++--- package.json | 1 + yarn.lock | 12 +++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index 4c0ff53f..7fa80ede 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -29,6 +29,7 @@ const locations = { }; module.exports = { + defaultParityPath, /** * This function checks if parity has been installed on the local machine: * - first check if the program is in $PATH, using `command-exists` diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index 40201d3d..39c6204d 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -5,16 +5,18 @@ const { app } = require('electron'); const axios = require('axios'); +const cs = require('checksum'); const { download } = require('electron-dl'); const fs = require('fs'); const { promisify } = require('util'); const retry = require('async-retry'); -const { doesParityExist } = require('./doesParityExist'); +const { defaultParityPath, doesParityExist } = require('./doesParityExist'); const handleError = require('./handleError'); const { parity: { channel } } = require('../../package.json'); const pino = require('../utils/pino')({ name: 'electron' }); +const checksum = promisify(cs.file); const fsChmod = promisify(fs.chmod); const VANITY_URL = 'https://vanity-service.parity.io/parity-binaries'; @@ -50,6 +52,15 @@ const getOs = () => { } }; +/** + * Remove parity binary in the userData folder + */ +const deleteParity = () => { + if (fs.statSync(defaultParityPath)) { + fs.unlinkSync(defaultParityPath); + } +}; + // Fetch parity from https://vanity-service.parity.io/parity-binaries module.exports = mainWindow => { try { @@ -61,7 +72,7 @@ module.exports = mainWindow => { // Fetch the metadata of the correct version of parity pino.info( - `Downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` + `Parity not found on machine, downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}.` ); const { data } = await axios.get( `${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` @@ -72,25 +83,52 @@ module.exports = mainWindow => { ({ name }) => name === 'parity' || name === 'parity.exe' ); - // Start downloading. This will install parity into defaultParityPath(). + // Start downloading. This will install parity into defaultParityPath. const downloadItem = await download(mainWindow, downloadUrl, { directory: app.getPath('userData'), onProgress: progress => // Notify the renderers mainWindow.webContents.send('parity-download-progress', progress) }); + const downloadPath = downloadItem.getSavePath(); // Equal to defaultParityPath + + // Once downloaded, we fetch the sha256 checksum + const { downloadUrl: checksumDownloadUrl } = data[0].files.find( + ({ name }) => name === 'parity.sha256' || name === 'parity.exe.sha256' + ); + const { data: checksumData } = await axios.get(checksumDownloadUrl); + // Downloaded checksumData is in the format: "{checksum} {filename}" + const [expectedChecksum] = checksumData.split(' '); + // Calculate the actual checksum + const actualChecksum = await checksum(downloadPath, { + algorithm: 'sha256' + }); + // The 2 checksums should of course match + if (expectedChecksum !== actualChecksum) { + throw new Error( + `Checksum mismatch, expecting ${expectedChecksum}, got ${actualChecksum}.` + ); + } // Set a+x permissions on the downloaded binary - await fsChmod(downloadItem.getSavePath(), '755'); + await fsChmod(downloadPath, '755'); // Double-check that Parity exists now. return doesParityExist(); }, { + onRetry: err => { + pino.warn(err); + + // Everytime we retry, we remove the parity file we just downloaded. + // This needs to be done syncly, since onRetry is sync + deleteParity(); + }, retries: 3 } ); } catch (err) { + deleteParity(); handleError(err, 'An error occured while fetching parity.'); } }; diff --git a/package.json b/package.json index 2fede85a..c5a609a8 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@parity/light.js": "https://github.com/parity-js/light.js#0c57c29de42122b0f07ff82dfaf38b48863d8d2e", "async-retry": "^1.2.1", "axios": "^0.18.0", + "checksum": "^0.1.1", "command-exists": "^1.2.6", "commander": "^2.15.1", "electron": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index a470ee11..22be0b7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1820,6 +1820,12 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +checksum@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9" + dependencies: + optimist "~0.3.5" + chokidar@^1.6.0, chokidar@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -6507,6 +6513,12 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" +optimist@~0.3.5: + version "0.3.7" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" + dependencies: + wordwrap "~0.0.2" + optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" -- GitLab From 1ada53e85c7ed5ac621dcbabf978bc2205bcb978 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Thu, 24 May 2018 18:24:58 +0200 Subject: [PATCH 12/14] Small logging fix --- electron/operations/doesParityExist.js | 4 ++++ electron/operations/fetchParity.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index 7fa80ede..22abc7a4 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -58,6 +58,10 @@ module.exports = { parityPath = path; // Save the final result in module variable pino.info(`Parity found on machine, can be run with "${path}".`); return path; + }) + .catch(err => { + pino.info(`Parity not found on machine.`); + throw err; }); }, parityPath () { diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index 39c6204d..fa62dfd7 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -72,7 +72,7 @@ module.exports = mainWindow => { // Fetch the metadata of the correct version of parity pino.info( - `Parity not found on machine, downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}.` + `Downloading from ${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}.` ); const { data } = await axios.get( `${VANITY_URL}?version=${channel}&os=${getOs()}&architecture=${getArch()}` -- GitLab From 6971d52477df1239d851c04147bf7e9efc8bed12 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 25 May 2018 09:58:40 +0200 Subject: [PATCH 13/14] Remove useless stuff --- electron/operations/runParity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index 9c05b2ac..a5799810 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -13,7 +13,7 @@ const isParityRunning = require('./isParityRunning'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); const pino = require('../utils/pino')({ name: 'electron' }); -const pinoParity = require('../utils/pino')({ level: 'info', name: 'parity' }); +const pinoParity = require('../utils/pino')({ name: 'parity' }); const fsChmod = promisify(fs.chmod); -- GitLab From 5002c5e0ba4a79f4cd2947689b1ec4fc8944b00d Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 25 May 2018 10:07:19 +0200 Subject: [PATCH 14/14] Pino default namespace --- electron/operations/doesParityExist.js | 2 +- electron/operations/fetchParity.js | 2 +- electron/operations/handleError.js | 2 +- electron/operations/isParityRunning.js | 2 +- electron/operations/runParity.js | 2 +- electron/utils/pino.js | 7 ++++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js index 22abc7a4..c67cebe0 100644 --- a/electron/operations/doesParityExist.js +++ b/electron/operations/doesParityExist.js @@ -9,7 +9,7 @@ const fs = require('fs'); const promiseAny = require('promise-any'); const { promisify } = require('util'); -const pino = require('../utils/pino')({ name: 'electron' }); +const pino = require('../utils/pino')(); const fsExists = promisify(fs.stat); diff --git a/electron/operations/fetchParity.js b/electron/operations/fetchParity.js index fa62dfd7..139a7f7a 100644 --- a/electron/operations/fetchParity.js +++ b/electron/operations/fetchParity.js @@ -14,7 +14,7 @@ const retry = require('async-retry'); const { defaultParityPath, doesParityExist } = require('./doesParityExist'); const handleError = require('./handleError'); const { parity: { channel } } = require('../../package.json'); -const pino = require('../utils/pino')({ name: 'electron' }); +const pino = require('../utils/pino')(); const checksum = promisify(cs.file); const fsChmod = promisify(fs.chmod); diff --git a/electron/operations/handleError.js b/electron/operations/handleError.js index 80d25e20..cb39b0e6 100644 --- a/electron/operations/handleError.js +++ b/electron/operations/handleError.js @@ -6,7 +6,7 @@ const { app, dialog } = require('electron'); const { bugs: { url }, parity: { channel } } = require('../../package.json'); -const pino = require('../utils/pino')({ name: 'electron' }); +const pino = require('../utils/pino')(); module.exports = (err, message = 'An error occurred.') => { pino.error(err); diff --git a/electron/operations/isParityRunning.js b/electron/operations/isParityRunning.js index d388042d..b032d3e8 100644 --- a/electron/operations/isParityRunning.js +++ b/electron/operations/isParityRunning.js @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT const { promisify } = require('util'); -const pino = require('../utils/pino')({ name: 'electron' }); +const pino = require('../utils/pino')(); const ps = require('ps-node'); const lookup = promisify(ps.lookup); diff --git a/electron/operations/runParity.js b/electron/operations/runParity.js index a5799810..aef9b907 100644 --- a/electron/operations/runParity.js +++ b/electron/operations/runParity.js @@ -12,7 +12,7 @@ const { cli, parityArgv } = require('../cli'); const isParityRunning = require('./isParityRunning'); const handleError = require('./handleError'); const { parityPath } = require('./doesParityExist'); -const pino = require('../utils/pino')({ name: 'electron' }); +const pino = require('../utils/pino')(); const pinoParity = require('../utils/pino')({ name: 'parity' }); const fsChmod = promisify(fs.chmod); diff --git a/electron/utils/pino.js b/electron/utils/pino.js index b44721f2..24c97e6d 100644 --- a/electron/utils/pino.js +++ b/electron/utils/pino.js @@ -24,8 +24,9 @@ const streams = [ ]; /** - * Usage: const pino = require('../path/to/pino')({ name: 'electron' }); + * Usage: const pino = require('../path/to/pino')({ name: 'something' }); * - * @param {Object} opts - Options to pass to pino. + * @param {Object} opts - Options to pass to pino. Defaults to { name: 'electron' }. */ -module.exports = opts => pino(opts, multistream(streams)); +module.exports = opts => + pino({ name: 'electron', ...opts }, multistream(streams)); -- GitLab