github_bin_downloader/
ghapi.rs

1use crate::sysinfo;
2use crate::utils;
3
4use crate::GBDResult;
5use reqwest::{StatusCode, Url};
6use serde_json::Value;
7use thiserror::Error;
8
9#[derive(Debug, Default)]
10pub struct RepoInfo {
11    user_name: String,
12    repo_name: String,
13    url: String,
14    releases_api_url: String,
15    pub releases: Vec<Release>,
16}
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct Release {
20    pub name: String,
21    pub url: String,
22}
23
24impl Release {
25    // Download the release
26    pub async fn download_release(&self) -> GBDResult<()> {
27        let url = Url::parse(self.url.as_str())?;
28        println!("Downloading {} from {}", self.name, url);
29        utils::download_file_from_url(url, &self.name).await?;
30        Ok(())
31    }
32}
33
34impl ToString for Release {
35    fn to_string(&self) -> String {
36        self.name.to_string()
37    }
38}
39
40#[derive(Error, Debug)]
41pub enum GithubError {
42    #[error("Repo Not Found")]
43    NotFound(StatusCode),
44}
45
46impl RepoInfo {
47    pub async fn from_url(url: &str) -> GBDResult<Self> {
48        let mut url = url.to_string();
49        if !url.contains("https://") && !url.contains("http://") {
50            url = format!("https://{}", url);
51        }
52        if !url.contains("github") {
53            return Err(Box::new(GithubError::NotFound(StatusCode::NOT_IMPLEMENTED)));
54        }
55        let resp = reqwest::get(&url).await?;
56        if resp.status() == StatusCode::OK {
57            let path = resp.url().path();
58            let repoinfo_vec: Vec<&str> = path.split('/').collect();
59            let releases_api_url = format!(
60                "https://api.github.com/repos/{}/{}/releases",
61                repoinfo_vec[1].to_string(),
62                repoinfo_vec[2].to_string()
63            );
64            Ok(RepoInfo {
65                user_name: repoinfo_vec[1].to_string(),
66                repo_name: repoinfo_vec[2].to_string(),
67                url,
68                releases_api_url,
69                ..Default::default()
70            })
71        } else {
72            Err(Box::new(GithubError::NotFound(resp.status())))
73        }
74    }
75
76    // Fetch the latest release from Github including Pre-release
77    pub async fn get_latest_release(&mut self) -> GBDResult<()> {
78        let client = reqwest::Client::builder()
79            .user_agent("github-bin-downloader")
80            .build()?;
81        let resp = client
82            .get(&self.releases_api_url)
83            .send()
84            .await?
85            .text()
86            .await?;
87        let repo: Value = serde_json::from_str(&resp)?;
88        let length = repo[0]["assets"]
89            .as_array()
90            .expect("Cannot convert to Array")
91            .len();
92        let mut releases: Vec<Release> = Vec::new();
93        for i in 0..length {
94            releases.push(Release {
95                name: utils::sanitize_str_to_string(&repo[0]["assets"][i]["name"]),
96                url: utils::sanitize_str_to_string(&repo[0]["assets"][i]["browser_download_url"]),
97            });
98        }
99        self.releases = releases;
100        Ok(())
101    }
102
103    // Get all the latest stable releases from Github releases
104    pub async fn get_latest_stable_release(&mut self) -> GBDResult<()> {
105        let client = reqwest::Client::builder()
106            .user_agent("github-bin-downloader")
107            .build()?;
108        let resp = client
109            .get(&self.releases_api_url)
110            .send()
111            .await?
112            .text()
113            .await?;
114        let repo: Value = serde_json::from_str(&resp)?;
115        let length = repo.as_array().expect("Cannot convert to Array").len();
116        let mut releases: Vec<Release> = Vec::new();
117        for i in 0..length {
118            if !repo[i]["prerelease"]
119                .as_bool()
120                .expect("Cannot convert to bool")
121            {
122                let length = repo[i]["assets"]
123                    .as_array()
124                    .expect("Cannot convert to Array")
125                    .len();
126                for j in 0..length {
127                    releases.push(Release {
128                        name: utils::sanitize_str_to_string(&repo[i]["assets"][j]["name"]),
129                        url: utils::sanitize_str_to_string(
130                            &repo[i]["assets"][j]["browser_download_url"],
131                        ),
132                    });
133                }
134                self.releases = releases;
135                return Ok(());
136            }
137        }
138        Ok(())
139    }
140
141    // Search the releases for the host OS
142    pub async fn search_releases_for_os(&self) -> GBDResult<Vec<Release>> {
143        let sys_info = sysinfo::SystemInfo::new();
144        let mut releases: Vec<Release> = Vec::new();
145        match sys_info.platform_os() {
146            sysinfo::PlatformOS::Darwin => {
147                sysinfo::APPLE.iter().for_each(|mac| {
148                    self.releases.iter().for_each(|release| {
149                        if release.name.to_lowercase().contains(mac) {
150                            releases.push(release.clone());
151                        }
152                    });
153                });
154            }
155            sysinfo::PlatformOS::Linux => {
156                sysinfo::LINUX.iter().for_each(|linux| {
157                    self.releases.iter().for_each(|release| {
158                        if release.name.to_lowercase().contains(linux) {
159                            releases.push(release.clone());
160                        }
161                    });
162                });
163            }
164            _ => {}
165        }
166        Ok(releases)
167    }
168
169    // Search the releases for the host Arch
170    pub async fn search_releases_for_arch(&self) -> GBDResult<Vec<Release>> {
171        let sys_info = sysinfo::SystemInfo::new();
172        let mut releases: Vec<Release> = Vec::new();
173        match sys_info.platform_arch() {
174            sysinfo::PlatformArch::X8664 => {
175                sysinfo::AMD64.iter().for_each(|arch| {
176                    self.releases.iter().for_each(|release| {
177                        if release.name.contains(arch) {
178                            releases.push(release.clone());
179                        }
180                    });
181                });
182            }
183            sysinfo::PlatformArch::Arm64 => {
184                sysinfo::ARM64.iter().for_each(|arch| {
185                    self.releases.iter().for_each(|release| {
186                        if release.name.contains(arch) {
187                            releases.push(release.clone());
188                        }
189                    });
190                });
191            }
192            _ => {}
193        }
194        Ok(releases)
195    }
196}