Prevent .deb packages from being opened with archive manager. (#7502)

* Prevent .deb packages from being opened with archive manager.

* Allow to properly cancel download upgrade and prevent multiple downloads.

* Fix missing app-update.yml file for .deb builds.

* Small fix for allowPrerelease prop in autoUpdater.

* Use release/tags endpoint to get the release details.

* Handle error case for auto updater.

* Make install now button display the upgrade modal.

* Use GitHub as provider for manual update url.

* Small fixes in updater.

* Fix small lint errors.

* Properly handle auto download on/off.
This commit is contained in:
Franco Montenegro 2022-07-07 16:48:42 -04:00 committed by GitHub
parent f79c622edf
commit f065218ff4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1408 additions and 1083 deletions

View file

@ -29,6 +29,10 @@
"from": "./static/font", "from": "./static/font",
"to": "static/font", "to": "static/font",
"filter": ["**/*"] "filter": ["**/*"]
},
{
"from": "./static/app-update.yml",
"to": "app-update.yml"
} }
], ],
"publish": [ "publish": [

View file

@ -22,6 +22,7 @@ import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/disks
const { download } = require('electron-dl'); const { download } = require('electron-dl');
const remote = require('@electron/remote/main'); const remote = require('@electron/remote/main');
const os = require('os'); const os = require('os');
const sudo = require('sudo-prompt');
remote.initialize(); remote.initialize();
const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled'); const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled');
@ -33,29 +34,23 @@ try {
upgradeDisabled = false; upgradeDisabled = false;
} }
autoUpdater.autoDownload = !upgradeDisabled; autoUpdater.autoDownload = !upgradeDisabled;
autoUpdater.allowPrerelease = false;
// This is set to true if an auto update has been downloaded through the Electron const UPDATE_STATE_INIT = 0;
// auto-update system and is ready to install. If the user declined an update earlier, const UPDATE_STATE_CHECKING = 1;
// it will still install on shutdown. const UPDATE_STATE_UPDATES_FOUND = 2;
let autoUpdateDownloaded = false; 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 // 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. // that we show on Windows after you decline an upgrade and close the app later.
let showingAutoUpdateCloseAlert = false; 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;
// Auto updater doesn't support Linux installations (only trough AppImages)
// this is why, for that case, we download a full executable (.deb package)
// as a fallback support. This variable will be used to prevent
// multiple downloads when auto updater isn't supported.
let downloadUpgradeInProgress = false;
// Keep a global reference, if you don't, they will be closed automatically when the JavaScript // Keep a global reference, if you don't, they will be closed automatically when the JavaScript
// object is garbage collected. // object is garbage collected.
let rendererWindow; let rendererWindow;
@ -243,7 +238,8 @@ app.on('activate', () => {
app.on('will-quit', event => { app.on('will-quit', event => {
if ( if (
process.platform === 'win32' && process.platform === 'win32' &&
autoUpdateDownloaded && updateState === UPDATE_STATE_DOWNLOADED &&
isAutoUpdateSupported &&
!appState.autoUpdateAccepted && !appState.autoUpdateAccepted &&
!showingAutoUpdateCloseAlert !showingAutoUpdateCloseAlert
) { ) {
@ -327,87 +323,6 @@ ipcMain.on('get-disk-space', async (event) => {
} }
}); });
ipcMain.on('download-upgrade', async (event, params) => {
if (downloadUpgradeInProgress) {
return;
}
const { url, options } = params;
const dir = fs.mkdtempSync(app.getPath('temp') + path.sep);
options.onProgress = function(p) {
rendererWindow.webContents.send('download-progress-update', p);
};
options.directory = dir;
options.onCompleted = function(c) {
downloadUpgradeInProgress = false;
rendererWindow.webContents.send('download-update-complete', c);
};
const win = BrowserWindow.getFocusedWindow();
downloadUpgradeInProgress = true;
await download(win, url, options).catch(e => console.log('e', e));
});
ipcMain.on('upgrade', (event, installerPath) => {
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);
});
// 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.');
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', () => { ipcMain.on('version-info-requested', () => {
function formatRc(ver) { function formatRc(ver) {
// Adds dash if needed to make RC suffix SemVer friendly // Adds dash if needed to make RC suffix SemVer friendly
@ -500,3 +415,162 @@ process.on('uncaughtException', error => {
if (daemon) daemon.quit(); if (daemon) daemon.quit();
app.exit(1); app.exit(1);
}); });
// Auto updater
autoUpdater.on('download-progress', () => {
updateState = UPDATE_STATE_DOWNLOADING;
});
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-available', () => {
if (updateState === UPDATE_STATE_DOWNLOADING) {
return;
}
updateState = UPDATE_STATE_UPDATES_FOUND;
});
autoUpdater.on('update-not-available', () => {
updateState = UPDATE_STATE_NO_UPDATES_FOUND;
});
autoUpdater.on('error', () => {
if (updateState === UPDATE_STATE_DOWNLOADING) {
updateState = UPDATE_STATE_UPDATES_FOUND;
return;
}
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 (updateState !== UPDATE_STATE_UPDATES_FOUND) {
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();
});

View file

@ -67,6 +67,7 @@
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"rss": "^1.2.2", "rss": "^1.2.2",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",
"sudo-prompt": "^9.2.1",
"tempy": "^0.6.0", "tempy": "^0.6.0",
"videojs-logo": "^2.1.4" "videojs-logo": "^2.1.4"
}, },

