melodium_repository/
repository.rs

1use crate::global::Package as PackageDetails;
2#[cfg(feature = "network")]
3use crate::network::remote;
4use crate::technical::{Availability, Element, Package, Platform, PlatformAvailability, Type};
5use crate::{RepositoryConfig, RepositoryError, RepositoryResult};
6use melodium_common::descriptor::{Version, VersionReq};
7use std::collections::HashMap;
8use std::fs;
9use std::path::PathBuf;
10
11#[derive(Debug)]
12pub struct Repository {
13    config: RepositoryConfig,
14    packages: Vec<Package>,
15}
16
17impl Repository {
18    pub fn new(config: RepositoryConfig) -> Self {
19        Self {
20            config,
21            packages: Vec::new(),
22        }
23    }
24
25    pub fn config(&self) -> &RepositoryConfig {
26        &self.config
27    }
28
29    pub fn packages(&self) -> &Vec<Package> {
30        &self.packages
31    }
32
33    pub fn add_package(&mut self, package: Package) -> RepositoryResult<()> {
34        if self.packages.contains(&package) {
35            Err(RepositoryError::already_existing_package(
36                1,
37                package.name,
38                package.version,
39            ))
40        } else {
41            self.packages.push(package);
42            self.save_packages()
43        }
44    }
45
46    pub fn load_packages(&mut self) -> RepositoryResult<()> {
47        let path = self.packages_path();
48        if path.exists() {
49            let content =
50                fs::read_to_string(path).map_err(|err| RepositoryError::fs_error(23, err))?;
51            let packages = serde_json::from_str(&content)
52                .map_err(|err| RepositoryError::json_error(24, err))?;
53            self.merge_packages(packages)
54        } else {
55            Ok(())
56        }
57    }
58
59    pub fn load_packages_with_network(&mut self) -> RepositoryResult<()> {
60        #[cfg(not(feature = "network"))]
61        {
62            Err(RepositoryError::no_network(25))
63        }
64        #[cfg(feature = "network")]
65        {
66            let config = self.config.network.clone();
67            if let Some(config) = config {
68                self.load_packages()?;
69                self.merge_packages(remote::get_tech_packages(&config)?)
70            } else {
71                Err(RepositoryError::no_network(26))
72            }
73        }
74    }
75
76    /**
77     * Set the availability of an element of a package for a given platform.
78     *
79     * If package is from a type that doesn't have platform availability notion, this function return error.
80     */
81    pub fn set_platform_availability(
82        &mut self,
83        package: &Package,
84        platform: &Platform,
85        availability: &Availability,
86        element: Element,
87    ) -> RepositoryResult<()> {
88        if let Some(package) = self.packages.iter_mut().find(|p| *p == package) {
89            match &mut package.r#type {
90                Type::Compiled {
91                    crate_name: _,
92                    platforms,
93                } => {
94                    if let Some(platform_availability) =
95                        platforms.iter_mut().find(|pa| &pa.platform == platform)
96                    {
97                        platform_availability
98                            .availability
99                            .insert(availability.clone(), element);
100                    } else {
101                        let pa = PlatformAvailability {
102                            platform: platform.clone(),
103                            availability: {
104                                let mut availabilities = HashMap::new();
105                                availabilities.insert(availability.clone(), element);
106                                availabilities
107                            },
108                        };
109                        platforms.push(pa);
110                    }
111
112                    self.save_packages()
113                }
114                _ => Err(RepositoryError::not_platform_dependent(
115                    18,
116                    package.name.clone(),
117                    package.version.clone(),
118                )),
119            }
120        } else {
121            Err(RepositoryError::unknown_package(
122                17,
123                package.name.clone(),
124                package.version.clone(),
125            ))
126        }
127    }
128
129    /**
130     * Get the availability of an element of a package for a given platform.
131     */
132    pub fn get_platform_availability(
133        &self,
134        package: &Package,
135        platform: &Platform,
136        availability: &Availability,
137    ) -> RepositoryResult<Element> {
138        if let Some(package) = self.packages.iter().find(|p| *p == package) {
139            match &package.r#type {
140                Type::Compiled {
141                    crate_name: _,
142                    platforms,
143                } => {
144                    if let Some(platform_availability) =
145                        platforms.iter().find(|pa| &pa.platform == platform)
146                    {
147                        if let Some(element) = platform_availability.availability.get(&availability)
148                        {
149                            Ok(element.clone())
150                        } else {
151                            Err(RepositoryError::platform_unavailable(
152                                22,
153                                package.name.clone(),
154                                package.version.clone(),
155                                platform.clone(),
156                                availability.clone(),
157                            ))
158                        }
159                    } else {
160                        Err(RepositoryError::platform_unavailable(
161                            21,
162                            package.name.clone(),
163                            package.version.clone(),
164                            platform.clone(),
165                            availability.clone(),
166                        ))
167                    }
168                }
169                _ => Err(RepositoryError::not_platform_dependent(
170                    19,
171                    package.name.clone(),
172                    package.version.clone(),
173                )),
174            }
175        } else {
176            Err(RepositoryError::unknown_package(
177                20,
178                package.name.clone(),
179                package.version.clone(),
180            ))
181        }
182    }
183
184    /**
185     * Get the availability of an element of a package for a given platform, interrogating through network if not available locally.
186     */
187    #[allow(unused_variables)]
188    pub fn get_platform_availability_with_network(
189        &mut self,
190        package: &Package,
191        platform: &Platform,
192        availability: &Availability,
193    ) -> RepositoryResult<Element> {
194        #[cfg(not(feature = "network"))]
195        {
196            Err(RepositoryError::no_network(36))
197        }
198        #[cfg(feature = "network")]
199        {
200            if self.config.network.is_some() {
201                if let Ok(element) = self.get_platform_availability(package, platform, availability)
202                {
203                    Ok(element)
204                } else {
205                    self.load_packages_with_network()?;
206                    self.get_platform_availability(package, platform, availability)
207                }
208            } else {
209                Err(RepositoryError::no_network(37))
210            }
211        }
212    }
213
214    /**
215     * Get element of a package for a given platform.
216     *
217     * This function only checks registered availability, but not if element is really present on disk.
218     * See also [reach_platform_element].
219     */
220    pub fn get_package_element(
221        &self,
222        package: &Package,
223        platform_availability: Option<(&Platform, &Availability)>,
224    ) -> RepositoryResult<Element> {
225        if let Some(package) = self.packages.iter().find(|p| *p == package) {
226            match &package.r#type {
227                Type::Compiled {
228                    crate_name: _,
229                    platforms,
230                } => {
231                    if let Some((platform, availability)) = platform_availability {
232                        if let Some(platform_availability) =
233                            platforms.iter().find(|pa| &pa.platform == platform)
234                        {
235                            if let Some(element) =
236                                platform_availability.availability.get(&availability)
237                            {
238                                Ok(element.clone())
239                            } else {
240                                Err(RepositoryError::platform_unavailable(
241                                    28,
242                                    package.name.clone(),
243                                    package.version.clone(),
244                                    platform.clone(),
245                                    availability.clone(),
246                                ))
247                            }
248                        } else {
249                            Err(RepositoryError::platform_unavailable(
250                                29,
251                                package.name.clone(),
252                                package.version.clone(),
253                                platform.clone(),
254                                availability.clone(),
255                            ))
256                        }
257                    } else {
258                        Err(RepositoryError::platform_dependent(
259                            30,
260                            package.name.clone(),
261                            package.version.clone(),
262                        ))
263                    }
264                }
265                Type::Jeu { file } => Ok(file.clone()),
266            }
267        } else {
268            Err(RepositoryError::unknown_package(
269                27,
270                package.name.clone(),
271                package.version.clone(),
272            ))
273        }
274    }
275
276    /**
277     * Get the full path of an element of a package.
278     *
279     * This function only checks registered availability, but not if element is really present on disk.
280     * See also [reach_platform_element].
281     */
282    pub fn get_package_element_path(
283        &self,
284        package: &Package,
285        platform_availability: Option<(&Platform, &Availability)>,
286    ) -> RepositoryResult<PathBuf> {
287        let element = self.get_package_element(package, platform_availability)?;
288        match &package.r#type {
289            Type::Compiled {
290                crate_name: _,
291                platforms: _,
292            } => {
293                if let Some((platform, availability)) = platform_availability {
294                    let mut path = self.config.repository_location.clone();
295                    path.push(package.get_path());
296                    path.push(platform.to_string());
297                    path.push(availability.to_string());
298                    path.push(element.name);
299                    Ok(path)
300                } else {
301                    Err(RepositoryError::platform_dependent(
302                        31,
303                        package.name.clone(),
304                        package.version.clone(),
305                    ))
306                }
307            }
308            Type::Jeu { file: _ } => {
309                let mut path = self.config.repository_location.clone();
310                path.push(package.get_path());
311                path.push(element.name);
312                Ok(path)
313            }
314        }
315    }
316
317    /**
318     * Get the full path of a present element of a package.
319     *
320     * This function return an error if the element is not present on filesystem.
321     */
322    pub fn reach_package_element(
323        &self,
324        package: &Package,
325        platform_availability: Option<(&Platform, &Availability)>,
326    ) -> RepositoryResult<PathBuf> {
327        let path = self.get_package_element_path(package, platform_availability)?;
328        if path.is_file() {
329            Ok(path)
330        } else {
331            Err(RepositoryError::package_element_absent(
332                32,
333                package.name.clone(),
334                package.version.clone(),
335                platform_availability.map(|(p, a)| (p.clone(), a.clone())),
336                self.get_package_element(package, platform_availability)?,
337                path,
338            ))
339        }
340    }
341
342    /**
343     * Get the full path of a present element of a package for a given platform.
344     *
345     * This function try to download the element if it is not present on the filesystem.
346     */
347    #[allow(unused_variables)]
348    pub fn reach_package_element_with_network(
349        &mut self,
350        package: &Package,
351        platform_availability: Option<(&Platform, &Availability)>,
352    ) -> RepositoryResult<PathBuf> {
353        #[cfg(not(feature = "network"))]
354        {
355            Err(RepositoryError::no_network(38))
356        }
357        #[cfg(feature = "network")]
358        {
359            let config = self.config.network.clone();
360            if let Some(config) = config {
361                let path = self.get_package_element_path(package, platform_availability)?;
362                if path.is_file() {
363                    Ok(path)
364                } else {
365                    let element = self.get_package_element(package, platform_availability)?;
366
367                    let url_path = match &package.r#type {
368                        Type::Compiled {
369                            crate_name: _,
370                            platforms: _,
371                        } => {
372                            if let Some((platform, availability)) = platform_availability {
373                                let mut path = package.get_path();
374                                path.push(platform.to_string());
375                                path.push(availability.to_string());
376                                path.push(element.name.clone());
377                                path
378                            } else {
379                                return Err(RepositoryError::platform_dependent(
380                                    40,
381                                    package.name.clone(),
382                                    package.version.clone(),
383                                ));
384                            }
385                        }
386                        Type::Jeu { file: _ } => {
387                            let mut path = package.get_path();
388                            path.push(element.name.clone());
389                            path
390                        }
391                    };
392
393                    remote::get_element_package(&config, element.clone(), url_path, path.clone())?;
394
395                    if path.is_file() {
396                        Ok(path)
397                    } else {
398                        Err(RepositoryError::package_element_absent(
399                            41,
400                            package.name.clone(),
401                            package.version.clone(),
402                            platform_availability.map(|(p, a)| (p.clone(), a.clone())),
403                            element,
404                            path,
405                        ))
406                    }
407                }
408            } else {
409                Err(RepositoryError::no_network(39))
410            }
411        }
412    }
413
414    pub fn remove_package(&mut self, name: &str, version: &Version) -> RepositoryResult<()> {
415        self.packages
416            .retain(|pkg| !(pkg.name == name && &pkg.version == version));
417        Ok(())
418    }
419
420    fn merge_packages(&mut self, packages: Vec<Package>) -> RepositoryResult<()> {
421        for package in packages {
422            if !self.packages.contains(&package) {
423                self.packages.push(package);
424            }
425        }
426        self.save_packages()
427    }
428
429    pub fn get_package(
430        &self,
431        name: &str,
432        version_req: &VersionReq,
433    ) -> RepositoryResult<Option<Package>> {
434        Ok(self
435            .packages
436            .iter()
437            .filter(|p| p.name == name && version_req.matches(&p.version))
438            .max_by_key(|p| p.version.clone())
439            .map(|p| p.clone()))
440    }
441
442    #[allow(unused_variables)]
443    pub fn get_package_with_network(
444        &mut self,
445        name: &str,
446        version_req: &VersionReq,
447    ) -> RepositoryResult<Option<Package>> {
448        #[cfg(not(feature = "network"))]
449        {
450            Err(RepositoryError::no_network(13))
451        }
452        #[cfg(feature = "network")]
453        {
454            let config = self.config.network.clone();
455            if let Some(config) = config {
456                if let Ok(Some(package)) = self.get_package(name, version_req) {
457                    Ok(Some(package))
458                } else {
459                    let packages = remote::get_tech_packages(&config)?;
460                    self.merge_packages(packages)?;
461                    self.get_package(name, version_req)
462                }
463            } else {
464                Err(RepositoryError::no_network(14))
465            }
466        }
467    }
468
469    pub fn set_package_details(&self, details: &PackageDetails) -> RepositoryResult<()> {
470        if let Some(package) = self
471            .packages
472            .iter()
473            .find(|p| p.name == details.name && p.version == details.version)
474        {
475            let mut path = self.config.repository_location.clone();
476            path.push(package.get_path());
477            fs::create_dir_all(path.clone()).map_err(|err| RepositoryError::fs_error(7, err))?;
478            path.push("package.json");
479            fs::write(
480                path,
481                serde_json::to_string(&details)
482                    .map_err(|err| RepositoryError::json_error(8, err))?,
483            )
484            .map_err(|err| RepositoryError::fs_error(9, err))
485        } else {
486            Err(RepositoryError::unknown_package(
487                6,
488                details.name.clone(),
489                details.version.clone(),
490            ))
491        }
492    }
493
494    pub fn get_package_details(&self, package: &Package) -> RepositoryResult<PackageDetails> {
495        if let Some(package) = self
496            .packages
497            .iter()
498            .find(|p| p.name == package.name && p.version == package.version)
499        {
500            let mut path = self.config.repository_location.clone();
501            path.push(package.get_path());
502            path.push("package.json");
503            fs::read_to_string(path)
504                .map_err(|err| RepositoryError::fs_error(10, err))
505                .and_then(|str| {
506                    serde_json::from_str(&str).map_err(|err| RepositoryError::json_error(11, err))
507                })
508        } else {
509            Err(RepositoryError::unknown_package(
510                12,
511                package.name.to_string(),
512                package.version.clone(),
513            ))
514        }
515    }
516
517    #[allow(unused_variables)]
518    pub fn get_package_details_with_network(
519        &self,
520        package: &Package,
521    ) -> RepositoryResult<PackageDetails> {
522        #[cfg(not(feature = "network"))]
523        {
524            Err(RepositoryError::no_network(16))
525        }
526        #[cfg(feature = "network")]
527        {
528            let config = self.config.network.clone();
529            if let Some(config) = config {
530                if let Ok(pkg) = self.get_package_details(package) {
531                    return Ok(pkg);
532                }
533
534                let pkg = remote::get_detail_package(&config, package);
535                if let Ok(pkg) = pkg {
536                    self.set_package_details(&pkg)?;
537                    self.get_package_details(package)
538                } else {
539                    pkg
540                }
541            } else {
542                Err(RepositoryError::no_network(15))
543            }
544        }
545    }
546
547    fn save_packages(&self) -> RepositoryResult<()> {
548        fs::create_dir_all(self.config.repository_location.clone())
549            .map_err(|err| RepositoryError::fs_error(3, err))?;
550        fs::write(
551            self.packages_path(),
552            serde_json::to_string(&self.packages)
553                .map_err(|err| RepositoryError::json_error(5, err))?,
554        )
555        .map_err(|err| RepositoryError::fs_error(4, err))
556    }
557
558    fn packages_path(&self) -> PathBuf {
559        let mut path = self.config.repository_location.clone();
560        path.push("packages.json");
561        path
562    }
563}