Skip to content

Commit 197385a

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 82717c2 commit 197385a

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,
@@ -240,6 +234,10 @@ export class Storage {
240234
return path.join(this.globalStorageUri.fsPath, "url")
241235
}
242236

237+
public getBinaryETag(): string {
238+
return crypto.createHash("sha1").update(this.binaryPath()).digest("hex")
239+
}
240+
243241
private appDataDir(): string {
244242
switch (process.platform) {
245243
case "darwin":
@@ -274,6 +272,47 @@ export class Storage {
274272
return binPath
275273
}
276274

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

0 commit comments

Comments
 (0)