#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ReelEntry {
pub reel_id: String,
pub tape_name: String,
pub volume_label: String,
pub path: Option<String>,
}
impl ReelEntry {
#[must_use]
pub fn new(
reel_id: impl Into<String>,
tape_name: impl Into<String>,
volume_label: impl Into<String>,
path: Option<String>,
) -> Self {
Self {
reel_id: reel_id.into(),
tape_name: tape_name.into(),
volume_label: volume_label.into(),
path,
}
}
#[must_use]
pub fn is_online(&self) -> bool {
self.path.is_some()
}
#[must_use]
pub fn media_path(&self) -> Option<&str> {
self.path.as_deref()
}
}
#[derive(Debug, Default)]
pub struct ReelMap {
pub entries: Vec<ReelEntry>,
}
impl ReelMap {
#[must_use]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add(&mut self, entry: ReelEntry) {
if let Some(existing) = self.entries.iter_mut().find(|e| e.reel_id == entry.reel_id) {
*existing = entry;
} else {
self.entries.push(entry);
}
}
#[must_use]
pub fn find_by_id(&self, reel_id: &str) -> Option<&ReelEntry> {
self.entries.iter().find(|e| e.reel_id == reel_id)
}
#[must_use]
pub fn find_by_tape(&self, tape_name: &str) -> Option<&ReelEntry> {
self.entries.iter().find(|e| e.tape_name == tape_name)
}
#[must_use]
pub fn resolve_path(&self, reel_id: &str) -> Option<&str> {
self.find_by_id(reel_id).and_then(|e| e.media_path())
}
#[must_use]
pub fn online_count(&self) -> usize {
self.entries.iter().filter(|e| e.is_online()).count()
}
#[must_use]
pub fn total_count(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn offline_count(&self) -> usize {
self.entries.iter().filter(|e| !e.is_online()).count()
}
#[must_use]
pub fn online_reels(&self) -> Vec<&ReelEntry> {
self.entries.iter().filter(|e| e.is_online()).collect()
}
#[must_use]
pub fn offline_reels(&self) -> Vec<&ReelEntry> {
self.entries.iter().filter(|e| !e.is_online()).collect()
}
#[must_use]
pub fn all_online(&self) -> bool {
!self.entries.is_empty() && self.entries.iter().all(|e| e.is_online())
}
pub fn remove(&mut self, reel_id: &str) -> bool {
let before = self.entries.len();
self.entries.retain(|e| e.reel_id != reel_id);
self.entries.len() < before
}
pub fn set_path(&mut self, reel_id: &str, path: impl Into<String>) -> bool {
if let Some(entry) = self.entries.iter_mut().find(|e| e.reel_id == reel_id) {
entry.path = Some(path.into());
true
} else {
false
}
}
pub fn set_offline(&mut self, reel_id: &str) -> bool {
if let Some(entry) = self.entries.iter_mut().find(|e| e.reel_id == reel_id) {
entry.path = None;
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_online(id: &str, tape: &str) -> ReelEntry {
ReelEntry::new(id, tape, "VOL_01", Some(format!("/media/{}", id)))
}
fn make_offline(id: &str, tape: &str) -> ReelEntry {
ReelEntry::new(id, tape, "VOL_02", None)
}
#[test]
fn test_reel_entry_is_online_with_path() {
let entry = make_online("A001", "TAPE_001");
assert!(entry.is_online());
assert_eq!(entry.media_path(), Some("/media/A001"));
}
#[test]
fn test_reel_entry_is_offline_without_path() {
let entry = make_offline("B001", "TAPE_002");
assert!(!entry.is_online());
assert!(entry.media_path().is_none());
}
#[test]
fn test_reel_map_add_and_total_count() {
let mut map = ReelMap::new();
assert_eq!(map.total_count(), 0);
map.add(make_online("A001", "TAPE_001"));
map.add(make_offline("A002", "TAPE_002"));
assert_eq!(map.total_count(), 2);
}
#[test]
fn test_reel_map_add_replaces_existing() {
let mut map = ReelMap::new();
map.add(make_offline("A001", "TAPE_001"));
assert_eq!(map.online_count(), 0);
map.add(make_online("A001", "TAPE_001"));
assert_eq!(map.total_count(), 1);
assert_eq!(map.online_count(), 1);
}
#[test]
fn test_reel_map_find_by_id() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_online("A002", "TAPE_002"));
let entry = map.find_by_id("A001");
assert!(entry.is_some());
assert_eq!(entry.expect("entry should be valid").tape_name, "TAPE_001");
assert!(map.find_by_id("UNKNOWN").is_none());
}
#[test]
fn test_reel_map_find_by_tape() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_online("A002", "TAPE_002"));
let entry = map.find_by_tape("TAPE_002");
assert!(entry.is_some());
assert_eq!(entry.expect("entry should be valid").reel_id, "A002");
assert!(map.find_by_tape("NONEXISTENT").is_none());
}
#[test]
fn test_reel_map_resolve_path() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_offline("A002", "TAPE_002"));
assert_eq!(map.resolve_path("A001"), Some("/media/A001"));
assert_eq!(map.resolve_path("A002"), None);
assert_eq!(map.resolve_path("UNKNOWN"), None);
}
#[test]
fn test_reel_map_online_count() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_online("A002", "TAPE_002"));
map.add(make_offline("A003", "TAPE_003"));
assert_eq!(map.online_count(), 2);
assert_eq!(map.offline_count(), 1);
}
#[test]
fn test_reel_map_online_reels() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_offline("A002", "TAPE_002"));
let online = map.online_reels();
assert_eq!(online.len(), 1);
assert_eq!(online[0].reel_id, "A001");
}
#[test]
fn test_reel_map_offline_reels() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_offline("A002", "TAPE_002"));
map.add(make_offline("A003", "TAPE_003"));
let offline = map.offline_reels();
assert_eq!(offline.len(), 2);
}
#[test]
fn test_reel_map_all_online() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_online("A002", "TAPE_002"));
assert!(map.all_online());
map.add(make_offline("A003", "TAPE_003"));
assert!(!map.all_online());
}
#[test]
fn test_reel_map_remove() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
map.add(make_online("A002", "TAPE_002"));
let removed = map.remove("A001");
assert!(removed);
assert_eq!(map.total_count(), 1);
let not_removed = map.remove("UNKNOWN");
assert!(!not_removed);
}
#[test]
fn test_reel_map_set_path() {
let mut map = ReelMap::new();
map.add(make_offline("A001", "TAPE_001"));
assert_eq!(map.online_count(), 0);
let updated = map.set_path("A001", "/media/new/A001");
assert!(updated);
assert_eq!(map.online_count(), 1);
assert_eq!(map.resolve_path("A001"), Some("/media/new/A001"));
}
#[test]
fn test_reel_map_set_offline() {
let mut map = ReelMap::new();
map.add(make_online("A001", "TAPE_001"));
assert_eq!(map.online_count(), 1);
let updated = map.set_offline("A001");
assert!(updated);
assert_eq!(map.online_count(), 0);
assert!(map.resolve_path("A001").is_none());
}
}