Make autoupdate work on electron.

Changes the update process on web a bit to pull more out to the
platform classes.
This commit is contained in:
David Baker 2016-10-25 16:41:20 +01:00
parent 3c44f8a2cb
commit c02f3a2230
7 changed files with 165 additions and 62 deletions

View file

@ -27,6 +27,8 @@ const PERMITTED_URL_SCHEMES = [
'mailto:', 'mailto:',
]; ];
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
let mainWindow = null; let mainWindow = null;
let appQuitting = false; let appQuitting = false;
@ -66,14 +68,57 @@ function onLinkContextMenu(ev, params) {
ev.preventDefault(); ev.preventDefault();
} }
function installUpdate() {
// for some reason, quitAndInstall does not fire the
// before-quit event, so we need to set the flag here.
appQuitting = true;
electron.autoUpdater.quitAndInstall();
}
function pollForUpdates() {
try {
electron.autoUpdater.checkForUpdates();
} catch (e) {
console.log("Couldn't check for update", e);
}
}
electron.ipcMain.on('install_update', installUpdate);
electron.app.on('ready', () => { electron.app.on('ready', () => {
// Enable auto-update once we can codesign on OS X try {
//electron.autoUpdater.setFeedURL("http://localhost:8888/"); // For reasons best known to Squirrel, the way it checks for updates
// is completely different between macOS and windows. On macOS, it
// hits a URL that either gives it a 200 with some json or
// 204 No Content. On windows it takes a base path and looks for
// files under that path.
if (process.platform == 'darwin') {
electron.autoUpdater.setFeedURL("https://riot.im/autoupdate/desktop/");
} else if (process.platform == 'win32') {
electron.autoUpdater.setFeedURL("https://riot.im/download/desktop/win32/");
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log("Auto update not supported on this platform");
}
// We check for updates ourselves rather than using 'updater' because we need to
// do it in the main process (and we don't really need to check every 10 minutes:
// every hour should be just fine for a desktop app)
// However, we still let the main window listen for the update events.
pollForUpdates();
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
} catch (err) {
// will fail if running in debug mode
console.log("Couldn't enable update checking", err);
}
mainWindow = new electron.BrowserWindow({ mainWindow = new electron.BrowserWindow({
icon: `${__dirname}/../../vector/img/logo.png`, icon: `${__dirname}/../../vector/img/logo.png`,
width: 1024, height: 768, width: 1024, height: 768,
}); });
// A useful one to uncomment for debugging
//mainWindow.webContents.openDevTools();
mainWindow.loadURL(`file://${__dirname}/../../vector/index.html`); mainWindow.loadURL(`file://${__dirname}/../../vector/index.html`);
electron.Menu.setApplicationMenu(VectorMenu); electron.Menu.setApplicationMenu(VectorMenu);

View file

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var sdk = require('matrix-react-sdk'); var sdk = require('matrix-react-sdk');
import Modal from 'matrix-react-sdk/lib/Modal'; import Modal from 'matrix-react-sdk/lib/Modal';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
/** /**
* Check a version string is compatible with the Changelog * Check a version string is compatible with the Changelog
@ -31,30 +32,50 @@ function checkVersion(ver) {
export default function NewVersionBar(props) { export default function NewVersionBar(props) {
const onChangelogClicked = () => { const onChangelogClicked = () => {
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog'); if (props.releaseNotes) {
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
Modal.createDialog(ChangelogDialog, { Modal.createDialog(QuestionDialog, {
version: props.version, title: "What's New",
newVersion: props.newVersion, description: <pre className="changelog_text">{props.releaseNotes}</pre>,
onFinished: (update) => { button: "Update",
if(update) { onFinished: (update) => {
window.location.reload(); if(update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
}
} }
} });
}); } else {
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
Modal.createDialog(ChangelogDialog, {
version: props.version,
newVersion: props.newVersion,
releaseNotes: releaseNotes,
onFinished: (update) => {
if(update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
}
}
});
}
}; };
let changelog_button; const onUpdateClicked = () => {
if (checkVersion(props.version) && checkVersion(props.newVersion)) { PlatformPeg.get().installUpdate();
changelog_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>Changelog</button>; };
let action_button;
if (props.releaseNotes || (checkVersion(props.version) && checkVersion(props.newVersion))) {
action_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>What's new?</button>;
} else if (PlatformPeg.get()) {
action_button = <button className="mx_MatrixToolbar_action" onClick={onUpdateClicked}>Update</button>;
} }
return ( return (
<div className="mx_MatrixToolbar"> <div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/> <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
<div className="mx_MatrixToolbar_content"> <div className="mx_MatrixToolbar_content">
A new version of Riot is available. Refresh your browser. A new version of Riot is available.
</div> </div>
{changelog_button} {action_button}
</div> </div>
); );
} }
@ -62,4 +83,5 @@ export default function NewVersionBar(props) {
NewVersionBar.propTypes = { NewVersionBar.propTypes = {
version: React.PropTypes.string.isRequired, version: React.PropTypes.string.isRequired,
newVersion: React.PropTypes.string.isRequired, newVersion: React.PropTypes.string.isRequired,
releaseNotes: React.PropTypes.string,
}; };

View file

@ -288,3 +288,7 @@ textarea {
cursor: pointer; cursor: pointer;
display: inline; display: inline;
} }
.changelog_text {
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
}

View file

@ -112,10 +112,6 @@ function onHashChange(ev) {
routeUrl(window.location); routeUrl(window.location);
} }
function onVersion(current, latest) {
window.matrixChat.onVersion(current, latest);
}
var loaded = false; var loaded = false;
var lastLoadedScreen = null; var lastLoadedScreen = null;
@ -164,8 +160,7 @@ window.onload = function() {
if (!validBrowser) { if (!validBrowser) {
return; return;
} }
UpdateChecker.setVersionListener(onVersion); UpdateChecker.start();
UpdateChecker.run();
routeUrl(window.location); routeUrl(window.location);
loaded = true; loaded = true;
if (lastLoadedScreen) { if (lastLoadedScreen) {

View file

@ -17,11 +17,22 @@ limitations under the License.
*/ */
import BasePlatform from './BasePlatform'; import BasePlatform from './BasePlatform';
import dis from 'matrix-react-sdk/lib/dispatcher';
function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) {
dis.dispatch({
action: 'new_version',
currentVersion: electron.remote.app.getVersion(),
newVersion: ver,
releaseNotes: releaseNotes,
});
}
// index.js imports us unconditionally, so we need this check here as well // index.js imports us unconditionally, so we need this check here as well
let electron = null, remote = null; let electron = null, remote = null;
if (window && window.process && window.process && window.process.type === 'renderer') { if (window && window.process && window.process && window.process.type === 'renderer') {
electron = require('electron'); electron = require('electron');
electron.remote.autoUpdater.on('update-downloaded', onUpdateDownloaded);
remote = electron.remote; remote = electron.remote;
} }
@ -56,4 +67,17 @@ export default class ElectronPlatform extends BasePlatform {
clearNotification(notif: Notification) { clearNotification(notif: Notification) {
notif.close(); notif.close();
} }
pollForUpdate() {
// In electron we control the update process ourselves, since
// it needs to run in the main process, so we just run the timer
// loop in the main electron process instead.
}
installUpdate() {
// IPC to the main process to install the update, since quitAndInstall
// doesn't fire the before-quit event so the main process needs to know
// it should exit.
electron.ipcRenderer.send('install_update');
}
} }

View file

@ -18,10 +18,14 @@ limitations under the License.
import BasePlatform from './BasePlatform'; import BasePlatform from './BasePlatform';
import Favico from 'favico.js'; import Favico from 'favico.js';
import request from 'browser-request';
import dis from 'matrix-react-sdk/lib/dispatcher.js';
import q from 'q';
export default class WebPlatform extends BasePlatform { export default class WebPlatform extends BasePlatform {
constructor() { constructor() {
super(); super();
this.runningVersion = null;
// The 'animations' are really low framerate and look terrible. // The 'animations' are really low framerate and look terrible.
// Also it re-starts the animationb every time you set the badge, // Also it re-starts the animationb every time you set the badge,
// and we set the state each time, even if the value hasn't changed, // and we set the state each time, even if the value hasn't changed,
@ -85,4 +89,43 @@ export default class WebPlatform extends BasePlatform {
notification.close(); notification.close();
}, 5 * 1000); }, 5 * 1000);
} }
_getVersion() {
const deferred = q.defer();
request(
{ method: "GET", url: "version" },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
if (err == null) err = { status: response.status };
deferred.reject(err);
return;
}
const ver = body.trim();
deferred.resolve(ver);
}
);
return deferred.promise;
}
pollForUpdate() {
this._getVersion().done((ver) => {
if (this.runningVersion == null) {
this.runningVersion = ver;
} else if (this.runningVersion != ver) {
dis.dispatch({
action: 'new_version',
currentVersion: this.runningVersion,
newVersion: ver,
releaseNotes: "Theses are some release notes innit\n * Do a thing\n * Do some more things",
});
}
}, (err) => {
console.error("Failed to poll for update", err);
});
}
installUpdate() {
window.location.reload();
}
} }

