Skip to main content

unlab_gpu/pkg/
gitlab.rs

1//
2// Copyright (c) 2026 Ɓukasz Szpakowski
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7//
8//! A module of [GitLab](https://about.gitlab.com) source.
9use std::str;
10use crate::serde_json;
11use super::*;
12
13/// A service domain.
14pub const SERVICE_DOMAIN: &'static str = "gitlab.com";
15/// A prefix of service domain.
16pub const SERVICE_DOMAIN_PREFIX: &'static str = "gitlab.";
17
18#[derive(Clone, Debug, Deserialize)]
19struct Tag
20{
21    name: String,
22}
23
24/// A structure of [GitLab](https://about.gitlab.com) source.
25#[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    /// Creates a [GitLab](https://about.gitlab.com) source.
41    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    /// Returns the package name.
65    pub fn name(&self) -> &PkgName
66    { &self.name }
67
68    /// Returns the old package name if the source has the old package name, otherwise `None`.
69    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    /// Returns the path to the Unlab-gpu home directory.
78    pub fn home_dir(&self) -> &Path
79    { self.home_dir.as_path() }
80
81    /// Returns the path to the work directory of current package.
82    pub fn work_dir(&self) -> &Path
83    { self.work_dir.as_path() }
84
85    /// Returns the printer.
86    pub fn printer(&self) -> &Arc<dyn Print + Send + Sync>
87    { &self.printer }
88
89    /// Returns the current package version.
90    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/// A structure of factory of [GitLab](https://about.gitlab.com) source.
190#[derive(Copy, Clone, Debug)]
191pub struct GitLabSrcFactory;
192
193impl GitLabSrcFactory
194{
195    /// Creates a factory of [GitLab](https://about.gitlab.com) source.
196    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}