Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cleanup repository before starting up IPFS #722

Merged
merged 21 commits into from
Dec 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 18 additions & 67 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { app, dialog, shell } from 'electron'
import { store, createDaemon } from './utils'
import { app, dialog } from 'electron'
import { store, createDaemon, showErrorMessage } from './utils'
import startupMenubar from './menubar'
import registerHooks from './hooks'

Expand All @@ -14,65 +14,6 @@ if (!app.requestSingleInstanceLock()) {
process.exit(1)
}

const issueTemplate = (e) => `Please describe what you were doing when this error happened.

**Specifications**

- **OS**: ${process.platform}
- **IPFS Desktop Version**: ${app.getVersion()}
- **Electron Version**: ${process.versions.electron}
- **Chrome Version**: ${process.versions.chrome}

**Error**

\`\`\`
${e.stack}
\`\`\`
`

function handleError (e) {
const option = dialog.showMessageBox({
type: 'error',
title: 'IPFS Desktop has shutdown',
message: 'IPFS Desktop has shutdown because of an error. You can restart the app or report the error to the developers, which requires a GitHub account.',
buttons: [
'Close',
'Report the error to the developers',
'Restart the app'
],
cancelId: 0
})

if (option === 1) {
shell.openExternal(`https://github.com/ipfs-shipyard/ipfs-desktop/issues/new?body=${encodeURI(issueTemplate(e))}`)
} else if (option === 2) {
app.relaunch()
}

app.exit(1)
}

async function setupConnection () {
let config = store.get('config')
let updateCfg = false

if (config === null) {
config = { type: 'go' }
updateCfg = true
}

const ipfsd = await createDaemon(config)

// createDaemon has changed the config object,
// but didn't add the repo variable.
if (updateCfg) {
config.path = ipfsd.repoPath
store.set('config', config)
}

return ipfsd
}

