diff --git a/assets/locales/en.json b/assets/locales/en.json index 6127977ec..dfe9dc037 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -42,6 +42,9 @@ "moveRepositoryLocation": "Move Repository Location", "runGarbageCollector": "Run Garbage Collector", "selectDirectory": "Select Directory", + "customIpfsBinary": "Custom IPFS Binary", + "setCustomIpfsBinary": "Set Custom IPFS Binary", + "clearCustomIpfsBinary": "Clear Custom IPFS Binary", "polkitDialog": { "title": "Polkit not found", "message": "IPFS can't be added to /usr/local/bin/ without polkit agent." @@ -224,5 +227,17 @@ "downloadHashShortcut": "Global Download Shortcut", "experiments": "Experiments", "npmOnIpfs": "npm on IPFS" + }, + "setCustomIpfsBinaryConfirmation": { + "title": "Custom IPFS binary", + "message": "By setting a custom IPFS, IPFS Desktop will no longer use the bundled IPFS version and will use the binary you specify. Do you wish to proceed?" + }, + "setCustomIpfsBinarySuccess": { + "title": "Custom IPFS binary", + "message": "IPFS Desktop will start using the binary located at { path }. To start using it, IPFS needs to be restarted first." + }, + "clearCustomIpfsBinarySuccess": { + "title": "Clear custom IPFS binary", + "message": "The custom IPFS binary was cleared. To start using the bundled IPFS version, IPFS needs to be restarted first." } } diff --git a/src/custom-ipfs-binary.js b/src/custom-ipfs-binary.js new file mode 100644 index 000000000..9884b921f --- /dev/null +++ b/src/custom-ipfs-binary.js @@ -0,0 +1,93 @@ +const i18n = require('i18next') +const { app, dialog } = require('electron') +const { showDialog } = require('./dialogs') +const logger = require('./common/logger') +const store = require('./common/store') +const dock = require('./utils/dock') + +const SETTINGS_KEY = 'binaryPath' + +async function setCustomBinary (ctx) { + await dock.run(async () => { + logger.info('[custom binary] request to change') + let opt = showDialog({ + showDock: false, + title: i18n.t('setCustomIpfsBinaryConfirmation.title'), + message: i18n.t('setCustomIpfsBinaryConfirmation.message'), + type: 'warning', + buttons: [ + i18n.t('yes'), + i18n.t('no') + ] + }) + + if (opt !== 0) { + logger.info('[custom binary] user canceled') + return + } + + const { canceled, filePaths } = await dialog.showOpenDialog({ + title: i18n.t('pickCustomIpfsBinary'), + defaultPath: app.getPath('home'), + properties: ['openFile'] + }) + + if (canceled || filePaths.length === 0) { + logger.info('[custom binary] user canceled') + return + } + + store.set(SETTINGS_KEY, filePaths[0]) + + opt = showDialog({ + showDock: false, + title: i18n.t('setCustomIpfsBinarySuccess.title'), + message: i18n.t('setCustomIpfsBinarySuccess.message', { path: filePaths[0] }), + buttons: [ + i18n.t('restart'), + i18n.t('close') + ] + }) + + logger.info(`[custom binary] updated to ${filePaths[0]}`) + + if (opt === 0) { + ctx.restartIpfs() + } + }) +} + +function clearCustomBinary (ctx) { + store.delete(SETTINGS_KEY) + logger.info('[custom binary] cleared') + + const opt = showDialog({ + title: i18n.t('clearCustomIpfsBinarySuccess.title'), + message: i18n.t('clearCustomIpfsBinarySuccess.message'), + buttons: [ + i18n.t('restart'), + i18n.t('close') + ] + }) + + if (opt === 0) { + ctx.restartIpfs() + } +} + +function hasCustomBinary () { + return typeof store.get(SETTINGS_KEY) === 'string' +} + +function getCustomBinary () { + if (hasCustomBinary()) { + return store.get(SETTINGS_KEY) + } +} + +module.exports = { + setCustomBinary, + clearCustomBinary, + hasCustomBinary, + getCustomBinary +} diff --git a/src/daemon/daemon.js b/src/daemon/daemon.js index b8ad10d0b..715814704 100644 --- a/src/daemon/daemon.js +++ b/src/daemon/daemon.js @@ -1,8 +1,12 @@ const Ctl = require('ipfsd-ctl') const i18n = require('i18next') +const fs = require('fs-extra') +const { join } = require('path') +const { app } = require('electron') const { showDialog } = require('../dialogs') const logger = require('../common/logger') const { applyDefaults, checkCorsConfig, checkPorts, configExists, rmApiFile, apiFileExists } = require('./config') +const { getCustomBinary } = require('../custom-ipfs-binary') function cannotConnectDialog (addr) { showDialog({ @@ -16,15 +20,28 @@ function cannotConnectDialog (addr) { } function getIpfsBinPath () { - return require('go-ipfs-dep') - .path() - .replace('app.asar', 'app.asar.unpacked') + return process.env.IPFS_GO_EXEC || + getCustomBinary() || + require('go-ipfs-dep') + .path() + .replace('app.asar', 'app.asar.unpacked') +} + +function writeIpfsBinaryPath (path) { + fs.outputFileSync( + join(app.getPath('home'), './.ipfs-desktop/IPFS_EXEC') + .replace('app.asar', 'app.asar.unpacked'), + path + ) } async function spawn ({ flags, path, keysize }) { + const ipfsBin = getIpfsBinPath() + writeIpfsBinaryPath(ipfsBin) + const ipfsd = await Ctl.createController({ ipfsHttpModule: require('ipfs-http-client'), - ipfsBin: getIpfsBinPath(), + ipfsBin, ipfsOptions: { repo: path }, diff --git a/src/ipfs-on-path/scripts/bin-win/ipfs.cmd b/src/ipfs-on-path/scripts/bin-win/ipfs.cmd index 4f534ccb1..ea90912a5 100644 --- a/src/ipfs-on-path/scripts/bin-win/ipfs.cmd +++ b/src/ipfs-on-path/scripts/bin-win/ipfs.cmd @@ -4,4 +4,5 @@ if exist "%USERPROFILE%\.ipfs-desktop\IPFS_PATH" ( SET /P IPFS_PATH=<"%USERPROFILE%\.ipfs-desktop\IPFS_PATH" ) -"%~dp0\..\..\..\..\node_modules\go-ipfs-dep\go-ipfs\ipfs.exe" %* +SET /P IPFS_EXEC=<"%USERPROFILE%\.ipfs-desktop\IPFS_EXEC" +%IPFS_EXEC% %* diff --git a/src/ipfs-on-path/scripts/ipfs.sh b/src/ipfs-on-path/scripts/ipfs.sh index 9c90c420c..b967c5bfa 100755 --- a/src/ipfs-on-path/scripts/ipfs.sh +++ b/src/ipfs-on-path/scripts/ipfs.sh @@ -8,7 +8,8 @@ fi # Get the full path of the app directory (resolving the symlink if needed) app=$(dirname "$(dirname "$(dirname "$dir")")") + # Get the full path to ipfs binary bundled with ipfs-desktop -ipfs="$app/node_modules/go-ipfs-dep/go-ipfs/ipfs" +ipfs="$(cat ~/.ipfs-desktop/IPFS_EXEC)" exec "$ipfs" "$@" diff --git a/src/tray.js b/src/tray.js index 5c5ab5c51..51d86117b 100644 --- a/src/tray.js +++ b/src/tray.js @@ -6,6 +6,7 @@ const logger = require('./common/logger') const store = require('./common/store') const moveRepositoryLocation = require('./move-repository-location') const runGarbageCollector = require('./run-gc') +const { setCustomBinary, clearCustomBinary, hasCustomBinary } = require('./custom-ipfs-binary') const { STATUS } = require('./daemon') const { IS_MAC, IS_WIN, VERSION, GO_IPFS_VERSION } = require('./common/consts') @@ -145,16 +146,29 @@ function buildMenu (ctx) { click: () => { shell.openItem(store.path) } }, { type: 'separator' }, + { + id: 'runGarbageCollector', + label: i18n.t('runGarbageCollector'), + click: () => { runGarbageCollector(ctx) }, + enabled: false + }, + { type: 'separator' }, { id: 'moveRepositoryLocation', label: i18n.t('moveRepositoryLocation'), click: () => { moveRepositoryLocation(ctx) } }, { - id: 'runGarbageCollector', - label: i18n.t('runGarbageCollector'), - click: () => { runGarbageCollector(ctx) }, - enabled: false + id: 'setCustomBinary', + label: i18n.t('setCustomIpfsBinary'), + click: () => { setCustomBinary(ctx) }, + visible: false + }, + { + id: 'clearCustomBinary', + label: i18n.t('clearCustomIpfsBinary'), + click: () => { clearCustomBinary(ctx) }, + visible: false } ] }, @@ -170,7 +184,9 @@ function buildMenu (ctx) { click: () => { shell.openExternal('https://github.com/ipfs-shipyard/ipfs-desktop/releases') } }, { - label: `go-ipfs ${GO_IPFS_VERSION}`, + label: hasCustomBinary() + ? i18n.t('customIpfsBinary') + : `go-ipfs ${GO_IPFS_VERSION}`, click: () => { shell.openExternal('https://github.com/ipfs/go-ipfs/releases') } }, { type: 'separator' }, @@ -278,6 +294,9 @@ module.exports = function (ctx) { menu.getMenuItemById('moveRepositoryLocation').enabled = !gcRunning && status !== STATUS.STOPPING_STARTED menu.getMenuItemById('runGarbageCollector').enabled = menu.getMenuItemById('ipfsIsRunning').visible && !gcRunning + menu.getMenuItemById('setCustomBinary').visible = !hasCustomBinary() + menu.getMenuItemById('clearCustomBinary').visible = hasCustomBinary() + if (status === STATUS.STARTING_FINISHED) { tray.setImage(icon(on)) } else {