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>
138 lines
3.9 KiB
Rust
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");
|
|
}
|
|
}
|