warpgate/src/cli/cache.rs
grabbit 08f8fc4667 Per-share independent mounts: each share gets its own rclone process
Replace the hierarchical single-mount design with independent mounts:
each [[shares]] entry is a (name, remote_path, mount_point) triplet
with its own rclone FUSE mount process and dedicated RC API port
(5572 + index). Remove top-level connection.remote_path and [mount]
section. Auto-warmup now runs in a background thread to avoid blocking
the supervision loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:32:18 +08:00

138 lines
3.9 KiB
Rust

//! `warpgate cache-list` and `warpgate cache-clean` commands.
use anyhow::Result;
use crate::config::Config;
use crate::rclone::rc;
/// List cached files via rclone RC API (aggregated across all shares).
pub fn list(config: &Config) -> Result<()> {
for (i, share) in config.shares.iter().enumerate() {
let port = config.rc_port(i);
println!("=== {} ===", share.name);
let result = match rc::vfs_list(port, "/") {
Ok(r) => r,
Err(e) => {
eprintln!(" Could not list cache for '{}': {}", share.name, e);
continue;
}
};
let entries = if let Some(arr) = result.as_array() {
arr.as_slice()
} else if let Some(list) = result.get("list").and_then(|v| v.as_array()) {
list.as_slice()
} else {
println!("{}", serde_json::to_string_pretty(&result)?);
continue;
};
if entries.is_empty() {
println!(" Cache is empty.");
continue;
}
println!("{:<10} PATH", "SIZE");
println!("{}", "-".repeat(60));
for entry in entries {
let name = entry.get("Name").and_then(|v| v.as_str()).unwrap_or("?");
let size = entry.get("Size").and_then(|v| v.as_u64()).unwrap_or(0);
let is_dir = entry
.get("IsDir")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if is_dir {
println!("{:<10} {}/", "<dir>", name);
} else {
println!("{:<10} {}", format_bytes(size), name);
}
}
println!();
}
Ok(())
}
/// Clean cached files (only clean files, never dirty).
pub fn clean(config: &Config, all: bool) -> Result<()> {
if all {
println!("Clearing VFS directory cache for all shares...");
for (i, share) in config.shares.iter().enumerate() {
let port = config.rc_port(i);
match rc::vfs_forget(port, "/") {
Ok(()) => println!(" {}: cleared", share.name),
Err(e) => eprintln!(" {}: failed — {}", share.name, e),
}
}
println!("Done.");
} else {
println!("Current cache status:");
for (i, share) in config.shares.iter().enumerate() {
let port = config.rc_port(i);
print!(" [{}] ", share.name);
match rc::vfs_stats(port) {
Ok(vfs) => {
if let Some(dc) = vfs.disk_cache {
println!(
"Used: {}, Uploading: {}, Queued: {}",
format_bytes(dc.bytes_used),
dc.uploads_in_progress,
dc.uploads_queued
);
} else {
println!("no cache stats");
}
}
Err(e) => println!("unreachable — {}", e),
}
}
println!("\nRun with --all to clear the directory cache.");
}
Ok(())
}
fn format_bytes(bytes: u64) -> String {
const KIB: f64 = 1024.0;
const MIB: f64 = KIB * 1024.0;
const GIB: f64 = MIB * 1024.0;
let b = bytes as f64;
if b >= GIB {
format!("{:.1} GiB", b / GIB)
} else if b >= MIB {
format!("{:.1} MiB", b / MIB)
} else if b >= KIB {
format!("{:.1} KiB", b / KIB)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_bytes_zero() {
assert_eq!(format_bytes(0), "0 B");
}
#[test]
fn test_format_bytes_kib() {
assert_eq!(format_bytes(2048), "2.0 KiB");
}
#[test]
fn test_format_bytes_mib() {
assert_eq!(format_bytes(5242880), "5.0 MiB");
}
#[test]
fn test_format_bytes_gib() {
assert_eq!(format_bytes(10737418240), "10.0 GiB");
}
}