View file

@ -13,48 +13,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
var POKE_RATE_MS = 10 * 60 * 1000; // 10 min
var currentVersion = null; import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
var latestVersion = null;
var listener = function(){}; // NOP var POKE_RATE_MS = 1 * 6 * 1000; // 10 min
module.exports = { module.exports = {
setVersionListener: function(fn) { // invoked with fn(currentVer, newVer) start: function() {
listener = fn; module.exports.poll();
setInterval(module.exports.poll, POKE_RATE_MS);
}, },
run: function() { poll: function() {
var req = new XMLHttpRequest(); PlatformPeg.get().pollForUpdate();
req.addEventListener("load", function() {
if (!req.responseText) {
return;
}
var ver = req.responseText.trim();
if (!currentVersion) {
currentVersion = ver;
listener(currentVersion, currentVersion);
}
if (ver !== latestVersion) {
latestVersion = ver;
if (module.exports.hasNewVersion()) {
console.log("Current=%s Latest=%s", currentVersion, latestVersion);
listener(currentVersion, latestVersion);
}
}
});
var cacheBuster = "?ts=" + new Date().getTime();
req.open("GET", "version" + cacheBuster);
req.send(); // can't suppress 404s from being logged.
setTimeout(module.exports.run, POKE_RATE_MS);
},
getCurrentVersion: function() {
return currentVersion;
},
hasNewVersion: function() {
return currentVersion && latestVersion && (currentVersion !== latestVersion);
} }
}; };