async function run () {
try {
await app.whenReady()
Expand All @@ -81,23 +22,33 @@ async function run () {
app.exit(1)
}

let config = store.get('ipfsConfig')

try {
// Initial context object
let ctx = {
ipfsd: await setupConnection()
ipfsd: await createDaemon(config)
}

/// Update the path if it was blank previously.
// This way we use the default path when it is
// not set.
if (config.path === '') {
hacdias marked this conversation as resolved.
Show resolved Hide resolved
config.path = ctx.ipfsd.repoPath
store.set('ipfsConfig', config)
}

// Initialize windows. These can add properties to context
await startupMenubar(ctx)

// Register hooks
await registerHooks(ctx)

if (!store.get('seenWelcome')) {
// TODO: open WebUI on Welcome screen
}
} catch (e) {
handleError(e)
if (e.message === 'exit') {
app.exit(1)
} else {
showErrorMessage(e)
}
}
}

Expand Down
97 changes: 69 additions & 28 deletions src/utils/daemon.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,104 @@
import IPFSFactory from 'ipfsd-ctl'
import logger from './logger'
import { showConnFailureErrorMessage } from './errors'
import { join } from 'path'
import fs from 'fs-extra'
import { spawnSync } from 'child_process'
import findExecutable from 'ipfsd-ctl/src/utils/find-ipfs-executable'
hacdias marked this conversation as resolved.
Show resolved Hide resolved

export default async function createDaemon (opts) {
opts.type = opts.type || 'go'
opts.path = opts.path || ''
opts.flags = opts.flags || ['--migrate=true', '--routing=dhtclient']
opts.keysize = opts.keysize || 4096
function repoFsck (path) {
const exec = findExecutable('go', join(__dirname, '..'))
spawnSync(exec, ['repo', 'fsck'], {
env: {
...process.env,
IPFS_PATH: path
}
})
}

if (opts.type !== 'go') {
throw new Error(`${opts.type} connection is not supported yet`)
async function configure (ipfsd) {
const cfgFile = join(ipfsd.repoPath, 'config')
const cfg = await fs.readJSON(cfgFile)

let origins = []
try {
origins = cfg.API.HTTPHeaders['Access-Control-Allow-Origin']
} catch (e) {
logger.warn(e)
}

const factory = IPFSFactory.create({ type: opts.type })
if (!Array.isArray(origins)) {
origins = []
}

if (!origins.includes('webui://-')) origins.push('webui://-')
lidel marked this conversation as resolved.
Show resolved Hide resolved
if (!origins.includes('https://webui.ipfs.io')) origins.push('https://webui.ipfs.io')

cfg.API.HTTPHeaders['Access-Control-Allow-Origin'] = origins
cfg.API.HTTPHeaders['Access-Control-Allow-Methods'] = ['PUT', 'GET', 'POST']

await fs.writeJSON(cfgFile, cfg)
}

async function spawn ({ type, path, keysize }) {
const factory = IPFSFactory.create({ type: type })

const ipfsd = await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
factory.spawn({
disposable: false,
defaultAddrs: true,
repoPath: opts.path
repoPath: path
}, (e, ipfsd) => {
if (e) return reject(e)
if (ipfsd.initialized) {
return resolve(ipfsd)
}

ipfsd.init({
directory: opts.path,
keysize: opts.keysize
directory: path,
keysize: keysize
}, e => {
if (e) return reject(e)
resolve(ipfsd)
})
})
})
}

if (!ipfsd.started) {
await new Promise((resolve, reject) => {
ipfsd.start(opts.flags, err => {
if (err) {
return reject(err)
}
async function start (ipfsd, { flags }) {
await new Promise((resolve, reject) => {
ipfsd.start(flags, err => {
if (err) {
return reject(err)
}

resolve()
})
resolve()
})
})
}

export default async function (opts) {
const ipfsd = await spawn(opts)
await configure(ipfsd)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ipfsd connects to an existing daemon process, and we change it's config, we would need to restart that process for the config to be applied.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olizilla we're now editing the config file directly so we won't even be able to change the config of remote daemons. Perhaps it would be better to revert this change and use the API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @olizilla

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olizilla I'm merging this so we can speed up dev. This piece of code will be removed on #736 so let's not take too much time here.


if (!ipfsd.started) {
await start(ipfsd, opts)
}

let origins = []
try {
origins = await ipfsd.api.config.get('API.HTTPHeaders.Access-Control-Allow-Origin')
await ipfsd.api.id()
} catch (e) {
logger.warn(e)
}
if (!e.message.includes('ECONNREFUSED')) {
hacdias marked this conversation as resolved.
Show resolved Hide resolved
throw e
}

if (!origins.includes('webui://-')) origins.push('webui://-')
if (!origins.includes('https://webui.ipfs.io')) origins.push('https://webui.ipfs.io')
if (!showConnFailureErrorMessage(ipfsd.repoPath, ipfsd.apiAddr)) {
throw new Error('exit')
}

await ipfsd.api.config.set('API.HTTPHeaders.Access-Control-Allow-Origin', origins)
await ipfsd.api.config.set('API.HTTPHeaders.Access-Control-Allow-Methods', ['PUT', 'GET', 'POST'])
repoFsck(ipfsd.repoPath)
await start(ipfsd, opts)
}

return ipfsd
}
54 changes: 54 additions & 0 deletions src/utils/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { app, dialog, shell } from 'electron'

const issueTemplate = (e) => `Please describe what you were doing when this error happened.
hacdias marked this conversation as resolved.
Show resolved Hide resolved

**Specifications**

- **OS**: ${process.platform}
- **IPFS Desktop Version**: ${app.getVersion()}
- **Electron Version**: ${process.versions.electron}
- **Chrome Version**: ${process.versions.chrome}

**Error**

\`\`\`
${e.stack}
\`\`\`
`

export function showErrorMessage (e) {
const option = dialog.showMessageBox({
type: 'error',
title: 'IPFS Desktop has shutdown',
message: 'IPFS Desktop has shutdown because of an error. You can restart the app or report the error to the developers, which requires a GitHub account.',
buttons: [
'Close',
'Report the error to the developers',
'Restart the app'
],
cancelId: 0
})

if (option === 1) {
shell.openExternal(`https://github.com/ipfs-shipyard/ipfs-desktop/issues/new?body=${encodeURI(issueTemplate(e))}`)
} else if (option === 2) {
app.relaunch()
}

app.exit(1)
}

export function showConnFailureErrorMessage (path, addr) {
const option = dialog.showMessageBox({
type: 'warning',
title: 'IPFS Desktop',
message: `IPFS Desktop failed to connect to an existing ipfs api at ${addr}. This can happen if you run 'ipfs daemon' manually and it has not shutdown cleanly. Would you like to try running 'ipfs repo fsck' to remove the lock and api files from ${path} and try again?`,
buttons: [
'No, just quit',
'Yes, run "ipfs repo fsck"'
],
cancelId: 0
})

return option === 1
}
5 changes: 4 additions & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createDaemon from './daemon'
import logo from './logo'
import { showConnFailureErrorMessage, showErrorMessage } from './errors'
import store from './store'
import logger from './logger'
import i18n from './i18n'
Expand All @@ -9,5 +10,7 @@ export {
logo,
store,
logger,
i18n
i18n,
showErrorMessage,
showConnFailureErrorMessage
}
15 changes: 11 additions & 4 deletions src/utils/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import Store from 'electron-store'

const store = new Store()

if (store.get('version') !== 4) {
if (store.get('version') !== 5) {
store.clear()
store.set('seenWelcome', false)
store.set('config', null)
store.set('version', 4)

// default config
store.set('ipfsConfig', {
type: 'go',
path: '',
flags: ['--migrate=true', '--routing=dhtclient'],
keysize: 2048
})

store.set('version', 5)
}

export default store