diff options
author | Veetaha <[email protected]> | 2020-06-20 13:38:08 +0100 |
---|---|---|
committer | Veetaha <[email protected]> | 2020-06-20 13:45:30 +0100 |
commit | dceb81856eebc35856081587fac6339374c6e177 (patch) | |
tree | 76e5bf29adb5078623a60446647313ff0a3d8e43 | |
parent | 0f7961d5570f17d6c2098ab11d2a3bcbbfb84ff6 (diff) |
Download artifacts into tmp dir
-rw-r--r-- | editors/code/src/main.ts | 16 | ||||
-rw-r--r-- | editors/code/src/net.ts | 55 |
2 files changed, 60 insertions, 11 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 270fbcb64..670f2ebfd 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -42,7 +42,16 @@ export async function activate(context: vscode.ExtensionContext) { | |||
42 | 42 | ||
43 | const config = new Config(context); | 43 | const config = new Config(context); |
44 | const state = new PersistentState(context.globalState); | 44 | const state = new PersistentState(context.globalState); |
45 | const serverPath = await bootstrap(config, state); | 45 | const serverPath = await bootstrap(config, state).catch(err => { |
46 | let message = "Failed to bootstrap rust-analyzer."; | ||
47 | if (err.code === "EBUSY" || err.code === "ETXTBSY") { | ||
48 | message += " Other vscode windows might be using rust-analyzer, " + | ||
49 | "you should close them and reload this window to retry."; | ||
50 | } | ||
51 | message += " Open \"Help > Toggle Developer Tools > Console\" to see the logs"; | ||
52 | log.error("Bootstrap error", err); | ||
53 | throw new Error(message); | ||
54 | }); | ||
46 | 55 | ||
47 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; | 56 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; |
48 | if (workspaceFolder === undefined) { | 57 | if (workspaceFolder === undefined) { |
@@ -285,6 +294,11 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
285 | const artifact = release.assets.find(artifact => artifact.name === binaryName); | 294 | const artifact = release.assets.find(artifact => artifact.name === binaryName); |
286 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 295 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
287 | 296 | ||
297 | // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. | ||
298 | await fs.unlink(dest).catch(err => { | ||
299 | if (err.code !== "ENOENT") throw err; | ||
300 | }); | ||
301 | |||
288 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); | 302 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); |
289 | 303 | ||
290 | // Patching executable if that's NixOS. | 304 | // Patching executable if that's NixOS. |
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index f0b085420..0e7dd29c2 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import fetch from "node-fetch"; | 1 | import fetch from "node-fetch"; |
2 | import * as vscode from "vscode"; | 2 | import * as vscode from "vscode"; |
3 | import * as fs from "fs"; | ||
4 | import * as stream from "stream"; | 3 | import * as stream from "stream"; |
4 | import * as fs from "fs"; | ||
5 | import * as os from "os"; | ||
6 | import * as path from "path"; | ||
5 | import * as util from "util"; | 7 | import * as util from "util"; |
6 | import { log, assert } from "./util"; | 8 | import { log, assert } from "./util"; |
7 | 9 | ||
@@ -87,7 +89,7 @@ export async function download( | |||
87 | } | 89 | } |
88 | 90 | ||
89 | /** | 91 | /** |
90 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. | 92 | * Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions). |
91 | * `onProgress` callback is called on recieveing each chunk of bytes | 93 | * `onProgress` callback is called on recieveing each chunk of bytes |
92 | * to track the progress of downloading, it gets the already read and total | 94 | * to track the progress of downloading, it gets the already read and total |
93 | * amount of bytes to read as its parameters. | 95 | * amount of bytes to read as its parameters. |
@@ -118,13 +120,46 @@ async function downloadFile( | |||
118 | onProgress(readBytes, totalBytes); | 120 | onProgress(readBytes, totalBytes); |
119 | }); | 121 | }); |
120 | 122 | ||
121 | const destFileStream = fs.createWriteStream(destFilePath, { mode }); | 123 | // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode |
122 | 124 | await withTempFile(async tempFilePath => { | |
123 | await pipeline(res.body, destFileStream); | 125 | const destFileStream = fs.createWriteStream(tempFilePath, { mode }); |
124 | return new Promise<void>(resolve => { | 126 | await pipeline(res.body, destFileStream); |
125 | destFileStream.on("close", resolve); | 127 | await new Promise<void>(resolve => { |
126 | destFileStream.destroy(); | 128 | destFileStream.on("close", resolve); |
127 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: | 129 | destFileStream.destroy(); |
128 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 | 130 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: |
131 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 | ||
132 | }); | ||
133 | await moveFile(tempFilePath, destFilePath); | ||
129 | }); | 134 | }); |
130 | } | 135 | } |
136 | |||
137 | async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { | ||
138 | // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ | ||
139 | |||
140 | // `.realpath()` should handle the cases where os.tmpdir() contains symlinks | ||
141 | const osTempDir = await fs.promises.realpath(os.tmpdir()); | ||
142 | |||
143 | const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer")); | ||
144 | |||
145 | try { | ||
146 | return await scope(path.join(tempDir, "file")); | ||
147 | } finally { | ||
148 | // We are good citizens :D | ||
149 | void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error); | ||
150 | } | ||
151 | }; | ||
152 | |||
153 | async function moveFile(src: fs.PathLike, dest: fs.PathLike) { | ||
154 | try { | ||
155 | await fs.promises.rename(src, dest); | ||
156 | } catch (err) { | ||
157 | if (err.code === 'EXDEV') { | ||
158 | // We are probably moving the file across partitions/devices | ||
159 | await fs.promises.copyFile(src, dest); | ||
160 | await fs.promises.unlink(src); | ||
161 | } else { | ||
162 | log.error(`Failed to rename the file ${src} -> ${dest}`, err); | ||
163 | } | ||
164 | } | ||
165 | } | ||