1use std::collections::HashSet;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use curl::multi::Multi;
6use semver::VersionReq;
7
8use crate::registry::*;
9
10mod package;
11mod registry;
12
13pub use package::{DepKind, Package, PackageVersion, Packages, Unit, Usage};
14
15type Purl = purl::GenericPurl<String>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum RegistryCachePolicy {
19 PreferLocal,
20 #[default]
21 Refresh,
22 NoCache,
23}
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct Options {
27 pub registry_cache_policy: RegistryCachePolicy,
28}
29
30pub struct State {
31 multi: Multi,
32 root: Option<PathBuf>,
33 registry_cache_policy: RegistryCachePolicy,
34}
35
36impl State {
37 pub fn new(root: Option<PathBuf>, options: Options) -> Self {
38 let mut multi = Multi::new();
39 multi.pipelining(false, true).ok();
40 Self {
41 multi,
42 root,
43 registry_cache_policy: options.registry_cache_policy,
44 }
45 }
46
47 pub fn multi(&self) -> &Multi {
48 &self.multi
49 }
50
51 pub fn root(&self) -> Option<&PathBuf> {
52 self.root.as_ref()
53 }
54
55 pub fn registry_cache_policy(&self) -> RegistryCachePolicy {
56 self.registry_cache_policy
57 }
58}
59
60#[derive(Debug, thiserror::Error)]
61pub enum Error {
62 #[error("registry error: {0}")]
63 Registry(#[from] registry::RegistryError),
64}
65
66pub struct CheckUpdates {
67 cargo: Registry,
68}
69
70impl CheckUpdates {
71 pub fn new(root: Option<PathBuf>) -> Self {
72 Self::with_options(root, Options::default())
73 }
74
75 pub fn with_options(root: Option<PathBuf>, options: Options) -> Self {
76 let state = Rc::new(State::new(root, options));
77 let cargo = CargoRegistry::new(state.clone());
78
79 Self {
80 cargo: cargo.into(),
81 }
82 }
83
84 pub fn packages(&self) -> Result<Packages, Error> {
85 let mut res: Packages = Default::default();
86 let mut seen: HashSet<(Unit, String, String, DepKind)> = HashSet::new();
89 for package in self.cargo.packages()? {
90 for usage in &package.usages {
91 if usage.req == VersionReq::STAR {
93 continue;
94 }
95 let key = (
96 usage.unit.clone(),
97 package.purl.name().to_string(),
98 usage.req.to_string(),
99 usage.kind,
100 );
101 if !seen.insert(key) {
102 continue;
103 }
104 res.entry(usage.unit.clone()).or_default().push((
105 usage.req.clone(),
106 usage.kind,
107 package.clone(),
108 ));
109 }
110 }
111 Ok(res)
112 }
113
114 pub fn update_versions<'a>(
116 &self,
117 packages: impl IntoIterator<Item = (&'a Usage, &'a Package, VersionReq)>,
118 ) -> Result<(), Error> {
119 self.cargo.update_versions(packages)?;
120 Ok(())
121 }
122}