Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Update with electron-updater (WIP) #808

Merged
merged 36 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
285c1ca
Add electron-updater requirement and import
alexliebowitz Nov 18, 2017
2031f36
Upgrade to electron-builder 19.45.5
alexliebowitz Nov 19, 2017
43297ce
Add electron-log
alexliebowitz Nov 27, 2017
dade117
Add updater settings to package.json
alexliebowitz Dec 3, 2017
b202de4
Upload assets for latest version to S3 in a separate directory
alexliebowitz Dec 2, 2017
bd8146a
Add electron-publisher-s3 requirement
alexliebowitz Dec 4, 2017
67c3863
Don't try to upload latest-linux.yml file on Linux
alexliebowitz Dec 7, 2017
e39470c
Copy dmg file into dist/mac for TeamCity
alexliebowitz Dec 8, 2017
5fff24b
Add "auto update downloaded" modal
alexliebowitz Dec 8, 2017
24ced8e
More core UI for auto-update
alexliebowitz Dec 8, 2017
565f411
Make app actually quit for update
alexliebowitz Dec 10, 2017
3957bf2
Finish core UI for auto update
alexliebowitz Dec 10, 2017
acec4a7
Remove S3 upload from upload_assets.py
alexliebowitz Dec 17, 2017
bc15d24
Add ability to decline updates
alexliebowitz Dec 22, 2017
b08d96d
Add alert before close after update is declined on Windows
alexliebowitz Jan 2, 2018
b03623e
Convert Windows update alert dialog to native dialog in main process
alexliebowitz Jan 5, 2018
3959d4a
Make app restart after user approves update
alexliebowitz Jan 8, 2018
cf3406f
Remove icon from the Windows auto-update alert dialog shown on closing
alexliebowitz Jan 8, 2018
fb8aee4
Version 0.31 [temp, for testing]
alexliebowitz Jan 8, 2018
5a475f4
Version 0.32 [temp, for testing]
alexliebowitz Jan 8, 2018
0c8ba50
Update Auto Update-related copy
alexliebowitz Jan 13, 2018
f244f90
Add back "Upgrade App" button on Mac/Win with different dialog on click
alexliebowitz Jan 16, 2018
dccb06c
Version 0.35 [temp, for testing]
alexliebowitz Jan 17, 2018
863f7dc
Make new dialogs behave correctly when video is playing
alexliebowitz Jan 17, 2018
853bf2c
Make video pause when you hit the "Upgrade Now" button on Mac/Win
alexliebowitz Jan 17, 2018
2d33767
Change version to 0.1.0 [temp, for testing]
alexliebowitz Jan 17, 2018
37b5ec9
Change version to 0.2.0 [temp, for testing]
alexliebowitz Jan 17, 2018
483809b
Merge remote-tracking branch 'origin/master' into auto-update
alexliebowitz Jan 23, 2018
3e84907
Version 0.3.0 [temp, for testing]
alexliebowitz Jan 23, 2018
fbc143a
Reword "Upgrade on Restart" to "Upgrade on Close"
alexliebowitz Jan 24, 2018
daca49d
Add release notes to auto update dialogs
alexliebowitz Jan 24, 2018
8c0bf32
Small changes to prepare auto update for release
alexliebowitz Jan 24, 2018
f302a0f
Merge branch 'master' into auto-update
alexliebowitz Jan 24, 2018
eef3f6a
Restore version number with -dev suffix (needed for new build process)
alexliebowitz Jan 24, 2018
76f6b1e
Remove remaining traces of logging code from main/index.js
alexliebowitz Jan 24, 2018
e98231f
Don't show auto-update dialog after decline on Win/Mac
alexliebowitz Jan 24, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ if [ "$FULL_BUILD" == "true" ]; then

yarn build

