Skip to content

Commit 3f41497

Browse files
committed
fix: writeAsarFile — use close event instead of end
1 parent 7bf594d commit 3f41497

File tree

18 files changed

+330
-377
lines changed

18 files changed

+330
-377
lines changed

lerna.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"lerna": "2.0.0-beta.37",
2+
"lerna": "2.0.0-beta.38",
33
"packages": [
44
"packages/*"
55
],

package.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@
2727
"///": "all dependencies for all packages (hoisted)",
2828
"dependencies": {
2929
"7zip-bin": "^2.0.4",
30-
"ajv": "^5.0.2-beta",
31-
"ajv-keywords": "^2.0.1-beta.0",
30+
"ajv": "^5.0.3-beta",
31+
"ajv-keywords": "^2.0.1-beta.1",
3232
"archiver": "^1.3.0",
33-
"asar": "~0.13.0",
3433
"aws-sdk": "^2.22.0",
3534
"bluebird-lst": "^1.0.1",
3635
"chalk": "^1.1.3",
@@ -44,7 +43,7 @@
4443
"ini": "^1.3.4",
4544
"is-ci": "^1.0.10",
4645
"isbinaryfile": "^3.0.2",
47-
"js-yaml": "^3.8.1",
46+
"js-yaml": "^3.8.2",
4847
"mime": "^1.3.4",
4948
"minimatch": "^3.0.3",
5049
"node-emoji": "^1.5.1",
@@ -56,10 +55,10 @@
5655
"sanitize-filename": "^1.6.1",
5756
"semver": "^5.3.0",
5857
"stat-mode": "^0.2.2",
59-
"tunnel-agent": "^0.4.3",
58+
"tunnel-agent": "^0.6.0",
6059
"update-notifier": "^2.1.0",
6160
"uuid-1345": "^0.99.6",
62-
"yargs": "^6.6.0"
61+
"yargs": "^7.0.1"
6362
},
6463
"devDependencies": {
6564
"@types/electron": "^1.4.33",
@@ -84,11 +83,10 @@
8483
"jest-environment-node-debug": "^2.0.0",
8584
"jest-junit": "^1.2.0",
8685
"jsdoc": "^3.4.3",
87-
"lerna": "2.0.0-beta.37",
8886
"path-sort": "^0.1.0",
8987
"source-map-support": "^0.4.11",
9088
"ts-babel": "^1.4.4",
91-
"tslint": "^4.5.0",
89+
"tslint": "^4.5.1",
9290
"typescript": "^2.2.1",
9391
"typescript-json-schema": "0.10.0",
9492
"whitespace": "^2.1.0",

packages/electron-builder-util/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"source-map-support": "^0.4.11",
2323
"7zip-bin": "^2.0.4",
2424
"ini": "^1.3.4",
25-
"tunnel-agent": "^0.4.3"
25+
"tunnel-agent": "^0.6.0"
2626
},
2727
"typings": "./out/electron-builder-util.d.ts"
2828
}

