diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index 1e3a8fbd..3b58070b 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -64,9 +64,7 @@ window.localStorage.debug = process.env.DEBUG * @constant * @private */ -const BLACKLISTED_DRIVES = process.env.ETCHER_BLACKLISTED_DRIVES - ? process.env.ETCHER_BLACKLISTED_DRIVES.split(',') - : [] +const BLACKLISTED_DRIVES = settings.get('driveBlacklist') const app = angular.module('Etcher', [ require('angular-ui-router'), @@ -362,7 +360,7 @@ app.controller('HeaderController', function (OSOpenExternalService) { * HeaderController.shouldShowHelp() */ this.shouldShowHelp = () => { - return !process.env.ETCHER_DISABLE_EXTERNAL_LINKS + return !settings.get('disableExternalLinks') } }) diff --git a/lib/gui/app/components/file-selector/controllers/file-selector.js b/lib/gui/app/components/file-selector/controllers/file-selector.js index b56057b3..9d89e089 100644 --- a/lib/gui/app/components/file-selector/controllers/file-selector.js +++ b/lib/gui/app/components/file-selector/controllers/file-selector.js @@ -16,6 +16,8 @@ 'use strict' +const settings = require('../../../models/settings') + module.exports = function ( $uibModalInstance ) { @@ -43,6 +45,7 @@ module.exports = function ( */ this.getFolderConstraint = () => { // TODO(Shou): get this dynamically from the mountpoint of a specific port in Etcher Pro - return process.env.ETCHER_FILE_BROWSER_CONSTRAIN_FOLDER + // TODO: Make this handle multiple constraints + return settings.get('fileBrowserConstraintPath') } } diff --git a/lib/gui/app/components/safe-webview.js b/lib/gui/app/components/safe-webview.js index 487185df..4d0c2a02 100644 --- a/lib/gui/app/components/safe-webview.js +++ b/lib/gui/app/components/safe-webview.js @@ -25,6 +25,7 @@ const react = require('react') const propTypes = require('prop-types') const { react2angular } = require('react2angular') const analytics = require('../modules/analytics') +const settings = require('../models/settings') const packageJSON = require('../../../../package.json') const MODULE_NAME = 'Etcher.Components.SafeWebview' @@ -217,7 +218,7 @@ class SafeWebview extends react.PureComponent { event.disposition === 'foreground-tab', // Don't open links if they're disabled by the env var - !process.env.ETCHER_DISABLE_EXTERNAL_LINKS + !settings.get('disableExternalLinks') ])) { electron.shell.openExternal(url.href) } diff --git a/lib/gui/app/components/update-notifier.js b/lib/gui/app/components/update-notifier.js index e23c4428..f886361a 100644 --- a/lib/gui/app/components/update-notifier.js +++ b/lib/gui/app/components/update-notifier.js @@ -62,7 +62,7 @@ const currentWindow = electron.remote.getCurrentWindow() * } */ exports.shouldCheckForUpdates = (options) => { - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { return false } diff --git a/lib/gui/app/models/local-settings.js b/lib/gui/app/models/local-settings.js index 3405b5fc..33ddc992 100644 --- a/lib/gui/app/models/local-settings.js +++ b/lib/gui/app/models/local-settings.js @@ -36,10 +36,14 @@ const JSON_INDENT = 2 * - `$XDG_CONFIG_HOME/etcher` or `~/.config/etcher` on Linux * - `~/Library/Application Support/etcher` on macOS * See https://electronjs.org/docs/api/app#appgetpathname + * NOTE: The ternary is due to this module being loaded both, + * in Electron's main and renderer processes * @constant * @type {String} */ -const USER_DATA_DIR = electron.remote.app.getPath('userData') +const USER_DATA_DIR = electron.app + ? electron.app.getPath('userData') + : electron.remote.app.getPath('userData') /** * @summary Configuration file path @@ -64,15 +68,21 @@ const CONFIG_PATH = path.join(USER_DATA_DIR, 'config.json') */ const readConfigFile = (filename) => { return new Bluebird((resolve, reject) => { - fs.readFile(filename, (error, buffer) => { + fs.readFile(filename, { encoding: 'utf8' }, (error, contents) => { + let data = {} if (error) { if (error.code === 'ENOENT') { - resolve({}) + resolve(data) } else { reject(error) } } else { - resolve(JSON.parse(buffer.toString())) + try { + data = JSON.parse(contents) + } catch (parseError) { + console.error(parseError) + } + resolve(data) } }) }) diff --git a/lib/gui/app/models/settings.js b/lib/gui/app/models/settings.js index 3364d69c..371922b2 100644 --- a/lib/gui/app/models/settings.js +++ b/lib/gui/app/models/settings.js @@ -184,6 +184,22 @@ exports.get = (key) => { return _.cloneDeep(_.get(settings, [ key ])) } +/** + * @summary Check if setting value exists + * @function + * @public + * + * @param {String} key - setting key + * @returns {Boolean} exists + * + * @example + * const hasValue = settings.has('unmountOnSuccess'); + */ +exports.has = (key) => { + /* eslint-disable no-eq-null */ + return settings[key] != null +} + /** * @summary Get all setting values * @function diff --git a/lib/gui/app/models/store.js b/lib/gui/app/models/store.js index 262a632a..7c79a955 100644 --- a/lib/gui/app/models/store.js +++ b/lib/gui/app/models/store.js @@ -25,6 +25,7 @@ const supportedFormats = require('../../../shared/supported-formats') const errors = require('../../../shared/errors') const fileExtensions = require('../../../shared/file-extensions') const utils = require('../../../shared/utils') +const settings = require('./settings') /** * @summary Verify and throw if any state fields are nil @@ -184,7 +185,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { return accState }, newState) - const shouldAutoselectAll = Boolean(process.env.ETCHER_DISABLE_EXPLICIT_DRIVE_SELECTION) + const shouldAutoselectAll = Boolean(settings.get('disableExplicitDriveSelection')) const AUTOSELECT_DRIVE_COUNT = 1 const nonStaleSelectedDevices = nonStaleNewState.getIn([ 'selection', 'devices' ]).toJS() const hasSelectedDevices = nonStaleSelectedDevices.length >= AUTOSELECT_DRIVE_COUNT diff --git a/lib/gui/app/modules/analytics.js b/lib/gui/app/modules/analytics.js index e4952d97..a6f0447d 100644 --- a/lib/gui/app/modules/analytics.js +++ b/lib/gui/app/modules/analytics.js @@ -23,9 +23,9 @@ const settings = require('../models/settings') resinCorvus.install({ services: { - sentry: process.env.ANALYTICS_SENTRY_TOKEN || + sentry: settings.get('analyticsSentryToken') || _.get(packageJSON, [ 'analytics', 'sentry', 'token' ]), - mixpanel: process.env.ANALYTICS_MIXPANEL_TOKEN || + mixpanel: settings.get('analyticsMixpanelToken') || _.get(packageJSON, [ 'analytics', 'mixpanel', 'token' ]) }, options: { diff --git a/lib/gui/app/modules/drive-scanner.js b/lib/gui/app/modules/drive-scanner.js index c96bdd48..5dbe78c2 100644 --- a/lib/gui/app/modules/drive-scanner.js +++ b/lib/gui/app/modules/drive-scanner.js @@ -23,7 +23,7 @@ const permissions = require('../../../shared/permissions') const scanner = SDK.createScanner({ blockdevice: { get includeSystemDrives () { - return settings.get('unsafeMode') && !process.env.ETCHER_HIDE_UNSAFE_MODE + return settings.get('unsafeMode') && !settings.get('disableUnsafeMode') } }, usbboot: {} diff --git a/lib/gui/app/modules/update-lock.js b/lib/gui/app/modules/update-lock.js index 397790f8..43fb1fb6 100644 --- a/lib/gui/app/modules/update-lock.js +++ b/lib/gui/app/modules/update-lock.js @@ -21,6 +21,7 @@ const EventEmitter = require('events') const createInactivityTimer = require('inactivity-timer') const debug = require('debug')('etcher:update-lock') const analytics = require('./analytics') +const settings = require('../models/settings') /* eslint-disable no-magic-numbers, callback-return */ @@ -29,8 +30,8 @@ const analytics = require('./analytics') * @type {Number} * @constant */ -const INTERACTION_TIMEOUT_MS = process.env.ETCHER_INTERACTION_TIMEOUT_MS - ? parseInt(process.env.ETCHER_INTERACTION_TIMEOUT_MS, 10) +const INTERACTION_TIMEOUT_MS = settings.has('interactionTimeout') + ? parseInt(settings.get('interactionTimeout'), 10) : 5 * 60 * 1000 /** @@ -60,7 +61,7 @@ class UpdateLock extends EventEmitter { * this.on('inactive', onInactive) */ static onInactive () { - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { UpdateLock.check((checkError, isLocked) => { debug('inactive-check', Boolean(checkError)) if (checkError) { @@ -89,7 +90,7 @@ class UpdateLock extends EventEmitter { */ static acquire (callback) { debug('lock') - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { electron.ipcRenderer.once('resin-update-lock', (event, error) => { callback(error) }) @@ -110,7 +111,7 @@ class UpdateLock extends EventEmitter { */ static release (callback) { debug('unlock') - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { electron.ipcRenderer.once('resin-update-lock', (event, error) => { callback(error) }) @@ -133,7 +134,7 @@ class UpdateLock extends EventEmitter { */ static check (callback) { debug('check') - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { electron.ipcRenderer.once('resin-update-lock', (event, error, isLocked) => { callback(error, isLocked) }) @@ -160,7 +161,7 @@ class UpdateLock extends EventEmitter { // When extending, check that we have the lock, // and acquire it, if not - if (process.env.ELECTRON_RESIN_UPDATE_LOCK) { + if (settings.get('resinUpdateLock')) { UpdateLock.check((checkError, isLocked) => { if (checkError) { analytics.logException(checkError) diff --git a/lib/gui/app/os/open-external/services/open-external.js b/lib/gui/app/os/open-external/services/open-external.js index 6294eb02..97d21e1c 100644 --- a/lib/gui/app/os/open-external/services/open-external.js +++ b/lib/gui/app/os/open-external/services/open-external.js @@ -18,6 +18,7 @@ const electron = require('electron') const analytics = require('../../../modules/analytics') +const settings = require('../../../models/settings') module.exports = function () { /** @@ -32,7 +33,7 @@ module.exports = function () { */ this.open = (url) => { // Don't open links if they're disabled by the env var - if (process.env.ETCHER_DISABLE_EXTERNAL_LINKS) { + if (settings.get('disableExternalLinks')) { return } diff --git a/lib/gui/app/pages/main/controllers/drive-selection.js b/lib/gui/app/pages/main/controllers/drive-selection.js index 8a834616..b099ec89 100644 --- a/lib/gui/app/pages/main/controllers/drive-selection.js +++ b/lib/gui/app/pages/main/controllers/drive-selection.js @@ -109,7 +109,7 @@ module.exports = function (DriveSelectorService) { analytics.logEvent('Select drive', { device: drive.device, - unsafeMode: settings.get('unsafeMode') && !process.env.ETCHER_HIDE_UNSAFE_MODE + unsafeMode: settings.get('unsafeMode') && !settings.get('disableUnsafeMode') }) }).catch(exceptionReporter.report) } @@ -148,6 +148,6 @@ module.exports = function (DriveSelectorService) { * DriveSelectionController.shouldShowDrivesButton() */ this.shouldShowDrivesButton = () => { - return !process.env.ETCHER_DISABLE_EXPLICIT_DRIVE_SELECTION + return !settings.get('disableExplicitDriveSelection') } } diff --git a/lib/gui/app/pages/main/controllers/image-selection.js b/lib/gui/app/pages/main/controllers/image-selection.js index 6a7be5bb..9485b46a 100644 --- a/lib/gui/app/pages/main/controllers/image-selection.js +++ b/lib/gui/app/pages/main/controllers/image-selection.js @@ -24,6 +24,7 @@ const errors = require('../../../../../shared/errors') const imageStream = require('../../../../../sdk/image-stream') const supportedFormats = require('../../../../../shared/supported-formats') const analytics = require('../../../modules/analytics') +const settings = require('../../../models/settings') const selectionState = require('../../../models/selection-state') const osDialog = require('../../../os/dialog') const exceptionReporter = require('../../../modules/exception-reporter') @@ -156,7 +157,7 @@ module.exports = function ( this.openImageSelector = () => { analytics.logEvent('Open image selector') - if (process.env.ETCHER_EXPERIMENTAL_FILE_PICKER) { + if (settings.get('experimentalFilePicker')) { FileSelectorService.open() } else { osDialog.selectImage().then((imagePath) => { diff --git a/lib/gui/app/pages/settings/controllers/settings.js b/lib/gui/app/pages/settings/controllers/settings.js index be56f1b6..96d6333d 100644 --- a/lib/gui/app/pages/settings/controllers/settings.js +++ b/lib/gui/app/pages/settings/controllers/settings.js @@ -116,6 +116,6 @@ module.exports = function (WarningModalService) { * SettingsController.shouldShowUnsafeMode() */ this.shouldShowUnsafeMode = () => { - return !process.env.ETCHER_HIDE_UNSAFE_MODE + return !settings.get('disableUnsafeMode') } } diff --git a/lib/gui/etcher.js b/lib/gui/etcher.js index 235000be..9a73fe9b 100644 --- a/lib/gui/etcher.js +++ b/lib/gui/etcher.js @@ -20,31 +20,29 @@ const electron = require('electron') const path = require('path') const EXIT_CODES = require('../shared/exit-codes') const buildWindowMenu = require('./menu') +const settings = require('./app/models/settings') + +/* eslint-disable lodash/prefer-lodash-method */ + +const config = settings.getDefaults() let mainWindow = null -electron.app.on('window-all-closed', electron.app.quit) - -// Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers -// a `beforeunload` window event handler results in a disconnected white -// browser window in GNU/Linux and macOS. -// The `before-quit` Electron event is triggered in `SIGINT`, so we can -// make use of it to ensure the browser window is completely destroyed. -// See https://github.com/electron/electron/issues/5273 -electron.app.on('before-quit', () => { - process.exit(EXIT_CODES.SUCCESS) -}) - -electron.app.on('ready', () => { +/** + * @summary Create Etcher's main window + * @example + * electron.app.on('ready', createMainWindow) + */ +const createMainWindow = () => { mainWindow = new electron.BrowserWindow({ width: 800, height: 380, useContentSize: true, show: false, - resizable: Boolean(process.env.ETCHER_FULLSCREEN), + resizable: Boolean(config.fullscreen), maximizable: false, - fullscreen: Boolean(process.env.ETCHER_FULLSCREEN), - fullscreenable: Boolean(process.env.ETCHER_FULLSCREEN), - kiosk: Boolean(process.env.ETCHER_FULLSCREEN), + fullscreen: Boolean(config.fullscreen), + fullscreenable: Boolean(config.fullscreen), + kiosk: Boolean(config.fullscreen), autoHideMenuBar: true, titleBarStyle: 'hidden-inset', icon: path.join(__dirname, '..', '..', 'assets', 'icon.png'), @@ -57,7 +55,10 @@ electron.app.on('ready', () => { buildWindowMenu(mainWindow) // Prevent flash of white when starting the application - mainWindow.on('ready-to-show', mainWindow.show) + mainWindow.on('ready-to-show', () => { + console.timeEnd('ready-to-show') + mainWindow.show() + }) mainWindow.on('closed', () => { mainWindow = null @@ -81,4 +82,32 @@ electron.app.on('ready', () => { }) mainWindow.loadURL(`file://${path.join(__dirname, 'app', 'index.html')}`) +} + +electron.app.on('window-all-closed', electron.app.quit) + +// Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers +// a `beforeunload` window event handler results in a disconnected white +// browser window in GNU/Linux and macOS. +// The `before-quit` Electron event is triggered in `SIGINT`, so we can +// make use of it to ensure the browser window is completely destroyed. +// See https://github.com/electron/electron/issues/5273 +electron.app.on('before-quit', () => { + process.exit(EXIT_CODES.SUCCESS) }) + +settings.load().then((localSettings) => { + Object.assign(config, localSettings) +}).catch((error) => { + // TODO: What do if loading the config fails? + console.error('Error loading settings:') + console.error(error) +}).finally(() => { + if (electron.app.isReady()) { + createMainWindow() + } else { + electron.app.on('ready', createMainWindow) + } +}) + +console.time('ready-to-show')