1use std::str;
10use crate::serde_json;
11use super::*;
12
13pub const SERVICE_DOMAIN: &'static str = "github.com";
15pub const SERVICE_API_DOMAIN: &'static str = "api.github.com";
17
18#[derive(Clone, Debug, Deserialize)]
19struct Ref
20{
21 #[serde(rename = "ref")]
22 ref1: String,
23}
24
25#[derive(Clone)]
27pub struct GitHubSrc
28{
29 name: PkgName,
30 old_name: Option<PkgName>,
31 home_dir: PathBuf,
32 work_dir: PathBuf,
33 printer: Arc<dyn Print + Send + Sync>,
34 versions: Option<BTreeSet<Version>>,
35 current_version: Option<Version>,
36 dir: Option<PathBuf>,
37}
38
39impl GitHubSrc
40{
41 pub fn new(name: PkgName, old_name: Option<PkgName>, home_dir: PathBuf, work_dir: PathBuf, printer: Arc<dyn Print + Send + Sync>) -> Option<Self>
43 {
44 let original_name = old_name.as_ref().unwrap_or(&name);
45 if original_name.name().split('/').count() != 3 {
46 return None;
47 }
48 match original_name.name().split_once('/') {
49 Some((domain, _)) if domain == SERVICE_DOMAIN => {
50 Some(GitHubSrc {
51 name,
52 old_name,
53 home_dir,
54 work_dir,
55 printer,
56 versions: None,
57 current_version: None,
58 dir: None,
59 })
60 },
61 _ => None,
62 }
63 }
64
65 pub fn name(&self) -> &PkgName
67 { &self.name }
68
69 pub fn old_name(&self) -> Option<&PkgName>
71 {
72 match &self.old_name {
73 Some(old_name) => Some(old_name),
74 None => None,
75 }
76 }
77
78 pub fn home_dir(&self) -> &Path
80 { self.home_dir.as_path() }
81
82 pub fn work_dir(&self) -> &Path
84 { self.work_dir.as_path() }
85
86 pub fn printer(&self) -> &Arc<dyn Print + Send + Sync>
88 { &self.printer }
89
90 pub fn current_version(&self) -> Option<&Version>
92 {
93 match &self.current_version {
94 Some(current_version) => Some(current_version),
95 None => None,
96 }
97 }
98
99 fn update_versions(&self, is_update: bool) -> Result<BTreeSet<Version>>
100 {
101 let original_name = self.old_name.as_ref().unwrap_or(&self.name);
102 let repo_path = match original_name.name().split_once('/') {
103 Some((_, tmp_repo_path)) => tmp_repo_path,
104 None => return Err(Error::PkgName(self.name.clone(), String::from("no package repository path"))),
105 };
106 update_pkg_versions(&self.name, &self.old_name, self.home_dir.as_path(), is_update, &self.printer, || {
107 let mut easy = curl::easy::Easy::new();
108 easy.url(format!("https://{}/repos/{}/git/matching-refs/tags", SERVICE_API_DOMAIN, str_to_url_name(repo_path, true)).as_str())?;
109 let mut http_headers = List::new();
110 http_headers.append(USER_AGENT_HTTP_HEADER)?;
111 http_headers.append("Accept: application/vnd.github+json")?;
112 http_headers.append("X-GitHub-Api-Version: 2022-11-28")?;
113 easy.http_headers(http_headers)?;
114 easy.follow_location(true)?;
115 Ok(easy)
116 }, |data| {
117 let s = match str::from_utf8(data) {
118 Ok(tmp_s) => tmp_s,
119 Err(_) => return Err(Error::PkgName(self.name.clone(), String::from("data contains invalid UTF-8 character"))),
120 };
121 let refs: Vec<Ref> = match serde_json::from_str(s) {
122 Ok(tmp_refs) => tmp_refs,
123 Err(err) => return Err(Error::SerdeJson(err)),
124 };
125 let mut versions: BTreeSet<Version> = BTreeSet::new();
126 for ref1 in &refs {
127 let tag_ref_prefix = "refs/tags/";
128 if ref1.ref1.starts_with(tag_ref_prefix) {
129 let tag_name = &ref1.ref1[tag_ref_prefix.len()..];
130 match tag_name_to_version(tag_name) {
131 Some(version) => {
132 versions.insert(version);
133 },
134 None => (),
135 }
136 }
137 }
138 Ok(versions)
139 })
140 }
141}
142
143impl Source for GitHubSrc
144{
145 fn update(&mut self) -> Result<()>
146 {
147 self.versions = Some(self.update_versions(true)?);
148 Ok(())
149 }
150
151 fn versions(&mut self) -> Result<&BTreeSet<Version>>
152 {
153 if self.versions.is_none() {
154 self.versions = Some(self.update_versions(false)?);
155 }
156 match &self.versions {
157 Some(versions) => Ok(versions),
158 None => return Err(Error::PkgName(self.name.clone(), String::from("no package versions"))),
159 }
160 }
161
162 fn set_current_version(&mut self, version: Version)
163 { self.current_version = Some(version); }
164
165 fn dir(&mut self) -> Result<&Path>
166 {
167 if self.dir.is_none() {
168 match &self.current_version {
169 Some(current_version) => {
170 self.dir = Some(extract_pkg_file(&self.name, current_version, &self.work_dir, &self.printer, || {
171 let original_name = self.old_name.as_ref().unwrap_or(&self.name);
172 let tag_name = version_to_tag_name(current_version);
173 let url = format!("https://{}/archive/refs/tags/{}.tar.gz", str_to_url_name(original_name.name(), true), str_to_url_name(tag_name.as_str(), false));
174 download_pkg_file(&self.name, &self.old_name, current_version, url.as_str(), &self.home_dir, &self.printer)
175 })?)
176 },
177 None => return Err(Error::PkgName(self.name.clone(), String::from("no current package version"))),
178 }
179 }
180 match &self.dir {
181 Some(versions) => Ok(versions),
182 None => return Err(Error::PkgName(self.name.clone(), String::from("no package directory"))),
183 }
184 }
185}
186
187#[derive(Copy, Clone, Debug)]
189pub struct GitHubSrcFactory;
190
191impl GitHubSrcFactory
192{
193 pub fn new() -> Self
195 { GitHubSrcFactory }
196}
197
198impl SourceCreate for GitHubSrcFactory
199{
200 fn create(&self, name: PkgName, old_name: Option<PkgName>, home_dir: PathBuf, work_dir: PathBuf, printer: Arc<dyn Print + Send + Sync>) -> Option<Box<dyn Source + Send + Sync>>
201 {
202 match GitHubSrc::new(name, old_name, home_dir, work_dir, printer) {
203 Some(src) => Some(Box::new(src)),
204 None => None,
205 }
206 }
207}