Skip to content

Commit cb651dd

Browse files
authored
feat(updater): Cache the new blockmap file and allow customization of the old blockmap file base URL. (electron-userland#9172)
Currently, downloading updates from GitHub's Latest Release does not support delta updates. This is mainly due to the inability to download the old map file. Now, by adding a configuration that allows users to customize the old map file URL, users can define the old map file URL themselves. This makes it possible to achieve delta updates without modifying the server side. Cache the new blockmap file, and during the next update, prioritize reading it from the local cache. This reduces requests for the blockmap and allows delta updates even if the old version’s blockmap file is no longer available on the server. Fix electron-userland#8811
1 parent 61aa855 commit cb651dd

File tree

4 files changed

+60
-10
lines changed

4 files changed

+60
-10
lines changed

.changeset/orange-experts-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"electron-updater": patch
3+
---
4+
5+
feat(updater): Cache the new blockmap file and allow customization of the old blockmap file base URL

packages/electron-updater/src/AppUpdater.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import { randomBytes } from "crypto"
1616
import { release } from "os"
1717
import { EventEmitter } from "events"
18-
import { mkdir, outputFile, readFile, rename, unlink } from "fs-extra"
18+
import { mkdir, outputFile, readFile, rename, unlink, copyFile, pathExists } from "fs-extra"
1919
import { OutgoingHttpHeaders } from "http"
2020
import { load } from "js-yaml"
2121
import { Lazy } from "lazy-val"
@@ -31,7 +31,7 @@ import { Provider, ProviderPlatform } from "./providers/Provider"
3131
import type { TypedEmitter } from "tiny-typed-emitter"
3232
import Session = Electron.Session
3333
import type { AuthInfo } from "electron"
34-
import { gunzipSync } from "zlib"
34+
import { gunzipSync, gzipSync } from "zlib"
3535
import { blockmapFiles } from "./util"
3636
import { DifferentialDownloaderOptions } from "./differentialDownloader/DifferentialDownloader"
3737
import { GenericDifferentialDownloader } from "./differentialDownloader/GenericDifferentialDownloader"
@@ -117,6 +117,18 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter
117117
*/
118118
forceDevUpdateConfig = false
119119

120+
/**
121+
* The base URL of the old block map file.
122+
*
123+
* When null, the updater will use the base URL of the update file to download the update.
124+
* When set, the updater will use this string as the base URL of the old block map file.
125+
* Some servers like github cannot download the old block map file from latest release,
126+
* so you need to compute the old block map file base URL manually.
127+
*
128+
* @default null
129+
*/
130+
public previousBlockmapBaseUrlOverride: string | null = null
131+
120132
/**
121133
* The current application version.
122134
*/
@@ -736,6 +748,10 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter
736748
...updateInfo,
737749
downloadedFile: updateFile,
738750
})
751+
const currentBlockMapFile = path.join(cacheDir, "current.blockmap")
752+
if (await pathExists(currentBlockMapFile)) {
753+
await copyFile(currentBlockMapFile, path.join(downloadedUpdateHelper.cacheDir, "current.blockmap"))
754+
}
739755
return packageFile == null ? [updateFile] : [updateFile, packageFile]
740756
}
741757

