aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/net.ts
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-03-17 11:44:31 +0000
committerAleksey Kladov <[email protected]>2020-03-19 08:04:59 +0000
commitfb6e655de8a44c65275ad45a27bf5bd684670ba0 (patch)
tree9c307ac69c8fc59465ee2fb6f9a8a619fc064167 /editors/code/src/net.ts
parentf0a1b64d7ee3baa7ccf980b35b85f0a4a3b85b1a (diff)
Rewrite auto-update
Everything now happens in main.ts, in the bootstrap family of functions. The current flow is: * check everything only on extension installation. * if the user is on nightly channel, try to download the nightly extension and reload. * when we install nightly extension, we persist its release id, so that we can check if the current release is different. * if server binary was not downloaded by the current version of the extension, redownload it (we persist the version of ext that downloaded the server).
Diffstat (limited to 'editors/code/src/net.ts')
-rw-r--r--editors/code/src/net.ts131
1 files changed, 131 insertions, 0 deletions
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
new file mode 100644
index 000000000..492213937
--- /dev/null
+++ b/editors/code/src/net.ts
@@ -0,0 +1,131 @@
1import fetch from "node-fetch";
2import * as vscode from "vscode";
3import * as fs from "fs";
4import * as stream from "stream";
5import * as util from "util";
6import { log, assert } from "./util";
7
8const pipeline = util.promisify(stream.pipeline);
9
10const GITHUB_API_ENDPOINT_URL = "https://api.github.com";
11const OWNER = "rust-analyzer";
12const REPO = "rust-analyzer";
13
14export async function fetchRelease(
15 releaseTag: string
16): Promise<GithubRelease> {
17
18 const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`;
19
20 const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath;
21
22 log.debug("Issuing request for released artifacts metadata to", requestUrl);
23
24 const response = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
25
26 if (!response.ok) {
27 log.error("Error fetching artifact release info", {
28 requestUrl,
29 releaseTag,
30 response: {
31 headers: response.headers,
32 status: response.status,
33 body: await response.text(),
34 }
35 });
36
37 throw new Error(
38 `Got response ${response.status} when trying to fetch ` +
39 `release info for ${releaseTag} release`
40 );
41 }
42
43 // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`)
44 const release: GithubRelease = await response.json();
45 return release;
46}
47
48// We omit declaration of tremendous amount of fields that we are not using here
49export interface GithubRelease {
50 name: string;
51 id: number;
52 // eslint-disable-next-line camelcase
53 published_at: string;
54 assets: Array<{
55 name: string;
56 // eslint-disable-next-line camelcase
57 browser_download_url: string;
58 }>;
59}
60
61
62export async function download(
63 downloadUrl: string,
64 destinationPath: string,
65 progressTitle: string,
66 { mode }: { mode?: number } = {},
67) {
68 await vscode.window.withProgress(
69 {
70 location: vscode.ProgressLocation.Notification,
71 cancellable: false,
72 title: progressTitle
73 },
74 async (progress, _cancellationToken) => {
75 let lastPercentage = 0;
76 await downloadFile(downloadUrl, destinationPath, mode, (readBytes, totalBytes) => {
77 const newPercentage = (readBytes / totalBytes) * 100;
78 progress.report({
79 message: newPercentage.toFixed(0) + "%",
80 increment: newPercentage - lastPercentage
81 });
82
83 lastPercentage = newPercentage;
84 });
85 }
86 );
87}
88
89/**
90 * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`.
91 * `onProgress` callback is called on recieveing each chunk of bytes
92 * to track the progress of downloading, it gets the already read and total
93 * amount of bytes to read as its parameters.
94 */
95async function downloadFile(
96 url: string,
97 destFilePath: fs.PathLike,
98 mode: number | undefined,
99 onProgress: (readBytes: number, totalBytes: number) => void
100): Promise<void> {
101 const res = await fetch(url);
102
103 if (!res.ok) {
104 log.error("Error", res.status, "while downloading file from", url);
105 log.error({ body: await res.text(), headers: res.headers });
106
107 throw new Error(`Got response ${res.status} when trying to download a file.`);
108 }
109
110 const totalBytes = Number(res.headers.get('content-length'));
111 assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol");
112
113 log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath);
114
115 let readBytes = 0;
116 res.body.on("data", (chunk: Buffer) => {
117 readBytes += chunk.length;
118 onProgress(readBytes, totalBytes);
119 });
120
121 const destFileStream = fs.createWriteStream(destFilePath, { mode });
122
123 await pipeline(res.body, destFileStream);
124 return new Promise<void>(resolve => {
125 destFileStream.on("close", resolve);
126 destFileStream.destroy();
127
128 // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131
129 // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776
130 });
131}