diff --git a/build/build.sh b/build/build.sh index 2273eff00..1ace2ad48 100755 --- a/build/build.sh +++ b/build/build.sh @@ -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 + 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. diff --git a/build/upload_assets.py b/build/upload_assets.py index 2ba8b56b5..b0fb5513c 100644 --- a/build/upload_assets.py +++ b/build/upload_assets.py @@ -9,48 +9,42 @@ import github 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() +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() - asset_path = get_asset_filename() - bucket = 'releases.lbry.io' - key = folder + '/' + commit_date + '-' + tag + '/' + os.path.basename(asset_path) - print "Uploading " + asset_path + " to s3://" + bucket + '/' + key + '' +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__)) - 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 - - 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: @@ -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) diff --git a/electron-builder.json b/electron-builder.json index c3b878d30..09c928130 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -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" }, diff --git a/package.json b/package.json index 1aeda7511..9c357a9f1 100644 --- a/package.json +++ b/package.json @@ -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": { @@ -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", diff --git a/src/main/index.js b/src/main/index.js index 135611b69..6894ee5a1 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -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; @@ -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(); }); @@ -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 @@ -195,4 +238,4 @@ const isSecondInstance = app.makeSingleInstance(argv => { if (isSecondInstance) { app.exit(); -} +} \ No newline at end of file diff --git a/src/renderer/component/header/index.js b/src/renderer/component/header/index.js index f25257214..59c98d0ff 100644 --- a/src/renderer/component/header/index.js +++ b/src/renderer/component/header/index.js @@ -5,13 +5,14 @@ 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), }); @@ -19,7 +20,7 @@ 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); diff --git a/src/renderer/component/header/view.jsx b/src/renderer/component/header/view.jsx index c1289c889..7109d7b8b 100644 --- a/src/renderer/component/header/view.jsx +++ b/src/renderer/component/header/view.jsx @@ -10,8 +10,9 @@ export const Header = props => { isBackDisabled, isForwardDisabled, isUpgradeAvailable, + autoUpdateDownloaded, navigate, - downloadUpgrade, + downloadUpgradeRequested, } = props; return (