Real update system

- Now asks the daemon to close, even if it wasn't started by the same
   app
 - Improved UX during upgrade process (cancel buttons, final dialog
   where you approve the update, etc.)
 - Saves updates in temp directory, closes app and launches the installer
This commit is contained in:
Alex Liebowitz 2017-03-17 05:06:02 -04:00 committed by Alex Grintsvayg
parent 06f77936a3
commit 3c2f3abe8e
4 changed files with 140 additions and 17 deletions

View file

@ -1,4 +1,4 @@
const {app, BrowserWindow, ipcMain} = require('electron');
const {app, BrowserWindow, ipcMain, shell} = require('electron');
const path = require('path');
const jayson = require('jayson');
// tree-kill has better cross-platform handling of
@ -27,7 +27,7 @@ function createWindow () {
};
function lauchDaemon() {
function launchDaemon() {
if (subpy) {
return;
}
@ -78,7 +78,7 @@ function launchDaemonIfNotRunning() {
function (err, res) {
if (err) {
console.log('lbrynet daemon needs to be launched')
lauchDaemon();
launchDaemon();
} else {
console.log('lbrynet daemon is already running')
}
@ -116,22 +116,49 @@ app.on('activate', () => {
function shutdownDaemon() {
console.log('Shutdown triggered');
if (subpy == null) {
if (subpy) {
console.log('Killing lbrynet-daemon process');
kill(subpy.pid, undefined, (err) => {
console.log('Killed lbrynet-daemon process');
});
} else {
client.request('stop', []);
// TODO: If the daemon errors or times out when we make this request, find
// the process and force quit it.
}
// Is it safe to start the installer before the daemon finishes running?
// If not, we should wait until the daemon is closed before we start the install.
}
function shutdown() {
/* if (!subpy) {
// TODO: In this case, we didn't start the process so I'm hesitant
// to shut it down. We might want to send a stop command
// though instead of just letting it run.
console.log('Not killing lbrynet daemon because we did not start it')
return
}
} */
if (win) {
win.loadURL(`file://${__dirname}/dist/quit.html`);
}
quitting = true;
console.log('Killing lbrynet-daemon process');
kill(subpy.pid, undefined, (err) => {
console.log('Killed lbrynet-daemon process');
});
shutdownDaemon();
}
ipcMain.on('shutdown', shutdownDaemon);
function upgrade(event, installerPath) {
app.on('quit', () => {
console.log('installerPath is', installerPath);
shell.openItem(installerPath);
console.log('after installerPath');
});
if (win) {
win.loadURL(`file://${__dirname}/dist/upgrade.html`);
}
quitting = true;
shutdownDaemon();
}
ipcMain.on('upgrade', upgrade);
ipcMain.on('shutdown', shutdown);

View file

@ -13,7 +13,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
* Wherever possible, use outpoints for unique IDs instead of names or SD hashes
### Changed
*
* Update process now easier and more reliable
*
*

37
ui/dist/upgrade.html vendored Normal file
View file

@ -0,0 +1,37 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>LBRY</title>
<link href='https://fonts.googleapis.com/css?family=Raleway:600,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600italic,600' rel='stylesheet' type='text/css'>
<link href="./css/all.css" rel="stylesheet" type="text/css" media="screen,print" />
<link href="./js/mediaelement/mediaelementplayer.css" rel="stylesheet" type="text/css" />
<link rel="icon" type="image/png" href="./img/fav/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="./img/fav/favicon-194x194.png" sizes="194x194">
<link rel="icon" type="image/png" href="./img/fav/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="./img/fav/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="./img/fav/favicon-16x16.png" sizes="16x16">
<meta name="msapplication-TileColor" content="#155B4A">
<meta name="msapplication-TileImage" content="/img/fav/mstile-144x144.png">
<meta name="theme-color" content="#155B4A">
<style>
body {
background-color: "#155b4a"
}
</style>
</head>
<div id="canvas">
<div class="load-screen" style="color: white; min-height: 100vh; min-width: 100vw; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<img src="./img/lbry-white-485x160.png" alt="LBRY">
<div style="margin-top: 24px; width: 325px; text-align: center;">
<h3>
<span>Starting LBRY Upgrade <span class="busy-indicator"></span>
</span>
</h3>
</div>
</div>
</div>
</html>

View file

@ -24,6 +24,9 @@ import {Link} from './component/link.js';
const {remote, ipcRenderer, shell} = require('electron');
const {download} = remote.require('electron-dl');
const os = require('os');
const path = require('path');
const app = require('electron').remote.app;
var App = React.createClass({
@ -36,6 +39,19 @@ var App = React.createClass({
data: 'Error data',
},
_upgradeDownloadItem: null,
_version: null,
// Temporary workaround since electron-dl throws errors when you try to get the filename
getUpgradeFilename: function() {
if (os.platform() == 'darwin') {
return `LBRY-${this._version}.dmg`;
} else if (os.platform() == 'linux') {
return `LBRY-${this._version}.deb`;
} else {
return `LBRY-${this._version}_amd64.deb`;
}
},
getInitialState: function() {
// For now, routes are in format ?page or ?page=args
var match, param, val, viewingPage,
@ -53,6 +69,7 @@ var App = React.createClass({
updateUrl: null,
isOldOSX: null,
downloadProgress: null,
downloadComplete: false,
};
},
componentWillMount: function() {
@ -79,6 +96,8 @@ var App = React.createClass({
}
lbry.getVersionInfo((versionInfo) => {
this._version = versionInfo.lbrynet_version; // temp for building upgrade filename
var isOldOSX = false;
if (versionInfo.os_system == 'Darwin') {
var updateUrl = 'https://lbry.io/get/lbry.dmg';
@ -123,17 +142,51 @@ var App = React.createClass({
handleUpgradeClicked: function() {
// TODO: create a callback for onProgress and have the UI
// show download progress
// TODO: remove the saveAs popup. Thats just me being lazy and having
// some indication that the download is happening
// TODO: calling lbry.stop() ends up displaying the "daemon
// unexpectedly stopped" page. Have a better way of shutting down
let dir = app.getPath('temp');
let options = {
onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}),
}
directory: dir,
};
download(remote.getCurrentWindow(), this.state.updateUrl, options)
.then(dl => ipcRenderer.send('shutdown'));
.then(downloadItem => {
/**
* TODO: get the download path directly from the download object. It should just be
* downloadItem.getSavePath(), but the copy on the main process is being garbage collected
* too soon.
*/
this._upgradeDownloadItem = downloadItem;
this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename());
this.setState({
downloadComplete: true
});
});
this.setState({modal: 'downloading'});
},
handleStartUpgradeClicked: function() {
ipcRenderer.send('upgrade', this._upgradeDownloadPath);
},
cancelUpgrade: function() {
if (this._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 {
this._upgradeDownloadItem.cancel();
} catch (err) {
// Do nothing
}
}
this.setState({
downloadProgress: null,
downloadComplete: false,
modal: null,
});
},
handleSkipClicked: function() {
sessionStorage.setItem('upgradeSkipped', true);
this.setState({
@ -246,8 +299,14 @@ var App = React.createClass({
</Modal>
<Modal isOpen={this.state.modal == 'downloading'} contentLabel="Downloading Update" type="custom">
Downloading Update: {this.state.downloadProgress}% Complete
Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}% Complete` : null}
<Line percent={this.state.downloadProgress} strokeWidth="4"/>
<div className="modal__buttons">
<Link button="alt" label="Cancel" className="modal__button" onClick={this.cancelUpgrade} />
{this.state.downloadComplete
? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={this.handleStartUpgradeClicked} />
: null}
</div>
</Modal>
<ExpandableModal isOpen={this.state.modal == 'error'} contentLabel="Error" className="error-modal"
overlayClassName="error-modal-overlay" onConfirmed={this.closeModal}