aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/code/src/installation/extension.ts131
1 files changed, 131 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..7709cd3cd
--- /dev/null
+++ b/editors/code/src/installation/extension.ts
@@ -0,0 +1,131 @@
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 } 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 const currentUpdChannel = config.installedExtensionUpdateChannel;
19 const desiredUpdChannel = config.updatesChannel;
20
21 if (currentUpdChannel === UpdatesChannel.Stable) {
22 // Release date is present only when we are on nightly
23 config.installedNightlyExtensionReleaseDate.set(null);
24 }
25
26 // User has built lsp server from sources, she should manage updates manually
27 if (currentUpdChannel === null) return;
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 assert(currentExtReleaseDate !== null, "nightly release date must've been set during installation");
48
49 const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, new Date());
50 log.debug(`Current rust-analyzer nightly was downloaded ${hoursSinceLastUpdate} hours ago`);
51
52 if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) {
53 return;
54 }
55 if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) {
56 return;
57 }
58
59 await tryDownloadNightlyExtension(config, releaseInfo => {
60 assert(
61 currentExtReleaseDate === config.installedNightlyExtensionReleaseDate.get(),
62 "Other active VSCode instance has reinstalled the extension"
63 );
64
65 if (releaseInfo.releaseDate === currentExtReleaseDate) {
66 vscode.window.showInformationMessage(
67 "Whoops, it appears that your nightly version is up-to-date. " +
68 "There might be some problems with the upcomming nightly release " +
69 "or you traveled too far into the future. Sorry for that 😅! "
70 );
71 return false;
72 }
73 return true;
74 });
75}
76
77async function askToDownloadProperExtensionVersion(config: Config, reason = "") {
78 if (!config.askBeforeDownload) return true;
79
80 const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly";
81
82 // In case of reentering this function and showing the same info message
83 // (e.g. after we had shown this message, the user changed the config)
84 // vscode will dismiss the already shown one (i.e. return undefined).
85 // This behaviour is what we want, but likely it is not documented
86
87 const userResponse = await vscode.window.showInformationMessage(
88 reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` +
89 `version and reload the window now?`,
90 "Download now", "Cancel"
91 );
92 log.debug("Response: ", userResponse);
93 return userResponse === "Download now";
94}
95
96/**
97 * Shutdowns the process in case of success (i.e. reloads the window) or throws an error.
98 */
99const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNightlyExtension(
100 config: Config,
101 shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
102): Promise<never | void> {
103 const vsixSource = config.nightlyVsixSource;
104 try {
105 const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag);
106
107 if (!shouldDownload(releaseInfo)) return;
108
109 await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension");
110
111 const vsixPath = path.join(vsixSource.dir, vsixSource.file);
112
113 await vscodeInstallExtensionFromVsix(vsixPath)
114 await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
115 await fs.unlink(vsixPath);
116
117 await vscodeReloadWindow(); // never returns
118 } catch (err) {
119 log.downloadError(err, "nightly extension", vsixSource.repo.name);
120 }
121});
122
123function diffInHours(a: Date, b: Date): number {
124 // Discard the time and time-zone information (to abstract from daylight saving time bugs)
125 // https://stackoverflow.com/a/15289883/9259330
126
127 const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
128 const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
129
130 return (utcA - utcB) / (1000 * 60 * 60);
131}