From ae63574b239a7a7c834850040d9b6bde412466b2 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 7 Aug 2018 12:03:38 +0100 Subject: [PATCH 01/10] feat: support external and embedded nodes --- package.json | 1 + src/components/Heartbeat.js | 11 +-- src/index.js | 42 +++-------- src/init.js | 133 +++++++++++++++++++++++++++++++++++ src/panes/Intro.js | 92 +++++++++++++++--------- src/screens/welcome.js | 13 +--- src/utils/key-value-store.js | 5 +- 7 files changed, 216 insertions(+), 81 deletions(-) create mode 100644 src/init.js diff --git a/package.json b/package.json index 7abc9051e..abb7f20ca 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "electron-squirrel-startup": "^1.0.0", "file-extension": "^4.0.5", "go-ipfs-dep": "^0.4.17", + "ipfs": "^0.31.2", "ipfs-stats": "^1.2.4", "ipfsd-ctl": "^0.39.0", "is-ipfs": "^0.4.2", diff --git a/src/components/Heartbeat.js b/src/components/Heartbeat.js index 8a83ca432..dec62ad17 100644 --- a/src/components/Heartbeat.js +++ b/src/components/Heartbeat.js @@ -15,14 +15,14 @@ const blackLogo = resolve(join(__dirname, '../img/ipfs-logo-black.png')) * * @return {ReactElement} */ -export default function Heartbeat (props) { - if (props.dead) { +export default function Heartbeat ({dead, className, ...props}) { + if (dead) { return ( - + ) } else { return ( - + ) } } @@ -32,5 +32,6 @@ Heartbeat.propTypes = { } Heartbeat.defaultProps = { - dead: false + dead: false, + className: '' } diff --git a/src/index.js b/src/index.js index d39f258c6..365e9cef8 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,8 @@ import DaemonFactory from 'ipfsd-ctl' import {join} from 'path' import {dialog, ipcMain, app, BrowserWindow} from 'electron' +import init from './init' + import config from './config' import registerControls from './controls/main' import handleKnownErrors from './errors' @@ -48,34 +50,6 @@ function onRequestState (node, event) { send('node-status', state) } -// Moves files from appData/file-history.json to MFS so -// v0.4.0 is backwards compatible with v0.3.0. -function moveFilesOver () { - const path = join(config.appData, 'file-history.json') - - if (!fs.existsSync(path)) { - return - } - - let files - - try { - files = JSON.parse(fs.readFileSync(path)) - } catch (e) { - debug(e) - return - } - - Promise.all(files.map((file) => IPFS.files.cp([`/ipfs/${file.hash}`, `/${file.name}`]))) - .then(() => { - fs.unlinkSync(path) - }) - .catch((e) => { - fs.unlinkSync(path) - debug(e) - }) -} - function onStartDaemon (node) { debug('Starting daemon') updateState('starting') @@ -127,9 +101,6 @@ function onStartDaemon (node) { }) } - // Move files from V0.3.0 - moveFilesOver() - menubar.tray.setImage(config.logo.ice) updateState('running') }) @@ -248,6 +219,7 @@ function initialize (path, node) { }) } +/* // main entry point DaemonFactory.create().spawn({ repoPath: config.settingsStore.get('ipfsPath'), @@ -289,4 +261,10 @@ DaemonFactory.create().spawn({ if (menubar.isReady()) appReady() else menubar.on('ready', appReady) -}) +}) */ + +menubar = new Menubar(config.menubar) +config.menubar = menubar + +if (menubar.isReady()) init() +else menubar.on('ready', init) diff --git a/src/init.js b/src/init.js new file mode 100644 index 000000000..46182ea25 --- /dev/null +++ b/src/init.js @@ -0,0 +1,133 @@ +import IPFSFactory from 'ipfsd-ctl' +import config from './config' +import { join } from 'path' +import { BrowserWindow, dialog, ipcMain, app } from 'electron' + +async function getIpfs ({ type, path, flags, keysize, init }) { + let factOpts = { type: type } + + if (type === 'proc') { + factOpts.exec = require('ipfs') + } + + const factory = IPFSFactory.create(factOpts) + + return new Promise((resolve, reject) => { + const start = (ipfsd) => ipfsd.start(flags, (err, api) => { + if (err) return reject(err) + else resolve(ipfsd) + }) + + factory.spawn({ + init: false, + start: false, + disposable: false, + defaultAddrs: true, + repoPath: path + }, (err, ipfsd) => { + if (err) return reject(err) + + if (ipfsd.started) { + return resolve(ipfsd) + } + + if (!ipfsd.initialized && init) { + return ipfsd.init({ + directory: path, + keysize: keysize + }, err => { + if (err) return reject(err) + else start(ipfsd) + }) + } + + start(ipfsd) + }) + }) +} + +function welcome ({ path }) { + return new Promise((resolve, reject) => { + let ipfs = null + + // Initialize the welcome window. + const window = new BrowserWindow({ + title: 'Welcome to IPFS', + icon: config.logo.ice, + show: false, + // resizable: false, + width: 850, + height: 450 + }) + + // Only show the window when the contents have finished loading. + window.on('ready-to-show', () => { + window.show() + window.focus() + }) + + // window.setMenu(null) + window.loadURL(`file://${__dirname}/views/welcome.html`) + + // Send the default path as soon as the window is ready. + window.webContents.on('did-finish-load', () => { + window.webContents.send('setup-config-path', path) + }) + + window.once('close', () => { + if (!ipfs) app.quit() + }) + + ipcMain.on('setup-browse-path', () => { + dialog.showOpenDialog(window, { + title: 'Select a directory', + defaultPath: path, + properties: [ + 'openDirectory', + 'createDirectory' + ] + }, (res) => { + if (!res) return + + let userPath = res[0] + + if (!userPath.match(/.ipfs\/?$/)) { + userPath = join(userPath, '.ipfs') + } + + window.webContents.send('setup-config-path', userPath) + }) + }) + + ipcMain.on('install', async (event, opts) => { + window.webContents.send('initializing') + + try { + ipfs = await getIpfs(opts) + window.close() + resolve(ipfs) + } catch (_) { + window.webContents.send('errored') + } + }) + }) +} + +export default async function init () { + // const opts = applyDefaults(config.settingsStore.get('connection', {})) + const opts = { + type: 'go', + path: config.settingsStore.get('ipfsPath'), + flags: [] + } + + let ipfs + + try { + ipfs = await getIpfs(opts) + } catch (_) { + ipfs = await welcome(opts) + } + + console.log(await ipfs.api.id()) +} diff --git a/src/panes/Intro.js b/src/panes/Intro.js index 25c9bb03c..d56112f9d 100644 --- a/src/panes/Intro.js +++ b/src/panes/Intro.js @@ -8,19 +8,11 @@ import Icon from '../components/Icon' import IconDropdownList from '../components/IconDropdownList' export default class Intro extends Component { - constructor (props) { - super(props) - this.state = { - showAdvanced: false - } - } - static propTypes = { onInstallClick: PropTypes.func, configPath: PropTypes.string, keySizes: PropTypes.arrayOf(PropTypes.number), - keySize: PropTypes.number, - onKeySizeChange: PropTypes.func + defaultKeySize: PropTypes.number } static defaultProps = { @@ -31,8 +23,21 @@ export default class Intro extends Component { onKeySizeChange () {} } + state = { + advanced: false, + type: 'Embedded', + engine: 'Go', + keysize: 2048 + } + + onTypeChange = type => { this.setState({ type }) } + + onEngineChange = engine => { this.setState({ engine }) } + + onKeySizeChange = keysize => { this.setState({ keysize }) } + onAdvancedClick = () => { - this.setState({ showAdvanced: true }) + this.setState({ advanced: true }) } onClick = (e) => { @@ -41,24 +46,6 @@ export default class Intro extends Component { } render () { - let advanced = null - if (this.state.showAdvanced) { - advanced = ([ -
- - - {this.props.configPath} - -
, - - ]) - } - return (
@@ -66,12 +53,53 @@ export default class Intro extends Component {

Welcome to the Distributed Web

You are about to install IPFS, the InterPlanetary File System.

-
- {advanced} -
+ { this.state.advanced && +
+ + + { this.state.type === 'Embedded' + ? ( + + ) : ( +
+

Please insert the API address of the node you want to connect to:

+ +
+ ) + } +
+ }
}
-
diff --git a/src/screens/welcome.js b/src/screens/welcome.js index 180053dc3..bcb4f8ee1 100644 --- a/src/screens/welcome.js +++ b/src/screens/welcome.js @@ -12,28 +12,25 @@ const INTRO = 'intro' const INTITIALZING = 'initializing' const ERROR = 'error' -const KEY_SIZES = [2048, 4096] - export default class Welcome extends Component { state = { status: INTRO, error: void 0, - configPath: '', - keySize: KEY_SIZES[1] + configPath: '' } _onInitializing = () => { this.setState({status: INTITIALZING}) } - _onError = (event, error) => { + _onError = (_, error) => { this.setState({ status: ERROR, error }) } - _onConfigPath = (event, path) => { + _onConfigPath = (_, path) => { this.setState({configPath: path}) } @@ -41,10 +38,6 @@ export default class Welcome extends Component { ipcRenderer.send('install', options) } - _onKeySizeChange = (keySize) => { - this.setState({keySize}) - } - componentDidMount () { ipcRenderer.on('initializing', this._onInitializing) ipcRenderer.on('initialization-error', this._onError) @@ -68,9 +61,7 @@ export default class Welcome extends Component { + configPath={this.state.configPath} /> ) case ERROR: From 29b49ed98b8b6c943bf55eecb9e30eb09f2b3ad5 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 27 Aug 2018 09:49:45 +0100 Subject: [PATCH 09/10] fix: work with API License: MIT Signed-off-by: Henrique Dias --- src/controls/main/settings.js | 17 ++++++++++++++--- src/panes/Intro.js | 29 ++++++++++++++++++++++------- src/panes/Settings.js | 10 ++++++++++ src/setup.js | 10 +++++----- src/start.js | 5 ++++- src/utils/ipfs.js | 10 ++++++++-- 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/controls/main/settings.js b/src/controls/main/settings.js index 093bbe4f4..1d9ed93b4 100644 --- a/src/controls/main/settings.js +++ b/src/controls/main/settings.js @@ -1,5 +1,5 @@ -import {join} from 'path' -import {shell, ipcMain} from 'electron' +import { join } from 'path' +import { shell, ipcMain, app, dialog } from 'electron' import { store } from '../../utils' const openNodeConfig = () => { @@ -26,7 +26,7 @@ const sendSettings = ({ send }) => () => { options[opt] = store.get(opt, false) } - const flags = store.get('ipfs.flags') + const flags = store.get('ipfs.flags', []) for (const flag of Object.keys(settingsToSend.flags)) { options[flag] = flags.includes(settingsToSend.flags[flag]) @@ -59,8 +59,19 @@ const updateSettings = (opts) => (_, key, value) => { sendSettings(opts)() } +const cleanIpfsSettings = () => { + store.delete('ipfs') + dialog.showMessageBox({ + type: 'info', + message: 'The IPFS settings were cleared and IPFS Desktop will shutdown now. You need to start it again afterwards.' + }, () => { + app.quit() + }) +} + export default function (opts) { ipcMain.on('request-settings', sendSettings(opts)) ipcMain.on('update-setting', updateSettings(opts)) ipcMain.on('open-node-settings', openNodeConfig) + ipcMain.on('clean-ipfs-settings', cleanIpfsSettings) } diff --git a/src/panes/Intro.js b/src/panes/Intro.js index 45e6d60b9..7697049ba 100644 --- a/src/panes/Intro.js +++ b/src/panes/Intro.js @@ -25,7 +25,8 @@ export default class Intro extends Component { advanced: false, type: 'Embedded', engine: 'Go', - keysize: 4096 + keysize: 4096, + apiAddress: '' } onTypeChange = type => { this.setState({ type }) } @@ -34,6 +35,8 @@ export default class Intro extends Component { onKeySizeChange = keysize => { this.setState({ keysize }) } + handleApiAddressInput = event => { this.setState({ apiAddress: event.target.value })} + onAdvancedClick = () => { this.setState({ advanced: true }) } @@ -44,12 +47,23 @@ export default class Intro extends Component { } onInstall = () => { - this.props.onInstallClick({ - type: this.state.engine.toLowerCase(), - path: this.props.configPath, - flags: ['--routing=dhtclient'], - keysize: this.state.keysize - }) + const settings = {} + + if (this.state.type === 'Embedded') { + settings.type = this.state.engine.toLowerCase() + if (settings.type === 'javascript') { + settings.type = 'proc' + } + + settings.path = this.props.configPath + settings.flags = ['--routing=dhtclient'] + settings.keysize = this.state.keysize + } else { + settings.type = 'api' + settings.apiAddress = this.state.apiAddress + } + + this.props.onInstallClick(settings) } render () { @@ -97,6 +111,7 @@ export default class Intro extends Component {

Please insert the API address of the node you want to connect to:

diff --git a/src/panes/Settings.js b/src/panes/Settings.js index a41226e8a..a22c3285e 100644 --- a/src/panes/Settings.js +++ b/src/panes/Settings.js @@ -18,6 +18,10 @@ function garbageCollector () { ipcRenderer.send('run-gc') } +function cleanConnSettings () { + ipcRenderer.send('clean-ipfs-settings') +} + function quit () { ipcRenderer.send('quit-application') } @@ -88,6 +92,12 @@ export default function Settings (props) { button={false} onClick={garbageCollector} /> + + { - if (err) { - throw err - } - }) + if (opts.type === 'api') { + await ipfs.id() + } else { + await ipfs.api.id() + } store.set('ipfs', opts) window.close() diff --git a/src/start.js b/src/start.js index 56325d35b..745d24827 100644 --- a/src/start.js +++ b/src/start.js @@ -33,11 +33,14 @@ export default async function (ipfsd) { } } + const isApi = store.get('ipfs.type') === 'api' + const config = { events: new EventEmitter(), menubar: menubar, send: send, - ipfs: () => ipfsd.api + isApi: isApi, + ipfs: () => isApi ? ipfsd : ipfsd.api } const updateState = (st) => { diff --git a/src/utils/ipfs.js b/src/utils/ipfs.js index 8b0665d84..5d50ed86b 100644 --- a/src/utils/ipfs.js +++ b/src/utils/ipfs.js @@ -1,4 +1,5 @@ import IPFSFactory from 'ipfsd-ctl' +import IPFSApi from 'ipfs-api' import { join } from 'path' import fs from 'fs-extra' import logger from './logger' @@ -28,14 +29,19 @@ function cleanLocks (path) { } } -export default async function ({ type, path, flags, keysize, init }) { - cleanLocks(path) +export default async function ({ type, apiAddress, path, flags, keysize, init }) { let factOpts = { type: type } if (type === 'proc') { factOpts.exec = require('ipfs') } + if (type === 'api') { + return IPFSApi(apiAddress) + } + + cleanLocks(path) + const factory = IPFSFactory.create(factOpts) return new Promise((resolve, reject) => { From 583d2ab33da40058805c55b3e61f9481098d840c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 27 Aug 2018 17:32:05 +0100 Subject: [PATCH 10/10] verify if folder is empty License: MIT Signed-off-by: Henrique Dias --- src/setup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/setup.js b/src/setup.js index e7424dda0..94a3583d8 100644 --- a/src/setup.js +++ b/src/setup.js @@ -51,7 +51,7 @@ function welcome ({ path }) { let userPath = res[0] - if (!userPath.match(/.ipfs\/?$/)) { + if (!userPath.endsWith('ipfs')) { userPath = join(userPath, '.ipfs') } @@ -65,7 +65,7 @@ function welcome ({ path }) { opts = { ...opts, - init: !(await fs.pathExists(opts.path)) + init: !(await fs.pathExists(opts.path)) || fs.readdirSync(opts.path).length === 0 } logger.info('Trying connection with: %o', opts)