add watch command

This commit is contained in:
Robin Appelman 2025-06-21 14:40:01 +02:00
commit c89efc87e7
3 changed files with 164 additions and 18 deletions

View file

@ -1,8 +1,12 @@
use crate::prefetch::prefetch;
use std::process::Command;
use std::time::Duration;
use crate::prefetch::{prefetch, PrefetchError};
use crate::product_info::{ProductInfoError, ProductInfoFetcher};
use clap::Parser;
use main_error::MainResult;
use serde::Serialize;
use tokio::time::sleep;
use tracing::{debug, error, info};
mod error;
mod prefetch;
@ -11,15 +15,17 @@ mod product_info;
#[derive(Debug, Parser)]
struct Args {
#[command(subcommand)]
command: Command,
command: ArgsCommand,
}
#[derive(Debug, Parser)]
enum Command {
enum ArgsCommand {
/// Get the latest manifest and pre-fetch it
Fetch(ProductArgs),
/// Get the latest manifest without fetching
Manifest(ProductArgs),
/// Watch for manifest changes and pre-fetch any updates
Watch(WatchArgs),
}
#[derive(Debug, Parser)]
@ -34,32 +40,68 @@ struct ProductArgs {
depot: Vec<u32>,
}
#[derive(Debug, Parser)]
struct WatchArgs {
#[clap(flatten)]
product_args: ProductArgs,
/// How often to check for updates
#[clap(long, default_value_t = 300)]
interval: u64,
/// Command to run when updates are detected, the manifest data will be passed as the first argument as json.
#[clap(long)]
run: Option<String>,
}
#[tokio::main]
async fn main() -> MainResult {
let args = Args::parse();
tracing_subscriber::fmt::init();
let info_fetcher = ProductInfoFetcher::new().await?;
match args.command {
Command::Fetch(args) => {
ArgsCommand::Fetch(args) => {
let inputs = get_manifests(&info_fetcher, &args).await?;
let outputs = inputs.into_iter()
.map(|input| {
prefetch(&input).map(|hash| ManifestOutput {
app_id: input.app_id,
depot_id: input.depot_id,
manifest: input.manifest,
hash,
})
})
.collect::<Result<Vec<_>, _>>()?;
let outputs = fetch_inputs(inputs)?;
serde_json::to_writer_pretty(std::io::stdout(), &outputs)?;
}
Command::Manifest(args) => {
ArgsCommand::Manifest(args) => {
let inputs = get_manifests(&info_fetcher, &args).await?;
serde_json::to_writer_pretty(std::io::stdout(), &inputs)?;
}
ArgsCommand::Watch(args) => {
let mut last_inputs = get_manifests(&info_fetcher, &args.product_args).await?;
loop {
sleep(Duration::from_secs(args.interval)).await;
let new_inputs = get_manifests(&info_fetcher, &args.product_args).await?;
if new_inputs != last_inputs {
last_inputs = new_inputs.clone();
let inputs_json = serde_json::to_string(&new_inputs)?;
debug!(inputs = inputs_json, "updated detected");
let outputs = fetch_inputs(new_inputs)?;
let output_json = serde_json::to_string(&outputs)?;
info!(outputs = ?output_json, "prefetch complete");
if let Some(run) = args.run.as_deref() {
match Command::new(run)
.arg(output_json)
.status() {
Ok(status) => {
info!(%status, "Command existed");
}
Err(error) => {
error!(%error, "Failed to run command");
}
}
}
}
}
}
}
Ok(())
@ -91,7 +133,19 @@ async fn get_manifests(
.collect())
}
#[derive(Debug, Serialize)]
fn fetch_inputs(inputs: impl IntoIterator<Item = ManifestInput>) -> Result<Vec<ManifestOutput>, PrefetchError> {
inputs.into_iter()
.map(|input| {
prefetch(&input).map(|hash| ManifestOutput {
app_id: input.app_id,
depot_id: input.depot_id,
manifest: input.manifest,
hash,
})
}).collect()
}
#[derive(Debug, Serialize, Eq, PartialEq, Copy, Clone)]
struct ManifestInput {
app_id: u32,
depot_id: u32,