packages/electron-builder/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@
4444
"bugs": "https://github.com/electron-userland/electron-builder/issues",
4545
"homepage": "https://github.com/electron-userland/electron-builder",
4646
"dependencies": {
47-
"ajv": "^5.0.2-beta",
48-
"ajv-keywords": "^2.0.1-beta.0",
47+
"ajv": "^5.0.3-beta",
48+
"ajv-keywords": "^2.0.1-beta.1",
4949
"7zip-bin": "^2.0.4",
50-
"asar": "~0.13.0",
5150
"bluebird-lst": "^1.0.1",
5251
"chalk": "^1.1.3",
5352
"chromium-pickle-js": "^0.2.0",
@@ -62,7 +61,7 @@
6261
"hosted-git-info": "^2.2.0",
6362
"is-ci": "^1.0.10",
6463
"isbinaryfile": "^3.0.2",
65-
"js-yaml": "^3.8.1",
64+
"js-yaml": "^3.8.2",
6665
"minimatch": "^3.0.3",
6766
"normalize-package-data": "^2.3.5",
6867
"parse-color": "^1.0.0",
@@ -71,7 +70,7 @@
7170
"semver": "^5.3.0",
7271
"update-notifier": "^2.1.0",
7372
"uuid-1345": "^0.99.6",
74-
"yargs": "^6.6.0",
73+
"yargs": "^7.0.1",
7574
"node-forge": "^0.7.0"
7675
},
7776
"typings": "./out/electron-builder.d.ts",
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { createFromBuffer } from "chromium-pickle-js"
2+
import { close, open, read, readFile, Stats } from "fs-extra-p"
3+
import * as path from "path"
4+
const UINT64 = require("cuint").UINT64
5+
6+
export class Node {
7+
// we don't use Map because later it will be stringified
8+
files?: { [key: string]: Node }
9+
10+
unpacked?: boolean
11+
12+
size: number
13+
offset: number
14+
15+
executable?: boolean
16+
17+
link?: string
18+
}
19+
20+
export class AsarFilesystem {
21+
private offset = UINT64(0)
22+
23+
constructor (readonly src: string, readonly header = new Node(), readonly headerSize: number = -1) {
24+
if (this.header.files == null) {
25+
this.header.files = {}
26+
}
27+
}
28+
29+
searchNodeFromDirectory(p: string): Node {
30+
let node = this.header
31+
for (const dir of p.split(path.sep)) {
32+
if (dir !== ".") {
33+
node = node.files![dir]!
34+
}
35+
}
36+
return node
37+
}
38+
39+
getOrCreateNode(p: string): Node {
40+
p = path.relative(this.src, p)
41+
if (p == null || p.length === 0) {
42+
return this.header
43+
}
44+
45+
const name = path.basename(p)
46+
const dirNode = this.searchNodeFromDirectory(path.dirname(p))
47+
if (dirNode.files == null) {
48+
dirNode.files = {}
49+
}
50+
51+
let result = dirNode.files[name]
52+
if (result == null) {
53+
result = new Node()
54+
dirNode.files[name] = result
55+
}
56+
return result
57+
}
58+
59+
insertDirectory(p: string, unpacked: boolean = false) {
60+
const node = this.getOrCreateNode(p)
61+
node.files = {}
62+
if (unpacked) {
63+
node.unpacked = unpacked
64+
}
65+
return node.files
66+
}
67+
68+
insertFileNode(node: Node, stat: Stats, file: string) {
69+
if (node.size > 4294967295) {
70+
throw new Error(`${file}: file size can not be larger than 4.2GB`)
71+
}
72+
73+
node.offset = this.offset.toString()
74+
if (process.platform !== "win32" && (stat.mode & 0o100)) {
75+
node.executable = true
76+
}
77+
this.offset.add(UINT64(node.size))
78+
}
79+
80+
// listFiles() {
81+
// const files: Array<string> = []
82+
// const fillFilesFromHeader = (p: string, json: Node) => {
83+
// if (json.files == null) {
84+
// return
85+
// }
86+
//
87+
// for (const f in json.files) {
88+
// const fullPath = path.join(p, f)
89+
// files.push(fullPath)
90+
// fillFilesFromHeader(fullPath, json.files[f]!)
91+
// }
92+
// }
93+
//
94+
// fillFilesFromHeader("/", this.header)
95+
// return files
96+
// }
97+
98+
getNode(p: string) {
99+
const node = this.searchNodeFromDirectory(path.dirname(p))
100+
return node.files![path.basename(p)]
101+
}
102+
103+
getFile(p: string, followLinks: boolean = true): Node {
104+
const info = this.getNode(p)!
105+
// if followLinks is false we don't resolve symlinks
106+
return followLinks && info.link != null ? this.getFile(info.link) : info
107+
}
108+
109+
async readJson(file: string): Promise<Buffer> {
110+
return JSON.parse((await this.readFile(file)).toString())
111+
}
112+
113+
async readFile(file: string): Promise<Buffer> {
114+
return await readFileFromAsar(this, file, this.getFile(file))
115+
}
116+
}
117+
118+
export async function readAsar(archive: string): Promise<AsarFilesystem> {
119+
const fd = await open(archive, "r")
120+
let size
121+
let headerBuf
122+
try {
123+
const sizeBuf = new Buffer(8)
124+
if (await read(fd, sizeBuf, 0, 8, <any>null) !== 8) {
125+
throw new Error("Unable to read header size")
126+
}
127+
128+
const sizePickle = createFromBuffer(sizeBuf)
129+
size = sizePickle.createIterator().readUInt32()
130+
headerBuf = new Buffer(size)
131+
if (await read(fd, headerBuf, 0, size, <any>null) !== size) {
132+
throw new Error("Unable to read header")
133+
}
134+
}
135+
finally {
136+
await close(fd)
137+
}
138+
139+
const headerPickle = createFromBuffer(headerBuf!)
140+
const header = headerPickle.createIterator().readString()
141+
return new AsarFilesystem(archive, JSON.parse(header), size)
142+
}
143+
144+
export async function readAsarJson(archive: string, file: string): Promise<Buffer> {
145+
const fs = await readAsar(archive)
146+
return await fs.readJson(file)
147+
}
148+
149+
async function readFileFromAsar(filesystem: AsarFilesystem, filename: string, info: Node): Promise<Buffer> {
150+
let buffer = new Buffer(info.size)
151+
if (info.size <= 0) {
152+
return buffer
153+
}
154+
155+
if (info.unpacked) {
156+
return await readFile(path.join(`${filesystem.src}.unpacked`, filename))
157+
}
158+
159+
const fd = await open(filesystem.src, "r")
160+
try {
161+
const offset = 8 + filesystem.headerSize + parseInt(<any>info.offset)
162+
await read(fd, buffer, 0, info.size, offset)
163+
}
164+
finally {
165+
await close(fd)
166+
}
167+
return buffer
168+
}

