diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index 3fa6b64f..d30002f8 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -38,7 +38,8 @@ const availableDrives = require('./models/available-drives') const driveScanner = require('./modules/drive-scanner') const osDialog = require('./os/dialog') const exceptionReporter = require('./modules/exception-reporter') -const updateLock = require('./modules/update-lock') +// eslint-disable-next-line node/no-missing-require +const { updateLock } = require('./modules/update-lock') /* eslint-disable lodash/prefer-lodash-method,lodash/prefer-get */ diff --git a/lib/gui/app/components/finish/finish.tsx b/lib/gui/app/components/finish/finish.tsx index c76bc77a..1f670c99 100644 --- a/lib/gui/app/components/finish/finish.tsx +++ b/lib/gui/app/components/finish/finish.tsx @@ -23,7 +23,7 @@ import * as flashState from '../../models/flash-state'; import * as selectionState from '../../models/selection-state'; import * as store from '../../models/store'; import * as analytics from '../../modules/analytics'; -import * as updateLock from '../../modules/update-lock'; +import { updateLock } from '../../modules/update-lock'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { FlashAnother } from '../flash-another/flash-another'; import { FlashResults } from '../flash-results/flash-results'; diff --git a/lib/gui/app/modules/image-writer.js b/lib/gui/app/modules/image-writer.js index e23eff1d..6d8dfb1c 100644 --- a/lib/gui/app/modules/image-writer.js +++ b/lib/gui/app/modules/image-writer.js @@ -30,7 +30,8 @@ const permissions = require('../../../shared/permissions') // eslint-disable-next-line node/no-missing-require const windowProgress = require('../os/window-progress') const analytics = require('../modules/analytics') -const updateLock = require('./update-lock') +// eslint-disable-next-line node/no-missing-require +const { updateLock } = require('./update-lock') const packageJSON = require('../../../../package.json') const selectionState = require('../models/selection-state') diff --git a/lib/gui/app/modules/update-lock.js b/lib/gui/app/modules/update-lock.js deleted file mode 100644 index 01c4b41b..00000000 --- a/lib/gui/app/modules/update-lock.js +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2018 balena.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict' - -const electron = require('electron') -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 */ - -/** - * Interaction timeout in milliseconds (defaults to 5 minutes) - * @type {Number} - * @constant - */ -const INTERACTION_TIMEOUT_MS = settings.has('interactionTimeout') - ? parseInt(settings.get('interactionTimeout'), 10) - : 5 * 60 * 1000 - -/** - * Balena Update Lock - * @class - */ -class UpdateLock extends EventEmitter { - /** - * @summary Balena Update Lock - * @example - * new UpdateLock() - */ - constructor () { - super() - this.paused = false - this.on('inactive', UpdateLock.onInactive) - this.lockTimer = createInactivityTimer(INTERACTION_TIMEOUT_MS, () => { - debug('inactive') - this.emit('inactive') - }) - } - - /** - * @summary Inactivity event handler, releases the balena update lock on inactivity - * @private - * @example - * this.on('inactive', onInactive) - */ - static onInactive () { - if (settings.get('resinUpdateLock')) { - UpdateLock.check((checkError, isLocked) => { - debug('inactive-check', Boolean(checkError)) - if (checkError) { - analytics.logException(checkError) - } - if (isLocked) { - UpdateLock.release((error) => { - debug('inactive-release', Boolean(error)) - if (error) { - analytics.logException(error) - } - }) - } - }) - } - } - - /** - * @summary Acquire the update lock - * @private - * @param {Function} callback - callback(error) - * @example - * UpdateLock.acquire((error) => { - * // ... - * }) - */ - static acquire (callback) { - debug('lock') - if (settings.get('resinUpdateLock')) { - electron.ipcRenderer.once('resin-update-lock', (event, error) => { - callback(error) - }) - electron.ipcRenderer.send('resin-update-lock', 'lock') - } else { - callback(new Error('Update lock disabled')) - } - } - - /** - * @summary Release the update lock - * @private - * @param {Function} callback - callback(error) - * @example - * UpdateLock.release((error) => { - * // ... - * }) - */ - static release (callback) { - debug('unlock') - if (settings.get('resinUpdateLock')) { - electron.ipcRenderer.once('resin-update-lock', (event, error) => { - callback(error) - }) - electron.ipcRenderer.send('resin-update-lock', 'unlock') - } else { - callback(new Error('Update lock disabled')) - } - } - - /** - * @summary Check the state of the update lock - * @private - * @param {Function} callback - callback(error, isLocked) - * @example - * UpdateLock.check((error, isLocked) => { - * if (isLocked) { - * // ... - * } - * }) - */ - static check (callback) { - debug('check') - if (settings.get('resinUpdateLock')) { - electron.ipcRenderer.once('resin-update-lock', (event, error, isLocked) => { - callback(error, isLocked) - }) - electron.ipcRenderer.send('resin-update-lock', 'check') - } else { - callback(new Error('Update lock disabled')) - } - } - - /** - * @summary Extend the lock timer - * @example - * updateLock.extend() - */ - extend () { - debug('extend') - - if (this.paused) { - debug('extend:paused') - return - } - - this.lockTimer.signal() - - // When extending, check that we have the lock, - // and acquire it, if not - if (settings.get('resinUpdateLock')) { - UpdateLock.check((checkError, isLocked) => { - if (checkError) { - analytics.logException(checkError) - } - if (!isLocked) { - UpdateLock.acquire((error) => { - if (error) { - analytics.logException(error) - } - debug('extend-acquire', Boolean(error)) - }) - } - }) - } - } - - /** - * @summary Clear the lock timer - * @example - * updateLock.clearTimer() - */ - clearTimer () { - debug('clear') - this.lockTimer.clear() - } - - /** - * @summary Clear the lock timer, and pause extension, avoiding triggering until resume()d - * @example - * updateLock.pause() - */ - pause () { - debug('pause') - this.paused = true - this.clearTimer() - } - - /** - * @summary Un-pause lock extension, and restart the timer - * @example - * updateLock.resume() - */ - resume () { - debug('resume') - this.paused = false - this.extend() - } -} - -module.exports = new UpdateLock() diff --git a/lib/gui/app/modules/update-lock.ts b/lib/gui/app/modules/update-lock.ts new file mode 100644 index 00000000..978e9c43 --- /dev/null +++ b/lib/gui/app/modules/update-lock.ts @@ -0,0 +1,188 @@ +/* + * Copyright 2018 balena.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _debug from 'debug'; +import * as electron from 'electron'; +import { EventEmitter } from 'events'; +import * as createInactivityTimer from 'inactivity-timer'; + +import * as settings from '../models/settings'; +import { logException } from './analytics'; + +const debug = _debug('etcher:update-lock'); + +/** + * Interaction timeout in milliseconds (defaults to 5 minutes) + * @type {Number} + * @constant + */ +const INTERACTION_TIMEOUT_MS = settings.has('interactionTimeout') + ? parseInt(settings.get('interactionTimeout'), 10) + : 5 * 60 * 1000; + +class UpdateLock extends EventEmitter { + private paused: boolean; + private lockTimer: any; + + constructor() { + super(); + this.paused = false; + this.on('inactive', UpdateLock.onInactive); + this.lockTimer = createInactivityTimer(INTERACTION_TIMEOUT_MS, () => { + debug('inactive'); + this.emit('inactive'); + }); + } + + /** + * @summary Inactivity event handler, releases the balena update lock on inactivity + */ + private static onInactive() { + if (settings.get('resinUpdateLock')) { + UpdateLock.check((checkError: Error, isLocked: boolean) => { + debug('inactive-check', Boolean(checkError)); + if (checkError) { + logException(checkError); + } + if (isLocked) { + UpdateLock.release((error?: Error) => { + debug('inactive-release', Boolean(error)); + if (error) { + logException(error); + } + }); + } + }); + } + } + + /** + * @summary Acquire the update lock + */ + private static acquire(callback: (error?: Error) => void) { + debug('lock'); + if (settings.get('resinUpdateLock')) { + electron.ipcRenderer.once('resin-update-lock', (_event, error) => { + callback(error); + }); + electron.ipcRenderer.send('resin-update-lock', 'lock'); + } else { + callback(new Error('Update lock disabled')); + } + } + + /** + * @summary Release the update lock + */ + public static release(callback: (error?: Error) => void) { + debug('unlock'); + if (settings.get('resinUpdateLock')) { + electron.ipcRenderer.once('resin-update-lock', (_event, error) => { + callback(error); + }); + electron.ipcRenderer.send('resin-update-lock', 'unlock'); + } else { + callback(new Error('Update lock disabled')); + } + } + + /** + * @summary Check the state of the update lock + * @param {Function} callback - callback(error, isLocked) + * @example + * UpdateLock.check((error, isLocked) => { + * if (isLocked) { + * // ... + * } + * }) + */ + private static check( + callback: (error: Error | null, isLocked?: boolean) => void, + ) { + debug('check'); + if (settings.get('resinUpdateLock')) { + electron.ipcRenderer.once( + 'resin-update-lock', + (_event, error, isLocked) => { + callback(error, isLocked); + }, + ); + electron.ipcRenderer.send('resin-update-lock', 'check'); + } else { + callback(new Error('Update lock disabled')); + } + } + + /** + * @summary Extend the lock timer + */ + public extend() { + debug('extend'); + + if (this.paused) { + debug('extend:paused'); + return; + } + + this.lockTimer.signal(); + + // When extending, check that we have the lock, + // and acquire it, if not + if (settings.get('resinUpdateLock')) { + UpdateLock.check((checkError, isLocked) => { + if (checkError) { + logException(checkError); + } + if (!isLocked) { + UpdateLock.acquire(error => { + if (error) { + logException(error); + } + debug('extend-acquire', Boolean(error)); + }); + } + }); + } + } + + /** + * @summary Clear the lock timer + */ + private clearTimer() { + debug('clear'); + this.lockTimer.clear(); + } + + /** + * @summary Clear the lock timer, and pause extension, avoiding triggering until resume()d + */ + public pause() { + debug('pause'); + this.paused = true; + this.clearTimer(); + } + + /** + * @summary Un-pause lock extension, and restart the timer + */ + public resume() { + debug('resume'); + this.paused = false; + this.extend(); + } +} + +export const updateLock = new UpdateLock();