View file

@ -2312,7 +2312,8 @@
"Free --[legend, unused disk space]--": "Free", "Free --[legend, unused disk space]--": "Free",
"Top content in %language%": "Top content in %language%", "Top content in %language%": "Top content in %language%",
"Apply": "Apply", "Apply": "Apply",
"24-hour clock": "24-hour clock",
"Disable background": "Disable background", "Disable background": "Disable background",
"Installing, please wait...": "Installing, please wait...",
"There was an error during installation. Please, try again.": "There was an error during installation. Please, try again.",
"--end--": "--end--" "--end--": "--end--"
} }

3
static/app-update.yml Normal file
View file

@ -0,0 +1,3 @@
owner: lbryio
repo: lbry-desktop
provider: github

View file

@ -1,3 +1,11 @@
import { connect } from 'react-redux';
import { selectRemoteVersion } from 'redux/selectors/app';
import LastReleaseChanges from './view'; import LastReleaseChanges from './view';
export default LastReleaseChanges; const select = (state) => ({
releaseVersion: selectRemoteVersion(state),
});
const perform = {};
export default connect(select, perform)(LastReleaseChanges);

View file

@ -5,11 +5,12 @@ import Button from 'component/button';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
type Props = { type Props = {
releaseVersion: string,
hideReleaseVersion?: boolean, hideReleaseVersion?: boolean,
}; };
const LastReleaseChanges = (props: Props) => { const LastReleaseChanges = (props: Props) => {
const { hideReleaseVersion } = props; const { hideReleaseVersion, releaseVersion } = props;
const [releaseTag, setReleaseTag] = useState(''); const [releaseTag, setReleaseTag] = useState('');
const [releaseChanges, setReleaseChanges] = useState(''); const [releaseChanges, setReleaseChanges] = useState('');
const [fetchingReleaseChanges, setFetchingReleaseChanges] = useState(false); const [fetchingReleaseChanges, setFetchingReleaseChanges] = useState(false);
@ -35,7 +36,7 @@ const LastReleaseChanges = (props: Props) => {
); );
useEffect(() => { useEffect(() => {
const lastReleaseUrl = 'https://api.github.com/repos/lbryio/lbry-desktop/releases/latest'; const lastReleaseUrl = `https://api.github.com/repos/lbryio/lbry-desktop/releases/tags/${releaseVersion}`;
const options = { const options = {
method: 'GET', method: 'GET',
headers: { Accept: 'application/vnd.github.v3+json' }, headers: { Accept: 'application/vnd.github.v3+json' },
@ -54,7 +55,7 @@ const LastReleaseChanges = (props: Props) => {
setFetchingReleaseChanges(false); setFetchingReleaseChanges(false);
setFetchReleaseFailed(true); setFetchReleaseFailed(true);
}); });
}, []); }, [releaseVersion, setFetchingReleaseChanges, setReleaseTag, setReleaseChanges, setFetchReleaseFailed]);
if (fetchingReleaseChanges) { if (fetchingReleaseChanges) {
return <p>{__('Loading...')}</p>; return <p>{__('Loading...')}</p>;

View file

@ -45,6 +45,8 @@ export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE';
export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED'; export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED';
export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED'; export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED';
export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED'; export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED';
export const UPGRADE_INIT_INSTALL = 'UPGRADE_INIT_INSTALL';
export const UPGRADE_INSTALL_ERROR = 'UPGRADE_INSTALL_ERROR';
export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE'; export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE';
export const CHECK_UPGRADE_START = 'CHECK_UPGRADE_START'; export const CHECK_UPGRADE_START = 'CHECK_UPGRADE_START';
export const CHECK_UPGRADE_SUCCESS = 'CHECK_UPGRADE_SUCCESS'; export const CHECK_UPGRADE_SUCCESS = 'CHECK_UPGRADE_SUCCESS';
@ -55,7 +57,10 @@ export const UPDATE_REMOTE_VERSION = 'UPDATE_REMOTE_VERSION';
export const SKIP_UPGRADE = 'SKIP_UPGRADE'; export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE'; export const START_UPGRADE = 'START_UPGRADE';
export const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED'; 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 AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED';
export const AUTO_UPDATE_DOWNLOADING = 'AUTO_UPDATE_DOWNLOADING';
export const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER'; export const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER';
// Wallet // Wallet

View file

@ -21,6 +21,10 @@ import {
doToggle3PAnalytics, doToggle3PAnalytics,
doUpdateDownloadProgress, doUpdateDownloadProgress,
doNotifyUpdateAvailable, doNotifyUpdateAvailable,
doShowUpgradeInstallationError,
doAutoUpdateDownloading,
doAutoUpdateReset,
doAutoUpdateFail,
} from 'redux/actions/app'; } from 'redux/actions/app';
import { isURIValid } from 'util/lbryURI'; import { isURIValid } from 'util/lbryURI';
import { setSearchApi } from 'redux/actions/search'; import { setSearchApi } from 'redux/actions/search';
@ -128,10 +132,30 @@ ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
handleError(); handleError();
}); });
autoUpdater.on('download-progress', () => {
app.store.dispatch(doAutoUpdateDownloading());
});
autoUpdater.on('checking-for-update', () => {
app.store.dispatch(doAutoUpdateReset());
});
autoUpdater.on('update-available', (e) => { autoUpdater.on('update-available', (e) => {
app.store.dispatch(doNotifyUpdateAvailable(e)); 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());
});
ipcRenderer.on('download-progress-update', (e, p) => { ipcRenderer.on('download-progress-update', (e, p) => {
app.store.dispatch(doUpdateDownloadProgress(Math.round(p.percent * 100))); app.store.dispatch(doUpdateDownloadProgress(Math.round(p.percent * 100)));
}); });
@ -208,6 +232,8 @@ function AppWrapper() {
const enabled = makeSelectClientSetting(SETTINGS.ENABLE_PRERELEASE_UPDATES)(state); const enabled = makeSelectClientSetting(SETTINGS.ENABLE_PRERELEASE_UPDATES)(state);
if (enabled) { if (enabled) {
autoUpdater.allowPrerelease = true; autoUpdater.allowPrerelease = true;
} else {
autoUpdater.allowPrerelease = false;
} }
} }
}, [persistDone]); }, [persistDone]);

