aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/installation/extension.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/installation/extension.ts')
-rw-r--r--editors/code/src/installation/extension.ts144
1 files changed, 144 insertions, 0 deletions
diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts
new file mode 100644
index 000000000..eea6fded2
--- /dev/null
+++ b/editors/code/src/installation/extension.ts
@@ -0,0 +1,144 @@
1import * as vscode from "vscode";
2import * as path from "path";
3import { promises as fs } from 'fs';
4
5import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util";
6import { Config, UpdatesChannel } from "../config";
7import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces";
8import { downloadArtifactWithProgressUi } from "./downloads";
9import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
10
11const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
12
13/**
14 * Installs `stable` or latest `nightly` version or does nothing if the current
15 * extension version is what's needed according to `desiredUpdateChannel`.
16 */
17export async function ensureProperExtensionVersion(config: Config): Promise<never | void> {
18 // User has built lsp server from sources, she should manage updates manually
19 if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return;
20
21 const currentUpdChannel = config.installedExtensionUpdateChannel;
22 const desiredUpdChannel = config.updatesChannel;
23
24 if (currentUpdChannel === UpdatesChannel.Stable) {
25 // Release date is present only when we are on nightly
26 await config.installedNightlyExtensionReleaseDate.set(null);
27 }
28
29 if (desiredUpdChannel === UpdatesChannel.Stable) {
30 // VSCode should handle updates for stable channel
31 if (currentUpdChannel === UpdatesChannel.Stable) return;
32
33 if (!await askToDownloadProperExtensionVersion(config)) return;
34
35 await vscodeReinstallExtension(config.extensionId);
36 await vscodeReloadWindow(); // never returns
37 }
38
39 if (currentUpdChannel === UpdatesChannel.Stable) {
40 if (!await askToDownloadProperExtensionVersion(config)) return;
41
42 return await tryDownloadNightlyExtension(config);
43 }
44
45 const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get();
46
47 if (currentExtReleaseDate === null) {
48 void vscode.window.showErrorMessage(
49 "Nightly release date must've been set during the installation. " +
50 "Did you download and install the nightly .vsix package manually?"
51 );
52 throw new Error("Nightly release date was not set in globalStorage");
53 }
54
55 const dateNow = new Date;
56 const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, dateNow);
57 log.debug(
58 "Current rust-analyzer nightly was downloaded", hoursSinceLastUpdate,
59 "hours ago, namely:", currentExtReleaseDate, "and now is", dateNow
60 );
61
62 if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) {
63 return;
64 }
65 if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) {
66 return;
67 }
68
69 await tryDownloadNightlyExtension(config, releaseInfo => {
70 assert(
71 currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(),
72 "Other active VSCode instance has reinstalled the extension"
73 );
74
75 if (releaseInfo.releaseDate.getTime() === currentExtReleaseDate.getTime()) {
76 vscode.window.showInformationMessage(
77 "Whoops, it appears that your nightly version is up-to-date. " +
78 "There might be some problems with the upcomming nightly release " +
79 "or you traveled too far into the future. Sorry for that 😅! "
80 );
81 return false;
82 }
83 return true;
84 });
85}
86
87async function askToDownloadProperExtensionVersion(config: Config, reason = "") {
88 if (!config.askBeforeDownload) return true;
89
90 const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly";
91
92 // In case of reentering this function and showing the same info message
93 // (e.g. after we had shown this message, the user changed the config)
94 // vscode will dismiss the already shown one (i.e. return undefined).
95 // This behaviour is what we want, but likely it is not documented
96
97 const userResponse = await vscode.window.showInformationMessage(
98 reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` +
99 `version and reload the window now?`,
100 "Download now", "Cancel"
101 );
102 return userResponse === "Download now";
103}
104
105/**
106 * Shutdowns the process in case of success (i.e. reloads the window) or throws an error.
107 *
108 * ACHTUNG!: this function has a crazy amount of state transitions, handling errors during
109 * each of them would result in a ton of code (especially accounting for cross-process
110 * shared mutable `globalState` access). Enforcing no reentrancy for this is best-effort.
111 */
112const tryDownloadNightlyExtension = notReentrant(async (
113 config: Config,
114 shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
115): Promise<never | void> => {
116 const vsixSource = config.nightlyVsixSource;
117 try {
118 const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag);
119
120 if (!shouldDownload(releaseInfo)) return;
121
122 await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension");
123
124 const vsixPath = path.join(vsixSource.dir, vsixSource.file);
125
126 await vscodeInstallExtensionFromVsix(vsixPath);
127 await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
128 await fs.unlink(vsixPath);
129
130 await vscodeReloadWindow(); // never returns
131 } catch (err) {
132 log.downloadError(err, "nightly extension", vsixSource.repo.name);
133 }
134});
135
136function diffInHours(a: Date, b: Date): number {
137 // Discard the time and time-zone information (to abstract from daylight saving time bugs)
138 // https://stackoverflow.com/a/15289883/9259330
139
140 const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
141 const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
142
143 return (utcA - utcB) / (1000 * 60 * 60);
144}