aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVeetaha <[email protected]>2020-06-22 19:36:56 +0100
committerVeetaha <[email protected]>2020-06-22 19:43:53 +0100
commit0514d817db84c683aaf250b823edc8b08bfee3da (patch)
tree56af0bdf3724d4bed6101c18b3a8579102e2b46c
parentceb69203b55d83aeaf4e58bff4a58f2f17d4087d (diff)
Decouple http file stream logic from temp dir logic
-rw-r--r--editors/code/src/main.ts13
-rw-r--r--editors/code/src/net.ts88
2 files changed, 57 insertions, 44 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 670f2ebfd..7bae8bb33 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -178,7 +178,11 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
178 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); 178 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
179 179
180 const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); 180 const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix");
181 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension"); 181 await download({
182 url: artifact.browser_download_url,
183 dest,
184 progressTitle: "Downloading rust-analyzer extension",
185 });
182 186
183 await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); 187 await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest));
184 await fs.unlink(dest); 188 await fs.unlink(dest);
@@ -299,7 +303,12 @@ async function getServer(config: Config, state: PersistentState): Promise<string
299 if (err.code !== "ENOENT") throw err; 303 if (err.code !== "ENOENT") throw err;
300 }); 304 });
301 305
302 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); 306 await download({
307 url: artifact.browser_download_url,
308 dest,
309 progressTitle: "Downloading rust-analyzer server",
310 mode: 0o755
311 });
303 312
304 // Patching executable if that's NixOS. 313 // Patching executable if that's NixOS.
305 if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) { 314 if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) {
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 9debdc57b..e02fd6d4f 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -60,32 +60,40 @@ export interface GithubRelease {
60 }>; 60 }>;
61} 61}
62 62
63interface DownloadOpts {
64 progressTitle: string;
65 url: string;
66 dest: string;
67 mode?: number;
68}
63 69
64export async function download( 70export async function download(opts: DownloadOpts) {
65 downloadUrl: string, 71 // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode
66 destinationPath: string, 72 await withTempDir(async tempDir => {
67 progressTitle: string, 73 const tempFile = path.join(tempDir, path.basename(opts.dest));
68 { mode }: { mode?: number } = {}, 74
69) { 75 await vscode.window.withProgress(
70 await vscode.window.withProgress( 76 {
71 { 77 location: vscode.ProgressLocation.Notification,
72 location: vscode.ProgressLocation.Notification, 78 cancellable: false,
73 cancellable: false, 79 title: opts.progressTitle
74 title: progressTitle 80 },
75 }, 81 async (progress, _cancellationToken) => {
76 async (progress, _cancellationToken) => { 82 let lastPercentage = 0;
77 let lastPercentage = 0; 83 await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => {
78 await downloadFile(downloadUrl, destinationPath, mode, (readBytes, totalBytes) => { 84 const newPercentage = (readBytes / totalBytes) * 100;
79 const newPercentage = (readBytes / totalBytes) * 100; 85 progress.report({
80 progress.report({ 86 message: newPercentage.toFixed(0) + "%",
81 message: newPercentage.toFixed(0) + "%", 87 increment: newPercentage - lastPercentage
82 increment: newPercentage - lastPercentage 88 });
89
90 lastPercentage = newPercentage;
83 }); 91 });
92 }
93 );
84 94
85 lastPercentage = newPercentage; 95 await moveFile(tempFile, opts.dest);
86 }); 96 });
87 }
88 );
89} 97}
90 98
91/** 99/**
@@ -114,28 +122,23 @@ async function downloadFile(
114 122
115 log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); 123 log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath);
116 124
117 // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode 125 let readBytes = 0;
118 await withTempFile(async tempFilePath => { 126 res.body.on("data", (chunk: Buffer) => {
119 const destFileStream = fs.createWriteStream(tempFilePath, { mode }); 127 readBytes += chunk.length;
120 128 onProgress(readBytes, totalBytes);
121 let readBytes = 0; 129 });
122 res.body.on("data", (chunk: Buffer) => {
123 readBytes += chunk.length;
124 onProgress(readBytes, totalBytes);
125 });
126 130
127 await pipeline(res.body, destFileStream); 131 const destFileStream = fs.createWriteStream(destFilePath, { mode });
128 await new Promise<void>(resolve => { 132 await pipeline(res.body, destFileStream);
129 destFileStream.on("close", resolve); 133 await new Promise<void>(resolve => {
130 destFileStream.destroy(); 134 destFileStream.on("close", resolve);
131 // This workaround is awaiting to be removed when vscode moves to newer nodejs version: 135 destFileStream.destroy();
132 // https://github.com/rust-analyzer/rust-analyzer/issues/3167 136 // This workaround is awaiting to be removed when vscode moves to newer nodejs version:
133 }); 137 // https://github.com/rust-analyzer/rust-analyzer/issues/3167
134 await moveFile(tempFilePath, destFilePath);
135 }); 138 });
136} 139}
137 140
138async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { 141async function withTempDir(scope: (tempDirPath: string) => Promise<void>) {
139 // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ 142 // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/
140 143
141 // `.realpath()` should handle the cases where os.tmpdir() contains symlinks 144 // `.realpath()` should handle the cases where os.tmpdir() contains symlinks
@@ -144,7 +147,7 @@ async function withTempFile(scope: (tempFilePath: string) => Promise<void>) {
144 const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer")); 147 const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer"));
145 148
146 try { 149 try {
147 return await scope(path.join(tempDir, "file")); 150 return await scope(tempDir);
148 } finally { 151 } finally {
149 // We are good citizens :D 152 // We are good citizens :D
150 void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error); 153 void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error);
@@ -161,6 +164,7 @@ async function moveFile(src: fs.PathLike, dest: fs.PathLike) {
161 await fs.promises.unlink(src); 164 await fs.promises.unlink(src);
162 } else { 165 } else {
163 log.error(`Failed to rename the file ${src} -> ${dest}`, err); 166 log.error(`Failed to rename the file ${src} -> ${dest}`, err);
167 throw err;
164 } 168 }
165 } 169 }
166} 170}