1use crate::app_info::AppInfo;
2use crate::built_info;
3use crate::RI_USER_AGENT;
4use anyhow::{anyhow as err, Error};
5use futures::StreamExt;
6use indicatif::ProgressBar;
7use reqwest::header::{CONTENT_LENGTH, USER_AGENT};
8use reqwest::Client;
9use semver::Version;
10use serde::Deserialize;
11use tempfile::tempfile;
12use tokio::fs::File;
13use tokio::io::{AsyncSeekExt, AsyncWriteExt, SeekFrom};
14
15pub struct GitHubApi {
16 client: Client,
17}
18
19impl GitHubApi {
20 pub fn new() -> Self {
21 Self {
22 client: Client::new(),
23 }
24 }
25
26 pub async fn releases(&mut self, url: &str) -> Result<Vec<Release>, Error> {
27 let releases = self
28 .client
29 .get(url)
30 .header(USER_AGENT, RI_USER_AGENT)
31 .send()
32 .await?
33 .json()
34 .await?;
35 Ok(releases)
36 }
37
38 pub async fn latest_release(&mut self, app_info: &AppInfo) -> Result<Release, Error> {
39 let latest_release = self
40 .releases(&app_info.link)
41 .await?
42 .into_iter()
43 .next()
44 .ok_or_else(|| Error::msg("No releases available"))?;
45 Ok(latest_release)
46 }
47
48 pub async fn download_assets(&mut self, url: &str) -> Result<File, Error> {
49 let resp = self
50 .client
51 .get(url)
52 .header(USER_AGENT, RI_USER_AGENT)
53 .send()
54 .await?;
55 let total = resp
56 .headers()
57 .get(CONTENT_LENGTH)
58 .ok_or_else(|| Error::msg("Can't detect size of assets"))?
59 .to_str()?
60 .parse()?;
61 let mut chunks = resp.bytes_stream();
62 let mut archive = File::from_std(tempfile()?);
63 let bar = ProgressBar::new(total);
64 while let Some(chunk) = chunks.next().await.transpose()? {
65 bar.inc(chunk.len() as u64);
66 archive.write_all(&chunk).await?;
67 }
68 bar.finish();
69 archive.seek(SeekFrom::Start(0)).await?;
70 Ok(archive)
71 }
72}
73
74#[derive(Debug, Deserialize)]
75pub struct Release {
76 pub name: String,
78 pub html_url: String,
79 #[serde(rename = "tag_name")]
81 pub version: Version,
82 pub assets: Vec<Asset>,
83}
84
85impl Release {
86 pub fn get_asset_for_os(&self, app_info: &AppInfo, os: &str) -> Result<&str, Error> {
87 let arch = built_info::CFG_TARGET_ARCH;
88 let ver = &self.version;
89 let name = &app_info.name;
90 let expected_asset = format!("{name}-{ver}-{os}-{arch}.tar.gz");
91 for asset in &self.assets {
92 if asset.name == expected_asset {
93 return Ok(&asset.browser_download_url);
94 }
95 }
96 Err(err!("Assets for '{os}' system was not found"))
97 }
98}
99
100#[derive(Debug, Deserialize)]
101pub struct Asset {
102 pub name: String,
104 pub browser_download_url: String,
105}