Skip to content

Commit

Permalink
feat(electron-updater): download update on macOS in the same way as f…
Browse files Browse the repository at this point in the history
…or other OS

Much more reliable to download file and only then pipe it to Squirrel.Mac then proxy directly
It in any case required for upcoming delta updates.

Close #3168
  • Loading branch information
develar committed Jul 26, 2018
1 parent 80eaf23 commit f966f1a
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 209 deletions.
16 changes: 3 additions & 13 deletions packages/electron-updater/src/AppImageUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllPublishOptions, DownloadOptions, newError } from "builder-util-runtime"
import { AllPublishOptions, newError } from "builder-util-runtime"
import { execFileSync, spawn } from "child_process"
import isDev from "electron-is-dev"
import { chmod, unlinkSync } from "fs-extra-p"
Expand Down Expand Up @@ -37,21 +37,11 @@ export class AppImageUpdater extends BaseUpdater {
protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise<Array<string>> {
const provider = await this.provider
const fileInfo = findFile(provider.resolveFiles(downloadUpdateOptions.updateInfo), "AppImage")!!

const downloadOptions: DownloadOptions = {
skipDirCreation: true,
headers: downloadUpdateOptions.requestHeaders,
cancellationToken: downloadUpdateOptions.cancellationToken,
sha2: (fileInfo.info as any).sha2,
sha512: fileInfo.info.sha512,
}

return await this.executeDownload({
fileExtension: "AppImage",
downloadOptions,
fileInfo,
updateInfo: downloadUpdateOptions.updateInfo,
task: async updateFile => {
downloadUpdateOptions,
task: async (updateFile, downloadOptions) => {
const oldFile = process.env.APPIMAGE!!
if (oldFile == null) {
throw newError("APPIMAGE env is not defined", "ERR_UPDATER_OLD_FILE_NOT_FOUND")
Expand Down
116 changes: 113 additions & 3 deletions packages/electron-updater/src/AppUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { AllPublishOptions, asArray, CancellationToken, newError, PublishConfiguration, UpdateInfo, UUID } from "builder-util-runtime"
import { AllPublishOptions, asArray, CancellationToken, newError, PublishConfiguration, UpdateInfo, UUID, DownloadOptions, CancellationError } from "builder-util-runtime"
import { randomBytes } from "crypto"
import { Notification } from "electron"
import isDev from "electron-is-dev"
import { EventEmitter } from "events"
import { outputFile, readFile } from "fs-extra-p"
import { ensureDir, outputFile, readFile, rename, unlink } from "fs-extra-p"
import { OutgoingHttpHeaders } from "http"
import { safeLoad } from "js-yaml"
import { Lazy } from "lazy-val"
Expand All @@ -13,7 +13,7 @@ import "source-map-support/register"
import { DownloadedUpdateHelper } from "./DownloadedUpdateHelper"
import { ElectronHttpExecutor } from "./electronHttpExecutor"
import { GenericProvider } from "./providers/GenericProvider"
import { Logger, Provider, UpdateCheckResult, UpdaterSignal } from "./main"
import { DOWNLOAD_PROGRESS, Logger, Provider, ResolvedUpdateFileInfo, UPDATE_DOWNLOADED, UpdateCheckResult, UpdaterSignal } from "./main"
import { createClient, isUrlProbablySupportMultiRangeRequests } from "./providerFactory"

export abstract class AppUpdater extends EventEmitter {
Expand Down Expand Up @@ -483,6 +483,107 @@ export abstract class AppUpdater extends EventEmitter {
}
return true
}

protected async executeDownload(taskOptions: DownloadExecutorTask): Promise<Array<string>> {
const fileInfo = taskOptions.fileInfo
const downloadOptions: DownloadOptions = {
skipDirCreation: true,
headers: taskOptions.downloadUpdateOptions.requestHeaders,
cancellationToken: taskOptions.downloadUpdateOptions.cancellationToken,
sha2: (fileInfo.info as any).sha2,
sha512: fileInfo.info.sha512,
}

if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) {
downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it)
}

const updateInfo = taskOptions.downloadUpdateOptions.updateInfo
const version = updateInfo.version
const packageInfo = fileInfo.packageInfo

function getCacheUpdateFileName(): string {
// bloody NodeJS URL doesn't decode automatically
const urlPath = decodeURIComponent(taskOptions.fileInfo.url.pathname)
if (urlPath.endsWith(`.${taskOptions.fileExtension}`)) {
return path.posix.basename(urlPath)
}
else {
// url like /latest, generate name
return `update.${taskOptions.fileExtension}`
}
}

const cacheDir = this.downloadedUpdateHelper.cacheDir
await ensureDir(cacheDir)
const updateFileName = getCacheUpdateFileName()
let updateFile = path.join(cacheDir, updateFileName)
const packageFile = packageInfo == null ? null : path.join(cacheDir, `package-${version}${path.extname(packageInfo.path) || ".7z"}`)

const done = async (isSaveCache: boolean) => {
this.downloadedUpdateHelper.setDownloadedFile(updateFile, packageFile, updateInfo, fileInfo)
if (isSaveCache) {
await this.downloadedUpdateHelper.cacheUpdateInfo(updateFileName)
}

await taskOptions.done!!(updateFile)

this.emit(UPDATE_DOWNLOADED, updateInfo)
return packageFile == null ? [updateFile] : [updateFile, packageFile]
}

const log = this._logger
const cachedUpdateFile = await this.downloadedUpdateHelper.validateDownloadedPath(updateFile, updateInfo, fileInfo, log)
if (cachedUpdateFile != null) {
updateFile = cachedUpdateFile
return await done(false)
}

const removeFileIfAny = async () => {
await this.downloadedUpdateHelper.clear()
.catch(() => {
// ignore
})
return await unlink(updateFile)
.catch(() => {
// ignore
})
}

// https://github.com/electron-userland/electron-builder/pull/2474#issuecomment-366481912
let nameCounter = 0
let tempUpdateFile = path.join(cacheDir, `temp-${updateFileName}`)
for (let i = 0; i < 3; i++) {
try {
await unlink(tempUpdateFile)
}
catch (e) {
if (e.code === "ENOENT") {
break
}

log.warn(`Error on remove temp update file: ${e}`)
tempUpdateFile = path.join(cacheDir, `temp-${nameCounter++}-${updateFileName}`)
}
}

try {
await taskOptions.task(tempUpdateFile, downloadOptions, packageFile, removeFileIfAny)
await rename(tempUpdateFile, updateFile)
}
catch (e) {
await removeFileIfAny()

if (e instanceof CancellationError) {
log.info("Cancelled")
this.emit("update-cancelled", updateInfo)
}
throw e
}

log.info(`New version ${version} has been downloaded to ${updateFile}`)
return await done(true)
}
}

export interface DownloadUpdateOptions {
Expand Down Expand Up @@ -510,3 +611,12 @@ export class NoOpLogger implements Logger {
// ignore
}
}

export interface DownloadExecutorTask {
readonly fileExtension: string
readonly fileInfo: ResolvedUpdateFileInfo
readonly downloadUpdateOptions: DownloadUpdateOptions
readonly task: (destinationFile: string, downloadOptions: DownloadOptions, packageFile: string | null, removeTempDirIfAny: () => Promise<any>) => Promise<any>

readonly done?: (destinationFile: string) => Promise<any>
}
118 changes: 13 additions & 105 deletions packages/electron-updater/src/BaseUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { AllPublishOptions, CancellationError, DownloadOptions, UpdateInfo } from "builder-util-runtime"
import { ensureDir, rename, unlink } from "fs-extra-p"
import * as path from "path"
import { AppUpdater } from "./AppUpdater"
import { DOWNLOAD_PROGRESS, ResolvedUpdateFileInfo, UPDATE_DOWNLOADED } from "./main"
import { AllPublishOptions } from "builder-util-runtime"
import { AppUpdater, DownloadExecutorTask } from "./AppUpdater"

export abstract class BaseUpdater extends AppUpdater {
protected quitAndInstallCalled = false
Expand All @@ -21,101 +18,19 @@ export abstract class BaseUpdater extends AppUpdater {
this.app.quit()
}
})
} else {
}
else {
this.quitAndInstallCalled = false
}
}

protected async executeDownload(taskOptions: DownloadExecutorTask): Promise<Array<string>> {
if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) {
taskOptions.downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it)
}

