diff --git a/electron/index.js b/electron/index.js
index 9671a6eb2..fda80eac3 100644
--- a/electron/index.js
+++ b/electron/index.js
@@ -36,26 +36,21 @@ try {
autoUpdater.autoDownload = !upgradeDisabled;
autoUpdater.allowPrerelease = false;
-// This is set to true if an auto update has been downloaded through the Electron
-// auto-update system and is ready to install. If the user declined an update earlier,
-// it will still install on shutdown.
-let autoUpdateDownloaded = false;
+const UPDATE_STATE_INIT = 0;
+const UPDATE_STATE_CHECKING = 1;
+const UPDATE_STATE_UPDATES_FOUND = 2;
+const UPDATE_STATE_NO_UPDATES_FOUND = 3;
+const UPDATE_STATE_DOWNLOADING = 4;
+const UPDATE_STATE_DOWNLOADED = 5;
+let updateState = UPDATE_STATE_INIT;
+let updateDownloadItem;
+
+const isAutoUpdateSupported = ['win32', 'darwin'].includes(process.platform) || !!process.env.APPIMAGE;
// This is used to keep track of whether we are showing the special dialog
// 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 when
-// using the auto updater API.
-// 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;
-
-let downloadUpgradeInitiated = false;
-
-let downloadUpgradeItem;
-
// Keep a global reference, if you don't, they will be closed automatically when the JavaScript
// object is garbage collected.
let rendererWindow;
@@ -243,7 +238,8 @@ app.on('activate', () => {
app.on('will-quit', event => {
if (
process.platform === 'win32' &&
- autoUpdateDownloaded &&
+ updateState === UPDATE_STATE_DOWNLOADED &&
+ isAutoUpdateSupported &&
!appState.autoUpdateAccepted &&
!showingAutoUpdateCloseAlert
) {
@@ -327,124 +323,6 @@ ipcMain.on('get-disk-space', async (event) => {
}
});
-ipcMain.on('cancel-download-upgrade', () => {
- if (downloadUpgradeItem) {
- // Cancel the download and execute the onCancel
- // callback set in the options.
- downloadUpgradeItem.cancel();
- }
-});
-
-ipcMain.on('download-upgrade', async (event, params) => {
- // Prevent downloading multiple times.
- if (downloadUpgradeInitiated || downloadUpgradeItem) {
- return;
- }
- const { url, options } = params;
- const dir = fs.mkdtempSync(app.getPath('temp') + path.sep);
-
- downloadUpgradeInitiated = true;
-
- // Grab the download item's handler to allow
- // cancelling the operation if required.
- options.onStarted = function(downloadItem) {
- downloadUpgradeItem = downloadItem;
- };
- options.onCancel = function() {
- downloadUpgradeItem = undefined;
- downloadUpgradeInitiated = false;
- };
- options.onProgress = function(p) {
- rendererWindow.webContents.send('download-progress-update', p);
- };
- options.directory = dir;
- options.onCompleted = function(c) {
- downloadUpgradeInitiated = false;
- downloadUpgradeItem = undefined;
- rendererWindow.webContents.send('download-update-complete', c);
- };
- const win = BrowserWindow.getFocusedWindow();
- await download(win, url, options).catch(e => console.log('e', e));
-});
-
-ipcMain.on('upgrade', (event, installerPath) => {
- // what to do if no shutdown in a long time?
- console.log('Update downloaded to', installerPath);
- console.log('The app will close and you will be prompted to install the latest version of LBRY.');
- console.log('After the install is complete, please reopen the app.');
-
- // Prevent .deb package from opening with archive manager (Ubuntu >= 20)
- if (process.platform === 'linux' && !process.env.APPIMAGE) {
- sudo.exec(`dpkg -i ${installerPath}`, { name: app.name }, (err, stdout, stderr) => {
- if (err || stderr) {
- rendererWindow.webContents.send('upgrade-installing-error');
- return;
- }
-
- // Re-launch the application when the installation finishes.
- app.relaunch();
- app.quit();
- });
-
- return;
- }
-
- app.on('quit', () => {
- console.log('Launching upgrade installer at', installerPath);
- // This gets triggered called after *all* other quit-related events, so
- // we'll only get here if we're fully prepared and quitting for real.
- shell.openPath(installerPath);
- });
- app.quit();
-});
-
-ipcMain.on('check-for-updates', (event, autoDownload) => {
- // Prevent downloading the same update multiple times.
- if (!keepCheckingForUpdates) {
- return;
- }
-
- keepCheckingForUpdates = false;
- autoUpdater.autoDownload = autoDownload;
- 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;
-
- // 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', () => {
function formatRc(ver) {
// Adds dash if needed to make RC suffix SemVer friendly
@@ -537,3 +415,147 @@ process.on('uncaughtException', error => {
if (daemon) daemon.quit();
app.exit(1);
});
+
+// Auto updater
+// autoUpdater.on('download-progress', (p) => {
+// rendererWindow.webContents.send('download-progress-update', p);
+// });
+
+autoUpdater.on('update-downloaded', () => {
+ updateState = UPDATE_STATE_DOWNLOADED;
+
+ // 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', () => {
+ updateState = UPDATE_STATE_NO_UPDATES_FOUND;
+});
+
+autoUpdater.on('error', () => {
+ updateState = UPDATE_STATE_INIT;
+});
+
+// Manual (.deb) update
+ipcMain.on('cancel-download-upgrade', () => {
+ if (updateDownloadItem) {
+ // Cancel the download and execute the onCancel
+ // callback set in the options.
+ updateDownloadItem.cancel();
+ }
+});
+
+ipcMain.on('download-upgrade', (event, params) => {
+ if (updateState !== UPDATE_STATE_UPDATES_FOUND) {
+ return;
+ }
+ if (isAutoUpdateSupported) {
+ updateState = UPDATE_STATE_DOWNLOADING;
+ autoUpdater.downloadUpdate();
+ return;
+ }
+
+ const { url, options } = params;
+ const dir = fs.mkdtempSync(app.getPath('temp') + path.sep);
+
+ updateState = UPDATE_STATE_DOWNLOADING;
+
+ // Grab the download item's handler to allow
+ // cancelling the operation if required.
+ options.onStarted = function(downloadItem) {
+ updateDownloadItem = downloadItem;
+ };
+ options.onCancel = function() {
+ updateState = UPDATE_STATE_UPDATES_FOUND;
+ updateDownloadItem = undefined;
+ };
+ options.onProgress = function(p) {
+ rendererWindow.webContents.send('download-progress-update', p);
+ };
+ options.onCompleted = function(c) {
+ updateState = UPDATE_STATE_DOWNLOADED;
+ updateDownloadItem = undefined;
+ rendererWindow.webContents.send('download-update-complete', c);
+ };
+ options.directory = dir;
+ const win = BrowserWindow.getFocusedWindow();
+ download(win, url, options).catch(e => {
+ updateState = UPDATE_STATE_UPDATES_FOUND;
+ console.log('e', e);
+ });
+});
+
+// Update behavior
+ipcMain.on('autoUpdateAccepted', () => {
+ appState.autoUpdateAccepted = true;
+
+ // 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 (updateState === UPDATE_STATE_DOWNLOADED) {
+ 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.
+ updateState = UPDATE_STATE_DOWNLOADING;
+ autoUpdater.downloadUpdate();
+});
+
+ipcMain.on('check-for-updates', (event, autoDownload) => {
+ if (![UPDATE_STATE_INIT, UPDATE_STATE_NO_UPDATES_FOUND].includes(updateState)) {
+ return;
+ }
+
+ updateState = UPDATE_STATE_CHECKING;
+
+ // If autoDownload is true, checkForUpdates will begin the
+ // download automatically.
+ if (autoDownload) {
+ updateState = UPDATE_STATE_DOWNLOADING;
+ }
+
+ autoUpdater.autoDownload = autoDownload;
+ autoUpdater.checkForUpdates();
+});
+
+ipcMain.on('upgrade', (event, installerPath) => {
+ // what to do if no shutdown in a long time?
+ console.log('Update downloaded to', installerPath);
+ console.log('The app will close and you will be prompted to install the latest version of LBRY.');
+ console.log('After the install is complete, please reopen the app.');
+
+ // Prevent .deb package from opening with archive manager (Ubuntu >= 20)
+ if (process.platform === 'linux' && !process.env.APPIMAGE) {
+ sudo.exec(`dpkg -i ${installerPath}`, { name: app.name }, (err, stdout, stderr) => {
+ if (err || stderr) {
+ rendererWindow.webContents.send('upgrade-installing-error');
+ return;
+ }
+
+ // Re-launch the application when the installation finishes.
+ app.relaunch();
+ app.quit();
+ });
+
+ return;
+ }
+
+ app.on('quit', () => {
+ console.log('Launching upgrade installer at', installerPath);
+ // This gets triggered called after *all* other quit-related events, so
+ // we'll only get here if we're fully prepared and quitting for real.
+ shell.openPath(installerPath);
+ });
+ app.quit();
+});
\ No newline at end of file
diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js
index 37954c7e2..470f44c7f 100644
--- a/ui/constants/action_types.js
+++ b/ui/constants/action_types.js
@@ -57,6 +57,8 @@ export const UPDATE_REMOTE_VERSION = 'UPDATE_REMOTE_VERSION';
export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE';
export const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED';
+export const AUTO_UPDATE_RESET = 'AUTO_UPDATE_RESET';
+export const AUTO_UPDATE_FAILED = 'AUTO_UPDATE_FAILED';
export const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED';
export const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER';
diff --git a/ui/index.jsx b/ui/index.jsx
index b31b4e418..00b72e6df 100644
--- a/ui/index.jsx
+++ b/ui/index.jsx
@@ -22,6 +22,8 @@ import {
doUpdateDownloadProgress,
doNotifyUpdateAvailable,
doShowUpgradeInstallationError,
+ doAutoUpdateReset,
+ doAutoUpdateFail,
} from 'redux/actions/app';
import { isURIValid } from 'util/lbryURI';
import { setSearchApi } from 'redux/actions/search';
@@ -129,10 +131,23 @@ ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
handleError();
});
+autoUpdater.on('checking-for-update', () => {
+ app.store.dispatch(doAutoUpdateReset());
+});
+
autoUpdater.on('update-available', (e) => {
+ app.store.dispatch(doAutoUpdateReset());
app.store.dispatch(doNotifyUpdateAvailable(e));
});
+autoUpdater.on('update-downloaded', () => {
+ app.store.dispatch(doAutoUpdateReset());
+});
+
+autoUpdater.on('error', () => {
+ app.store.dispatch(doAutoUpdateFail());
+});
+
ipcRenderer.on('upgrade-installing-error', () => {
app.store.dispatch(doShowUpgradeInstallationError());
});
diff --git a/ui/modal/modalAutoUpdateDownloaded/index.js b/ui/modal/modalAutoUpdateDownloaded/index.js
index afe8e8fa4..2b98538dd 100644
--- a/ui/modal/modalAutoUpdateDownloaded/index.js
+++ b/ui/modal/modalAutoUpdateDownloaded/index.js
@@ -1,13 +1,15 @@
import { connect } from 'react-redux';
import { doAutoUpdateDeclined, doHideModal } from 'redux/actions/app';
+import { selectAutoUpdateFailed } from 'redux/selectors/app';
import ModalAutoUpdateDownloaded from './view';
-const perform = dispatch => ({
+const select = (state, props) => ({
+ errorWhileUpdating: selectAutoUpdateFailed(state),
+});
+
+const perform = (dispatch) => ({
closeModal: () => dispatch(doHideModal()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
});
-export default connect(
- null,
- perform
-)(ModalAutoUpdateDownloaded);
+export default connect(select, perform)(ModalAutoUpdateDownloaded);
diff --git a/ui/modal/modalAutoUpdateDownloaded/view.jsx b/ui/modal/modalAutoUpdateDownloaded/view.jsx
index aecb049b4..7110cfb94 100644
--- a/ui/modal/modalAutoUpdateDownloaded/view.jsx
+++ b/ui/modal/modalAutoUpdateDownloaded/view.jsx
@@ -7,13 +7,15 @@ import { Modal } from 'modal/modal';
import LastReleaseChanges from 'component/lastReleaseChanges';
type Props = {
- closeModal: any => any,
+ closeModal: (any) => any,
declineAutoUpdate: () => any,
+ errorWhileUpdating: boolean,
};
const ModalAutoUpdateDownloaded = (props: Props) => {
- const { closeModal, declineAutoUpdate } = props;
+ const { closeModal, declineAutoUpdate, errorWhileUpdating } = props;
const [disabled, setDisabled] = useState(false);
+ const isDownloading = disabled && !errorWhileUpdating;
const handleConfirm = () => {
setDisabled(true);
@@ -31,13 +33,14 @@ const ModalAutoUpdateDownloaded = (props: Props) => {
type="confirm"
contentLabel={__('Upgrade Downloaded')}
title={__('LBRY leveled up')}
- confirmButtonLabel={__('Upgrade Now')}
+ confirmButtonLabel={isDownloading ? __('Downloading...') : __('Upgrade Now')}
abortButtonLabel={__('Not Now')}
- confirmButtonDisabled={disabled}
+ confirmButtonDisabled={isDownloading}
onConfirmed={handleConfirm}
onAborted={handleAbort}
>
__('There was an error while updating. Please try again.')
} ); }; diff --git a/ui/redux/actions/app.js b/ui/redux/actions/app.js index f8916d36e..f4aaa2c46 100644 --- a/ui/redux/actions/app.js +++ b/ui/redux/actions/app.js @@ -220,6 +220,18 @@ export function doNotifyUpdateAvailable(e) { }; } +export function doAutoUpdateReset() { + return { + type: ACTIONS.AUTO_UPDATE_RESET, + }; +} + +export function doAutoUpdateFail() { + return { + type: ACTIONS.AUTO_UPDATE_FAILED, + }; +} + /* Initiate a timer that will check for an app upgrade every 10 minutes. */ diff --git a/ui/redux/reducers/app.js b/ui/redux/reducers/app.js index 5426e56b8..41ec971b5 100644 --- a/ui/redux/reducers/app.js +++ b/ui/redux/reducers/app.js @@ -27,6 +27,7 @@ export type AppState = { badgeNumber: number, volume: number, autoUpdateDeclined: boolean, + autoUpdateFailed: boolean, modalsAllowed: boolean, downloadProgress: ?number, upgradeDownloading: ?boolean, @@ -64,6 +65,7 @@ const defaultState: AppState = { muted: false, autoUpdateDownloaded: false, autoUpdateDeclined: false, + autoUpdateFailed: false, modalsAllowed: true, hasClickedComment: false, downloadProgress: undefined, @@ -152,6 +154,16 @@ reducers[ACTIONS.AUTO_UPDATE_DECLINED] = (state) => autoUpdateDeclined: true, }); +reducers[ACTIONS.AUTO_UPDATE_RESET] = (state) => + Object.assign({}, state, { + autoUpdateFailed: false, + }); + +reducers[ACTIONS.AUTO_UPDATE_FAILED] = (state) => + Object.assign({}, state, { + autoUpdateFailed: true, + }); + reducers[ACTIONS.UPGRADE_DOWNLOAD_COMPLETED] = (state, action) => Object.assign({}, state, { downloadPath: action.data.path, diff --git a/ui/redux/selectors/app.js b/ui/redux/selectors/app.js index 920267b62..8e1cefeb9 100644 --- a/ui/redux/selectors/app.js +++ b/ui/redux/selectors/app.js @@ -54,6 +54,8 @@ export const selectAutoUpdateDownloaded = createSelector(selectState, (state) => export const selectAutoUpdateDeclined = createSelector(selectState, (state) => state.autoUpdateDeclined); +export const selectAutoUpdateFailed = createSelector(selectState, (state) => state.autoUpdateFailed); + export const selectIsUpdateModalDisplayed = createSelector(selectState, (state) => { return [MODALS.AUTO_UPDATE_DOWNLOADED, MODALS.UPGRADE, MODALS.DOWNLOADING].includes(state.modal); });