1use std::{ffi::OsStr, fmt, path::Path};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 constants::VSCODE_CLI_UPDATE_ENDPOINT,
12 debug, log, options, spanf,
13 util::{
14 errors::{AnyError, CodeError, WrappedError},
15 http::{BoxedHttp, SimpleResponse},
16 io::ReportCopyProgress,
17 tar, zipper,
18 },
19};
20
21#[derive(Clone)]
23pub struct UpdateService {
24 client: BoxedHttp,
25 log: log::Logger,
26}
27
28#[derive(Clone, Eq, PartialEq)]
30pub struct Release {
31 pub name: String,
32 pub platform: Platform,
33 pub target: TargetKind,
34 pub quality: options::Quality,
35 pub commit: String,
36}
37
38impl std::fmt::Display for Release {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "{} (commit {})", self.name, self.commit)
41 }
42}
43
44#[derive(Deserialize)]
45struct UpdateServerVersion {
46 pub version: String,
47 pub name: String,
48}
49
50fn quality_download_segment(quality: options::Quality) -> &'static str {
51 match quality {
52 options::Quality::Stable => "stable",
53 options::Quality::Insiders => "insider",
54 options::Quality::Exploration => "exploration",
55 }
56}
57
58fn get_update_endpoint() -> Result<&'static str, CodeError> {
59 VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(|| CodeError::UpdatesNotConfigured("no service url"))
60}
61
62impl UpdateService {
63 pub fn new(log: log::Logger, http: BoxedHttp) -> Self {
64 UpdateService { client: http, log }
65 }
66
67 pub async fn get_release_by_semver_version(
68 &self,
69 platform: Platform,
70 target: TargetKind,
71 quality: options::Quality,
72 version: &str,
73 ) -> Result<Release, AnyError> {
74 let update_endpoint = get_update_endpoint()?;
75 let download_segment = target
76 .download_segment(platform)
77 .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
78 let download_url = format!(
79 "{}/api/versions/{}/{}/{}",
80 update_endpoint,
81 version,
82 download_segment,
83 quality_download_segment(quality),
84 );
85
86 let mut response = spanf!(
87 self.log,
88 self.log.span("server.version.resolve"),
89 self.client.make_request("GET", download_url)
90 )?;
91
92 if !response.status_code.is_success() {
93 return Err(response.into_err().await.into());
94 }
95
96 let res = response.json::<UpdateServerVersion>().await?;
97 debug!(self.log, "Resolved version {} to {}", version, res.version);
98
99 Ok(Release {
100 target,
101 platform,
102 quality,
103 name: res.name,
104 commit: res.version,
105 })
106 }
107
108 pub async fn get_latest_commit(
110 &self,
111 platform: Platform,
112 target: TargetKind,
113 quality: options::Quality,
114 ) -> Result<Release, AnyError> {
115 let update_endpoint = get_update_endpoint()?;
116 let download_segment = target
117 .download_segment(platform)
118 .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
119 let download_url = format!(
120 "{}/api/latest/{}/{}",
121 update_endpoint,
122 download_segment,
123 quality_download_segment(quality),
124 );
125
126 let mut response = spanf!(
127 self.log,
128 self.log.span("server.version.resolve"),
129 self.client.make_request("GET", download_url)
130 )?;
131
132 if !response.status_code.is_success() {
133 return Err(response.into_err().await.into());
134 }
135
136 let res = response.json::<UpdateServerVersion>().await?;
137 debug!(self.log, "Resolved quality {} to {}", quality, res.version);
138
139 Ok(Release {
140 target,
141 platform,
142 quality,
143 name: res.name,
144 commit: res.version,
145 })
146 }
147
148 pub async fn get_download_stream(&self, release: &Release) -> Result<SimpleResponse, AnyError> {
150 let update_endpoint = get_update_endpoint()?;
151 let download_segment = release
152 .target
153 .download_segment(release.platform)
154 .ok_or_else(|| CodeError::UnsupportedPlatform(release.platform.to_string()))?;
155
156 let download_url = format!(
157 "{}/commit:{}/{}/{}",
158 update_endpoint,
159 release.commit,
160 download_segment,
161 quality_download_segment(release.quality),
162 );
163
164 let response = self.client.make_request("GET", download_url).await?;
165 if !response.status_code.is_success() {
166 return Err(response.into_err().await.into());
167 }
168
169 Ok(response)
170 }
171}
172
173pub fn unzip_downloaded_release<T>(
174 compressed_file: &Path,
175 target_dir: &Path,
176 reporter: T,
177) -> Result<(), WrappedError>
178where
179 T: ReportCopyProgress,
180{
181 if compressed_file.extension() == Some(OsStr::new("zip")) {
182 zipper::unzip_file(compressed_file, target_dir, reporter)
183 } else {
184 tar::decompress_tarball(compressed_file, target_dir, reporter)
185 }
186}
187
188#[derive(Eq, PartialEq, Copy, Clone)]
189pub enum TargetKind {
190 Server,
191 Archive,
192 Web,
193 Cli,
194}
195
196impl TargetKind {
197 fn download_segment(&self, platform: Platform) -> Option<String> {
198 match *self {
199 TargetKind::Server => Some(platform.headless()),
200 TargetKind::Archive => platform.archive(),
201 TargetKind::Web => Some(platform.web()),
202 TargetKind::Cli => Some(platform.cli()),
203 }
204 }
205}
206
207#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
208pub enum Platform {
209 LinuxAlpineX64,
210 LinuxAlpineARM64,
211 LinuxX64,
212 LinuxARM64,
213 LinuxARM32,
214 DarwinX64,
215 DarwinARM64,
216 WindowsX64,
217 WindowsX86,
218 WindowsARM64,
219}
220
221impl Platform {
222 pub fn archive(&self) -> Option<String> {
223 match self {
224 Platform::LinuxX64 => Some("linux-x64".to_owned()),
225 Platform::LinuxARM64 => Some("linux-arm64".to_owned()),
226 Platform::LinuxARM32 => Some("linux-armhf".to_owned()),
227 Platform::DarwinX64 => Some("darwin".to_owned()),
228 Platform::DarwinARM64 => Some("darwin-arm64".to_owned()),
229 Platform::WindowsX64 => Some("win32-x64-archive".to_owned()),
230 Platform::WindowsX86 => Some("win32-archive".to_owned()),
231 Platform::WindowsARM64 => Some("win32-arm64-archive".to_owned()),
232 _ => None,
233 }
234 }
235 pub fn headless(&self) -> String {
236 match self {
237 Platform::LinuxAlpineARM64 => "server-alpine-arm64",
238 Platform::LinuxAlpineX64 => "server-linux-alpine",
239 Platform::LinuxX64 => "server-linux-x64",
240 Platform::LinuxARM64 => "server-linux-arm64",
241 Platform::LinuxARM32 => "server-linux-armhf",
242 Platform::DarwinX64 => "server-darwin",
243 Platform::DarwinARM64 => "server-darwin-arm64",
244 Platform::WindowsX64 => "server-win32-x64",
245 Platform::WindowsX86 => "server-win32",
246 Platform::WindowsARM64 => "server-win32-x64", }
248 .to_owned()
249 }
250
251 pub fn cli(&self) -> String {
252 match self {
253 Platform::LinuxAlpineARM64 => "cli-alpine-arm64",
254 Platform::LinuxAlpineX64 => "cli-alpine-x64",
255 Platform::LinuxX64 => "cli-linux-x64",
256 Platform::LinuxARM64 => "cli-linux-arm64",
257 Platform::LinuxARM32 => "cli-linux-armhf",
258 Platform::DarwinX64 => "cli-darwin-x64",
259 Platform::DarwinARM64 => "cli-darwin-arm64",
260 Platform::WindowsARM64 => "cli-win32-arm64",
261 Platform::WindowsX64 => "cli-win32-x64",
262 Platform::WindowsX86 => "cli-win32",
263 }
264 .to_owned()
265 }
266
267 pub fn web(&self) -> String {
268 format!("{}-web", self.headless())
269 }
270
271 pub fn env_default() -> Option<Platform> {
272 if cfg!(all(
273 target_os = "linux",
274 target_arch = "x86_64",
275 target_env = "musl"
276 )) {
277 Some(Platform::LinuxAlpineX64)
278 } else if cfg!(all(
279 target_os = "linux",
280 target_arch = "aarch64",
281 target_env = "musl"
282 )) {
283 Some(Platform::LinuxAlpineARM64)
284 } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
285 Some(Platform::LinuxX64)
286 } else if cfg!(all(target_os = "linux", target_arch = "arm")) {
287 Some(Platform::LinuxARM32)
288 } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
289 Some(Platform::LinuxARM64)
290 } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
291 Some(Platform::DarwinX64)
292 } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
293 Some(Platform::DarwinARM64)
294 } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
295 Some(Platform::WindowsX64)
296 } else if cfg!(all(target_os = "windows", target_arch = "x86")) {
297 Some(Platform::WindowsX86)
298 } else if cfg!(all(target_os = "windows", target_arch = "aarch64")) {
299 Some(Platform::WindowsARM64)
300 } else {
301 None
302 }
303 }
304}
305
306impl fmt::Display for Platform {
307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308 f.write_str(match self {
309 Platform::LinuxAlpineARM64 => "LinuxAlpineARM64",
310 Platform::LinuxAlpineX64 => "LinuxAlpineX64",
311 Platform::LinuxX64 => "LinuxX64",
312 Platform::LinuxARM64 => "LinuxARM64",
313 Platform::LinuxARM32 => "LinuxARM32",
314 Platform::DarwinX64 => "DarwinX64",
315 Platform::DarwinARM64 => "DarwinARM64",
316 Platform::WindowsX64 => "WindowsX64",
317 Platform::WindowsX86 => "WindowsX86",
318 Platform::WindowsARM64 => "WindowsARM64",
319 })
320 }
321}