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