diff options
Diffstat (limited to 'editors/code/src/installation/downloads.ts')
-rw-r--r-- | editors/code/src/installation/downloads.ts | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/editors/code/src/installation/downloads.ts b/editors/code/src/installation/downloads.ts new file mode 100644 index 000000000..7ce2e2960 --- /dev/null +++ b/editors/code/src/installation/downloads.ts | |||
@@ -0,0 +1,97 @@ | |||
1 | import fetch from "node-fetch"; | ||
2 | import * as vscode from "vscode"; | ||
3 | import * as path from "path"; | ||
4 | import * as fs from "fs"; | ||
5 | import * as stream from "stream"; | ||
6 | import * as util from "util"; | ||
7 | import { log, assert } from "../util"; | ||
8 | import { ArtifactReleaseInfo } from "./interfaces"; | ||
9 | |||
10 | const pipeline = util.promisify(stream.pipeline); | ||
11 | |||
12 | /** | ||
13 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. | ||
14 | * `onProgress` callback is called on recieveing each chunk of bytes | ||
15 | * to track the progress of downloading, it gets the already read and total | ||
16 | * amount of bytes to read as its parameters. | ||
17 | */ | ||
18 | export async function downloadFile( | ||
19 | url: string, | ||
20 | destFilePath: fs.PathLike, | ||
21 | destFilePermissions: number, | ||
22 | onProgress: (readBytes: number, totalBytes: number) => void | ||
23 | ): Promise<void> { | ||
24 | const res = await fetch(url); | ||
25 | |||
26 | if (!res.ok) { | ||
27 | log.error("Error", res.status, "while downloading file from", url); | ||
28 | log.error({ body: await res.text(), headers: res.headers }); | ||
29 | |||
30 | throw new Error(`Got response ${res.status} when trying to download a file.`); | ||
31 | } | ||
32 | |||
33 | const totalBytes = Number(res.headers.get('content-length')); | ||
34 | assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); | ||
35 | |||
36 | log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); | ||
37 | |||
38 | let readBytes = 0; | ||
39 | res.body.on("data", (chunk: Buffer) => { | ||
40 | readBytes += chunk.length; | ||
41 | onProgress(readBytes, totalBytes); | ||
42 | }); | ||
43 | |||
44 | const destFileStream = fs.createWriteStream(destFilePath, { mode: destFilePermissions }); | ||
45 | |||
46 | await pipeline(res.body, destFileStream); | ||
47 | return new Promise<void>(resolve => { | ||
48 | destFileStream.on("close", resolve); | ||
49 | destFileStream.destroy(); | ||
50 | |||
51 | // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 | ||
52 | // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 | ||
53 | }); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Downloads artifact from given `downloadUrl`. | ||
58 | * Creates `installationDir` if it is not yet created and puts the artifact under | ||
59 | * `artifactFileName`. | ||
60 | * Displays info about the download progress in an info message printing the name | ||
61 | * of the artifact as `displayName`. | ||
62 | */ | ||
63 | export async function downloadArtifactWithProgressUi( | ||
64 | { downloadUrl, releaseName }: ArtifactReleaseInfo, | ||
65 | artifactFileName: string, | ||
66 | installationDir: string, | ||
67 | displayName: string, | ||
68 | ) { | ||
69 | await fs.promises.mkdir(installationDir).catch(err => assert( | ||
70 | err?.code === "EEXIST", | ||
71 | `Couldn't create directory "${installationDir}" to download ` + | ||
72 | `${artifactFileName} artifact: ${err?.message}` | ||
73 | )); | ||
74 | |||
75 | const installationPath = path.join(installationDir, artifactFileName); | ||
76 | |||
77 | await vscode.window.withProgress( | ||
78 | { | ||
79 | location: vscode.ProgressLocation.Notification, | ||
80 | cancellable: false, // FIXME: add support for canceling download? | ||
81 | title: `Downloading rust-analyzer ${displayName} (${releaseName})` | ||
82 | }, | ||
83 | async (progress, _cancellationToken) => { | ||
84 | let lastPrecentage = 0; | ||
85 | const filePermissions = 0o755; // (rwx, r_x, r_x) | ||
86 | await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => { | ||
87 | const newPercentage = (readBytes / totalBytes) * 100; | ||
88 | progress.report({ | ||
89 | message: newPercentage.toFixed(0) + "%", | ||
90 | increment: newPercentage - lastPrecentage | ||
91 | }); | ||
92 | |||
93 | lastPrecentage = newPercentage; | ||
94 | }); | ||
95 | } | ||
96 | ); | ||
97 | } | ||