harmont_cli/commands/cache/
clean.rs1use anyhow::Result;
2use hm_vm::VmBackend as _;
3
4pub async fn handle_clean() -> Result<i32> {
7 let ws_cleaned = if let Some(ws_cache) = hm_util::dirs::hm_workspace_cache_dir()
8 && ws_cache.exists()
9 {
10 let size = dir_size(&ws_cache);
11 std::fs::remove_dir_all(&ws_cache)?;
12 tracing::info!(
13 path = %ws_cache.display(),
14 "removed workspace cache ({})",
15 human_bytes(size),
16 );
17 true
18 } else {
19 false
20 };
21
22 let db_cleaned = if let Some(cache_dir) = hm_util::dirs::hm_cache_dir() {
23 let db_path = cache_dir.join("registry.db");
24 if db_path.exists() {
25 remove_registered_images(&db_path).await;
33
34 std::fs::remove_file(&db_path)?;
35 tracing::info!(path = %db_path.display(), "removed VM image registry");
36 true
37 } else {
38 false
39 }
40 } else {
41 false
42 };
43
44 if !ws_cleaned && !db_cleaned {
45 tracing::info!("nothing to clean");
46 }
47
48 Ok(0)
49}
50
51async fn remove_registered_images(db_path: &std::path::Path) {
57 let registry = match hm_vm::ImageRegistry::open(db_path, std::num::NonZeroU64::MAX) {
59 Ok(r) => r,
60 Err(e) => {
61 tracing::warn!(error = %e, "could not open image registry; skipping image removal");
62 return;
63 }
64 };
65
66 let snapshots = registry.all_snapshot_ids();
67 if snapshots.is_empty() {
68 return;
69 }
70
71 let backend = match hm_vm::docker::DockerBackend::connect() {
72 Ok(b) => b,
73 Err(e) => {
74 tracing::warn!(
75 error = %e,
76 "could not connect to Docker; {} cached image(s) may remain — remove them with `docker image rm`",
77 snapshots.len(),
78 );
79 return;
80 }
81 };
82
83 let mut removed = 0usize;
84 for snap in &snapshots {
85 match backend.remove_snapshot(snap).await {
86 Ok(()) => removed += 1,
87 Err(e) => {
88 tracing::warn!(image = %snap, error = %e, "failed to remove cached image");
89 }
90 }
91 }
92 tracing::info!(
93 "removed {removed} of {} cached Docker image(s)",
94 snapshots.len()
95 );
96}
97
98fn dir_size(path: &std::path::Path) -> u64 {
99 fn walk(p: &std::path::Path) -> u64 {
100 std::fs::read_dir(p)
101 .into_iter()
102 .flatten()
103 .filter_map(std::result::Result::ok)
104 .map(|e| {
105 let path = e.path();
106 if path.is_dir() {
107 walk(&path)
108 } else {
109 e.metadata().map_or(0, |m| m.len())
110 }
111 })
112 .sum()
113 }
114 walk(path)
115}
116
117#[allow(
118 clippy::cast_precision_loss,
119 reason = "human-readable display; sub-byte precision irrelevant"
120)]
121fn human_bytes(bytes: u64) -> String {
122 let b = bytes as f64;
123 if bytes < 1024 {
124 format!("{bytes}B")
125 } else if bytes < 1024 * 1024 {
126 format!("{:.1}KB", b / 1024.0)
127 } else if bytes < 1024 * 1024 * 1024 {
128 format!("{:.1}MB", b / (1024.0 * 1024.0))
129 } else {
130 format!("{:.1}GB", b / (1024.0 * 1024.0 * 1024.0))
131 }
132}