Skip to content
This repository has been archived by the owner on Sep 5, 2020. It is now read-only.

check for system-wide installation of nodes; dynamically populate node-menu #1064

Closed
wants to merge 16 commits into from
2 changes: 2 additions & 0 deletions interface/i18n/mist.en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
"runTests": "Run tests",
"logFiles": "Show log file",
"ethereumNode": "Ethereum Node",
"starting": "starting",
"externalNode": "using external node",
"network": "Network",
"mainNetwork": "Main Network",
"startMining": "⛏ Start Mining (Testnet only)",
Expand Down
15 changes: 9 additions & 6 deletions modules/ethereumNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Q = require('bluebird');
const getNodePath = require('./getNodePath.js');
const EventEmitter = require('events').EventEmitter;
const Sockets = require('./sockets');
const SocketsBase = require('./sockets/base.js');
const Settings = require('./settings');

const DEFAULT_NODE_TYPE = 'geth';
Expand Down Expand Up @@ -327,13 +328,15 @@ class EthereumNode extends EventEmitter {
this._network = network;
this._type = nodeType;

const binPath = getNodePath(nodeType);

log.debug(`Start node using ${binPath}`);

return new Q((resolve, reject) => {
this.__startProcess(nodeType, network, binPath)
.then(resolve, reject);
Q.all(SocketsBase.getNodePathProm)
.then(() => {
const binPath = Object.keys(getNodePath.query()[nodeType])[0];
log.debug(`Start node using ${binPath}`);

this.__startProcess(nodeType, network, binPath)
.then(resolve, reject);
});
});
}

Expand Down
187 changes: 153 additions & 34 deletions modules/getNodePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,172 @@ Gets the right Node path
@module getNodePath
*/

const fs = require('fs');
const path = require('path');
const Q = require('bluebird');
const exec = require('child_process').exec;
const cmpVer = require('semver-compare');
const binaryPath = path.resolve(__dirname + '/../nodes');
const log = require('./utils/logger').create('getNodePath');
const Settings = require('./settings');


// cache
const resolvedPaths = {};
const paths = {}, // all versions (system + bundled): { type: { path: version } }
resolvedPaths = {}; // only latest version per type