View file

@ -1,13 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doAutoUpdateDeclined, doHideModal } from 'redux/actions/app'; import { doAutoUpdateDeclined, doHideModal } from 'redux/actions/app';
import { selectAutoUpdateFailed, selectAutoUpdateDownloading, selectIsUpgradeAvailable } from 'redux/selectors/app';
import ModalAutoUpdateDownloaded from './view'; import ModalAutoUpdateDownloaded from './view';
const perform = dispatch => ({ const select = (state, props) => ({
errorWhileUpdating: selectAutoUpdateFailed(state),
isDownloading: selectAutoUpdateDownloading(state),
isUpdateAvailable: selectIsUpgradeAvailable(state),
});
const perform = (dispatch) => ({
closeModal: () => dispatch(doHideModal()), closeModal: () => dispatch(doHideModal()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()), declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
}); });
export default connect( export default connect(select, perform)(ModalAutoUpdateDownloaded);
null,
perform
)(ModalAutoUpdateDownloaded);

View file

@ -1,5 +1,5 @@
// @flow // @flow
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
// @if TARGET='app' // @if TARGET='app'
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
// @endif // @endif
@ -7,16 +7,19 @@ import { Modal } from 'modal/modal';
import LastReleaseChanges from 'component/lastReleaseChanges'; import LastReleaseChanges from 'component/lastReleaseChanges';
type Props = { type Props = {
closeModal: any => any, closeModal: (any) => any,
declineAutoUpdate: () => any, declineAutoUpdate: () => any,
errorWhileUpdating: boolean,
isDownloading: boolean,
isUpdateAvailable: boolean,
}; };
const ModalAutoUpdateDownloaded = (props: Props) => { const ModalAutoUpdateDownloaded = (props: Props) => {
const { closeModal, declineAutoUpdate } = props; const { closeModal, declineAutoUpdate, errorWhileUpdating, isDownloading, isUpdateAvailable } = props;
const [disabled, setDisabled] = useState(false); const [waitingForAutoUpdateResponse, setWaitingForAutoUpdateResponse] = useState(false);
const handleConfirm = () => { const handleConfirm = () => {
setDisabled(true); setWaitingForAutoUpdateResponse(true);
ipcRenderer.send('autoUpdateAccepted'); ipcRenderer.send('autoUpdateAccepted');
}; };
@ -25,19 +28,24 @@ const ModalAutoUpdateDownloaded = (props: Props) => {
closeModal(); closeModal();
}; };
useEffect(() => {
setWaitingForAutoUpdateResponse(false);
}, [errorWhileUpdating, isDownloading, isUpdateAvailable]);
return ( return (
<Modal <Modal
isOpen isOpen
type="confirm" type="confirm"
contentLabel={__('Upgrade Downloaded')} contentLabel={__('Upgrade Downloaded')}
title={__('LBRY leveled up')} title={__('LBRY leveled up')}
confirmButtonLabel={__('Upgrade Now')} confirmButtonLabel={isDownloading ? __('Downloading...') : __('Upgrade Now')}
abortButtonLabel={__('Not Now')} abortButtonLabel={isDownloading ? __('Keep browsing') : __('Not Now')}
confirmButtonDisabled={disabled} confirmButtonDisabled={!isUpdateAvailable || isDownloading || waitingForAutoUpdateResponse}
onConfirmed={handleConfirm} onConfirmed={handleConfirm}
onAborted={handleAbort} onAborted={handleAbort}
> >
<LastReleaseChanges /> <LastReleaseChanges />
{errorWhileUpdating && <p>{__('There was an error while updating. Please try again.')}</p>}
</Modal> </Modal>
); );
}; };