@@ -790,7 +806,7 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter
790806
if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) {
791807
return true
792808
}
793-
const blockmapFileUrls = blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version)
809+
const blockmapFileUrls = blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version, this.previousBlockmapBaseUrlOverride)
794810
this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`)
795811

796812
const downloadBlockMap = async (url: URL): Promise<BlockMap> => {
@@ -824,8 +840,33 @@ export abstract class AppUpdater extends (EventEmitter as new () => TypedEmitter
824840
downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it)
825841
}
826842

827-
const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u)))
828-
await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1])
843+
const saveBlockMapToCacheDir = async (blockMapData: BlockMap, cacheDir: string) => {
844+
const blockMapFile = path.join(cacheDir, "current.blockmap")
845+
await outputFile(blockMapFile, gzipSync(JSON.stringify(blockMapData)))
846+
}
847+
848+
const getBlockMapFromCacheDir = async (cacheDir: string) => {
849+
const blockMapFile = path.join(cacheDir, "current.blockmap")
850+
try {
851+
if (await pathExists(blockMapFile)) {
852+
return JSON.parse(gunzipSync(await readFile(blockMapFile)).toString())
853+
}
854+
} catch (e: any) {
855+
this._logger.warn(`Cannot parse blockmap "${blockMapFile}", error: ${e}`)
856+
}
857+
return null
858+
}
859+
860+
const newBlockMapData = await downloadBlockMap(blockmapFileUrls[1])
861+
await saveBlockMapToCacheDir(newBlockMapData, this.downloadedUpdateHelper!.cacheDirForPendingUpdate)
862+
863+
// get old blockmap from cache dir first, if not found, download it
864+
let oldBlockMapData = await getBlockMapFromCacheDir(this.downloadedUpdateHelper!.cacheDir)
865+
if (oldBlockMapData == null) {
866+
oldBlockMapData = await downloadBlockMap(blockmapFileUrls[0])
867+
}
868+
869+
await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(oldBlockMapData, newBlockMapData)
829870
return false
830871
} catch (e: any) {
831872
this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`)

packages/electron-updater/src/util.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ export function getChannelFilename(channel: string): string {
3030
return `${channel}.yml`
3131
}
3232

33-
export function blockmapFiles(baseUrl: URL, oldVersion: string, newVersion: string): URL[] {
33+
export function blockmapFiles(baseUrl: URL, oldVersion: string, newVersion: string, oldBlockMapFileBaseUrl: string | null = null): URL[] {
3434
const newBlockMapUrl = newUrlFromBase(`${baseUrl.pathname}.blockmap`, baseUrl)
35-
const oldBlockMapUrl = newUrlFromBase(`${baseUrl.pathname.replace(new RegExp(escapeRegExp(newVersion), "g"), oldVersion)}.blockmap`, baseUrl)
35+
const oldBlockMapUrl = newUrlFromBase(
36+
`${baseUrl.pathname.replace(new RegExp(escapeRegExp(newVersion), "g"), oldVersion)}.blockmap`,
37+
oldBlockMapFileBaseUrl ? new URL(oldBlockMapFileBaseUrl) : baseUrl
38+
)
39+
3640
return [oldBlockMapUrl, newBlockMapUrl]
3741
}

test/src/binDownloadTest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test("download binary from Mirror with custom dir", async ({ expect }) => {
1717
"linux-tools-mac-10.12.3.7z",
1818
"SQ8fqIRVXuQVWnVgaMTDWyf2TLAJjJYw3tRSqQJECmgF6qdM7Kogfa6KD49RbGzzMYIFca9Uw3MdsxzOPRWcYw=="
1919
)
20-
delete process.env.ELECTRON_BUILDER_BINARIES_MIRROR
20+
delete process.env.ELECTRON_BUILDER_BINARIES_MIRROR
2121
delete process.env.ELECTRON_BUILDER_BINARIES_CUSTOM_DIR
2222
expect(bin).toBeTruthy()
2323
})
@@ -29,7 +29,7 @@ test("download binary from Mirror", async ({ expect }) => {
2929
"linux-tools-mac-10.12.3.7z",
3030
"SQ8fqIRVXuQVWnVgaMTDWyf2TLAJjJYw3tRSqQJECmgF6qdM7Kogfa6KD49RbGzzMYIFca9Uw3MdsxzOPRWcYw=="
3131
)
32-
delete process.env.ELECTRON_BUILDER_BINARIES_MIRROR
32+
delete process.env.ELECTRON_BUILDER_BINARIES_MIRROR
3333
expect(bin).toBeTruthy()
3434
})
3535

@@ -40,6 +40,6 @@ test("download binary from Mirror with Url override", async ({ expect }) => {
4040
"linux-tools-mac-10.12.3.7z",
4141
"SQ8fqIRVXuQVWnVgaMTDWyf2TLAJjJYw3tRSqQJECmgF6qdM7Kogfa6KD49RbGzzMYIFca9Uw3MdsxzOPRWcYw=="
4242
)
43-
delete process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL
43+
delete process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL
4444
expect(bin).toBeTruthy()
4545
})

0 commit comments

Comments
 (0)