Skip to content

Commit

Permalink
Download artifacts into tmp dir
Browse files Browse the repository at this point in the history
  • Loading branch information
Veetaha committed Jun 20, 2020
1 parent 0f7961d commit dceb818
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 11 deletions.
16 changes: 15 additions & 1 deletion editors/code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ export async function activate(context: vscode.ExtensionContext) {

const config = new Config(context);
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];
if (workspaceFolder === undefined) {
Expand Down Expand Up @@ -285,6 +294,11 @@ async function getServer(config: Config, state: PersistentState): Promise<string
const artifact = release.assets.find(artifact => artifact.name === binaryName);
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 });

// Patching executable if that's NixOS.
Expand Down
55 changes: 45 additions & 10 deletions editors/code/src/net.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import fetch from "node-fetch";
import * as vscode from "vscode";
import * as fs from "fs";
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 { log, assert } from "./util";

Expand Down Expand Up @@ -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
* to track the progress of downloading, it gets the already read and total
* amount of bytes to read as its parameters.
Expand Down Expand Up @@ -118,13 +120,46 @@ async function downloadFile(
onProgress(readBytes, totalBytes);
});

const destFileStream = fs.createWriteStream(destFilePath, { mode });

await pipeline(res.body, destFileStream);
return new Promise<void>(resolve => {
destFileStream.on("close", resolve);
destFileStream.destroy();
// This workaround is awaiting to be removed when vscode moves to newer nodejs version:
// https://github.com/rust-analyzer/rust-analyzer/issues/3167
// 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 new Promise<void>(resolve => {
destFileStream.on("close", resolve);
destFileStream.destroy();
// This workaround is awaiting to be removed when vscode moves to newer nodejs version:
// 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);
}
}
}

0 comments on commit dceb818

Please sign in to comment.