View file

@ -1,15 +1,23 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doStartUpgrade, doCancelUpgrade, doHideModal } from 'redux/actions/app'; import { doStartUpgrade, doCancelUpgrade, doHideModal } from 'redux/actions/app';
import { selectDownloadProgress, selectDownloadComplete, selectUpgradeDownloadPath } from 'redux/selectors/app'; import {
selectDownloadProgress,
selectDownloadComplete,
selectUpgradeDownloadPath,
selectUpgradeInitialized,
selectUpgradeFailedInstallation,
} from 'redux/selectors/app';
import ModalDownloading from './view'; import ModalDownloading from './view';
const select = state => ({ const select = (state) => ({
downloadProgress: selectDownloadProgress(state), downloadProgress: selectDownloadProgress(state),
downloadComplete: selectDownloadComplete(state), downloadComplete: selectDownloadComplete(state),
downloadItem: selectUpgradeDownloadPath(state), downloadItem: selectUpgradeDownloadPath(state),
upgradeInitialized: selectUpgradeInitialized(state),
failedInstallation: selectUpgradeFailedInstallation(state),
}); });
const perform = dispatch => ({ const perform = (dispatch) => ({
startUpgrade: () => dispatch(doStartUpgrade()), startUpgrade: () => dispatch(doStartUpgrade()),
cancelUpgrade: () => { cancelUpgrade: () => {
dispatch(doHideModal()); dispatch(doHideModal());
@ -17,7 +25,4 @@ const perform = dispatch => ({
}, },
}); });
export default connect( export default connect(select, perform)(ModalDownloading);
select,
perform
)(ModalDownloading);

View file

@ -10,11 +10,21 @@ type Props = {
downloadItem: string, downloadItem: string,
startUpgrade: () => void, startUpgrade: () => void,
cancelUpgrade: () => void, cancelUpgrade: () => void,
upgradeInitialized: boolean,
failedInstallation: boolean,
}; };
class ModalDownloading extends React.PureComponent<Props> { class ModalDownloading extends React.PureComponent<Props> {
render() { render() {
const { downloadProgress, downloadComplete, downloadItem, startUpgrade, cancelUpgrade } = this.props; const {
downloadProgress,
downloadComplete,
downloadItem,
startUpgrade,
cancelUpgrade,
upgradeInitialized,
failedInstallation,
} = this.props;
return ( return (
<Modal title={__('Downloading update')} isOpen contentLabel={__('Downloading update')} type="custom"> <Modal title={__('Downloading update')} isOpen contentLabel={__('Downloading update')} type="custom">
@ -40,9 +50,18 @@ class ModalDownloading extends React.PureComponent<Props> {
</React.Fragment> </React.Fragment>
) : null} ) : null}
{failedInstallation && <p>{__('There was an error during installation. Please, try again.')}</p>}
<div className="card__actions"> <div className="card__actions">
{downloadComplete ? <Button button="primary" label={__('Begin Upgrade')} onClick={startUpgrade} /> : null} {downloadComplete ? (
<Button button="link" label={__('Cancel')} onClick={cancelUpgrade} /> <Button
disabled={upgradeInitialized}
button="primary"
label={__(upgradeInitialized ? 'Installing, please wait...' : 'Begin Upgrade')}
onClick={startUpgrade}
/>
) : null}
<Button disabled={upgradeInitialized} button="link" label={__('Cancel')} onClick={cancelUpgrade} />
</div> </div>
</Modal> </Modal>
); );

View file

@ -28,7 +28,6 @@ import {
import { import {
selectIsUpgradeSkipped, selectIsUpgradeSkipped,
selectUpdateUrl, selectUpdateUrl,
selectUpgradeDownloadItem,
selectUpgradeDownloadPath, selectUpgradeDownloadPath,
selectAutoUpdateDeclined, selectAutoUpdateDeclined,
selectRemoteVersion, selectRemoteVersion,
@ -85,6 +84,15 @@ export function doStartUpgrade() {
const upgradeDownloadPath = selectUpgradeDownloadPath(state); const upgradeDownloadPath = selectUpgradeDownloadPath(state);
ipcRenderer.send('upgrade', upgradeDownloadPath); ipcRenderer.send('upgrade', upgradeDownloadPath);
dispatch({
type: ACTIONS.UPGRADE_INIT_INSTALL,
});
};
}
export function doShowUpgradeInstallationError() {
return {
type: ACTIONS.UPGRADE_INSTALL_ERROR,
}; };
} }
@ -154,25 +162,8 @@ export function doAutoUpdateDeclined() {
} }
export function doCancelUpgrade() { export function doCancelUpgrade() {
return (dispatch, getState) => { ipcRenderer.send('cancel-download-upgrade');
const state = getState(); return { type: ACTIONS.UPGRADE_CANCELLED };
const upgradeDownloadItem = selectUpgradeDownloadItem(state);
if (upgradeDownloadItem) {
/*
* Right now the remote reference to the download item gets garbage collected as soon as the
* the download is over (maybe even earlier), so trying to cancel a finished download may
* throw an error.
*/
try {
upgradeDownloadItem.cancel();
} catch (err) {
console.error(err); // eslint-disable-line no-console
}
}
dispatch({ type: ACTIONS.UPGRADE_CANCELLED });
};
} }
export function doCheckUpgradeAvailable() { export function doCheckUpgradeAvailable() {
@ -229,6 +220,24 @@ export function doNotifyUpdateAvailable(e) {
}; };
} }
export function doAutoUpdateDownloading() {
return {
type: ACTIONS.AUTO_UPDATE_DOWNLOADING,
};
}
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. Initiate a timer that will check for an app upgrade every 10 minutes.
*/ */

