crates/pet-life/src/main.rs
86 lines · 2901 bytes
1//! # pet-life
2//!
3//! Prints the pet's current state as JSON and exits. The menu-bar app runs it
4//! on a timer; everything interesting — ageing, death, the cemetery — happens
5//! here, where it can be tested.
6//!
7//! Usage:
8//! ```text
9//! pet-life [--db <path>] [--state <path>]
10//! pet-life --help | --version
11//! ```
12
13use std::path::PathBuf;
14
15use signald::history::History;
16
17const USAGE: &str = "\
18Usage: pet-life [--db <path>] [--state <path>]
19
20 --db <path> signald's history database (default: beside its socket)
21 --state <path> the pet's own state (default $XDG_DATA_HOME, else
22 ~/.local/share/ambient-companions/pet.state)
23 -h, --help print this and exit
24 -V, --version print the version and exit
25
26Prints the pet's state as JSON. Exits 0 even with no daemon: a pet whose
27history cannot be read is not a dead pet, only an unobserved one.
28";
29
30fn main() {
31 let mut db: Option<PathBuf> = None;
32 let mut state_path: Option<PathBuf> = None;
33 let mut args = std::env::args().skip(1);
34 while let Some(arg) = args.next() {
35 match arg.as_str() {
36 "--db" => db = args.next().map(PathBuf::from),
37 "--state" => state_path = args.next().map(PathBuf::from),
38 "--help" | "-h" => {
39 print!("{USAGE}");
40 return;
41 }
42 "--version" | "-V" => {
43 println!("pet-life {}", env!("CARGO_PKG_VERSION"));
44 return;
45 }
46 _ => {
47 eprintln!("pet-life: unknown option {arg}\n");
48 eprint!("{USAGE}");
49 std::process::exit(2);
50 }
51 }
52 }
53
54 let state_path = state_path.unwrap_or_else(pet_life::default_state_path);
55 let db = db.unwrap_or_else(default_db_path);
56
57 // No daemon, or no history yet, is not fatal. The persisted activity still
58 // stands, so the pet ages on the record it already has rather than dying
59 // because nothing could be read this once.
60 let observed = History::open_read_only(&db)
61 .ok()
62 .and_then(|h| h.last_activity_ms().ok().flatten())
63 .unwrap_or(0);
64
65 let (state, snapshot) = pet_life::advance(pet_life::load(&state_path), observed, now_millis());
66 if let Err(e) = pet_life::save(&state_path, &state) {
67 eprintln!("pet-life: cannot write {}: {e}", state_path.display());
68 }
69 println!("{}", pet_life::to_json(&snapshot));
70}
71
72/// The history database sits beside the daemon's socket.
73fn default_db_path() -> PathBuf {
74 let socket = signal_client::default_socket_path();
75 socket
76 .parent()
77 .map(|d| d.join("signald.sqlite"))
78 .unwrap_or_else(|| PathBuf::from("signald.sqlite"))
79}
80
81fn now_millis() -> u64 {
82 std::time::SystemTime::now()
83 .duration_since(std::time::UNIX_EPOCH)
84 .map(|d| d.as_millis() as u64)
85 .unwrap_or(0)
86}