diff --git a/app/main.js b/app/main.js index d0b72edae..64deeb573 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') } @@ -115,23 +115,49 @@ app.on('activate', () => { }) -function shutdownDaemon() { - console.log('Shutdown triggered'); - if (subpy == null) { - // 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 +function shutdownDaemon(evenIfNotStartedByApp = false) { + if (subpy) { + console.log('Killing lbrynet-daemon process'); + kill(subpy.pid, undefined, (err) => { + console.log('Killed lbrynet-daemon process'); + }); + } else if (evenIfNotStartedByApp) { + console.log('Killing lbrynet-daemon, even though app did not start it'); + client.request('daemon_stop', []); + // TODO: If the daemon errors or times out when we make this request, find + // the process and force quit it. + } else { + console.log('Not killing lbrynet-daemon because app did not start 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 (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', () => { + shell.openItem(installerPath); + }); + if (win) { + win.loadURL(`file://${__dirname}/dist/upgrade.html`); + } + quitting = true; + shutdownDaemon(true); + // wait for daemon to shut down before upgrading + // 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.'); +} + +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..79ff80873 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'; @@ -91,10 +110,7 @@ var App = React.createClass({ } else if (versionInfo.os_system == 'Linux') { var updateUrl = 'https://lbry.io/get/lbry.deb'; } else if (versionInfo.os_system == 'Windows') { - // A little weird, but for electron, the installer is - // actually an exe. Maybe a better url would - // be something like /get/windows ? - var updateUrl = 'https://lbry.io/get/lbry.msi'; + var updateUrl = 'https://lbry.io/get/lbry.exe'; } else { var updateUrl = 'https://lbry.io/get'; } @@ -123,17 +139,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 +296,22 @@ var App = React.createClass({ - Downloading Update: {this.state.downloadProgress}% Complete + Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null} + {this.state.downloadComplete ? ( +
+
+

Click "Begin Upgrade" to start the upgrade process.

+

The app will close, and you will be prompted to install the latest version of LBRY.

+

After the install is complete, please reopen the app.

+
+ ) : null } +
+ {this.state.downloadComplete + ? + : null} + +
- {this.props.type == 'confirm' - ? - : null} - + + {this.props.type == 'confirm' + ? + : null} } ); diff --git a/ui/js/page/developer.js b/ui/js/page/developer.js index 88a45542d..93eb1cc11 100644 --- a/ui/js/page/developer.js +++ b/ui/js/page/developer.js @@ -1,6 +1,10 @@ import lbry from '../lbry.js'; import React from 'react'; import FormField from '../component/form.js'; +import {Link} from '../component/link.js'; + +const fs = require('fs'); +const {ipcRenderer} = require('electron'); const DeveloperPage = React.createClass({ getInitialState: function() { @@ -8,6 +12,7 @@ const DeveloperPage = React.createClass({ showDeveloperMenu: lbry.getClientSetting('showDeveloperMenu'), useCustomLighthouseServers: lbry.getClientSetting('useCustomLighthouseServers'), customLighthouseServers: lbry.getClientSetting('customLighthouseServers').join('\n'), + upgradePath: '', }; }, handleShowDeveloperMenuChange: function(event) { @@ -23,6 +28,30 @@ const DeveloperPage = React.createClass({ useCustomLighthouseServers: event.target.checked, }); }, + handleUpgradeFileChange: function(event) { + this.setState({ + upgradePath: event.target.files[0].path, + }); + }, + handleForceUpgradeClick: function() { + let upgradeSent = false; + if (!this.state.upgradePath) { + alert('Please select a file to upgrade from'); + } else { + try { + const stats = fs.lstatSync(this.state.upgradePath); + if (stats.isFile()) { + console.log('Starting upgrade using ' + this.state.upgradePath); + ipcRenderer.send('upgrade', this.state.upgradePath); + upgradeSent = true; + } + } + catch (e) {} + if (!upgradeSent) { + alert('Failed to start upgrade. Is "' + this.state.upgradePath + '" a valid path to the upgrade?'); + } + } + }, render: function() { return (
@@ -43,6 +72,13 @@ const DeveloperPage = React.createClass({ : null} +
+
+ +   + +
+
); }