View file

@ -26,7 +26,9 @@ export type AppState = {
hasSignature: boolean, hasSignature: boolean,
badgeNumber: number, badgeNumber: number,
volume: number, volume: number,
autoUpdateDownloading: boolean,
autoUpdateDeclined: boolean, autoUpdateDeclined: boolean,
autoUpdateFailed: boolean,
modalsAllowed: boolean, modalsAllowed: boolean,
downloadProgress: ?number, downloadProgress: ?number,
upgradeDownloading: ?boolean, upgradeDownloading: ?boolean,
@ -62,11 +64,15 @@ const defaultState: AppState = {
upgradeSkipped: sessionStorage.getItem('upgradeSkipped') === 'true', upgradeSkipped: sessionStorage.getItem('upgradeSkipped') === 'true',
// @endif // @endif
muted: false, muted: false,
autoUpdateDownloading: false,
autoUpdateDownloaded: false, autoUpdateDownloaded: false,
autoUpdateDeclined: false, autoUpdateDeclined: false,
autoUpdateFailed: false,
modalsAllowed: true, modalsAllowed: true,
hasClickedComment: false, hasClickedComment: false,
downloadProgress: undefined, downloadProgress: undefined,
upgradeInitialized: false,
upgradeFailedInstallation: false,
upgradeDownloading: undefined, upgradeDownloading: undefined,
upgradeDownloadComplete: undefined, upgradeDownloadComplete: undefined,
checkUpgradeTimer: undefined, checkUpgradeTimer: undefined,
@ -140,9 +146,18 @@ reducers[ACTIONS.UPGRADE_CANCELLED] = (state) =>
modal: null, modal: null,
}); });
reducers[ACTIONS.AUTO_UPDATE_DOWNLOADING] = (state) =>
Object.assign({}, state, {
autoUpdateDownloading: true,
autoUpdateDownloaded: false,
autoUpdateFailed: false,
});
reducers[ACTIONS.AUTO_UPDATE_DOWNLOADED] = (state) => reducers[ACTIONS.AUTO_UPDATE_DOWNLOADED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
autoUpdateDownloading: false,
autoUpdateDownloaded: true, autoUpdateDownloaded: true,
autoUpdateFailed: false,
}); });
reducers[ACTIONS.AUTO_UPDATE_DECLINED] = (state) => reducers[ACTIONS.AUTO_UPDATE_DECLINED] = (state) =>
@ -150,6 +165,20 @@ reducers[ACTIONS.AUTO_UPDATE_DECLINED] = (state) =>
autoUpdateDeclined: true, autoUpdateDeclined: true,
}); });
reducers[ACTIONS.AUTO_UPDATE_RESET] = (state) =>
Object.assign({}, state, {
autoUpdateFailed: false,
autoUpdateDownloading: false,
autoUpdateDownloaded: false,
});
reducers[ACTIONS.AUTO_UPDATE_FAILED] = (state) =>
Object.assign({}, state, {
autoUpdateDownloading: false,
autoUpdateDownloaded: false,
autoUpdateFailed: true,
});
reducers[ACTIONS.UPGRADE_DOWNLOAD_COMPLETED] = (state, action) => reducers[ACTIONS.UPGRADE_DOWNLOAD_COMPLETED] = (state, action) =>
Object.assign({}, state, { Object.assign({}, state, {
downloadPath: action.data.path, downloadPath: action.data.path,
@ -162,6 +191,18 @@ reducers[ACTIONS.UPGRADE_DOWNLOAD_STARTED] = (state) =>
upgradeDownloading: true, upgradeDownloading: true,
}); });
reducers[ACTIONS.UPGRADE_INIT_INSTALL] = (state) =>
Object.assign({}, state, {
upgradeInitialized: true,
upgradeFailedInstallation: false,
});
reducers[ACTIONS.UPGRADE_INSTALL_ERROR] = (state) =>
Object.assign({}, state, {
upgradeInitialized: false,
upgradeFailedInstallation: true,
});
reducers[ACTIONS.CHANGE_MODALS_ALLOWED] = (state, action) => reducers[ACTIONS.CHANGE_MODALS_ALLOWED] = (state, action) =>
Object.assign({}, state, { Object.assign({}, state, {
modalsAllowed: action.data.modalsAllowed, modalsAllowed: action.data.modalsAllowed,

View file

@ -6,12 +6,20 @@ export const selectState = (state) => state.app || {};
export const selectPlatform = createSelector(selectState, (state) => state.platform); export const selectPlatform = createSelector(selectState, (state) => state.platform);
export const selectUpdateUrl = createSelector(selectPlatform, (platform) => { export const selectRemoteVersion = createSelector(selectState, (state) => state.remoteVersion);
export const selectUpdateUrl = createSelector(selectPlatform, selectRemoteVersion, (platform, releaseVersion) => {
switch (platform) { switch (platform) {
case 'darwin': case 'darwin':
return 'https://lbry.com/get/lbry.dmg'; return 'https://lbry.com/get/lbry.dmg';
case 'linux': case 'linux':
return 'https://lbry.com/get/lbry.deb'; // releaseVersion can be used as the tag name
// Example: v0.53.5-alpha.test7495b
// When downloading, we need to remove the initial
// v, ending up with a file name like
// LBRY_0.53.5-alpha.test7495b.deb
const fileName = 'LBRY_' + (releaseVersion || '').replace('v', '') + '.deb';
return `https://github.com/lbryio/lbry-desktop/releases/download/${releaseVersion}/${fileName}`;
case 'win32': case 'win32':
return 'https://lbry.com/get/lbry.exe'; return 'https://lbry.com/get/lbry.exe';
default: default:
@ -21,10 +29,12 @@ export const selectUpdateUrl = createSelector(selectPlatform, (platform) => {
export const selectHasClickedComment = createSelector(selectState, (state) => state.hasClickedComment); export const selectHasClickedComment = createSelector(selectState, (state) => state.hasClickedComment);
export const selectRemoteVersion = createSelector(selectState, (state) => state.remoteVersion);
export const selectIsUpgradeAvailable = createSelector(selectState, (state) => state.isUpgradeAvailable); export const selectIsUpgradeAvailable = createSelector(selectState, (state) => state.isUpgradeAvailable);
export const selectUpgradeInitialized = createSelector(selectState, (state) => state.upgradeInitialized);
export const selectUpgradeFailedInstallation = createSelector(selectState, (state) => state.upgradeFailedInstallation);
export const selectUpgradeFilename = createSelector(selectPlatform, selectRemoteVersion, (platform, version) => { export const selectUpgradeFilename = createSelector(selectPlatform, selectRemoteVersion, (platform, version) => {
switch (platform) { switch (platform) {
case 'darwin': case 'darwin':
@ -46,12 +56,14 @@ export const selectIsUpgradeSkipped = createSelector(selectState, (state) => sta
export const selectUpgradeDownloadPath = createSelector(selectState, (state) => state.downloadPath); export const selectUpgradeDownloadPath = createSelector(selectState, (state) => state.downloadPath);
export const selectUpgradeDownloadItem = createSelector(selectState, (state) => state.downloadItem);
export const selectAutoUpdateDownloaded = createSelector(selectState, (state) => state.autoUpdateDownloaded); export const selectAutoUpdateDownloaded = createSelector(selectState, (state) => state.autoUpdateDownloaded);
export const selectAutoUpdateDeclined = createSelector(selectState, (state) => state.autoUpdateDeclined); export const selectAutoUpdateDeclined = createSelector(selectState, (state) => state.autoUpdateDeclined);
export const selectAutoUpdateFailed = createSelector(selectState, (state) => state.autoUpdateFailed);
export const selectAutoUpdateDownloading = createSelector(selectState, (state) => state.autoUpdateDownloading);
export const selectIsUpdateModalDisplayed = createSelector(selectState, (state) => { export const selectIsUpdateModalDisplayed = createSelector(selectState, (state) => {
return [MODALS.AUTO_UPDATE_DOWNLOADED, MODALS.UPGRADE, MODALS.DOWNLOADING].includes(state.modal); return [MODALS.AUTO_UPDATE_DOWNLOADED, MODALS.UPGRADE, MODALS.DOWNLOADING].includes(state.modal);
}); });

1964
yarn.lock

File diff suppressed because it is too large Load diff