From ef1e0d3ec90e68b728a20e2242f9900fec7358f7 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 15 Jan 2019 18:29:17 +0900 Subject: [PATCH] Add "Check for Updates" menu item (#1090) * Refactor updater * Add menu item to Check for Updates * Show update/download progress * Ensure that preDownloadProgressBar is closed * Add comment about delay * Refactor initProgressBar * Make only the download progress window closable * Remove unneeded variable * Rename progressUpdater * Extract progress updating logic * Fix breakage on Linux * Set OK button for Linux * Handle errors --- desktop/app.js | 7 +- desktop/menus/help-menu.js | 6 +- desktop/menus/mac-app-menu.js | 1 + desktop/menus/menu-items.js | 7 ++ desktop/updater/auto-updater/index.js | 11 ++ desktop/updater/index.js | 44 +++----- desktop/updater/lib/setup-progress-updates.js | 104 ++++++++++++++++++ desktop/updater/manual-updater/index.js | 16 ++- package-lock.json | 32 ++++-- package.json | 2 + 10 files changed, 190 insertions(+), 40 deletions(-) create mode 100644 desktop/updater/lib/setup-progress-updates.js diff --git a/desktop/app.js b/desktop/app.js index 13bd4a482..36b8da3c4 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -12,14 +12,19 @@ const { const path = require('path'); const windowStateKeeper = require('electron-window-state'); +const config = require('./config'); const createMenuTemplate = require('./menus'); const platform = require('./platform'); +const updater = require('./updater'); const { isDev } = require('./env'); require('module').globalPaths.push(path.resolve(path.join(__dirname))); module.exports = function main() { - require('./updater')(); + app.on('will-finish-launching', function() { + setTimeout(updater.ping.bind(updater), config.updater.delay); + }); + const url = isDev && process.env.DEV_SERVER ? 'http://localhost:4000' // TODO: find a solution to use host and port based on make config. diff --git a/desktop/menus/help-menu.js b/desktop/menus/help-menu.js index d07163a1e..dbee5a9d3 100644 --- a/desktop/menus/help-menu.js +++ b/desktop/menus/help-menu.js @@ -21,7 +21,11 @@ const submenu = [ }, ]; -const defaultSubmenuAdditions = [{ type: 'separator' }, menuItems.about]; +const defaultSubmenuAdditions = [ + { type: 'separator' }, + menuItems.checkForUpdates, + menuItems.about, +]; const helpMenu = { label: '&Help', diff --git a/desktop/menus/mac-app-menu.js b/desktop/menus/mac-app-menu.js index 27f036193..2adafdf93 100644 --- a/desktop/menus/mac-app-menu.js +++ b/desktop/menus/mac-app-menu.js @@ -6,6 +6,7 @@ const macAppMenu = { label: app.getName(), submenu: [ menuItems.about, + menuItems.checkForUpdates, { type: 'separator' }, menuItems.preferences, { type: 'separator' }, diff --git a/desktop/menus/menu-items.js b/desktop/menus/menu-items.js index 880d9b5d2..085bc4158 100644 --- a/desktop/menus/menu-items.js +++ b/desktop/menus/menu-items.js @@ -1,6 +1,7 @@ const { app } = require('electron'); const { appCommandSender } = require('./utils'); +const updater = require('../updater'); const DialogTypes = require('../../shared/dialog-types'); const about = { @@ -11,6 +12,11 @@ const about = { }), }; +const checkForUpdates = { + label: '&Check for Updates…', + click: updater.pingAndShowProgress.bind(updater), +}; + const preferences = { label: 'P&references…', accelerator: 'CommandOrControl+,', @@ -22,5 +28,6 @@ const preferences = { module.exports = { about, + checkForUpdates, preferences, }; diff --git a/desktop/updater/auto-updater/index.js b/desktop/updater/auto-updater/index.js index 9c6f10414..5efa414aa 100644 --- a/desktop/updater/auto-updater/index.js +++ b/desktop/updater/auto-updater/index.js @@ -10,6 +10,7 @@ const { autoUpdater } = require('electron-updater'); */ const Updater = require('../lib/Updater'); const AppQuit = require('../../app-quit'); +const setupProgressUpdates = require('../lib/setup-progress-updates'); class AutoUpdater extends Updater { constructor({ changelogUrl, options = {} }) { @@ -22,10 +23,20 @@ class AutoUpdater extends Updater { autoUpdater.autoInstallOnAppQuit = false; } + // For non-user-initiated checks. + // Check and download in the background, and only notify the user if + // an update exists and has completed downloading. ping() { autoUpdater.checkForUpdates(); } + // For user-initiated checks. + // Will check and download, displaying progress dialogs. + pingAndShowProgress() { + setupProgressUpdates({ updater: autoUpdater, willAutoDownload: true }); + autoUpdater.checkForUpdates(); + } + onConfirm() { AppQuit.allowQuit(); autoUpdater.quitAndInstall(); diff --git a/desktop/updater/index.js b/desktop/updater/index.js index b0b752b50..98fcd3272 100644 --- a/desktop/updater/index.js +++ b/desktop/updater/index.js @@ -1,10 +1,5 @@ 'use strict'; -/** - * External Dependencies - */ -const { app } = require('electron'); - /** * Internal dependencies */ @@ -15,26 +10,21 @@ const ManualUpdater = require('./manual-updater'); let updater = false; -module.exports = function() { - app.on('will-finish-launching', function() { - if (platform.isOSX() || platform.isWindows() || process.env.APPIMAGE) { - updater = new AutoUpdater({ - changelogUrl: config.updater.changelogUrl, - }); - } else { - updater = new ManualUpdater({ - downloadUrl: config.updater.downloadUrl, - apiUrl: config.updater.apiUrl, - changelogUrl: config.updater.changelogUrl, - options: { - dialogMessage: - '{name} {newVersion} is now available — you have {currentVersion}. Would you like to download it now?', - confirmLabel: 'Download', - }, - }); - } - - // Start one straight away - setTimeout(updater.ping.bind(updater), config.updater.delay); +if (platform.isOSX() || platform.isWindows() || process.env.APPIMAGE) { + updater = new AutoUpdater({ + changelogUrl: config.updater.changelogUrl, + }); +} else { + updater = new ManualUpdater({ + downloadUrl: config.updater.downloadUrl, + apiUrl: config.updater.apiUrl, + changelogUrl: config.updater.changelogUrl, + options: { + dialogMessage: + '{name} {newVersion} is now available — you have {currentVersion}. Would you like to download it now?', + confirmLabel: 'Download', + }, }); -}; +} + +module.exports = updater; diff --git a/desktop/updater/lib/setup-progress-updates.js b/desktop/updater/lib/setup-progress-updates.js new file mode 100644 index 000000000..ac631ee10 --- /dev/null +++ b/desktop/updater/lib/setup-progress-updates.js @@ -0,0 +1,104 @@ +const { app, dialog } = require('electron'); +const ProgressBar = require('electron-progressbar'); +const prettyBytes = require('pretty-bytes'); + +const progressBarBlue = '#4895d9'; + +/** + * Set up progress bar dialogs, and add/remove listeners on the updater. + */ +const setupProgressUpdates = ({ updater, willAutoDownload }) => { + let progressBar; + const title = 'Update Simplenote'; + const style = { + bar: { + height: '8px', + 'border-radius': '0', + 'box-shadow': 'none', + }, + value: { + 'background-color': progressBarBlue, + 'border-radius': '0', + 'box-shadow': 'none', + }, + }; + + const preDownloadProgressBar = new ProgressBar({ + title, + text: 'Checking for Updates…', + style, + }); + + const notifyNoUpdate = () => { + updater.removeListener('update-not-available', notifyNoUpdate); + closeProgressAndShowMessage({ + message: 'You’re up to date!', + detail: `Simplenote ${app.getVersion()} is currently the newest version available.`, + }); + }; + + const notifyError = () => { + updater.removeListener('error', notifyError); + closeProgressAndShowMessage({ + message: 'Something went wrong!', + detail: `Please try again later.`, + }); + }; + + const closeProgressAndShowMessage = ({ message, detail }) => { + preDownloadProgressBar.setCompleted(); + setTimeout(() => { + dialog.showMessageBox({ + message, + detail, + buttons: ['OK'], // needs to be set explicitly for Linux + }); + }, 500); // Allow time for preDownloadProgressBar to close + }; + + const initProgressBar = totalBytes => { + progressBar = new ProgressBar({ + indeterminate: false, + title, + text: 'Downloading…', + maxValue: totalBytes, + browserWindow: { closable: true }, + style, + }); + progressBar.on('progress', value => { + progressBar.detail = + prettyBytes(value) + ` of ${prettyBytes(totalBytes)} downloaded…`; + }); + progressBar.on('aborted', () => + updater.removeListener('download-progress', updateProgress) + ); + }; + + const updateProgress = progress => { + if (!progressBar) { + preDownloadProgressBar.setCompleted(); + initProgressBar(progress.total); + } + progressBar.value = progress.transferred; + }; + + updater.on('error', notifyError); + updater.on('update-not-available', notifyNoUpdate); + updater.on('update-available', () => { + if (!willAutoDownload) { + preDownloadProgressBar.setCompleted(); + + // Allow time for preDownloadProgressBar to close + setTimeout(() => updater.notify(), 500); + } + }); + updater.on('download-progress', updateProgress); + updater.on('update-downloaded', () => { + if (preDownloadProgressBar) { + preDownloadProgressBar.setCompleted(); + } + updater.removeListener('download-progress', updateProgress); + }); +}; + +module.exports = setupProgressUpdates; diff --git a/desktop/updater/manual-updater/index.js b/desktop/updater/manual-updater/index.js index fadf89a0e..ad994e8cf 100644 --- a/desktop/updater/manual-updater/index.js +++ b/desktop/updater/manual-updater/index.js @@ -12,6 +12,7 @@ const semver = require('semver'); * Internal dependencies */ const Updater = require('../lib/Updater'); +const setupProgressUpdates = require('../lib/setup-progress-updates'); class ManualUpdater extends Updater { constructor({ apiUrl, downloadUrl, changelogUrl, options = {} }) { @@ -21,6 +22,13 @@ class ManualUpdater extends Updater { this.downloadUrl = downloadUrl; } + // For user-initiated checks. + // Will check and display a progress dialog. + pingAndShowProgress() { + setupProgressUpdates({ updater: this, willAutoDownload: false }); + this.ping(); + } + async ping() { const options = { headers: { @@ -32,6 +40,8 @@ class ManualUpdater extends Updater { const releaseResp = await fetch(this.apiUrl, options); if (releaseResp.status !== 200) { + this.emit('error'); + console.log(releaseResp); return; } @@ -47,6 +57,8 @@ class ManualUpdater extends Updater { ); if (configResp.status !== 200) { + this.emit('error'); + console.log(configResp); return; } @@ -59,12 +71,14 @@ class ManualUpdater extends Updater { releaseConfig.version ); this.setVersion(releaseConfig.version); - this.notify(); + this.emit('update-available'); } else { + this.emit('update-not-available'); return; } } } catch (err) { + this.emit('error'); console.log(err.message); } } diff --git a/package-lock.json b/package-lock.json index 2094059ff..61cfabb31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6837,6 +6837,14 @@ } } }, + "electron-progressbar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-1.1.0.tgz", + "integrity": "sha512-0p2qSZfeIU8WsW/diOtkRTJza64C2IBuSMX4lWmR3ovAmJs6w/vdcw3qiiBUAYMgPWsMdVasDZ531hZSlIkRSw==", + "requires": { + "extend": "^3.0.1" + } + }, "electron-publish": { "version": "20.28.0", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-20.28.0.tgz", @@ -7867,8 +7875,7 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { "version": "3.0.2", @@ -14024,6 +14031,16 @@ "requires": { "ms": "2.0.0" } + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } } } }, @@ -17130,14 +17147,9 @@ "dev": true }, "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.1.0" - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.1.0.tgz", + "integrity": "sha512-wa5+qGVg9Yt7PB6rYm3kXlKzgzgivYTLRandezh43jjRqgyDyP+9YxfJpJiLs9yKD1WeU8/OvtToWpW7255FtA==" }, "pretty-error": { "version": "2.1.1", diff --git a/package.json b/package.json index 384f77fe0..f6674f880 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "draft-js": "0.10.5", "draft-js-simpledecorator": "1.0.2", "electron-fetch": "^1.2.1", + "electron-progressbar": "1.1.0", "electron-spellchecker": "^1.1.2", "electron-updater": "^3.1.6", "electron-window-state": "4.1.1", @@ -120,6 +121,7 @@ "js-yaml": "^3.12.0", "jszip": "3.1.5", "lodash": "4.17.5", + "pretty-bytes": "5.1.0", "promise": "7.1.1", "prop-types": "15.6.0", "randombytes": "2.0.6",