use crate::model::World;
pub const FOUNDATION_THRESHOLD: usize = 25;
pub fn is_kernel_pkg(name: &str) -> bool {
name.starts_with("linux-")
|| name == "intel-microcode"
|| name == "amd64-microcode"
|| name.starts_with("firmware-")
|| name.starts_with("initramfs")
}
pub fn bfs_root(world: &World, start: &str) -> Option<Vec<String>> {
use std::collections::{HashMap, HashSet, VecDeque};
let mut visited: HashSet<&str> = HashSet::new();
let mut parent: HashMap<&str, &str> = HashMap::new();
let mut queue: VecDeque<&str> = VecDeque::new();
visited.insert(start);
queue.push_back(start);
let mut found: Option<&str> = None;
'search: while let Some(cur) = queue.pop_front() {
for dep in world.rdeps_of(cur) {
let dep = dep.as_str();
if !visited.insert(dep) {
continue;
}
parent.insert(dep, cur);
if world.is_manual(dep) {
found = Some(dep);
break 'search;
}
queue.push_back(dep);
}
}
let found = found?;
let mut path = vec![found.to_string()];
let mut node = found;
while let Some(&p) = parent.get(node) {
path.push(p.to_string());
node = p;
}
path.reverse();
Some(path)
}
pub fn same_session(world: &World, pkg: &str) -> Vec<String> {
let anchor = match world.packages.get(pkg).and_then(|p| p.install_epoch) {
Some(e) => e,
None => return Vec::new(),
};
const WINDOWS: [i64; 4] = [259_200, 86_400, 43_200, 21_600];
let collect = |half: i64| -> Vec<String> {
let (lo, hi) = (anchor - half, anchor + half);
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for (epoch, name) in &world.install_log {
if *epoch < lo || *epoch > hi {
continue;
}
if name == pkg {
continue;
}
if seen.insert(name.clone()) {
out.push(name.clone());
}
}
out.sort();
out
};
for half in WINDOWS {
let result = collect(half);
if result.len() <= 20 {
return result;
}
}
collect(21_600)
}
pub fn relative_time(epoch: i64) -> String {
let secs = chrono::Utc::now().timestamp() - epoch;
if secs < 0 {
return "in the future".to_string();
}
let days = secs / 86_400;
let plural = |n: i64| if n == 1 { "" } else { "s" };
match days {
0 => "today".to_string(),
1 => "yesterday".to_string(),
2..=6 => format!("{days} days ago"),
7..=29 => {
let w = days / 7;
format!("{w} week{} ago", plural(w))
}
30..=364 => {
let m = days / 30;
format!("{m} month{} ago", plural(m))
}
_ => {
let y = days / 365;
format!("{y} year{} ago", plural(y))
}
}
}
pub fn format_size(kb: u64) -> String {
if kb == 0 {
"n/a".to_string()
} else if kb >= 1024 {
format!("{:.1} MB", kb as f64 / 1024.0)
} else {
format!("{kb} KB")
}
}