//! # pet-life //! //! Prints the pet's current state as JSON and exits. The menu-bar app runs it //! on a timer; everything interesting — ageing, death, the cemetery — happens //! here, where it can be tested. //! //! Usage: //! ```text //! pet-life [--db ] [--state ] //! pet-life --help | --version //! ``` use std::path::PathBuf; use signald::history::History; const USAGE: &str = "\ Usage: pet-life [--db ] [--state ] --db signald's history database (default: beside its socket) --state the pet's own state (default $XDG_DATA_HOME, else ~/.local/share/ambient-companions/pet.state) -h, --help print this and exit -V, --version print the version and exit Prints the pet's state as JSON. Exits 0 even with no daemon: a pet whose history cannot be read is not a dead pet, only an unobserved one. "; fn main() { let mut db: Option = None; let mut state_path: Option = None; let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { match arg.as_str() { "--db" => db = args.next().map(PathBuf::from), "--state" => state_path = args.next().map(PathBuf::from), "--help" | "-h" => { print!("{USAGE}"); return; } "--version" | "-V" => { println!("pet-life {}", env!("CARGO_PKG_VERSION")); return; } _ => { eprintln!("pet-life: unknown option {arg}\n"); eprint!("{USAGE}"); std::process::exit(2); } } } let state_path = state_path.unwrap_or_else(pet_life::default_state_path); let db = db.unwrap_or_else(default_db_path); // No daemon, or no history yet, is not fatal. The persisted activity still // stands, so the pet ages on the record it already has rather than dying // because nothing could be read this once. let observed = History::open_read_only(&db) .ok() .and_then(|h| h.last_activity_ms().ok().flatten()) .unwrap_or(0); let (state, snapshot) = pet_life::advance(pet_life::load(&state_path), observed, now_millis()); if let Err(e) = pet_life::save(&state_path, &state) { eprintln!("pet-life: cannot write {}: {e}", state_path.display()); } println!("{}", pet_life::to_json(&snapshot)); } /// The history database sits beside the daemon's socket. fn default_db_path() -> PathBuf { let socket = signal_client::default_socket_path(); socket .parent() .map(|d| d.join("signald.sqlite")) .unwrap_or_else(|| PathBuf::from("signald.sqlite")) } fn now_millis() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_millis() as u64) .unwrap_or(0) }