warpgate/src/main.rs
grabbit 74b0e72549 Add periodic dir-refresh and per-share refresh status display
Introduces a ScheduledTask mechanism that periodically calls rclone RC
vfs/refresh to keep directory listing caches warm (no file downloads),
with two-level config (global default + per-share override). Adds
dir-refresh status badges and timestamps to the web UI shares tab and
CLI status output, following the same pattern as warmup/warmed.

- src/scheduler.rs: New generic ScheduledTask runner with generation-based
  cancellation and parse_interval() helper
- src/rclone/rc.rs: Add vfs_refresh() RC API call
- src/config.rs: Add DirRefreshConfig, per-share dir_refresh_interval
  override, effective_dir_refresh_interval() resolution method
- src/config_diff.rs: Track dir_refresh_changed for hot-reload
- src/daemon.rs: Track per-share last_dir_refresh timestamps (HashMap),
  add dir_refresh_ago_for() helper and format_ago()
- src/supervisor.rs: spawn_dir_refresh() per-share background threads,
  called on startup and config reload
- src/web/api.rs: Expose dir_refresh_active + last_dir_refresh_ago in
  ShareStatusResponse
- src/web/pages.rs: Populate dir_refresh_active + last_dir_refresh_ago
  in ShareView and ShareDetailView
- templates/web/tabs/shares.html: DIR-REFRESH badge (yellow=pending,
  green=N ago) in health column; Dir Refresh row in detail panel
- templates/web/tabs/config.html: Dir Refresh section and per-share
  interval field in interactive config editor
- src/cli/status.rs: Append Dir-Refresh suffix to mount status lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 10:54:08 +08:00

133 lines
4.0 KiB
Rust

mod cli;
mod config;
mod config_diff;
mod daemon;
mod deploy;
mod rclone;
mod scheduler;
mod services;
mod supervisor;
mod web;
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, Subcommand};
use config::Config;
/// Warpgate — Make your NAS feel local.
///
/// SSD read-write caching proxy for remote NAS access.
#[derive(Parser)]
#[command(name = "warpgate", version, about)]
struct Cli {
/// Path to config file.
#[arg(short, long, default_value = config::DEFAULT_CONFIG_PATH)]
config: PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// One-click deploy: install deps, generate configs, enable services.
Deploy,
/// Show service status, cache stats, write-back queue, bandwidth.
Status,
/// List files currently in the SSD cache.
CacheList,
/// Clean cached files (only evicts clean files, never dirty).
CacheClean {
/// Remove all clean files (default: only expired).
#[arg(long)]
all: bool,
},
/// Pre-cache a remote directory to local SSD.
Warmup {
/// Name of the share to warm up.
#[arg(long)]
share: String,
/// Path within the share to warm up.
path: String,
/// Only files newer than this duration (e.g. "7d", "24h").
#[arg(long)]
newer_than: Option<String>,
},
/// View or adjust bandwidth limits at runtime.
Bwlimit {
/// Upload limit (e.g. "10M", "0" for unlimited).
#[arg(long)]
up: Option<String>,
/// Download limit (e.g. "50M", "0" for unlimited).
#[arg(long)]
down: Option<String>,
},
/// Stream service logs in real time.
Log {
/// Number of recent lines to show.
#[arg(short, long, default_value = "50")]
lines: u32,
/// Follow log output (like tail -f).
#[arg(short, long)]
follow: bool,
},
/// Test network speed to remote NAS.
SpeedTest,
/// Run all services under a single supervisor process.
Run,
/// Generate a default config file.
ConfigInit {
/// Output path (default: /etc/warpgate/config.toml).
#[arg(short, long)]
output: Option<PathBuf>,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
// config-init doesn't need an existing config file
Commands::ConfigInit { output } => cli::config_init::run(output),
// deploy loads config if it exists, or generates one
Commands::Deploy => {
let config = load_config_or_default(&cli.config)?;
deploy::setup::run(&config)
}
// all other commands require a valid config
cmd => {
let config = Config::load(&cli.config)?;
match cmd {
Commands::Status => cli::status::run(&config),
Commands::CacheList => cli::cache::list(&config),
Commands::CacheClean { all } => cli::cache::clean(&config, all),
Commands::Warmup { share, path, newer_than } => {
cli::warmup::run(&config, &share, &path, newer_than.as_deref())
}
Commands::Bwlimit { up, down } => {
cli::bwlimit::run(&config, up.as_deref(), down.as_deref())
}
Commands::Log { lines, follow } => cli::log::run(&config, lines, follow),
Commands::SpeedTest => cli::speed_test::run(&config),
Commands::Run => supervisor::run(&config, cli.config.clone()),
// already handled above
Commands::ConfigInit { .. } | Commands::Deploy => unreachable!(),
}
}
}
}
/// Load config from file, or return a useful error.
fn load_config_or_default(path: &std::path::Path) -> Result<Config> {
if path.exists() {
Config::load(path)
} else {
anyhow::bail!(
"Config file not found: {}. Run `warpgate config-init` to generate one.",
path.display()
)
}
}