packages/electron-builder/src/asarUtil.ts

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { AsarFileInfo, listPackage, statFile } from "asar"
21
import BluebirdPromise from "bluebird-lst"
32
import { debug } from "electron-builder-util"
43
import { deepAssign } from "electron-builder-util/out/deepAssign"
54
import { CONCURRENCY, FileCopier, Filter, MAX_FILE_REQUESTS, statOrNull, walk } from "electron-builder-util/out/fs"
65
import { log } from "electron-builder-util/out/log"
76
import { createReadStream, createWriteStream, ensureDir, readFile, readJson, readlink, stat, Stats, writeFile } from "fs-extra-p"
87
import * as path from "path"
8+
import { AsarFilesystem, Node, readAsar } from "./asar"
99
import { AsarOptions } from "./metadata"
1010

1111
const isBinaryFile: any = BluebirdPromise.promisify(require("isbinaryfile"))
1212
const pickle = require ("chromium-pickle-js")
13-
const Filesystem = require("asar/lib/filesystem")
14-
const UINT64 = require("cuint").UINT64
1513

1614
const NODE_MODULES_PATTERN = `${path.sep}node_modules${path.sep}`
1715

@@ -51,7 +49,7 @@ function writeUnpackedFiles(filesToUnpack: Array<UnpackedFileTask>, fileCopier:
5149

5250
class AsarPackager {
5351
private readonly toPack: Array<string> = []
54-
private readonly fs = new Filesystem(this.src)
52+
private readonly fs = new AsarFilesystem(this.src)
5553
private readonly changedFiles = new Map<string, string>()
5654
private readonly outFile: string
5755

@@ -188,7 +186,7 @@ class AsarPackager {
188186
const stat = metadata.get(file)!
189187
if (stat.isFile()) {
190188
const fileParent = path.dirname(file)
191-
const dirNode = this.fs.searchNodeFromPath(fileParent)
189+
const dirNode = this.fs.getOrCreateNode(fileParent)
192190
const packageDataPromise = fileIndexToModulePackageData.get(i)
193191
let newData: string | null = null
194192
if (packageDataPromise == null) {
@@ -213,7 +211,7 @@ class AsarPackager {
213211
}
214212

215213
const fileSize = newData == null ? stat.size : Buffer.byteLength(newData)
216-
const node = this.fs.searchNodeFromPath(file)
214+
const node = this.fs.getOrCreateNode(file)
217215
node.size = fileSize
218216
if (dirNode.unpacked || (this.unpackPattern != null && this.unpackPattern(file, stat))) {
219217
node.unpacked = true
@@ -234,18 +232,8 @@ class AsarPackager {
234232
if (newData != null) {
235233
this.changedFiles.set(file, newData)
236234
}
237-
238-
if (fileSize > 4294967295) {
239-
throw new Error(`${file}: file size can not be larger than 4.2GB`)
240-
}
241-
242-
node.offset = this.fs.offset.toString()
243-
//noinspection JSBitwiseOperatorUsage
244-
if (process.platform !== "win32" && stat.mode & 0x40) {
245-
node.executable = true
246-
}
235+
this.fs.insertFileNode(node, stat, file)
247236
this.toPack.push(file)
248-
this.fs.offset.add(UINT64(fileSize))
249237
}
250238
}
251239
else if (stat.isDirectory()) {
@@ -268,7 +256,7 @@ class AsarPackager {
268256
this.fs.insertDirectory(file, unpacked)
269257
}
270258
else if (stat.isSymbolicLink()) {
271-
this.fs.searchNodeFromPath(file).link = (<any>stat).relativeLink
259+
this.fs.getOrCreateNode(file).link = (<any>stat).relativeLink
272260
}
273261
}
274262

@@ -289,7 +277,7 @@ class AsarPackager {
289277
const writeStream = createWriteStream(this.outFile)
290278
return new BluebirdPromise((resolve, reject) => {
291279
writeStream.on("error", reject)
292-
writeStream.once("finish", resolve)
280+
writeStream.on("close", resolve)
293281
writeStream.write(sizeBuf)
294282

295283
let w: (list: Array<any>, index: number) => void
@@ -386,23 +374,24 @@ export async function checkFileInArchive(asarFile: string, relativeFile: string,
386374
return new Error(`${messagePrefix} "${relativeFile}" in the "${asarFile}" ${text}`)
387375
}
388376

389-
let stat: AsarFileInfo | null
377+
let fs
378+
try {
379+
fs = await readAsar(asarFile)
380+
}
381+
catch (e) {
382+
throw error(`is corrupted: ${e}`)
383+
}
384+
385+
let stat: Node | null
390386
try {
391-
stat = statFile(asarFile, relativeFile)
387+
stat = fs.getFile(relativeFile)
392388
}
393389
catch (e) {
394390
const fileStat = await statOrNull(asarFile)
395391
if (fileStat == null) {
396392
throw error(`does not exist. Seems like a wrong configuration.`)
397393
}
398394

399-
try {
400-
listPackage(asarFile)
401-
}
402-
catch (e) {
403-
throw error(`is corrupted: ${e}`)
404-
}
405-
406395
// asar throws error on access to undefined object (info.link)
407396
stat = null
408397
}

0 commit comments

Comments
 (0)