use std::collections::HashMap;
pub struct HardlinkTracker {
seen: HashMap<(u64, u64), String>,
}
impl HardlinkTracker {
pub fn new() -> Self {
Self {
seen: HashMap::new(),
}
}
pub fn check(&mut self, dev: u64, ino: u64, path: &str) -> Option<String> {
let key = (dev, ino);
if let Some(existing) = self.seen.get(&key) {
Some(existing.clone())
} else {
self.seen.insert(key, path.to_string());
None
}
}
pub fn get(&self, dev: u64, ino: u64) -> Option<&str> {
self.seen.get(&(dev, ino)).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.seen.len()
}
pub fn is_empty(&self) -> bool {
self.seen.is_empty()
}
pub fn clear(&mut self) {
self.seen.clear();
}
}
impl Default for HardlinkTracker {
fn default() -> Self {
Self::new()
}
}
pub struct CheckoutHardlinkTracker {
paths: HashMap<String, std::path::PathBuf>,
}
impl CheckoutHardlinkTracker {
pub fn new() -> Self {
Self {
paths: HashMap::new(),
}
}
pub fn record(&mut self, logical_path: &str, fs_path: std::path::PathBuf) {
self.paths.insert(logical_path.to_string(), fs_path);
}
pub fn get(&self, logical_path: &str) -> Option<&std::path::Path> {
self.paths.get(logical_path).map(|p| p.as_path())
}
}
impl Default for CheckoutHardlinkTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first_occurrence() {
let mut tracker = HardlinkTracker::new();
let result = tracker.check(1, 12345, "path/to/file");
assert!(result.is_none());
assert_eq!(tracker.len(), 1);
}
#[test]
fn test_second_occurrence() {
let mut tracker = HardlinkTracker::new();
tracker.check(1, 12345, "path/to/first");
let result = tracker.check(1, 12345, "path/to/second");
assert_eq!(result, Some("path/to/first".to_string()));
}
#[test]
fn test_different_inodes() {
let mut tracker = HardlinkTracker::new();
tracker.check(1, 12345, "file1");
tracker.check(1, 67890, "file2");
assert_eq!(tracker.len(), 2);
assert!(tracker.get(1, 11111).is_none());
}
#[test]
fn test_same_ino_different_dev() {
let mut tracker = HardlinkTracker::new();
tracker.check(1, 12345, "file1");
let result = tracker.check(2, 12345, "file2");
assert!(result.is_none());
assert_eq!(tracker.len(), 2);
}
#[test]
fn test_clear() {
let mut tracker = HardlinkTracker::new();
tracker.check(1, 12345, "file1");
assert!(!tracker.is_empty());
tracker.clear();
assert!(tracker.is_empty());
}
#[test]
fn test_checkout_tracker() {
let mut tracker = CheckoutHardlinkTracker::new();
tracker.record("usr/bin/foo", "/mnt/rootfs/usr/bin/foo".into());
let path = tracker.get("usr/bin/foo");
assert_eq!(path, Some(std::path::Path::new("/mnt/rootfs/usr/bin/foo")));
assert!(tracker.get("nonexistent").is_none());
}
}