const updateInfo = taskOptions.updateInfo
const version = updateInfo.version
const fileInfo = taskOptions.fileInfo
const packageInfo = fileInfo.packageInfo

function getCacheUpdateFileName(): string {
// bloody NodeJS URL doesn't decode automatically
const urlPath = decodeURIComponent(taskOptions.fileInfo.url.pathname)
if (urlPath.endsWith(`.${taskOptions.fileExtension}`)) {
return path.posix.basename(urlPath)
}
else {
// url like /latest, generate name
return `update.${taskOptions.fileExtension}`
}
}

const cacheDir = this.downloadedUpdateHelper.cacheDir
await ensureDir(cacheDir)
const updateFileName = getCacheUpdateFileName()
let updateFile = path.join(cacheDir, updateFileName)
const packageFile = packageInfo == null ? null : path.join(cacheDir, `package-${version}${path.extname(packageInfo.path) || ".7z"}`)

const done = async (isSaveCache: boolean) => {
this.downloadedUpdateHelper.setDownloadedFile(updateFile, packageFile, updateInfo, fileInfo)
if (isSaveCache) {
await this.downloadedUpdateHelper.cacheUpdateInfo(updateFileName)
}

this.addQuitHandler()
this.emit(UPDATE_DOWNLOADED, updateInfo)
return packageFile == null ? [updateFile] : [updateFile, packageFile]
}

