diff options
Diffstat (limited to 'editors/code/src/net.ts')
-rw-r--r-- | editors/code/src/net.ts | 54 |
1 files changed, 44 insertions, 10 deletions
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 492213937..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,14 +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 |
124 | await withTempFile(async tempFilePath => { | ||
125 | const destFileStream = fs.createWriteStream(tempFilePath, { mode }); | ||
126 | await pipeline(res.body, destFileStream); | ||
127 | await new Promise<void>(resolve => { | ||
128 | destFileStream.on("close", resolve); | ||
129 | destFileStream.destroy(); | ||
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); | ||
134 | }); | ||
135 | } | ||
122 | 136 | ||
123 | await pipeline(res.body, destFileStream); | 137 | async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { |
124 | return new Promise<void>(resolve => { | 138 | // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ |
125 | destFileStream.on("close", resolve); | ||
126 | destFileStream.destroy(); | ||
127 | 139 | ||
128 | // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 | 140 | // `.realpath()` should handle the cases where os.tmpdir() contains symlinks |
129 | // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 | 141 | const osTempDir = await fs.promises.realpath(os.tmpdir()); |
130 | }); | 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 | } | ||
131 | } | 165 | } |