Skip to content

Commit

Permalink
feat(electron-updater): MacOS delta update
Browse files Browse the repository at this point in the history
Close #2995, Close #3269
  • Loading branch information
develar committed Sep 8, 2018
1 parent 9eec0a9 commit a6c5bc4
Show file tree
Hide file tree
Showing 22 changed files with 413 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/node:10.9.0
- image: circleci/node:10.10.0
steps:
- checkout
- restore_cache:
Expand Down
3 changes: 0 additions & 3 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docker/10/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM electronuserland/builder:base

ENV NODE_VERSION 10.9.0
ENV NODE_VERSION 10.10.0

# this package is used for snapcraft and we should not clear apt list - to avoid apt-get update during snap build
RUN apt-get -qq update && \
Expand Down
24 changes: 20 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"7zip-bin": "~4.0.2",
"@types/is-ci": "^1.1.0",
"app-builder-bin": "2.1.2",
"app-builder-bin": "2.1.3",
"archiver": "^3.0.0",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.5",
Expand Down Expand Up @@ -66,18 +66,33 @@
"temp-file": "^3.1.3",
"tunnel-agent": "^0.6.0",
"update-notifier": "^2.5.0",
"yargs": "^12.0.1"
"yargs": "^12.0.2"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-do-expressions": "^7.0.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-function-sent": "^7.0.0",
"@babel/plugin-proposal-json-strings": "^7.0.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-stage-0": "^7.0.0",
"@types/debug": "^0.0.30",
"@types/ejs": "^2.6.0",
"@types/electron-is-dev": "^0.3.0",
"@types/ini": "^1.3.29",
"@types/jest": "^23.3.1",
"@types/jest": "^23.3.2",
"@types/js-yaml": "^3.11.2",
"@types/lodash.isequal": "^4.5.3",
"@types/node-emoji": "^1.8.0",
Expand All @@ -86,6 +101,7 @@
"@types/semver": "^5.5.0",
"@types/source-map-support": "^0.4.1",
"@types/stat-mode": "^0.2.0",
"babel-core": "^7.0.0-bridge.0",
"babel-preset-ts-node6-bluebird": "^2.0.11",
"convert-source-map": "^1.6.0",
"cross-env": "^5.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"homepage": "https://github.com/electron-userland/electron-builder",
"dependencies": {
"7zip-bin": "~4.0.2",
"app-builder-bin": "2.1.2",
"app-builder-bin": "2.1.3",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.5",
"chromium-pickle-js": "^0.2.0",
Expand Down
26 changes: 25 additions & 1 deletion packages/app-builder-lib/src/ProtonFramework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,33 @@ class ProtonFramework implements Framework {
// out test dir can be located outside of electron-builder node_modules and babel cannot resolve string names of preset
babelOptions.presets = [
[require("@babel/preset-env").default, {targets: {node: this.version}}],
[require("@babel/preset-stage-0"), {decoratorsLegacy: true}],
require("@babel/preset-react"),
]
babelOptions.plugins = [
// stage 0
require("@babel/plugin-proposal-function-bind").default,

// stage 1
require("@babel/plugin-proposal-export-default-from").default,
require("@babel/plugin-proposal-logical-assignment-operators").default,
[require("@babel/plugin-proposal-optional-chaining").default, {loose: false}],
[require("@babel/plugin-proposal-pipeline-operator").default, {proposal: "minimal"}],
[require("@babel/plugin-proposal-nullish-coalescing-operator").default, {loose: false}],
require("@babel/plugin-proposal-do-expressions").default,

// stage 2
[require("@babel/plugin-proposal-decorators").default, {legacy: true}],
require("@babel/plugin-proposal-function-sent").default,
require("@babel/plugin-proposal-export-namespace-from").default,
require("@babel/plugin-proposal-numeric-separator").default,
require("@babel/plugin-proposal-throw-expressions").default,

// stage 3
require("@babel/plugin-syntax-dynamic-import").default,
require("@babel/plugin-syntax-import-meta").default,
[require("@babel/plugin-proposal-class-properties").default, {loose: false}],
require("@babel/plugin-proposal-json-strings").default,
]
babelOptions.babelrc = false
}
else {
Expand Down
7 changes: 7 additions & 0 deletions packages/app-builder-lib/src/targets/ArchiveTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Platform, Target, TargetSpecificOptions } from "../core"
import { copyFiles, getFileMatchers } from "../fileMatcher"
import { PlatformPackager } from "../platformPackager"
import { archive, tar } from "./archive"
import { appendBlockmap } from "./differentialUpdateInfoBuilder"

export class ArchiveTarget extends Target {
readonly options: TargetSpecificOptions = (this.packager.config as any)[this.name]
Expand Down Expand Up @@ -31,6 +32,7 @@ export class ArchiveTarget extends Target {
const artifactPath = path.join(this.outDir, artifactName)

this.logBuilding(`${isMac ? "macOS " : ""}${format}`, artifactPath, arch)
let updateInfo: any = null
if (format.startsWith("tar.")) {
await tar(packager.compression, format, artifactPath, appOutDir, isMac, packager.info.tempDirManager)
}
Expand All @@ -54,9 +56,14 @@ export class ArchiveTarget extends Target {
withoutDir,
}
await archive(format, artifactPath, dirToArchive, archiveOptions)

if (this.isWriteUpdateInfo && format === "zip") {
updateInfo = await appendBlockmap(artifactPath)

This comment has been minimized.

Copy link
@lutzroeder

lutzroeder Mar 25, 2019

Contributor
}
}

packager.info.dispatchArtifactCreated({
updateInfo,
file: artifactPath,
// tslint:disable-next-line:no-invalid-template-strings
safeArtifactName: packager.computeSafeArtifactName(artifactName, format, arch, false, defaultPattern.replace("${productName}", "${name}")),
Expand Down
1 change: 0 additions & 1 deletion packages/app-builder-lib/src/targets/nsis/NsisTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export class NsisTarget extends Target {
const data = await appendBlockmap(archiveFile)
return {
...data,
size: data.size!!,
path: archiveFile,
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/builder-util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"out"
],
"dependencies": {
"app-builder-bin": "2.1.2",
"app-builder-bin": "2.1.3",
"temp-file": "^3.1.3",
"fs-extra-p": "^4.6.1",
"is-ci": "^1.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"read-config-file": "3.1.2",
"sanitize-filename": "^1.6.1",
"update-notifier": "^2.5.0",
"yargs": "^12.0.1",
"yargs": "^12.0.2",
"lazy-val": "^1.0.3",
"app-builder-lib": "0.0.0-semantic-release",
"dmg-builder": "0.0.0-semantic-release"
Expand Down
3 changes: 1 addition & 2 deletions packages/electron-updater/src/AppUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "source-map-support/register"
import { DownloadedUpdateHelper } from "./DownloadedUpdateHelper"
import { ElectronHttpExecutor } from "./electronHttpExecutor"
import { GenericProvider } from "./providers/GenericProvider"
import { DOWNLOAD_PROGRESS, Logger, Provider, ResolvedUpdateFileInfo, UPDATE_DOWNLOADED, UpdateCheckResult, UpdaterSignal } from "./main"
import { DOWNLOAD_PROGRESS, Logger, Provider, ResolvedUpdateFileInfo, UpdateCheckResult, UpdaterSignal } from "./main"
import { createClient, isUrlProbablySupportMultiRangeRequests } from "./providerFactory"

export abstract class AppUpdater extends EventEmitter {
Expand Down Expand Up @@ -526,7 +526,6 @@ export abstract class AppUpdater extends EventEmitter {
await this.downloadedUpdateHelper.cacheUpdateInfo(updateFileName)
}

this.emit(UPDATE_DOWNLOADED, updateInfo)
await taskOptions.done!!(updateFile)
return packageFile == null ? [updateFile] : [updateFile, packageFile]
}
Expand Down
2 changes: 2 additions & 0 deletions packages/electron-updater/src/BaseUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AllPublishOptions } from "builder-util-runtime"
import { AppUpdater, DownloadExecutorTask } from "./AppUpdater"
import { UPDATE_DOWNLOADED } from "./main"

export abstract class BaseUpdater extends AppUpdater {
protected quitAndInstallCalled = false
Expand Down Expand Up @@ -28,6 +29,7 @@ export abstract class BaseUpdater extends AppUpdater {
return super.executeDownload({
...taskOptions,
done: async () => {
this.emit(UPDATE_DOWNLOADED, taskOptions.downloadUpdateOptions.updateInfo)
this.addQuitHandler()
}
})
Expand Down
54 changes: 14 additions & 40 deletions packages/electron-updater/src/MacUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { AllPublishOptions, CancellationToken, configureRequestOptionsFromUrl, DigestTransform, newError, RequestHeaders, safeStringifyJson } from "builder-util-runtime"
import { createServer, IncomingMessage, OutgoingHttpHeaders, ServerResponse } from "http"
import { AllPublishOptions, newError, safeStringifyJson, UpdateInfo } from "builder-util-runtime"
import { createReadStream, stat } from "fs-extra-p"
import { createServer, IncomingMessage, ServerResponse } from "http"
import { AddressInfo } from "net"
import { AppUpdater, DownloadUpdateOptions } from "./AppUpdater"
import { DOWNLOAD_PROGRESS } from "./main"
import { UPDATE_DOWNLOADED } from "./main"
import { findFile } from "./providers/Provider"
import { createReadStream, stat } from "fs-extra-p"
import AutoUpdater = Electron.AutoUpdater

export class MacUpdater extends AppUpdater {
private readonly nativeUpdater: AutoUpdater = require("electron").autoUpdater

private updateInfoForPendingUpdateDownloadedEvent: UpdateInfo | null = null

constructor(options?: AllPublishOptions) {
super(options)

this.nativeUpdater.on("error", it => {
this._logger.warn(it)
this.emit("error", it)
})
this.nativeUpdater.on("update-downloaded", () => {
const updateInfo = this.updateInfoForPendingUpdateDownloadedEvent
this.updateInfoForPendingUpdateDownloadedEvent = null
this.emit(UPDATE_DOWNLOADED, updateInfo)
})
}

protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise<Array<string>> {
this.updateInfoForPendingUpdateDownloadedEvent = null

const files = (await this.provider).resolveFiles(downloadUpdateOptions.updateInfo)
const zipFileInfo = findFile(files, "zip", ["pkg", "dmg"])
if (zipFileInfo == null) {
Expand All @@ -44,6 +53,7 @@ export class MacUpdater extends AppUpdater {
return this.httpExecutor.download(zipFileInfo.url.href, destinationFile, downloadOptions)
},
done: async updateFile => {
this.updateInfoForPendingUpdateDownloadedEvent = downloadUpdateOptions.updateInfo
let updateFileSize = zipFileInfo.info.size
if (updateFileSize == null) {
updateFileSize = (await stat(updateFile)).size
Expand Down Expand Up @@ -109,42 +119,6 @@ export class MacUpdater extends AppUpdater {
})
}

private doProxyUpdateFile(nativeResponse: ServerResponse, url: string, headers: OutgoingHttpHeaders, sha512: string | null, cancellationToken: CancellationToken, errorHandler: (error: Error) => void) {
const downloadRequest = this.httpExecutor.doRequest(configureRequestOptionsFromUrl(url, {headers}), downloadResponse => {
const nativeHeaders: RequestHeaders = {"Content-Type": "application/zip"}
const streams: Array<any> = []
const downloadListenerCount = this.listenerCount(DOWNLOAD_PROGRESS)
this._logger.info(`${DOWNLOAD_PROGRESS} listener count: ${downloadListenerCount}`)
nativeResponse.writeHead(200, nativeHeaders)

// for mac only sha512 is produced (sha256 is published for windows only to preserve backward compatibility)
if (sha512 != null) {
// "hex" to easy migrate to new base64 encoded hash (we already produces latest-mac.yml with hex encoded hash)
streams.push(new DigestTransform(sha512, "sha512", sha512.length === 128 && !sha512.includes("+") && !sha512.includes("Z") && !sha512.includes("=") ? "hex" : "base64"))
}

streams.push(nativeResponse)

let lastStream = downloadResponse
for (const stream of streams) {
stream.on("error", errorHandler)
lastStream = lastStream.pipe(stream)
}
})

downloadRequest.on("redirect", (statusCode: number, method: string, redirectUrl: string) => {
if (headers.authorization != null && (headers!!.authorization as string).startsWith("token")) {
const parsedNewUrl = new URL(redirectUrl)
if (parsedNewUrl.hostname.endsWith(".amazonaws.com")) {
delete headers.authorization
}
}
this.doProxyUpdateFile(nativeResponse, redirectUrl, headers, sha512, cancellationToken, errorHandler)
})
downloadRequest.on("error", errorHandler)
downloadRequest.end()
}

quitAndInstall(): void {
this.nativeUpdater.quitAndInstall()
}
Expand Down
Loading

0 comments on commit a6c5bc4

Please sign in to comment.