diff --git a/app/background/db/client.js b/app/background/db/client.js index cf1e97c0a..9432deb03 100644 --- a/app/background/db/client.js +++ b/app/background/db/client.js @@ -6,4 +6,5 @@ export const clientStub = (ipcRendererInjector) => makeClient(ipcRendererInjecto 'put', 'get', 'del', + 'getUserDir', ]); diff --git a/app/background/db/service.js b/app/background/db/service.js index ada56e63d..3b8d5e1c1 100644 --- a/app/background/db/service.js +++ b/app/background/db/service.js @@ -51,6 +51,10 @@ export async function iteratePrefix(prefix, cb) { await iter.each(cb); } +export async function getUserDir() { + return app.getPath('userData'); +} + function ensureDB() { if (!db) { throw new Error('db not open'); @@ -65,6 +69,7 @@ const methods = { get, del, iteratePrefix, + getUserDir, }; export async function start(server) { diff --git a/app/background/node/client.js b/app/background/node/client.js index d65c8cd64..249b00381 100644 --- a/app/background/node/client.js +++ b/app/background/node/client.js @@ -17,5 +17,9 @@ export const clientStub = ipcRendererInjector => makeClient(ipcRendererInjector, 'sendRawAirdrop', 'getFees', 'getAverageBlockTime', - 'getCoin' + 'getCoin', + 'setNodeDir', + 'setAPIKey', + 'getDir', + 'getAPIKey', ]); diff --git a/app/background/node/service.js b/app/background/node/service.js index 31d73c9d8..7ea2dcde6 100644 --- a/app/background/node/service.js +++ b/app/background/node/service.js @@ -17,17 +17,52 @@ const Network = require('hsd/lib/protocol/network'); const MIN_FEE = new BigNumber(0.01); const DEFAULT_BLOCK_TIME = 10 * 60 * 1000; +const HSD_PREFIX_DIR_KEY = 'hsdPrefixDir'; +const NODE_API_KEY = 'nodeApiKey'; export class NodeService extends EventEmitter { constructor() { super(); + } + + async getAPIKey() { + const apiKey = await get(NODE_API_KEY); + + if (apiKey) return apiKey; + + const newKey = crypto.randomBytes(20).toString('hex'); + await put(NODE_API_KEY, newKey); + return newKey; + } + + async getDir() { + const hsdPrefixDir = await get(HSD_PREFIX_DIR_KEY); + + if (hsdPrefixDir) { + return hsdPrefixDir; + } + + const newPath = path.join(app.getPath('userData'), 'hsd_data'); + await put(HSD_PREFIX_DIR_KEY, newPath); + return newPath; + } + + async setNodeDir(dir) { + if (!fs.existsSync(dir)) { + throw new Error(`${dir} does not exist`); + } + + await put(HSD_PREFIX_DIR_KEY, dir); + } - this.hsdPrefixDir = path.join(app.getPath('userData'), 'hsd_data'); + async setAPIKey(apiKey) { + await put(NODE_API_KEY, apiKey); } async configurePaths() { - if (!fs.existsSync(this.hsdPrefixDir)) { - await pify(cb => fs.mkdir(this.hsdPrefixDir, {recursive: true}, cb)); + const dir = await this.getDir(); + if (!fs.existsSync(dir)) { + await pify(cb => fs.mkdir(dir, {recursive: true}, cb)); } } @@ -56,11 +91,10 @@ export class NodeService extends EventEmitter { } const network = Network.get(networkName); - const apiKey = crypto.randomBytes(20).toString('hex'); this.networkName = networkName; this.network = network; - this.apiKey = apiKey; + this.apiKey = await this.getAPIKey(); this.emit('started', this.networkName, this.network, this.apiKey); } @@ -78,6 +112,8 @@ export class NodeService extends EventEmitter { console.log(`Starting node on ${this.networkName} network.`); + const dir = await this.getDir(); + const hsd = new FullNode({ config: true, argv: true, @@ -89,7 +125,7 @@ export class NodeService extends EventEmitter { workers: false, network: this.networkName, loader: require, - prefix: this.hsdPrefixDir, + prefix: dir, listen: true, bip37: true, indexAddress: true, @@ -173,11 +209,6 @@ export class NodeService extends EventEmitter { return this._execRPC('generatetoaddress', [numblocks, address]); } - async getAPIKey() { - await this._ensureStarted(); - return this.apiKey; - } - async getInfo() { await this._ensureStarted(); return this.client.getInfo(); @@ -354,6 +385,9 @@ const methods = { getFees: () => service.getFees(), getAverageBlockTime: () => service.getAverageBlockTime(), getCoin: (hash, index) => service.getCoin(hash, index), + setNodeDir: data => service.setNodeDir(data), + setAPIKey: data => service.setAPIKey(data), + getDir: () => service.getDir(), }; export async function start(server) { diff --git a/app/components/Modal/MiniModal.js b/app/components/Modal/MiniModal.js index 98bcf6344..ddd85a35c 100644 --- a/app/components/Modal/MiniModal.js +++ b/app/components/Modal/MiniModal.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import './mini-modal.scss'; +@withRouter export class MiniModal extends Component { static propTypes = { closeRoute: PropTypes.string, diff --git a/app/pages/Settings/ChangeDirectoryModal.js b/app/pages/Settings/ChangeDirectoryModal.js new file mode 100644 index 000000000..301e05a46 --- /dev/null +++ b/app/pages/Settings/ChangeDirectoryModal.js @@ -0,0 +1,125 @@ +import React, { Component } from "react"; +import {MiniModal} from "../../components/Modal/MiniModal"; +import {clientStub} from "../../background/node/client"; +import c from "classnames"; +import {connect} from "react-redux"; +import {withRouter} from "react-router-dom"; +import Alert from "../../components/Alert"; +import dbClient from "../../utils/dbClient"; +const nodeClient = clientStub(() => require('electron').ipcRenderer); +const {dialog} = require('electron').remote; + + +@withRouter +@connect( + null, + null, +) +export default class ChangeDirectoryModal extends Component { + state = { + directory: '', + errorMessage: '', + saving: false, + }; + + async componentDidMount() { + const directory = await nodeClient.getDir(); + const userDir = await dbClient.getUserDir(); + this.setState({ + originalDirectory: directory, + directory, + userDir, + }); + } + + reset = async () => { + const {originalDirectory} = this.state; + this.setState({ + originalDirectory, + directory: originalDirectory, + }); + }; + + saveDir = async () => { + const {directory} = this.state; + this.setState({ saving: true }); + try { + await nodeClient.setNodeDir(directory); + } catch (e) { + this.setState({ + errorMessage: e.message, + }); + } + + this.setState({ + saving: false, + directory, + originalDirectory: directory, + }); + }; + + pickDirectory = async () => { + let savePath = dialog.showOpenDialogSync({ + properties: ["openDirectory", "promptToCreate", "createDirectory"], + }); + + this.setState({ directory: savePath[0] }); + }; + + render() { + const { errorMessage, directory, originalDirectory, userDir, saving } = this.state; + + return ( + + + This will only change your hsd home directory. Your other data, such as user setting and wallet data, will still be saved in {userDir}. + +
+
+ Home Directory + + Pick Directory + +
+ this.setState({ + directory: e.target.value, + errorMessage: '', + })} + /> +
+ { + errorMessage && ( +
+ {errorMessage} +
+ ) + } +
+ + +
+
+ ) + } +} diff --git a/app/pages/Settings/index.js b/app/pages/Settings/index.js index 626eb11fc..f8314bcbb 100644 --- a/app/pages/Settings/index.js +++ b/app/pages/Settings/index.js @@ -30,9 +30,13 @@ import BackupListingModal from "./BackupListingModal"; import fs from "fs"; const {dialog} = require('electron').remote; import {clientStub as sClientStub} from "../../background/shakedex/client"; +import ChangeDirectoryModal from "./ChangeDirectoryModal"; +import dbClient from "../../utils/dbClient"; +import {clientStub} from "../../background/node/client"; const analytics = aClientStub(() => require('electron').ipcRenderer); const shakedex = sClientStub(() => require('electron').ipcRenderer); +const nodeClient = clientStub(() => require('electron').ipcRenderer); @withRouter @connect( @@ -91,9 +95,15 @@ export default class Settings extends Component { } } - componentDidMount() { + async componentDidMount() { analytics.screenView('Settings'); this.props.fetchWalletAPIKey(); + const directory = await nodeClient.getDir(); + const userDir = await dbClient.getUserDir(); + this.setState({ + directory, + userDir, + }); } onDownload = async () => { @@ -428,6 +438,17 @@ export default class Settings extends Component { , isRunning || isTestingCustomRPC || isChangingNodeStatus, )} + {this.renderSection( + 'HSD Home Directory', +
+
User Directory: {this.state.userDir}
+
HSD Directory: {this.state.directory}
+
, + 'Change Directory', + () => history.push("/settings/connection/changeDirectory"), + null, + isRunning || isChangingNodeStatus, + )} {this.renderSection( 'Network type', ( @@ -483,6 +504,7 @@ export default class Settings extends Component { +