use std::collections::BTreeMap;
use std::path::PathBuf;
use arcbox_core::machine::HostHold;
#[derive(Debug)]
pub(super) struct Entry {
pub path: PathBuf,
pub generation: u64,
pub stale: bool,
hold: Option<HostHold>,
}
pub(super) struct Mounts {
entries: BTreeMap<String, Entry>,
generation: u64,
}
impl Mounts {
pub const fn new() -> Self {
Self {
entries: BTreeMap::new(),
generation: 0,
}
}
pub fn record(&mut self, name: &str, path: PathBuf, hold: HostHold) -> u64 {
self.generation += 1;
self.entries.insert(
name.to_owned(),
Entry {
path,
generation: self.generation,
stale: false,
hold: Some(hold),
},
);
self.generation
}
pub fn get(&self, name: &str) -> Option<&Entry> {
self.entries.get(name)
}
pub fn is_current(&self, name: &str, generation: u64) -> bool {
self.entries
.get(name)
.is_some_and(|entry| entry.generation == generation)
}
pub fn mark_stale(&mut self, name: &str, generation: u64) {
if let Some(entry) = self.entries.get_mut(name)
&& entry.generation == generation
{
entry.stale = true;
entry.hold = None;
}
}
pub fn forget(&mut self, name: &str, generation: u64) {
if self.is_current(name, generation) {
self.entries.remove(name);
}
}
pub fn names(&self) -> Vec<String> {
self.entries.keys().cloned().collect()
}
pub fn entries(&self) -> Vec<(String, PathBuf, u64)> {
self.entries
.iter()
.map(|(name, entry)| (name.clone(), entry.path.clone(), entry.generation))
.collect()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arcbox_core::event::EventBus;
use arcbox_core::machine::MachineManager;
use arcbox_core::vm::{HostNetwork, VmManager};
use super::*;
fn machine_manager(dir: &std::path::Path) -> MachineManager {
MachineManager::new(
Arc::new(VmManager::new(dir.join("snapshots"))),
dir.to_path_buf(),
HostNetwork::default(),
EventBus::new(),
)
}
#[test]
fn a_remount_outdates_the_generation_a_retry_holds() {
let dir = tempfile::tempdir().unwrap();
let manager = machine_manager(dir.path());
let mut mounts = Mounts::new();
let first = mounts.record("dev", PathBuf::from("/m/dev"), manager.host_hold("dev"));
assert!(mounts.is_current("dev", first));
mounts.mark_stale("dev", first);
assert!(mounts.get("dev").is_some_and(|entry| entry.stale));
assert_eq!(mounts.names(), vec!["dev".to_owned()]);
let second = mounts.record("dev", PathBuf::from("/m/dev"), manager.host_hold("dev"));
assert!(!mounts.is_current("dev", first));
assert!(mounts.is_current("dev", second));
assert!(mounts.get("dev").is_some_and(|entry| !entry.stale));
mounts.forget("dev", first);
assert!(mounts.is_current("dev", second));
mounts.forget("dev", second);
assert!(mounts.get("dev").is_none());
assert_eq!(mounts.entries(), Vec::new());
}
#[test]
fn marking_or_forgetting_an_unknown_generation_changes_nothing() {
let dir = tempfile::tempdir().unwrap();
let manager = machine_manager(dir.path());
let mut mounts = Mounts::new();
mounts.mark_stale("dev", 7);
mounts.forget("dev", 7);
assert!(mounts.get("dev").is_none());
let generation = mounts.record("dev", PathBuf::from("/m/dev"), manager.host_hold("dev"));
mounts.mark_stale("dev", generation + 1);
assert!(mounts.get("dev").is_some_and(|entry| !entry.stale));
}
}