module.exports = function(type) {
if (resolvedPaths[type]) {
return resolvedPaths[type];
}

let ret = '';
/**
* Get path of system node
*
* @param {String} type the type of node (i.e. 'geth', 'eth')
*/
function getSystemPath(type) {
return new Q((resolve, reject) => {
var proc = exec('type ' + type);
proc.stdout.on('data', resolve);
proc.stderr.on('data', reject);
});
}

// global override?
let globallySetType = Settings[`${type}Path`];

if (globallySetType) {
resolvedPaths[type] = globallySetType;
} else {
var binPath = (Settings.inProductionMode)
? binaryPath.replace('nodes','node') + '/'+ type +'/'+ type
: binaryPath + '/'+ type +'/'+ process.platform +'-'+ process.arch + '/'+ type;

if(Settings.inProductionMode) {
binPath = binPath.replace('app.asar/','').replace('app.asar\\','');

if(process.platform === 'darwin') {
binPath = path.resolve(binPath.replace('/node/', '/../Frameworks/node/'));
}
/**
* Get versions of node (system and bundled)
*
* @param {String} type the type of node (i.e. 'geth', 'eth')
* @param {String} path the path of the node instance
*/
function getVersion(type, path) {
return new Q((resolve, reject) => {
switch (type) {
case 'geth':
var command = path + ' version';
break;
case 'eth':
case 'parity':
var command = path + ' --version';
break;
}
var proc = exec(command);
proc.stdout.on('data', resolve);
});
}


if(process.platform === 'win32') {
binPath = binPath.replace(/\/+/,'\\');
binPath += '.exe';
/**
* Compare versions of system and bundled nodes
*/
function compareNodes() {
return new Q((resolve, reject) => {
for (let type in paths) {
var entries = Object.keys(paths[type]);
var preferredPath = entries[0];
if (Object.keys(paths[type]).length > 1)
var preferredPath = (cmpVer(paths[type][entries[0]], paths[type][entries[1]]))
? entries[1] : entries[0]
var version = paths[type][preferredPath];
resolvedPaths[type] = {};
resolvedPaths[type][preferredPath] = version;
}

resolvedPaths[type] = binPath;
});
}


module.exports = {
/**
* Returns path of node
* linux and mac only: returns system or bundled path depending on the latest version
*
* @param {String} type the type of node (i.e. 'geth', 'eth')
*/
query: () => {
return resolvedPaths;
},
/**
* Evaluates node paths
* - Enumerates bundled nodes
* linux and mac only:
* - probes for system installation of nodes
* - compares the versions of bundled and system nodes (preferres latest version)
*/
probe: () => {
return new Q((resolve, reject) => {
if(Settings.inProductionMode) {
var binPath = binaryPath.replace('nodes','node').replace('app.asar/','').replace('app.asar\\','');

if(process.platform === 'darwin') {
binPath = path.resolve(binPath.replace('/node', '/../Frameworks/node'));
}

fs.readdirSync(binPath).forEach((type) => {
var nodePath = binPath + '/' + type + '/' + type;

if(process.platform === 'win32') {
nodePath = nodePath.replace(/\/+/,'\\');
nodePath += '.exe';
}

paths[type] = {};
paths[type][nodePath] = null;
});
} else {
fs.readdirSync('nodes/').forEach((type) => {
if (fs.statSync('nodes/' + type).isDirectory()) {
var nodePath = path.resolve('nodes/' + type + '/' + process.platform +'-'+ process.arch + '/' + type);
paths[type] = {};
paths[type][nodePath] = null;
}
});
}

if (process.platform === 'linux' || process.platform === 'darwin') {
var getPathProms = [],
getVersionProms = [];

for (let type in paths) {
getPathProms.push(getSystemPath(type)
.then((data) => {
paths[type][data.match(/(\/\w+)+/)[0]] = null;
})
.catch((data) => {
log.trace(data);
})
);
}

Q.all(getPathProms).then(() => {
for (let type in paths) {
for (let path in paths[type]) {
getVersionProms.push(getVersion(type, path)
.then((data) => {
var version = data.match(/[\d.]+/)[0];
paths[type][path] = version;
}));
}
}
}).then(() => {
Q.all(getVersionProms).then(() => {
compareNodes();
log.debug('Available backends:');
Object.keys(paths).forEach((type) => {
let i = 0;
for (let path in paths[type]) {
var name = (0 === i++) ? type + ' -' : type.replace(/./g, ' ') + ' '
log.debug(name, paths[type][path], '=>', path);
}
});
})
.then(resolve, reject);
})
} else {
for (let type in paths) {
for (let path in paths[type]) {
resolvedPaths[type] = path;
}
}
log.debug('Available backends: %j', paths);
resolve();
}
});
}

log.debug(`Resolved path for ${type}: ${resolvedPaths[type]}`);

return resolvedPaths[type];
};


}
68 changes: 36 additions & 32 deletions modules/menuItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const shell = electron.shell;
const log = require('./utils/logger').create('menuItems');
const ipc = electron.ipcMain;
const ethereumNode = require('./ethereumNode.js');
const getNodePath = require('./getNodePath.js');
const Windows = require('./windows');
const updateChecker = require('./updateChecker');
const Settings = require('./settings');
Expand Down Expand Up @@ -342,46 +343,48 @@ var menuTempl = function(webviews) {
}
];





// add node switching menu
devToolsMenu.push({
type: 'separator'
});
// add node switch
if(process.platform === 'darwin' || process.platform === 'win32') {
devToolsMenu.push({
label: i18n.t('mist.applicationMenu.develop.ethereumNode'),
submenu: [
{
label: 'Geth 1.4.10 (Go)',
checked: ethereumNode.isOwnNode && ethereumNode.isGeth,
var resolvedPaths = getNodePath.query();
if (Object.keys(resolvedPaths).length > 1) {
var nodes = [];
for (let type in resolvedPaths)
nodes.push({
label: type.charAt(0).toUpperCase() + type.slice(1)
+ ' (' + resolvedPaths[type][Object.keys(resolvedPaths[type])[0]] + ')',
checked: ethereumNode.isOwnNode && ethereumNode.type === type,
enabled: ethereumNode.isOwnNode,
type: 'checkbox',
click: function(){
restartNode('geth');
}
},
{
label: 'Eth 1.2.9 (C++) [no hardfork support!]',
/*checked: ethereumNode.isOwnNode && ethereumNode.isEth,
enabled: ethereumNode.isOwnNode,*/
enabled: false,
type: 'checkbox',
click: function(){
restartNode('eth');
restartNode(type);
}
}
]});
});
var submenu = { 'label': i18n.t('mist.applicationMenu.develop.ethereumNode') };
if (ethereumNode.state === undefined) {
submenu.label += ' (' + i18n.t('mist.applicationMenu.develop.starting') + ')';
submenu.enabled = false;
} else if (ethereumNode.isExternalNode) {
submenu.label += ' (' + i18n.t('mist.applicationMenu.develop.externalNode') + ')';
submenu.enabled = false;
} else if (nodes.length > 0) {
submenu.submenu = nodes;
}
devToolsMenu.push(submenu);
}

// add network switch
devToolsMenu.push({
label: i18n.t('mist.applicationMenu.develop.network'),
submenu: [
{
var submenu = { label: i18n.t('mist.applicationMenu.develop.network') };
if (ethereumNode.state === undefined) {
submenu.label += ' (' + i18n.t('mist.applicationMenu.develop.starting') + ')';
submenu.enabled = false;
} else if (ethereumNode.isExternalNode) {
submenu.label += ' (' + i18n.t('mist.applicationMenu.develop.externalNode') + ')';
submenu.enabled = false;
} else {
submenu.submenu = [{
label: i18n.t('mist.applicationMenu.develop.mainNetwork'),
accelerator: 'CommandOrControl+Shift+1',
checked: ethereumNode.isOwnNode && ethereumNode.isMainNetwork,
Expand All @@ -390,8 +393,8 @@ var menuTempl = function(webviews) {
click: function(){
restartNode(ethereumNode.type, 'main');
}
},
{
},
{
label: 'Testnet (Morden)',
accelerator: 'CommandOrControl+Shift+2',
checked: ethereumNode.isOwnNode && ethereumNode.isTestNetwork,
Expand All @@ -400,8 +403,9 @@ var menuTempl = function(webviews) {
click: function(){
restartNode(ethereumNode.type, 'test');
}
}
]});
}];
}
devToolsMenu.push(submenu);

// add fork support
devToolsMenu.push({
Expand Down
6 changes: 6 additions & 0 deletions modules/sockets/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const EventEmitter = require('events').EventEmitter;

const _ = global._;
const log = require('../utils/logger').create('Sockets');
const getNodePath = require('../getNodePath.js');



const CONNECT_INTERVAL_MS = 1000;
const CONNECT_TIMEOUT_MS = 3000;

var getNodePathProm = [];

/**
* Socket connecting to Ethereum Node.
Expand Down Expand Up @@ -83,6 +85,9 @@ class Socket extends EventEmitter {
if (STATE.CONNECTING === this._state) {
this._log.warn(`Connection failed, retrying after ${CONNECT_INTERVAL_MS}ms...`);

if (Object.keys(getNodePath.query()).length == 0)
getNodePathProm.push(getNodePath.probe());

connectTimerId = setTimeout(() => {
this._socket.connect(connectConfig);
}, CONNECT_INTERVAL_MS);
Expand Down Expand Up @@ -196,6 +201,7 @@ class Socket extends EventEmitter {


exports.Socket = Socket;
exports.getNodePathProm = getNodePathProm;


const STATE = exports.STATE = Socket.STATE = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"numeral": "^1.5.3",
"os-timesync": "^1.0.6",
"semver": "^5.1.0",
"semver-compare": "^1.0.0",
"solc": "^0.3.1-1",
"underscore": "^1.8.3",
"uuid": "^2.0.2",
Expand Down