aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/net.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/net.ts')
-rw-r--r--editors/code/src/net.ts54
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 @@
1import fetch from "node-fetch"; 1import fetch from "node-fetch";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import * as fs from "fs";
4import * as stream from "stream"; 3import * as stream from "stream";
4import * as fs from "fs";
5import * as os from "os";
6import * as path from "path";
5import * as util from "util"; 7import * as util from "util";
6import { log, assert } from "./util"; 8import { 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); 137async 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
153async 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}