From 3c2f3abe8e9b7481563b465a04ab3cdfba49129b Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 17 Mar 2017 05:06:02 -0400 Subject: [PATCH] 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 --- app/main.js | 49 ++++++++++++++++++++++++------- ui/CHANGELOG.md | 2 +- ui/dist/upgrade.html | 37 ++++++++++++++++++++++++ ui/js/app.js | 69 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 ui/dist/upgrade.html diff --git a/app/main.js b/app/main.js index d0b72edae..792066472 100644 --- a/app/main.js +++ b/app/main.js @@ -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); \ No newline at end of file diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index f875ffe07..b8c82b321 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -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 * * diff --git a/ui/dist/upgrade.html b/ui/dist/upgrade.html new file mode 100644 index 000000000..89345ccc4 --- /dev/null +++ b/ui/dist/upgrade.html @@ -0,0 +1,37 @@ + + + + + LBRY + + + + + + + + + + + + + + + + +
+
+ LBRY +
+

+ Starting LBRY Upgrade + +

+
+
+
+ diff --git a/ui/js/app.js b/ui/js/app.js index 466fde6ad..e73bf3e37 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -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({ - Downloading Update: {this.state.downloadProgress}% Complete + Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}% Complete` : null} +
+ + {this.state.downloadComplete + ? + : null} +