Skip to content

Commit d06c873

Browse files
committed
refactor: update logic for downloading binary
This makes significant changes to the `fetchBinary` logic. First, it refactors a couple pieces of logic into methods on the `Storage` class to make the code more readable. Then it modifies the flow to first check if the binary is outdated. If it is, then it downloads the latest version.
1 parent c598fdb commit d06c873

File tree

1 file changed

+64
-25
lines changed

1 file changed

+64
-25
lines changed

src/storage.ts

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import axios from "axios"
22
import { execFile } from "child_process"
33
import { getBuildInfo } from "coder/site/src/api/api"
4+
import * as crypto from "crypto"
45
import { createWriteStream } from "fs"
56
import { ensureDir } from "fs-extra"
67
import fs from "fs/promises"
@@ -81,31 +82,7 @@ export class Storage {
8182

8283
const buildInfo = await getBuildInfo()
8384
const binPath = this.binaryPath()
84-
const exists = await fs
85-
.stat(binPath)
86-
.then(() => true)
87-
.catch(() => false)
88-
if (exists) {
89-
// Even if the file exists, it could be corrupted.
90-
// We run `coder version` to ensure the binary can be executed.
91-
this.output.appendLine(`Using cached binary: ${binPath}`)
92-
const valid = await new Promise<boolean>((resolve) => {
93-
try {
94-
execFile(binPath, ["version"], (err) => {
95-
if (err) {
96-
this.output.appendLine("Check for binary corruption: " + err)
97-
}
98-
resolve(err === null)
99-
})
100-
} catch (ex) {
101-
this.output.appendLine("The cached binary cannot be executed: " + ex)
102-
resolve(false)
103-
}
104-
})
105-
if (valid) {
106-
return binPath
107-
}
108-
}
85+
const exists = await this.checkBinaryExists(binPath)
10986
const os = goos()
11087
const arch = goarch()
11188
let binName = `coder-${os}-${arch}`
@@ -114,6 +91,23 @@ export class Storage {
11491
binName += ".exe"
11592
}
11693
const controller = new AbortController()
94+
95+
if (exists) {
96+
this.output.appendLine(`Checking if binary outdated...`)
97+
const outdated = await this.checkBinaryOutdated(binName, baseURL, controller)
98+
// If it's outdated, we fall through to the download logic.
99+
if (outdated) {
100+
this.output.appendLine(`Found outdated version.`)
101+
} else {
102+
// Even if the file exists, it could be corrupted.
103+
// We run `coder version` to ensure the binary can be executed.
104+
this.output.appendLine(`Using existing binary: ${binPath}`)
105+
const valid = await this.checkBinaryValid(binPath)
106+
if (valid) {
107+
return binPath
108+
}
109+
}
110+
}
117111
const resp = await axios.get("/bin/" + binName, {
118112
signal: controller.signal,
119113
baseURL: baseURL,
@@ -236,6 +230,10 @@ export class Storage {
236230
return path.join(this.globalStorageUri.fsPath, "url")
237231
}
238232

233+
public getBinaryETag(): string {
234+
return crypto.createHash("sha1").update(this.binaryPath()).digest("hex")
235+
}
236+
239237
private appDataDir(): string {
240238
switch (process.platform) {
241239
case "darwin":
@@ -270,6 +268,47 @@ export class Storage {
270268
return binPath
271269
}
272270

271+
private async checkBinaryExists(binPath: string): Promise<boolean> {
272+
return await fs
273+
.stat(binPath)
274+
.then(() => true)
275+
.catch(() => false)
276+
}
277+
278+
private async checkBinaryValid(binPath: string): Promise<boolean> {
279+
return await new Promise<boolean>((resolve) => {
280+
try {
281+
execFile(binPath, ["version"], (err) => {
282+
if (err) {
283+
this.output.appendLine("Check for binary corruption: " + err)
284+
}
285+
resolve(err === null)
286+
})
287+
} catch (ex) {
288+
this.output.appendLine("The cached binary cannot be executed: " + ex)
289+
resolve(false)
290+
}
291+
})
292+
}
293+
294+
private async checkBinaryOutdated(binName: string, baseURL: string, controller: AbortController): Promise<boolean> {
295+
const resp = await axios.get("/bin/" + binName, {
296+
signal: controller.signal,
297+
baseURL: baseURL,
298+
headers: {
299+
"If-None-Match": this.getBinaryETag(),
300+
},
301+
})
302+
303+
switch (resp.status) {
304+
case 200:
305+
return true
306+
case 304:
307+
default:
308+
return false
309+
}
310+
}
311+
273312
private async updateSessionToken() {
274313
const token = await this.getSessionToken()
275314
if (token) {

0 commit comments

Comments
 (0)