# Workaround: TeamCity expects the dmg to be in dist/mac, but in the new electron-builder
# it's put directly in dist/ (the right way to solve this is to update the TeamCity config)
if $OSX; then
cp dist/*.dmg dist/mac
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't be better to change TeamCity config directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would, but I figured we would worry about it later. It's already going to be a little complicated to roll out auto update.

fi

# electron-build has a publish feature, but I had a hard time getting
# it to reliably work and it also seemed difficult to configure. Not proud of
# this, but it seemed better to write my own.
Expand Down
46 changes: 20 additions & 26 deletions build/upload_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,42 @@
import uritemplate
import boto3


def main():
upload_to_github_if_tagged('lbryio/lbry-app')
upload_to_s3('app')


def get_asset_filename():
def get_asset_path():
this_dir = os.path.dirname(os.path.realpath(__file__))
system = platform.system()
if system == 'Darwin':
return glob.glob(this_dir + '/../dist/LBRY*.dmg')[0]
suffix = 'dmg'
elif system == 'Linux':
return glob.glob(this_dir + '/../dist/LBRY*.deb')[0]
suffix = 'deb'
elif system == 'Windows':
return glob.glob(this_dir + '/../dist/LBRY*.exe')[0]
suffix = 'exe'
else:
raise Exception("I don't know about any artifact on {}".format(system))

return os.path.realpath(glob.glob(this_dir + '/../dist/LBRY*.' + suffix)[0])

def upload_to_s3(folder):
tag = subprocess.check_output(['git', 'describe', '--always', '--abbrev=8', 'HEAD']).strip()
commit_date = subprocess.check_output([
'git', 'show', '-s', '--format=%cd', '--date=format:%Y%m%d-%H%I%S', 'HEAD']).strip()

asset_path = get_asset_filename()
bucket = 'releases.lbry.io'
key = folder + '/' + commit_date + '-' + tag + '/' + os.path.basename(asset_path)
def get_update_asset_path():
# Get the asset used used for updates. On Mac, this is a .zip; on
# Windows it's just the installer file.
if platform.system() == 'Darwin':
this_dir = os.path.dirname(os.path.realpath(__file__))
return os.path.realpath(glob.glob(this_dir + '/../dist/LBRY*.zip')[0])
else:
return get_asset_path()

print "Uploading " + asset_path + " to s3://" + bucket + '/' + key + ''

if 'AWS_ACCESS_KEY_ID' not in os.environ or 'AWS_SECRET_ACCESS_KEY' not in os.environ:
print 'Must set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to publish assets to s3'
return 1
def get_latest_file_path():
# The update metadata file is called latest.yml on Windows, latest-mac.yml on
# Mac, latest-linux.yml on Linux
this_dir = os.path.dirname(os.path.realpath(__file__))

s3 = boto3.resource(
's3',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
config=boto3.session.Config(signature_version='s3v4')
)
s3.meta.client.upload_file(asset_path, bucket, key)
latestfilematches = glob.glob(this_dir + '/../dist/latest*.yml')

return latestfilematches[0] if latestfilematches else None

def upload_to_github_if_tagged(repo_name):
try:
Expand All @@ -75,7 +69,7 @@ def upload_to_github_if_tagged(repo_name):
# TODO: maybe this should be an error
return 1

asset_path = get_asset_filename()
asset_path = get_asset_path()
print "Uploading " + asset_path + " to Github tag " + current_tag
release = get_github_release(repo, current_tag)
upload_asset_to_github(release, asset_path, gh_token)
Expand Down
6 changes: 5 additions & 1 deletion electron-builder.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"appId": "io.lbry.LBRY",
"productName": "LBRY",
"publish": {
"provider": "s3",
"bucket": "releases.lbry.io",
"path": "app/latest"
},
"mac": {
"category": "public.app-category.entertainment"
},
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "LBRY",
"version": "0.19.4",
"version": "0.19.4-dev",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"homepage": "https://lbry.io/",
"bugs": {
Expand Down Expand Up @@ -38,6 +38,9 @@
"classnames": "^2.2.5",
"country-data": "^0.0.31",
"electron-dl": "^1.6.0",
"electron-log": "^2.2.12",
"electron-publisher-s3": "^19.47.0",
"electron-updater": "^2.16.1",
"formik": "^0.10.4",
"from2": "^2.3.0",
"install": "^0.10.2",
Expand Down
47 changes: 45 additions & 2 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@ import SemVer from 'semver';
import url from 'url';
import https from 'https';
import { shell, app, ipcMain, dialog } from 'electron';
import { autoUpdater } from 'electron-updater';
import Daemon from './Daemon';
import Tray from './Tray';
import createWindow from './createWindow';

autoUpdater.autoDownload = true;

// This is set to true if an auto update has been downloaded through the Electron
// auto-update system and is ready to install. If the user declined an update earlier,
// it will still install on shutdown.
let autoUpdateDownloaded = false;

// Keeps track of whether the user has accepted an auto-update through the interface.
let autoUpdateAccepted = false;

// This is used to keep track of whether we are showing the special dialog
// that we show on Windows after you decline an upgrade and close the app later.
let showingAutoUpdateCloseAlert = false;

// Keep a global reference, if you don't, they will be closed automatically when the JavaScript
// object is garbage collected.
let rendererWindow;
Expand Down Expand Up @@ -68,7 +83,26 @@ app.on('activate', () => {
if (!rendererWindow) rendererWindow = createWindow();
});

app.on('will-quit', () => {
app.on('will-quit', (event) => {
if (process.platform === 'win32' && autoUpdateDownloaded && !autoUpdateAccepted && !showingAutoUpdateCloseAlert) {
// We're on Win and have an update downloaded, but the user declined it (or closed
// the app without accepting it). Now the user is closing the app, so the new update
// will install. On Mac this is silent, but on Windows they get a confusing permission
// escalation dialog, so we show Windows users a warning dialog first.

showingAutoUpdateCloseAlert = true;
dialog.showMessageBox({
type: 'info',
title: 'LBRY Will Upgrade',
message: 'LBRY has a pending upgrade. Please select "Yes" to install it on the prompt shown after this one.',
}, () => {
app.quit();
});

event.preventDefault();
return;
}

isQuitting = true;
if (daemon) daemon.quit();
});
Expand Down Expand Up @@ -108,6 +142,15 @@ ipcMain.on('upgrade', (event, installerPath) => {
app.quit();
});

autoUpdater.on('update-downloaded', () => {
autoUpdateDownloaded = true;
})

ipcMain.on('autoUpdateAccepted', () => {
autoUpdateAccepted = true;
autoUpdater.quitAndInstall();
});

ipcMain.on('version-info-requested', () => {
function formatRc(ver) {
// Adds dash if needed to make RC suffix SemVer friendly
Expand Down Expand Up @@ -195,4 +238,4 @@ const isSecondInstance = app.makeSingleInstance(argv => {

if (isSecondInstance) {
app.exit();
}
}
7 changes: 4 additions & 3 deletions src/renderer/component/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import { selectIsBackDisabled, selectIsForwardDisabled } from 'redux/selectors/n
import { selectBalance } from 'redux/selectors/wallet';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import Header from './view';
import { selectIsUpgradeAvailable } from 'redux/selectors/app';
import { doDownloadUpgrade } from 'redux/actions/app';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
import { doDownloadUpgradeRequested } from 'redux/actions/app';

const select = state => ({
isBackDisabled: selectIsBackDisabled(state),
isForwardDisabled: selectIsForwardDisabled(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
balance: formatCredits(selectBalance(state) || 0, 2),
});

const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
downloadUpgradeRequested: () => dispatch(doDownloadUpgradeRequested()),
});

export default connect(select, perform)(Header);
7 changes: 4 additions & 3 deletions src/renderer/component/header/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ export const Header = props => {
isBackDisabled,
isForwardDisabled,
isUpgradeAvailable,
autoUpdateDownloaded,
navigate,
downloadUpgrade,
downloadUpgradeRequested,
} = props;
return (
<header id="header">
Expand Down Expand Up @@ -86,9 +87,9 @@ export const Header = props => {
title={__('Settings')}
/>
</div>
{isUpgradeAvailable && (
{(autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable)) && (
<Link
onClick={() => downloadUpgrade()}
onClick={() => downloadUpgradeRequested()}
button="primary button--flat"
icon="icon-arrow-up"
label={__('Upgrade App')}
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/constants/action_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const UPDATE_VERSION = 'UPDATE_VERSION';
export const UPDATE_REMOTE_VERSION = 'UPDATE_REMOTE_VERSION';
export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE';
export const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED';
export const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED';

// Wallet
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED';
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/constants/modal_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const CONFIRM_FILE_REMOVE = 'confirmFileRemove';
export const INCOMPATIBLE_DAEMON = 'incompatibleDaemon';
export const FILE_TIMEOUT = 'file_timeout';
export const DOWNLOADING = 'downloading';
export const AUTO_UPDATE_DOWNLOADED = "auto_update_downloaded";
export const AUTO_UPDATE_CONFIRM = 'auto_update_confirm';
export const ERROR = 'error';
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
export const UPGRADE = 'upgrade';
Expand Down
27 changes: 26 additions & 1 deletion src/renderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import lbry from 'lbry';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { doConditionalAuthNavigate, doDaemonReady, doShowSnackBar } from 'redux/actions/app';
import { doConditionalAuthNavigate, doDaemonReady, doShowSnackBar, doAutoUpdate } from 'redux/actions/app';
import { doUpdateIsNightAsync } from 'redux/actions/settings';
import { doNavigate } from 'redux/actions/navigation';
import { doDownloadLanguages } from 'redux/actions/settings';
Expand All @@ -18,6 +18,15 @@ import 'scss/all.scss';
import store from 'store';
import app from './app';

const { autoUpdater } = remote.require('electron-updater');

autoUpdater.logger = remote.require("electron-log");

window.addEventListener('contextmenu', event => {
contextMenu(remote.getCurrentWindow(), event.x, event.y, app.env === 'development');
event.preventDefault();
});

ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
if (uri && uri.startsWith('lbry://')) {
if (uri.startsWith('lbry://?verify=')) {
Expand Down Expand Up @@ -91,6 +100,22 @@ document.addEventListener('click', event => {
});

const init = () => {
autoUpdater.on("update-downloaded", () => {
app.store.dispatch(doAutoUpdate());
});

if (["win32", "darwin"].includes(process.platform)) {
autoUpdater.on("update-available", () => {
console.log("Update available");
});
autoUpdater.on("update-not-available", () => {
console.log("Update not available");
});
autoUpdater.on("update-downloaded", () => {
console.log("Update downloaded");
app.store.dispatch(doAutoUpdate());
});
}
app.store.dispatch(doUpdateIsNightAsync());
app.store.dispatch(doDownloadLanguages());

Expand Down
11 changes: 11 additions & 0 deletions src/renderer/modal/modalAutoUpdateConfirm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal, doAutoUpdateDeclined } from "redux/actions/app";
import ModalAutoUpdateConfirm from "./view";

const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
});

export default connect(null, perform)(ModalAutoUpdateConfirm);
44 changes: 44 additions & 0 deletions src/renderer/modal/modalAutoUpdateConfirm/view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { Modal } from "modal/modal";
import { Line } from "rc-progress";
import Link from "component/link/index";

const { ipcRenderer } = require("electron");

class ModalAutoUpdateConfirm extends React.PureComponent {
render() {
const { closeModal, declineAutoUpdate } = this.props;

return (
<Modal
isOpen={true}
type="confirm"
contentLabel={__("Update Downloaded")}
confirmButtonLabel={__("Upgrade")}
abortButtonLabel={__("Not now")}
onConfirmed={() => {
ipcRenderer.send("autoUpdateAccepted");
}}
onAborted={() => {
declineAutoUpdate();
closeModal();
}}
>
<section>
<h3 className="text-center">{__("LBRY Update Ready")}</h3>
<p>
{__(
'Your LBRY update is ready. Restart LBRY now to use it!'
)}
</p>
<p className="meta text-center">
{__('Want to know what has changed?')} See the{' '}
<Link label={__('release notes')} href="https://github.com/lbryio/lbry-app/releases" />.
</p>
</section>
</Modal>
);
}
}

export default ModalAutoUpdateConfirm;
11 changes: 11 additions & 0 deletions src/renderer/modal/modalAutoUpdateDownloaded/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal, doAutoUpdateDeclined } from "redux/actions/app";
import ModalAutoUpdateDownloaded from "./view";

const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
});

export default connect(null, perform)(ModalAutoUpdateDownloaded);
Loading