const log = this._logger
const cachedUpdateFile = await this.downloadedUpdateHelper.validateDownloadedPath(updateFile, updateInfo, fileInfo, log)
if (cachedUpdateFile != null) {
updateFile = cachedUpdateFile
return await done(false)
}

const removeFileIfAny = async () => {
await this.downloadedUpdateHelper.clear()
.catch(() => {
// ignore
})
return await unlink(updateFile)
.catch(() => {
// ignore
})
}

// https://github.com/electron-userland/electron-builder/pull/2474#issuecomment-366481912
let nameCounter = 0
let tempUpdateFile = path.join(cacheDir, `temp-${updateFileName}`)
for (let i = 0; i < 3; i++) {
try {
await unlink(tempUpdateFile)
protected executeDownload(taskOptions: DownloadExecutorTask): Promise<Array<string>> {
return super.executeDownload({
...taskOptions,
done: async () => {
this.addQuitHandler()
}
catch (e) {
if (e.code === "ENOENT") {
break
}

log.warn(`Error on remove temp update file: ${e}`)
tempUpdateFile = path.join(cacheDir, `temp-${nameCounter++}-${updateFileName}`)
}
}

try {
await taskOptions.task(tempUpdateFile, packageFile, removeFileIfAny)
await rename(tempUpdateFile, updateFile)
}
catch (e) {
await removeFileIfAny()

if (e instanceof CancellationError) {
log.info("Cancelled")
this.emit("update-cancelled", updateInfo)
}
throw e
}

log.info(`New version ${version} has been downloaded to ${updateFile}`)
return await done(true)
})
}

protected abstract doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean
Expand Down Expand Up @@ -158,17 +73,10 @@ export abstract class BaseUpdater extends AppUpdater {
if (!this.quitAndInstallCalled) {
this._logger.info("Auto install update on quit")
await this.install(true, false)
} else {
}
else {
this._logger.info("Update installer has already been triggered. Quitting application.")
}
})
}
}

export interface DownloadExecutorTask {
readonly fileExtension: string
readonly downloadOptions: DownloadOptions
readonly fileInfo: ResolvedUpdateFileInfo
readonly updateInfo: UpdateInfo
readonly task: (destinationFile: string, packageFile: string | null, removeTempDirIfAny: () => Promise<any>) => Promise<any>
}
}
Loading

0 comments on commit f966f1a

Please sign in to comment.