diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3364bed1e..b176ebefb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1958,6 +1958,12 @@ "relink": { "message": "Relink" }, + "autoUpdateSettingTitle": { + "message": "Auto Update" + }, + "autoUpdateSettingDescription": { + "message": "Automatically check for updates on launch" + }, "autoUpdateNewVersionTitle": { "message": "Session update available" }, diff --git a/app/base_config.d.ts b/app/base_config.d.ts new file mode 100644 index 000000000..3aec7c607 --- /dev/null +++ b/app/base_config.d.ts @@ -0,0 +1,15 @@ +export interface BaseConfig { + set(keyPath: string, value: any): void; + get(keyPath: string): any | undefined; + remove(): void; +} + +interface Options { + allowMalformedOnStartup: boolean; +} + +export function start( + name: string, + targetPath: string, + options: Options +): BaseConfig; diff --git a/app/user_config.d.ts b/app/user_config.d.ts new file mode 100644 index 000000000..4b0a2282c --- /dev/null +++ b/app/user_config.d.ts @@ -0,0 +1,3 @@ +import { BaseConfig } from './base_config'; + +type UserConfig = BaseConfig; diff --git a/main.js b/main.js index 5a922e00c..06ffcffd1 100644 --- a/main.js +++ b/main.js @@ -427,7 +427,7 @@ async function readyForUpdates() { // Second, start checking for app updates try { - await updater.start(getMainWindow, locale.messages, logger); + await updater.start(getMainWindow, userConfig, locale.messages, logger); } catch (error) { const log = logger || console; log.error( @@ -1090,6 +1090,24 @@ ipc.on('set-media-permissions', (event, value) => { } }); +// Loki - Auto updating +ipc.on('get-auto-update-setting', event => { + const configValue = userConfig.get('autoUpdate'); + // eslint-disable-next-line no-param-reassign + event.returnValue = typeof configValue !== 'boolean' ? true : configValue; +}); + +ipc.on('set-auto-update-setting', (event, enabled) => { + userConfig.set('autoUpdate', !!enabled); + + if (enabled) { + readyForUpdates(); + } else { + updater.stop(); + isReadyForUpdates = false; + } +}); + function getDataFromMainWindow(name, callback) { ipc.once(`get-success-${name}`, (_event, error, value) => callback(error, value) diff --git a/preload.js b/preload.js index 789d14113..94fa47338 100644 --- a/preload.js +++ b/preload.js @@ -211,8 +211,11 @@ window.getSettingValue = (settingID, comparisonValue = null) => { // Eg. window.getSettingValue('theme', 'light') // returns 'false' when the value is 'dark'. + // We need to get specific settings from the main process if (settingID === 'media-permissions') { return window.getMediaPermissions(); + } else if (settingID === 'auto-update') { + return window.getAutoUpdateEnabled(); } const settingVal = window.storage.get(settingID); @@ -220,6 +223,12 @@ window.getSettingValue = (settingID, comparisonValue = null) => { }; window.setSettingValue = (settingID, value) => { + // For auto updating we need to pass the value to the main process + if (settingID === 'auto-update') { + window.setAutoUpdateEnabled(value); + return; + } + window.storage.put(settingID, value); if (settingID === 'zoom-factor-setting') { @@ -231,6 +240,11 @@ window.setSettingValue = (settingID, value) => { window.getMessageTTL = () => window.storage.get('message-ttl', 24); window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); +// Auto update setting +window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-setting'); +window.setAutoUpdateEnabled = value => + ipc.send('set-auto-update-setting', !!value); + ipc.on('get-ready-for-shutdown', async () => { const { shutdown } = window.Events || {}; if (!shutdown) { diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 233ef340e..01ab4dd16 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -496,6 +496,19 @@ export class SettingsView extends React.Component { content: {}, confirmationDialogParams: undefined, }, + { + id: 'auto-update', + title: window.i18n('autoUpdateSettingTitle'), + description: window.i18n('autoUpdateSettingDescription'), + hidden: false, + type: SessionSettingType.Toggle, + category: SessionSettingCategory.Privacy, + setFn: undefined, + comparisonValue: undefined, + onClick: undefined, + content: {}, + confirmationDialogParams: undefined, + }, { id: 'set-password', title: window.i18n('setAccountPasswordTitle'), diff --git a/ts/updater/index.ts b/ts/updater/index.ts index dc1ec1310..036344388 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -1,12 +1,15 @@ import { get as getFromConfig } from 'config'; import { BrowserWindow } from 'electron'; -import { start as startUpdater } from './updater'; +import { start as startUpdater, stop as stopUpdater } from './updater'; import { LoggerType, MessagesType } from './common'; +import { UserConfig } from '../../app/user_config'; let initialized = false; +let config: UserConfig; export async function start( getMainWindow: () => BrowserWindow, + userConfig: UserConfig, messages?: MessagesType, logger?: LoggerType ) { @@ -14,6 +17,7 @@ export async function start( throw new Error('updater/start: Updates have already been initialized!'); } initialized = true; + config = userConfig; if (!messages) { throw new Error('updater/start: Must provide messages!'); @@ -40,8 +44,18 @@ export async function start( await startUpdater(getMainWindow, messages, logger); } +export function stop() { + if (initialized) { + stopUpdater(); + initialized = false; + } +} + function autoUpdateDisabled() { return ( - process.mas || !getFromConfig('updatesEnabled') // From Electron: Mac App Store build + process.mas || // From Electron: Mac App Store build + !getFromConfig('updatesEnabled') || // Hard coded config + // tslint:disable-next-line: no-backbone-get-set-outside-model + !config.get('autoUpdate') // User setting ); } diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index abde36b06..efd12b1bf 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -3,6 +3,7 @@ import * as fs from 'fs-extra'; import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; + import { getPrintableError, LoggerType, @@ -15,6 +16,8 @@ import { gt as isVersionGreaterThan, parse as parseVersion } from 'semver'; let isUpdating = false; let downloadIgnored = false; +let interval: NodeJS.Timeout | undefined; +let stopped = false; const SECOND = 1000; const MINUTE = SECOND * 60; @@ -25,28 +28,43 @@ export async function start( messages: MessagesType, logger: LoggerType ) { + if (interval) { + logger.info('auto-update: Already running'); + + return; + } + logger.info('auto-update: starting checks...'); autoUpdater.logger = logger; autoUpdater.autoDownload = false; - setInterval(async () => { + interval = setInterval(async () => { try { await checkForUpdates(getMainWindow, messages, logger); } catch (error) { logger.error('auto-update: error:', getPrintableError(error)); } }, INTERVAL); + stopped = false; await checkForUpdates(getMainWindow, messages, logger); } +export function stop() { + if (interval) { + clearInterval(interval); + interval = undefined; + stopped = true; + } +} + async function checkForUpdates( getMainWindow: () => BrowserWindow, messages: MessagesType, logger: LoggerType ) { - if (isUpdating || downloadIgnored) { + if (stopped || isUpdating || downloadIgnored) { return; }