Merge #4963
4963: Download artifacts into tmp dir r=matklad a=Veetaha This should prevent partially downloaded files in cases when the user closes vsode before the download is complete. There is also a new more descriptive error message when the user has multiple vscode windows open and tries to download the server. Related: https://github.com/rust-analyzer/rust-analyzer/issues/4938#issuecomment-646738360 Co-authored-by: Veetaha <veetaha2@gmail.com>
This commit is contained in:
commit
fe254857e6
2 changed files with 60 additions and 11 deletions
|
@ -42,7 +42,16 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||||
|
|
||||||
const config = new Config(context);
|
const config = new Config(context);
|
||||||
const state = new PersistentState(context.globalState);
|
const state = new PersistentState(context.globalState);
|
||||||
const serverPath = await bootstrap(config, state);
|
const serverPath = await bootstrap(config, state).catch(err => {
|
||||||
|
let message = "Failed to bootstrap rust-analyzer.";
|
||||||
|
if (err.code === "EBUSY" || err.code === "ETXTBSY") {
|
||||||
|
message += " Other vscode windows might be using rust-analyzer, " +
|
||||||
|
"you should close them and reload this window to retry.";
|
||||||
|
}
|
||||||
|
message += " Open \"Help > Toggle Developer Tools > Console\" to see the logs";
|
||||||
|
log.error("Bootstrap error", err);
|
||||||
|
throw new Error(message);
|
||||||
|
});
|
||||||
|
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||||
if (workspaceFolder === undefined) {
|
if (workspaceFolder === undefined) {
|
||||||
|
@ -285,6 +294,11 @@ async function getServer(config: Config, state: PersistentState): Promise<string
|
||||||
const artifact = release.assets.find(artifact => artifact.name === binaryName);
|
const artifact = release.assets.find(artifact => artifact.name === binaryName);
|
||||||
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
||||||
|
|
||||||
|
// Unlinking the exe file before moving new one on its place should prevent ETXTBSY error.
|
||||||
|
await fs.unlink(dest).catch(err => {
|
||||||
|
if (err.code !== "ENOENT") throw err;
|
||||||
|
});
|
||||||
|
|
||||||
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
|
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
|
||||||
|
|
||||||
// Patching executable if that's NixOS.
|
// Patching executable if that's NixOS.
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import * as fs from "fs";
|
|
||||||
import * as stream from "stream";
|
import * as stream from "stream";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as path from "path";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import { log, assert } from "./util";
|
import { log, assert } from "./util";
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ export async function download(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`.
|
* Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions).
|
||||||
* `onProgress` callback is called on recieveing each chunk of bytes
|
* `onProgress` callback is called on recieveing each chunk of bytes
|
||||||
* to track the progress of downloading, it gets the already read and total
|
* to track the progress of downloading, it gets the already read and total
|
||||||
* amount of bytes to read as its parameters.
|
* amount of bytes to read as its parameters.
|
||||||
|
@ -118,13 +120,46 @@ async function downloadFile(
|
||||||
onProgress(readBytes, totalBytes);
|
onProgress(readBytes, totalBytes);
|
||||||
});
|
});
|
||||||
|
|
||||||
const destFileStream = fs.createWriteStream(destFilePath, { mode });
|
// Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode
|
||||||
|
await withTempFile(async tempFilePath => {
|
||||||
|
const destFileStream = fs.createWriteStream(tempFilePath, { mode });
|
||||||
await pipeline(res.body, destFileStream);
|
await pipeline(res.body, destFileStream);
|
||||||
return new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
destFileStream.on("close", resolve);
|
destFileStream.on("close", resolve);
|
||||||
destFileStream.destroy();
|
destFileStream.destroy();
|
||||||
// This workaround is awaiting to be removed when vscode moves to newer nodejs version:
|
// This workaround is awaiting to be removed when vscode moves to newer nodejs version:
|
||||||
// https://github.com/rust-analyzer/rust-analyzer/issues/3167
|
// https://github.com/rust-analyzer/rust-analyzer/issues/3167
|
||||||
});
|
});
|
||||||
|
await moveFile(tempFilePath, destFilePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withTempFile(scope: (tempFilePath: string) => Promise<void>) {
|
||||||
|
// Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/
|
||||||
|
|
||||||
|
// `.realpath()` should handle the cases where os.tmpdir() contains symlinks
|
||||||
|
const osTempDir = await fs.promises.realpath(os.tmpdir());
|
||||||
|
|
||||||
|
const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await scope(path.join(tempDir, "file"));
|
||||||
|
} finally {
|
||||||
|
// We are good citizens :D
|
||||||
|
void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function moveFile(src: fs.PathLike, dest: fs.PathLike) {
|
||||||
|
try {
|
||||||
|
await fs.promises.rename(src, dest);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'EXDEV') {
|
||||||
|
// We are probably moving the file across partitions/devices
|
||||||
|
await fs.promises.copyFile(src, dest);
|
||||||
|
await fs.promises.unlink(src);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to rename the file ${src} -> ${dest}`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue