1use crate::codec::protos;
2use crate::coreobj::CoreObjectType;
3use cyfs_base::*;
4use serde::Serialize;
5
6use std::collections::hash_map::RandomState;
7use std::collections::{BTreeMap, HashMap};
8
9#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform, Serialize)]
10#[cyfs_protobuf_type(crate::codec::protos::DecAppDescContent)]
11pub struct DecAppDescContent {
12 id: String,
13}
14
15impl DescContent for DecAppDescContent {
16 fn obj_type() -> u16 {
17 CoreObjectType::DecApp as u16
18 }
19
20 fn format(&self) -> u8 {
21 OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF
22 }
23
24 type OwnerType = Option<ObjectId>;
25 type AreaType = SubDescNone;
26 type AuthorType = SubDescNone;
27 type PublicKeyType = SubDescNone;
28}
29
30#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType, Serialize)]
31#[cyfs_protobuf_type(crate::codec::protos::DecAppContent)]
32pub struct DecAppContent {
33 source: HashMap<String, ObjectId>,
34 icon: Option<String>,
35 desc: Option<String>,
36 source_desc: HashMap<String, String>,
37 tags: HashMap<String, String>,
38}
39
40impl BodyContent for DecAppContent {
41 fn format(&self) -> u8 {
42 OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF
43 }
44}
45
46impl ProtobufTransform<protos::DecAppContent> for DecAppContent {
47 fn transform(value: protos::DecAppContent) -> BuckyResult<Self> {
48 let mut source = HashMap::new();
49 for item in value.source {
50 source.insert(item.key, ObjectId::clone_from_slice(item.value.as_slice())?);
51 }
52
53 let mut source_desc = HashMap::new();
54 for item in value.source_desc {
55 source_desc.insert(item.key, item.value);
56 }
57
58 let mut tags = HashMap::new();
59 for item in value.tags {
60 tags.insert(item.key, item.value);
61 }
62
63 let mut ret = DecAppContent {
64 source,
65 source_desc,
66 icon: None,
67 desc: None,
68 tags,
69 };
70
71 if value.icon.is_some() {
72 ret.icon = Some(value.icon.unwrap());
73 }
74 if value.desc.is_some() {
75 ret.desc = Some(value.desc.unwrap());
76 }
77
78 Ok(ret)
79 }
80}
81impl ProtobufTransform<&DecAppContent> for protos::DecAppContent {
82 fn transform(value: &DecAppContent) -> BuckyResult<Self> {
83 let source_map: BTreeMap<String, ObjectId> = value.source.clone().into_iter().collect();
84 let mut source = vec![];
85 for (k, v) in source_map {
86 source.push(protos::StringBytesMapItem {
87 key: k,
88 value: v.to_vec()?,
89 });
90 }
91
92 let source_desc_map: BTreeMap<String, String> =
93 value.source_desc.clone().into_iter().collect();
94 let mut source_desc = vec![];
95 for (k, v) in source_desc_map {
96 source_desc.push(protos::StringStringMapItem { key: k, value: v });
97 }
98
99 let tags_map: BTreeMap<String, String> = value.tags.clone().into_iter().collect();
100 let mut tags = vec![];
101 for (k, v) in tags_map {
102 tags.push(protos::StringStringMapItem { key: k, value: v });
103 }
104
105 let mut ret = Self {
106 source,
107 source_desc,
108 icon: None,
109 desc: None,
110 tags,
111 };
112
113 if let Some(icon) = &value.icon {
114 ret.icon = Some(icon.to_owned());
115 }
116 if let Some(desc) = &value.desc {
117 ret.desc = Some(desc.to_owned());
118 }
119
120 Ok(ret)
121 }
122}
123
124type DecAppType = NamedObjType<DecAppDescContent, DecAppContent>;
125type DecAppBuilder = NamedObjectBuilder<DecAppDescContent, DecAppContent>;
126type DecAppDesc = NamedObjectDesc<DecAppDescContent>;
127
128pub type DecAppId = NamedObjectId<DecAppType>;
129pub type DecApp = NamedObjectBase<DecAppType>;
130
131pub trait DecAppObj {
132 fn create(owner: ObjectId, id: &str) -> Self;
133 fn name(&self) -> &str;
134 fn app_desc(&self) -> Option<&str>;
135 fn icon(&self) -> Option<&str>;
136
137 fn find_version(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<(&str, String)>;
139
140 fn find_source_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<ObjectId>;
141 fn find_source(&self, version: &str) -> BuckyResult<ObjectId>;
142
143 fn find_source_desc_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<Option<&str>>;
144 fn find_source_desc(&self, version: &str) -> Option<&str>;
145
146 fn remove_source(&mut self, version: &str);
147 fn clear_source(&mut self);
148 fn set_source(&mut self, version: String, id: ObjectId, desc: Option<String>);
149 fn source(&self) -> &HashMap<String, ObjectId>;
150
151 fn find_tag(&self, tag: &str) -> BuckyResult<&str>;
152 fn set_tag(&mut self, tag: String, version: String);
153 fn remove_tag(&mut self, tag: &str);
154 fn tags(&self) -> &HashMap<String, String>;
155
156 fn generate_id(owner: ObjectId, id: &str) -> ObjectId;
157}
158
159pub struct SemVerHelper {}
160
161impl SemVerHelper {
162 pub fn fix_semver(ver: &str) -> String {
165 let mut top: Vec<&str> = ver.split('-').collect();
166 let mut ret: Vec<&str> = top[0].split('.').collect();
167 if ret.len() == 4 {
168 ret.remove(2);
169 }
170
171 let ret = ret.join(".");
172 let ret = if top.len() > 1 {
173 top[0] = &ret;
174 top.join("-")
175 } else {
176 ret
177 };
178
179 ret
181 }
182}
183
184impl DecAppObj for DecApp {
186 fn create(owner: ObjectId, id: &str) -> Self {
187 let body = DecAppContent {
188 source: HashMap::new(),
189 icon: None,
190 desc: None,
191 source_desc: HashMap::new(),
192 tags: HashMap::new(),
193 };
194 let desc = DecAppDescContent { id: id.to_owned() };
195 DecAppBuilder::new(desc, body)
196 .owner(owner)
197 .no_create_time()
198 .build()
199 }
200
201 fn name(&self) -> &str {
202 &self.desc().content().id
203 }
204
205 fn app_desc(&self) -> Option<&str> {
206 self.body_expect("").content().desc.as_deref()
207 }
208
209 fn icon(&self) -> Option<&str> {
210 self.body_expect("").content().icon.as_deref()
211 }
212
213 fn find_version(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<(&str, String)> {
217 let name = self.name();
218 let id = self.desc().calculate_id();
219
220 let req_version = semver::VersionReq::parse(req_semver).map_err(|e| {
221 let msg = format!(
222 "invalid semver request string! id={}, name={}, value={}, pre={:?}, {}",
223 id, name, req_semver, pre, e
224 );
225 error!("{}", msg);
226 BuckyError::new(BuckyErrorCode::InvalidFormat, msg)
227 })?;
228
229 let list: Vec<_> = self
230 .body_expect("")
231 .content()
232 .source
233 .keys()
234 .map(|key| {
235 let new_version = SemVerHelper::fix_semver(&key);
236 (key, new_version)
237 })
238 .collect();
239
240 let mut semver_list = vec![];
241 for (version, new_version) in list {
242 let mut semver = semver::Version::parse(&new_version).map_err(|e| {
243 let msg = format!(
244 "invalid semver string! id={}, name={}, value={}, pre={:?}, {}",
245 id, name, version, pre, e,
246 );
247 error!("{}", msg);
248 BuckyError::new(BuckyErrorCode::InvalidFormat, msg)
249 })?;
250
251 if !semver.pre.is_empty() {
252 if let Some(pre) = pre {
253 if semver.pre.as_str() != pre {
254 continue;
255 }
256 semver.pre = semver::Prerelease::EMPTY;
257 } else {
258 continue;
259 }
260 }
261
262 semver_list.push((version, semver));
263 }
264
265 semver_list.sort_by(|left, right| right.1.partial_cmp(&left.1).unwrap());
266
267 let ret = semver_list.iter().find(|(version, semver)| {
268 if req_version.matches(semver) {
269 info!(
270 "app version matched: id={}, name={}, req={}, got={}, prev={:?}",
271 id, name, req_semver, version, pre,
272 );
273 true
274 } else {
275 false
276 }
277 });
278
279 if ret.is_none() {
280 let msg = format!(
281 "no matching semver found for app: id={}, name={}, req={}",
282 id, name, req_semver
283 );
284 warn!("{}", msg);
285 return Err(BuckyError::new(BuckyErrorCode::NotFound, msg));
286 }
287
288 let (version, semver) = ret.unwrap();
289 Ok((version, semver.to_string()))
290 }
291
292 fn find_source_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<ObjectId> {
293 let ret = self.find_version(req_semver, pre)?;
294 self.find_source(&ret.0)
295 }
296
297 fn find_source(&self, version: &str) -> BuckyResult<ObjectId> {
298 self.body_expect("")
299 .content()
300 .source
301 .get(version)
302 .cloned()
303 .ok_or(BuckyError::from(BuckyErrorCode::NotFound))
304 }
305
306 fn find_source_desc_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<Option<&str>> {
307 let ret = self.find_version(req_semver, pre)?;
308 Ok(self.find_source_desc(&ret.0))
309 }
310
311 fn find_source_desc(&self, version: &str) -> Option<&str> {
312 self.body_expect("")
313 .content()
314 .source_desc
315 .get(version)
316 .map(String::as_str)
317 }
318
319 fn remove_source(&mut self, version: &str) {
320 self.body_mut_expect("")
321 .content_mut()
322 .source
323 .remove(version);
324 self.body_mut_expect("")
325 .content_mut()
326 .source_desc
327 .remove(version);
328 self.body_mut_expect("")
329 .increase_update_time(bucky_time_now());
330 }
331
332 fn clear_source(&mut self) {
333 self.body_mut_expect("").content_mut().source.clear();
334 self.body_mut_expect("").content_mut().source_desc.clear();
335 self.body_mut_expect("")
336 .increase_update_time(bucky_time_now());
337 }
338
339 fn set_source(&mut self, version: String, id: ObjectId, desc: Option<String>) {
340 self.body_mut_expect("")
341 .content_mut()
342 .source
343 .insert(version.clone(), id);
344 if let Some(desc) = desc {
345 self.body_mut_expect("")
346 .content_mut()
347 .source_desc
348 .insert(version, desc);
349 }
350 self.body_mut_expect("")
351 .increase_update_time(bucky_time_now());
352 }
353
354 fn source(&self) -> &HashMap<String, ObjectId, RandomState> {
355 &self.body_expect("").content().source
356 }
357
358 fn find_tag(&self, tag: &str) -> BuckyResult<&str> {
359 self.body_expect("")
360 .content()
361 .tags
362 .get(tag)
363 .map(String::as_str)
364 .ok_or(BuckyError::from(BuckyErrorCode::NotFound))
365 }
366
367 fn set_tag(&mut self, tag: String, version: String) {
368 self.body_mut_expect("")
369 .content_mut()
370 .tags
371 .insert(tag, version);
372 self.body_mut_expect("")
373 .increase_update_time(bucky_time_now());
374 }
375
376 fn remove_tag(&mut self, tag: &str) {
377 self.body_mut_expect("").content_mut().tags.remove(tag);
378 }
379
380 fn tags(&self) -> &HashMap<String, String> {
381 &self.body_expect("").content().tags
382 }
383
384 fn generate_id(owner: ObjectId, id: &str) -> ObjectId {
385 Self::create(owner, id).desc().calculate_id()
386 }
387}
388
389#[cfg(test)]
390mod test {
391 use super::*;
392
393 #[test]
394 fn test() {
395 let owner = ObjectId::default();
396 let mut dec_app = DecApp::create(owner.clone(), "test-dec-app");
397 dec_app.set_source("1.0.0".to_owned(), owner.clone(), None);
398 dec_app.set_source("1.0.1".to_owned(), owner.clone(), None);
399 dec_app.set_source("1.0.2".to_owned(), owner.clone(), None);
400 dec_app.set_source("1.1.2".to_owned(), owner.clone(), None);
401 dec_app.set_source("1.1.5".to_owned(), owner.clone(), None);
402
403 dec_app.set_source("1.3.7".to_owned(), owner.clone(), None);
404 dec_app.set_source("1.3.10".to_owned(), owner.clone(), None);
405 dec_app.set_source("1.4.0.20".to_owned(), owner.clone(), None);
406 dec_app.set_source("1.4.1.21-preview".to_owned(), owner.clone(), None);
407 dec_app.set_source("1.5.1.22-preview".to_owned(), owner.clone(), None);
408
409 dec_app.set_source("2.5.28".to_owned(), owner.clone(), None);
410 dec_app.set_source("2.5.30".to_owned(), owner.clone(), None);
411
412 let ret = dec_app.find_version("*", None).unwrap();
413 assert_eq!(ret, "2.5.30");
414
415 let ret = dec_app.find_version("2.5.28", None).unwrap();
416 assert_eq!(ret, "2.5.30");
417
418 let ret = dec_app.find_version("=1.0", None).unwrap();
419 assert_eq!(ret, "1.0.2");
420
421 dec_app.find_version("=1.4.21-preview", None).unwrap_err();
424
425 let ret = dec_app.find_version("=1.4.21", Some("preview")).unwrap();
426 assert_eq!(ret, "1.4.1.21-preview");
427 let ret = dec_app.find_version("1.4", Some("preview")).unwrap();
428 assert_eq!(ret, "1.5.1.22-preview");
429 let ret = dec_app.find_version("1.0", Some("preview")).unwrap();
430 assert_eq!(ret, "1.5.1.22-preview");
431
432 let ret = dec_app.find_version("~1.4", None).unwrap();
433 assert_eq!(ret, "1.4.0.20");
434
435 let ret = dec_app.find_version("~1.1", None).unwrap();
437 assert_eq!(ret, "1.1.5");
438
439 let ret = dec_app.find_version("<1.3", None).unwrap();
440 assert_eq!(ret, "1.1.5");
441
442 let ret = dec_app.find_version("=1.3", None).unwrap();
443 assert_eq!(ret, "1.3.10");
444
445 let ret = dec_app.find_version("<=1.3.8", None).unwrap();
446 assert_eq!(ret, "1.3.7");
447 }
448}