From 48c5f58a8e7519f193712f164b36483c29c2c12b Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Fri, 18 Feb 2022 15:44:00 -0300 Subject: [PATCH] Multiple fixes in auto updates. - Add "disable auto updates" setting (prevents downloading updates in the background but will still notify if there are newer versions) - Prevent downloading multiple times the same update - Hide nag when auto update modal is displayed --- electron/index.js | 41 ++++++++++++++++++- static/app-strings.json | 2 + ui/component/app/index.js | 2 + ui/component/app/view.jsx | 4 +- .../settingDisableAutoUpdates/index.js | 17 ++++++++ .../settingDisableAutoUpdates/view.jsx | 30 ++++++++++++++ ui/component/settingSystem/view.jsx | 9 ++++ ui/constants/settings.js | 1 + ui/index.jsx | 6 +++ ui/redux/selectors/app.js | 5 +++ 10 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 ui/component/settingDisableAutoUpdates/index.js create mode 100644 ui/component/settingDisableAutoUpdates/view.jsx diff --git a/electron/index.js b/electron/index.js index a40822e4e..971a2c50e 100644 --- a/electron/index.js +++ b/electron/index.js @@ -39,6 +39,12 @@ let autoUpdateDownloaded = false; // that we show on Windows after you decline an upgrade and close the app later. let showingAutoUpdateCloseAlert = false; +// This is used to prevent downloading updates multiple times. +// As read in the documentation: +// "Calling autoUpdater.checkForUpdates() twice will download the update two times." +// https://www.electronjs.org/docs/latest/api/auto-updater#autoupdatercheckforupdates +let keepCheckingForUpdates = true; + // Keep a global reference, if you don't, they will be closed automatically when the JavaScript // object is garbage collected. let rendererWindow; @@ -315,16 +321,49 @@ ipcMain.on('upgrade', (event, installerPath) => { }); ipcMain.on('check-for-updates', () => { + // Prevent downloading the same update multiple times. + if (!keepCheckingForUpdates) { + return; + } + + keepCheckingForUpdates = false; autoUpdater.checkForUpdates(); }); autoUpdater.on('update-downloaded', () => { autoUpdateDownloaded = true; + + // If this download was trigger by + // autoUpdateAccepted it means, the user + // wants to install the new update but + // needed to downloaded the files first. + if (appState.autoUpdateAccepted) { + autoUpdater.quitAndInstall(); + } +}); + +autoUpdater.on('update-not-available', () => { + keepCheckingForUpdates = true; }); ipcMain.on('autoUpdateAccepted', () => { appState.autoUpdateAccepted = true; - autoUpdater.quitAndInstall(); + + // quitAndInstall can only be called if the + // update has been downloaded. Since the user + // can disable auto updates, we have to make + // sure it has been downloaded first. + if (autoUpdateDownloaded) { + autoUpdater.quitAndInstall(); + return; + } + + // If the update hasn't been downloaded, + // start downloading it. After it's done, the + // event 'update-downloaded' will be triggered, + // where we will be able to resume the + // update installation. + autoUpdater.downloadUpdate(); }); ipcMain.on('version-info-requested', () => { diff --git a/static/app-strings.json b/static/app-strings.json index dd7cfc530..831f549a8 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2285,5 +2285,7 @@ "Enable Automatic Hosting": "Enable Automatic Hosting", "Download and serve arbitrary data on the network.": "Download and serve arbitrary data on the network.", "View History Hosting": "View History Hosting", + "Disable automatic updates": "Disable automatic updates", + "Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)": "Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)", "--end--": "--end--" } diff --git a/ui/component/app/index.js b/ui/component/app/index.js index 114acd188..d6aca8a9b 100644 --- a/ui/component/app/index.js +++ b/ui/component/app/index.js @@ -19,6 +19,7 @@ import { selectAutoUpdateDownloaded, selectModal, selectActiveChannelClaim, + selectIsUpdateModelDisplayed, } from 'redux/selectors/app'; import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings'; import { doSyncLoop } from 'redux/actions/sync'; @@ -50,6 +51,7 @@ const select = (state) => ({ myChannelUrls: selectMyChannelUrls(state), myChannelClaimIds: selectMyChannelClaimIds(state), subscriptions: selectSubscriptions(state), + isUpdateModalDisplayed: selectIsUpdateModelDisplayed(state), }); const perform = (dispatch) => ({ diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index e442a7fcd..5ee2c452b 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -76,6 +76,7 @@ type Props = { fetchModBlockedList: () => void, resolveUris: (Array) => void, fetchModAmIList: () => void, + isUpdateModalDisplayed: boolean, }; function App(props: Props) { @@ -111,6 +112,7 @@ function App(props: Props) { resolveUris, subscriptions, fetchModAmIList, + isUpdateModalDisplayed, } = props; const appRef = useRef(); @@ -345,7 +347,7 @@ function App(props: Props) { {isEnhancedLayout && } - {showUpgradeButton && ( + {showUpgradeButton && !isUpdateModalDisplayed && ( { + return { + disableAutoUpdates: makeSelectClientSetting(SETTINGS.DISABLE_AUTO_UPDATES)(state), + }; +}; + +const perform = (dispatch) => ({ + setClientSetting: (value) => dispatch(doSetClientSetting(SETTINGS.DISABLE_AUTO_UPDATES, value)), +}); + +export default connect(select, perform)(SettingDisableAutoUpdates); diff --git a/ui/component/settingDisableAutoUpdates/view.jsx b/ui/component/settingDisableAutoUpdates/view.jsx new file mode 100644 index 000000000..96412c786 --- /dev/null +++ b/ui/component/settingDisableAutoUpdates/view.jsx @@ -0,0 +1,30 @@ +// @flow +import React from 'react'; +import * as remote from '@electron/remote'; +import { FormField } from 'component/common/form'; + +const { autoUpdater } = remote.require('electron-updater'); + +type Props = { + setClientSetting: (boolean) => void, + disableAutoUpdates: boolean, +}; +function SettingDisableAutoUpdates(props: Props) { + const { setClientSetting, disableAutoUpdates } = props; + return ( + + { + const newDisableAutoUpdates = !disableAutoUpdates; + autoUpdater.autoDownload = !newDisableAutoUpdates; + setClientSetting(newDisableAutoUpdates); + }} + checked={disableAutoUpdates} + /> + + ); +} + +export default SettingDisableAutoUpdates; diff --git a/ui/component/settingSystem/view.jsx b/ui/component/settingSystem/view.jsx index 9f4ad30dc..f461faa47 100644 --- a/ui/component/settingSystem/view.jsx +++ b/ui/component/settingSystem/view.jsx @@ -18,6 +18,7 @@ import Spinner from 'component/spinner'; import { getPasswordFromCookie } from 'util/saved-passwords'; import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import SettingEnablePrereleases from 'component/settingEnablePrereleases'; +import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates'; const IS_MAC = process.platform === 'darwin'; @@ -222,6 +223,14 @@ export default function SettingSystem(props: Props) { > + + + diff --git a/ui/constants/settings.js b/ui/constants/settings.js index 86d0b7e20..a225a6120 100644 --- a/ui/constants/settings.js +++ b/ui/constants/settings.js @@ -45,6 +45,7 @@ export const CUSTOM_COMMENTS_SERVERS = 'custom_comments_servers'; export const CUSTOM_SHARE_URL_ENABLED = 'custom_share_url_enabled'; export const CUSTOM_SHARE_URL = 'custom_share_url'; export const ENABLE_PRERELEASE_UPDATES = 'enable_prerelease_updates'; +export const DISABLE_AUTO_UPDATES = 'disable_auto_updates'; export const SETTINGS_GRP = { APPEARANCE: 'appearance', diff --git a/ui/index.jsx b/ui/index.jsx index 1ca521a0a..54b2d4aa8 100644 --- a/ui/index.jsx +++ b/ui/index.jsx @@ -200,6 +200,12 @@ function AppWrapper() { useEffect(() => { // @if TARGET='app' + + // Enable/disable automatic updates download based on user's settings. + const state = store.getState(); + const autoUpdatesDisabled = makeSelectClientSetting(SETTINGS.DISABLE_AUTO_UPDATES)(state); + autoUpdater.autoDownload = !autoUpdatesDisabled; + moment.locale(remote.app.getLocale()); autoUpdater.on('error', (error) => { diff --git a/ui/redux/selectors/app.js b/ui/redux/selectors/app.js index c3dee8f3e..6f9ae7839 100644 --- a/ui/redux/selectors/app.js +++ b/ui/redux/selectors/app.js @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; import { selectClaimsById, selectMyChannelClaims, selectTotalStakedAmountForChannelUri } from 'redux/selectors/claims'; +import { AUTO_UPDATE_DOWNLOADED } from 'constants/modal_types'; export const selectState = (state) => state.app || {}; @@ -51,6 +52,10 @@ export const selectAutoUpdateDownloaded = createSelector(selectState, (state) => export const selectAutoUpdateDeclined = createSelector(selectState, (state) => state.autoUpdateDeclined); +export const selectIsUpdateModelDisplayed = createSelector(selectState, (state) => { + return state.modal === AUTO_UPDATE_DOWNLOADED; +}); + export const selectDaemonVersionMatched = createSelector(selectState, (state) => state.daemonVersionMatched); export const selectVolume = createSelector(selectState, (state) => state.volume);