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 hash specified. If there isn't one returns None
167	/// `version.hash("md5sum")`
168	pub fn hash<T: ToString + ?Sized>(&self, hash_type: &T) -> Option<String> {
169		self.version_files()
170			.next()?
171			.lookup()
172			.hash_find(hash_type.to_string())
173	}
174
175	/// Get the sha256 hash. If there isn't one returns None
176	/// This is equivalent to `version.hash("sha256")`
177	pub fn sha256(&self) -> Option<String> { self.hash("sha256") }
178
179	/// Get the sha512 hash. If there isn't one returns None
180	/// This is equivalent to `version.hash("sha512")`
181	pub fn sha512(&self) -> Option<String> { self.hash("sha512") }
182
183	/// Returns an Iterator of URIs for the Version.
184	pub fn uris(&self) -> impl Iterator<Item = String> + 'a {
185		self.version_files().filter_map(|v| {
186			let pkg_file = v.package_file();
187			if !pkg_file.is_downloadable() {
188				return None;
189			}
190			Some(pkg_file.index_file().archive_uri(&v.lookup().filename()))
191		})
192	}
193
194	/// Set this version as the candidate.
195	pub fn set_candidate(&self) { self.cache.depcache().set_candidate_version(self); }
196
197	/// The priority of the Version as shown in `apt policy`.
198	pub fn priority(&self) -> i32 { self.cache.priority(self) }
199}
200
201// Implementations for comparing versions.
202impl PartialEq for Version<'_> {
203	fn eq(&self, other: &Self) -> bool {
204		matches!(
205			cmp_versions(self.version(), other.version()),
206			Ordering::Equal
207		)
208	}
209}
210
211impl Ord for Version<'_> {
212	fn cmp(&self, other: &Self) -> Ordering { cmp_versions(self.version(), other.version()) }
213}
214
215impl PartialOrd for Version<'_> {
216	fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
217}
218
219impl fmt::Display for Version<'_> {
220	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221		write!(f, "{}", self.version())?;
222		Ok(())
223	}
224}
225
226impl fmt::Debug for Version<'_> {
227	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228		let parent = self.parent();
229		f.debug_struct("Version")
230			.field("pkg", &parent.name())
231			.field("arch", &self.arch())
232			.field("version", &self.version())
233			.field(
234				"is_candidate",
235				&parent.candidate().is_some_and(|cand| self == &cand),
236			)
237			.field("is_installed", &self.is_installed())
238			.finish_non_exhaustive()
239	}
240}
241
242#[cxx::bridge]
243pub(crate) mod raw {
244	impl CxxVector<VerIterator> {}
245	unsafe extern "C++" {
246		include!("rust-apt/apt-pkg-c/package.h");
247
248		type VerIterator;
249
250		type PkgIterator = crate::iterators::PkgIterator;
251		type PrvIterator = crate::iterators::PrvIterator;
252		type DepIterator = crate::iterators::DepIterator;
253		type DescIterator = crate::iterators::DescIterator;
254		type VerFileIterator = crate::iterators::VerFileIterator;
255
256		/// The version string of the version. "1.4.10".
257		pub fn version(self: &VerIterator) -> &str;
258
259		/// The Arch of the version. "amd64".
260		pub fn arch(self: &VerIterator) -> &str;
261
262		/// Return the version's parent PkgIterator.
263		///
264		/// # Safety
265		///
266		/// The returned UniquePtr cannot outlive the cache.
267		unsafe fn parent_pkg(self: &VerIterator) -> UniquePtr<PkgIterator>;
268
269		/// The section of the version as shown in `apt show`.
270		pub fn section(self: &VerIterator) -> Result<&str>;
271
272		/// The priority string as shown in `apt show`.
273		pub fn priority_str(self: &VerIterator) -> Result<&str>;
274
275		/// The size of the .deb file.
276		pub fn size(self: &VerIterator) -> u64;
277
278		/// The uncompressed size of the .deb file.
279		pub fn installed_size(self: &VerIterator) -> u64;
280
281		// TODO: Possibly return an enum
282		pub fn multi_arch(self: &VerIterator) -> u8;
283
284		/// String representing MultiArch flag
285		/// same, foreign, allowed, none
286		pub fn multi_arch_type(self: &VerIterator) -> &str;
287
288		/// True if the version is able to be downloaded.
289		#[cxx_name = "Downloadable"]
290		pub fn is_downloadable(self: &VerIterator) -> bool;
291
292		/// True if the version is currently installed
293		pub fn is_installed(self: &VerIterator) -> bool;
294
295		/// Always contains the name, even if it is the same as the binary name
296		pub fn source_name(self: &VerIterator) -> &str;
297
298		// Always contains the version string,
299		// even if it is the same as the binary version.
300		pub fn source_version(self: &VerIterator) -> &str;
301
302		/// Return Providers Iterator
303		///
304		/// # Safety
305		///
306		/// If the inner pointer is null segfaults can occur.
307		///
308		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
309		/// is recommended.
310		///
311		/// The returned UniquePtr cannot outlive the cache.
312		unsafe fn provides(self: &VerIterator) -> UniquePtr<PrvIterator>;
313
314		/// Return Dependency Iterator
315		///
316		/// # Safety
317		///
318		/// If the inner pointer is null segfaults can occur.
319		///
320		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
321		/// is recommended.
322		///
323		/// The returned UniquePtr cannot outlive the cache.
324		unsafe fn depends(self: &VerIterator) -> UniquePtr<DepIterator>;
325
326		/// Return the version files.
327		/// You go through here to get the package files.
328		///
329		/// # Safety
330		///
331		/// If the inner pointer is null segfaults can occur.
332		///
333		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
334		/// is recommended.
335		///
336		/// The returned UniquePtr cannot outlive the cache.
337		unsafe fn version_files(self: &VerIterator) -> UniquePtr<VerFileIterator>;
338
339		/// This is for backend records lookups.
340		///
341		/// # Safety
342		///
343		/// If the inner pointer is null segfaults can occur.
344		///
345		/// Using [`crate::raw::IntoRawIter::make_safe`] to convert to an Option
346		/// is recommended.
347		///
348		/// The returned UniquePtr cannot outlive the cache.
349		unsafe fn translated_desc(self: &VerIterator) -> UniquePtr<DescIterator>;
350
351		#[cxx_name = "Index"]
352		pub fn index(self: &VerIterator) -> u64;
353		/// Clone the pointer.
354		///
355		/// # Safety
356		///
357		/// The returned UniquePtr cannot outlive the cache.
358		unsafe fn unique(self: &VerIterator) -> UniquePtr<VerIterator>;
359		pub fn raw_next(self: Pin<&mut VerIterator>);
360		pub fn end(self: &VerIterator) -> bool;
361	}
362}