Handle error case for auto updater.
This commit is contained in:
parent
8a7d89c857
commit
d2648c9f62
8 changed files with 213 additions and 143 deletions
|
@ -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();
|
||||
});
|
|
@ -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';
|
||||
|
||||
|
|
15
ui/index.jsx
15
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());
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
<LastReleaseChanges />
|
||||
{errorWhileUpdating && <p>__('There was an error while updating. Please try again.')</p>}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue