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

Merged
Ruk33 merged 11 commits from 7495-deb-update-fails-to-launch-on-ubuntu into master 2022-07-07 22:48:43 +02:00
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",
jessopb commented 2022-07-07 22:45:54 +02:00 (Migrated from github.com)
Review

This repository is archived on github. Something to keep an eye on, or fork for our selves.

This repository is archived on github. Something to keep an eye on, or fork for our selves.
"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':
jessopb commented 2022-07-07 22:47:30 +02:00 (Migrated from github.com)
Review

This could maybe use some error catching/validation. It's fine though.

This could maybe use some error catching/validation. It's fine though.
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