Skip to main content

rust_apt/iterators/
version.rs

1use std::cell::OnceCell;
2use std::cmp::Ordering;
3use std::collections::HashMap;
4use std::fmt;
5
6use cxx::UniquePtr;
7
8use crate::raw::{IntoRawIter, VerIterator};
9use crate::util::cmp_versions;
10use crate::{
11	Cache, DepType, Dependency, Package, PackageFile, PackageRecords, Provider, VersionFile,
12	create_depends_map,
13};
14
15/// Represents a single Version of a package.
16pub struct Version<'a> {
17	pub(crate) ptr: UniquePtr<VerIterator>,
18	cache: &'a Cache,
19	depends_map: OnceCell<HashMap<DepType, Vec<Dependency<'a>>>>,
20}
21
22impl Clone for Version<'_> {
23	fn clone(&self) -> Self {
24		Self {
25			ptr: unsafe { self.ptr.unique() },
26			cache: self.cache,
27			depends_map: self.depends_map.clone(),
28		}
29	}
30}
31
32impl<'a> Version<'a> {
33	pub fn new(ptr: UniquePtr<VerIterator>, cache: &'a Cache) -> Version<'a> {
34		Version {
35			ptr,
36			cache,
37			depends_map: OnceCell::new(),
38		}
39	}
40
41	/// Returns a list of providers
42	pub fn provides(&self) -> impl Iterator<Item = Provider<'a>> {
43		unsafe { self.ptr.provides() }
44			.raw_iter()
45			.map(|p| Provider::new(p, self.cache))
46	}
47
48	pub fn version_files(&self) -> impl Iterator<Item = VersionFile<'a>> {
49		unsafe { self.ptr.version_files() }
50			.raw_iter()
51			.map(|v| VersionFile::new(v, self.cache))
52	}
53
54	/// Returns an iterator of PackageFiles (Origins) for the version
55	pub fn package_files(&self) -> impl Iterator<Item = PackageFile<'a>> {
56		self.version_files().map(|v| v.package_file())
57	}
58
59	/// Return the version's parent package.
60	pub fn parent(&self) -> Package<'a> { Package::new(self.cache, unsafe { self.parent_pkg() }) }
61
62	/// Returns a reference to the Dependency Map owned by the Version
63	///
64	/// Dependencies are in a `Vec<Dependency>`
65	///
66	/// The Dependency struct represents an Or Group of dependencies.
67	/// The base deps are located in `Dependency.base_deps`
68	///
69	/// For example where we use the `DepType::Depends` key:
70	///
71	/// ```
72	/// use rust_apt::{new_cache, DepType};
73	/// let cache = new_cache!().unwrap();
74	/// let pkg = cache.get("apt").unwrap();
75	/// let version = pkg.candidate().unwrap();
76	/// for dep in version.depends_map().get(&DepType::Depends).unwrap() {
77	///    if dep.is_or() {
78	///        for base_dep in dep.iter() {
79	///            println!("{}", base_dep.name())
80	///        }
81	///    } else {
82	///        // is_or is false so there is only one BaseDep
83	///        println!("{}", dep.first().name())
84	///    }
85	/// }
86	/// ```
87	pub fn depends_map(&self) -> &HashMap<DepType, Vec<Dependency<'a>>> {
88		self.depends_map
89			.get_or_init(|| create_depends_map(self.cache, unsafe { self.depends().make_safe() }))
90	}
91
92	/// Returns a reference Vector, if it exists, for the given key.
93	///
94	/// See the doc for `depends_map()` for more information.
95	pub fn get_depends(&self, key: &DepType) -> Option<&Vec<Dependency<'a>>> {
96		self.depends_map().get(key)
97	}
98
99	/// Returns a Reference Vector, if it exists, for "Enhances".
100	pub fn enhances(&self) -> Option<&Vec<Dependency<'a>>> { self.get_depends(&DepType::Enhances) }
101
102	/// Returns a Reference Vector, if it exists,
103	/// for "Depends" and "PreDepends".
104	pub fn dependencies(&self) -> Option<Vec<&Dependency<'a>>> {
105		let mut ret_vec: Vec<&Dependency> = Vec::new();
106
107		for dep_type in [DepType::Depends, DepType::PreDepends] {
108			if let Some(dep_list) = self.get_depends(&dep_type) {
109				for dep in dep_list {
110					ret_vec.push(dep)
111				}
112			}
113		}
114
115		if ret_vec.is_empty() {
116			return None;
117		}
118		Some(ret_vec)
119	}
120
121	/// Returns a Reference Vector, if it exists, for "Recommends".
122	pub fn recommends(&self) -> Option<&Vec<Dependency<'a>>> {
123		self.get_depends(&DepType::Recommends)
124	}
125
126	/// Returns a Reference Vector, if it exists, for "suggests".
127	pub fn suggests(&self) -> Option<&Vec<Dependency<'a>>> { self.get_depends(&DepType::Suggests) }
128
129	/// Move the PkgRecords into the correct place for the Description
130	fn desc_lookup(&self) -> Option<&PackageRecords> {
131		let desc = unsafe { self.translated_desc().make_safe()? };
132		Some(self.cache.records().desc_lookup(&desc))
133	}
134
135	/// Get the translated long description
136	pub fn description(&self) -> Option<String> { self.desc_lookup()?.long_desc() }
137
138	/// Get the translated short description
139	pub fn summary(&self) -> Option<String> { self.desc_lookup()?.short_desc() }
140
141	/// Get data from the specified record field
142	///
143	/// # Returns:
144	///   * Some String or None if the field doesn't exist.
145	///
146	/// # Example:
147	/// ```
148	/// use rust_apt::new_cache;
149	/// use rust_apt::records::RecordField;
150	///
151	/// let cache = new_cache!().unwrap();
152	/// let pkg = cache.get("apt").unwrap();
153	/// let cand = pkg.candidate().unwrap();
154	///
155	/// println!("{}", cand.get_record(RecordField::Maintainer).unwrap());
156	/// // Or alternatively you can just pass any string
157	/// println!("{}", cand.get_record("Description-md5").unwrap());
158	/// ```
159	pub fn get_record<T: ToString + ?Sized>(&self, field: &T) -> Option<String> {
160		self.version_files()
161			.next()?
162			.lookup()
163			.get_field(field.to_string())
164	}
165
166	/// Get the `Phased-Update-Percentage` field for this version.
167	pub fn phased_update_percentage(&self) -> Option<u8> {
168		self.get_record(crate::records::RecordField::PhasedUpdatePercentage)?
169			.trim()
170			.parse::<u8>()
171			.ok()
172			.filter(|percentage| *percentage <= 100)
173	}
174
175	/// Get the hash specified. If there isn't one returns None
176	/// `version.hash("md5sum")`
177	pub fn hash<T: ToString + ?Sized>(&self, hash_type: &T) -> Option<String> {
178		self.version_files()
179			.next()?
180			.lookup()
181			.hash_find(hash_type.to_string())
182	}
183
184	/// Get the sha256 hash. If there isn't one returns None
185	/// This is equivalent to `version.hash("sha256")`
186	pub fn sha256(&self) -> Option<String> { self.hash("sha256") }
187
188	/// Get the sha512 hash. If there isn't one returns None
189	/// This is equivalent to `version.hash("sha512")`
190	pub fn sha512(&self) -> Option<String> { self.hash("sha512") }
191
192	/// Returns an Iterator of URIs for the Version.
193	pub fn uris(&self) -> impl Iterator<Item = String> + 'a {
194		self.version_files().filter_map(|v| {
195			let pkg_file = v.package_file();
196			if !pkg_file.is_downloadable() {
197				return None;
198			}
199			Some(pkg_file.index_file().archive_uri(&v.lookup().filename()))
200		})
201	}
202
203	/// Set this version as the candidate.
204	pub fn set_candidate(&self) { self.cache.depcache().set_candidate_version(self); }
205
206	/// The priority of the Version as shown in `apt policy`.
207	pub fn priority(&self) -> i32 { self.cache.priority(self) }
208
209	/// The priority of the Version as shown in `apt policy`.
210	///
211	/// When `consider_files` is `true`, this is equivalent to
212	/// [`Version::priority`] and includes package-file priorities in the
213	/// result.
214	///
215	/// When `consider_files` is `false`, this returns only pin priority without
216	/// considering package-file priorities. This is useful if you want to
217	/// detect explicit pinning without the normal repository defaults such as
218	/// `500`.
219	pub fn priority_with_files(&self, consider_files: bool) -> i32 {
220		self.cache.priority_with_files(self, consider_files)
221	}
222}
223
224// Implementations for comparing versions.
225impl PartialEq for Version<'_> {
226	fn eq(&self, other: &Self) -> bool {
227		matches!(
228			cmp_versions(self.version(), other.version()),
229			Ordering::Equal
230		)
231	}
232}
233
234impl Ord for Version<'_> {
235	fn cmp(&self, other: &Self) -> Ordering { cmp_versions(self.version(), other.version()) }
236}
237
238impl PartialOrd for Version<'_> {
239	fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
240}
241
242impl fmt::Display for Version<'_> {
243	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244		write!(f, "{}", self.version())?;
245		Ok(())
246	}
247}
248
249impl fmt::Debug for Version<'_> {
250	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251		let parent = self.parent();
252		f.debug_struct("Version")
253			.field("pkg", &parent.name())
254			.field("arch", &self.arch())
255			.field("version", &self.version())
256			.field(
257				"is_candidate",
258				&parent.candidate().is_some_and(|cand| self == &cand),
259			)
260			.field("is_installed", &self.is_installed())
261			.finish_non_exhaustive()
262	}
263}
264
265#[cxx::bridge]
266pub(crate) mod raw {
267	impl CxxVector<VerIterator> {}
268	unsafe extern "C++" {
269		include!("rust-apt/apt-pkg-c/package.h");
270
271		type VerIterator;
272
273		type PkgIterator = crate::iterators::PkgIterator;
274		type PrvIterator = crate::iterators::PrvIterator;
275		type DepIterator = crate::iterators::DepIterator;
276		type DescIterator = crate::iterators::DescIterator;
277		type VerFileIterator = crate::iterators::VerFileIterator;
278
279		/// The version string of the version. "1.4.10".
280		pub fn version(self: &VerIterator) -> &str;
281
282		/// The Arch of the version. "amd64".
283		pub fn arch(self: &VerIterator) -> &str;
284
285		/// Return the version's parent PkgIterator.
286		///
287		/// # Safety
288		///
289		/// The returned UniquePtr cannot outlive the cache.
290		unsafe fn parent_pkg(self: &VerIterator) -> UniquePtr<PkgIterator>;
291
292		/// The section of the version as shown in `apt show`.
293		pub fn section(self: &VerIterator) -> Result<&str>;
294
295		/// The priority string as shown in `apt show`.
296		pub fn priority_str(self: &VerIterator) -> Result<&str>;
297
298		/// The size of the .deb file.
299		pub fn size(self: &VerIterator) -> u64;
300
301		/// The uncompressed size of the .deb file.
302		pub fn installed_size(self: &VerIterator) -> u64;
303
304		// TODO: Possibly return an enum
305		pub fn multi_arch(self: &VerIterator) -> u8;
306
307		/// String representing MultiArch flag
308		/// same, foreign, allowed, none
309		pub fn multi_arch_type(self: &VerIterator) -> &str;
310
311		/// True if the version is able to be downloaded.
312		#[cxx_name = "Downloadable"]
313		pub fn is_downloadable(self: &VerIterator) -> bool;
314
315		/// True if the version is currently installed
316		pub fn is_installed(self: &VerIterator) -> bool;
317
318		/// Always contains the name, even if it is the same as the binary name
319		pub fn source_name(self: &VerIterator) -> &str;
320
321		// Always contains the version string,
322		// even if it is the same as the binary version.
323		pub fn source_version(self: &VerIterator) -> &str;
324
325		/// Return Providers Iterator
326		///
327		/// # Safety
328		///
329		/// If the inner pointer is null segfaults can occur.
330		///
331		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
332		/// is recommended.
333		///
334		/// The returned UniquePtr cannot outlive the cache.
335		unsafe fn provides(self: &VerIterator) -> UniquePtr<PrvIterator>;
336
337		/// Return Dependency Iterator
338		///
339		/// # Safety
340		///
341		/// If the inner pointer is null segfaults can occur.
342		///
343		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
344		/// is recommended.
345		///
346		/// The returned UniquePtr cannot outlive the cache.
347		unsafe fn depends(self: &VerIterator) -> UniquePtr<DepIterator>;
348
349		/// Return the version files.
350		/// You go through here to get the package files.
351		///
352		/// # Safety
353		///
354		/// If the inner pointer is null segfaults can occur.
355		///
356		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
357		/// is recommended.
358		///
359		/// The returned UniquePtr cannot outlive the cache.
360		unsafe fn version_files(self: &VerIterator) -> UniquePtr<VerFileIterator>;
361
362		/// This is for backend records lookups.
363		///
364		/// # Safety
365		///
366		/// If the inner pointer is null segfaults can occur.
367		///
368		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
369		/// is recommended.
370		///
371		/// The returned UniquePtr cannot outlive the cache.
372		unsafe fn translated_desc(self: &VerIterator) -> UniquePtr<DescIterator>;
373
374		#[cxx_name = "Index"]
375		pub fn index(self: &VerIterator) -> u64;
376		/// Clone the pointer.
377		///
378		/// # Safety
379		///
380		/// The returned UniquePtr cannot outlive the cache.
381		unsafe fn unique(self: &VerIterator) -> UniquePtr<VerIterator>;
382		pub fn raw_next(self: Pin<&mut VerIterator>);
383		pub fn end(self: &VerIterator) -> bool;
384	}
385}