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