rust_apt/
util.rs

1//! Contains miscellaneous helper utilities.
2use std::cmp::Ordering;
3
4use terminal_size::{terminal_size, Height, Width};
5
6use crate::error::AptErrors;
7use crate::{config, Cache, DepFlags, Package};
8
9/// Get the terminal's height, i.e. the number of rows it has.
10///
11/// # Returns:
12/// * The terminal height, or `24` if it cannot be determined.
13pub fn terminal_height() -> usize {
14	if let Some((_, Height(rows))) = terminal_size() {
15		usize::from(rows)
16	} else {
17		24
18	}
19}
20
21/// Get the terminal's width, i.e. the number of columns it has.
22///
23/// # Returns:
24/// * The terminal width, or `80` if it cannot be determined.
25pub fn terminal_width() -> usize {
26	if let Some((Width(cols), _)) = terminal_size() {
27		usize::from(cols)
28	} else {
29		80
30	}
31}
32
33/// Compares two package versions, `ver1` and `ver2`. The returned enum variant
34/// applies to the first version passed in.
35///
36/// # Examples
37/// ```
38/// use rust_apt::util::cmp_versions;
39/// use std::cmp::Ordering;
40///
41/// let ver1 = "5.0";
42/// let ver2 = "6.0";
43/// let result = cmp_versions(ver1, ver2);
44///
45/// assert_eq!(Ordering::Less, result);
46/// ```
47pub fn cmp_versions(ver1: &str, ver2: &str) -> Ordering {
48	let result = raw::cmp_versions(ver1, ver2);
49	match result {
50		_ if result < 0 => Ordering::Less,
51		_ if result == 0 => Ordering::Equal,
52		_ => Ordering::Greater,
53	}
54}
55
56/// Disk Space that `apt` will use for a transaction.
57pub enum DiskSpace {
58	/// Additional Disk Space required.
59	Require(u64),
60	/// Disk Space that will be freed
61	Free(u64),
62}
63
64/// Numeral System for unit conversion.
65pub enum NumSys {
66	/// Base 2 | 1024 | KibiByte (KiB)
67	Binary,
68	/// Base 10 | 1000 | KiloByte (KB)
69	Decimal,
70}
71
72/// Converts bytes into human readable output.
73///
74/// ```
75/// use rust_apt::new_cache;
76/// use rust_apt::util::{unit_str, NumSys};
77/// let cache = new_cache!().unwrap();
78/// let pkg = cache.get("apt").unwrap();
79/// let version = pkg.candidate().unwrap();
80///
81/// println!("{}", unit_str(version.size(), NumSys::Decimal));
82/// ```
83pub fn unit_str(val: u64, base: NumSys) -> String {
84	let val = val as f64;
85	let (num, tera, giga, mega, kilo) = match base {
86		NumSys::Binary => (1024.0_f64, "TiB", "GiB", "MiB", "KiB"),
87		NumSys::Decimal => (1000.0_f64, "TB", "GB", "MB", "KB"),
88	};
89
90	let powers = [
91		(num.powi(4), tera),
92		(num.powi(3), giga),
93		(num.powi(2), mega),
94		(num, kilo),
95	];
96
97	for (divisor, unit) in powers {
98		if val > divisor {
99			return format!("{:.2} {unit}", val / divisor);
100		}
101	}
102	format!("{val} B")
103}
104
105/// Converts seconds into a human readable time string.
106pub fn time_str(seconds: u64) -> String {
107	if seconds > 60 * 60 * 24 {
108		return format!(
109			"{}d {}h {}min {}s",
110			seconds / 60 / 60 / 24,
111			(seconds / 60 / 60) % 24,
112			(seconds / 60) % 60,
113			seconds % 60,
114		);
115	}
116	if seconds > 60 * 60 {
117		return format!(
118			"{}h {}min {}s",
119			(seconds / 60 / 60) % 24,
120			(seconds / 60) % 60,
121			seconds % 60,
122		);
123	}
124	if seconds > 60 {
125		return format!("{}min {}s", (seconds / 60) % 60, seconds % 60,);
126	}
127	format!("{seconds}s")
128}
129
130/// Get an APT-styled progress bar.
131///
132/// # Returns:
133/// * [`String`] representing the progress bar.
134///
135/// # Example:
136/// ```
137/// use rust_apt::util::get_apt_progress_string;
138/// let progress = get_apt_progress_string(0.5, 10);
139/// assert_eq!(progress, "[####....]");
140/// ```
141pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String {
142	raw::get_apt_progress_string(percent, output_width)
143}
144
145/// Lock the APT lockfile.
146/// This should be done before modifying any APT files
147/// such as with [`crate::cache::Cache::update`]
148/// and then [`apt_unlock`] should be called after.
149///
150/// This Function Requires root
151///
152/// If [`apt_lock`] is called `n` times, [`apt_unlock`] must also be called `n`
153/// times to release all acquired locks.
154///
155/// # Known Error Messages:
156/// * `E:Could not open lock file /var/lib/dpkg/lock-frontend - open (13:
157///   Permission denied)`
158/// * `E:Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend),
159///   are you root?`
160pub fn apt_lock() -> Result<(), AptErrors> {
161	config::init_config_system();
162	Ok(raw::apt_lock()?)
163}
164
165/// Unlock the APT lockfile.
166pub fn apt_unlock() {
167	config::init_config_system();
168	raw::apt_unlock()
169}
170
171/// Unlock the Dpkg lockfile.
172/// This should be done before manually running
173/// [`crate::cache::Cache::do_install`]
174/// and then [`apt_unlock_inner`] should be called after.
175///
176/// This Function Requires root
177pub fn apt_lock_inner() -> Result<(), AptErrors> {
178	config::init_config_system();
179	Ok(raw::apt_lock_inner()?)
180}
181
182/// Unlock the Dpkg lockfile.
183pub fn apt_unlock_inner() {
184	config::init_config_system();
185	raw::apt_unlock_inner()
186}
187
188/// Checks if any locks are currently active for the lockfile. Note that this
189/// will only return [`true`] if the current process has an active lock, while
190/// calls to [`apt_lock`] will return an [`AptErrors`] if another process has an
191/// active lock.
192pub fn apt_is_locked() -> bool {
193	config::init_config_system();
194	raw::apt_is_locked()
195}
196
197/// Reference implementation to print broken packages just like apt does.
198///
199/// ## Returns [`None`] if the package is not considered broken
200///
201/// ## now:
202///   * [true] = When checking broken packages before modifying the cache.
203///   * [false] = When checking broken packages after modifying the cache.
204pub fn show_broken_pkg(cache: &Cache, pkg: &Package, now: bool) -> Option<String> {
205	// If the package isn't broken for the state Return None
206	if (now && !pkg.is_now_broken()) || (!now && !pkg.is_inst_broken()) {
207		return None;
208	};
209
210	let mut broken_string = String::new();
211
212	broken_string += &format!(" {pkg} :");
213
214	// Pick the proper version based on now status.
215	// else Return with just the package name like Apt does.
216	let Some(ver) = (match now {
217		true => pkg.installed(),
218		false => pkg.install_version(),
219	}) else {
220		broken_string += "\n";
221		return Some(broken_string);
222	};
223
224	let indent = pkg.name().len() + 3;
225	let mut first = true;
226
227	// ShowBrokenDeps
228	for dep in ver.depends_map().values().flatten() {
229		for (i, base_dep) in dep.iter().enumerate() {
230			if !cache.depcache().is_important_dep(base_dep) {
231				continue;
232			}
233
234			let dep_flag = if now { DepFlags::DepGNow } else { DepFlags::DepInstall };
235
236			if cache.depcache().dep_state(base_dep) & dep_flag == dep_flag {
237				continue;
238			}
239
240			if !first {
241				broken_string += &" ".repeat(indent);
242			}
243			first = false;
244
245			// If it's the first or Dep
246			if i > 0 {
247				broken_string += &" ".repeat(base_dep.dep_type().as_ref().len() + 3);
248			} else {
249				broken_string += &format!(" {}: ", base_dep.dep_type())
250			}
251
252			broken_string += base_dep.target_package().name();
253
254			if let (Ok(ver_str), Some(comp)) = (base_dep.target_ver(), base_dep.comp_type()) {
255				broken_string += &format!(" ({comp} {ver_str})");
256			}
257
258			let target = base_dep.target_package();
259			if !target.has_provides() {
260				if let Some(target_ver) = target.install_version() {
261					broken_string += &format!(" but {target_ver} is to be installed")
262				} else if target.candidate().is_some() {
263					broken_string += " but it is not going to be installed";
264				} else if target.has_provides() {
265					broken_string += " but it is a virtual package";
266				} else {
267					broken_string += " but it is not installable";
268				}
269			}
270
271			if i + 1 != dep.len() {
272				broken_string += " or"
273			}
274			broken_string += "\n";
275		}
276	}
277	Some(broken_string)
278}
279
280#[cxx::bridge]
281pub(crate) mod raw {
282	unsafe extern "C++" {
283		include!("rust-apt/apt-pkg-c/util.h");
284
285		/// Compares two package versions, `ver1` and `ver2`. The returned
286		/// integer's value is mapped to one of the following integers:
287		/// - Less than 0: `ver1` is less than `ver2`.
288		/// - Equal to 0: `ver1` is equal to `ver2`.
289		/// - Greater than 0: `ver1` is greater than `ver2`.
290		///
291		/// Unless you have a specific need for otherwise, you should probably
292		/// use [`crate::util::cmp_versions`] instead.
293		pub fn cmp_versions(ver1: &str, ver2: &str) -> i32;
294
295		/// Return an APT-styled progress bar (`[####..]`).
296		pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String;
297
298		/// Lock the lockfile.
299		pub fn apt_lock() -> Result<()>;
300
301		/// Unock the lockfile.
302		pub fn apt_unlock();
303
304		/// Lock the Dpkg lockfile.
305		pub fn apt_lock_inner() -> Result<()>;
306
307		/// Unlock the Dpkg lockfile.
308		pub fn apt_unlock_inner();
309
310		/// Check if the lockfile is locked.
311		pub fn apt_is_locked() -> bool;
312	}
313}