feat(gui): WatchdogState pure-logic helper for first-run modal
Convert gui/src/firstrun.rs to a module dir with watchdog.rs, the pure-logic WatchdogState helper that the GTK widget code in Task 2 will poll. Three unit tests cover fresh/expired/poke transitions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
18
gui/src/firstrun/mod.rs
Normal file
18
gui/src/firstrun/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! First-login modal. Phase 10 implements the body of `run`.
|
||||
|
||||
#![allow(dead_code)] // body lands in Phase 10 Task 2.
|
||||
|
||||
mod watchdog;
|
||||
#[allow(unused_imports)] // wired into run() in Phase 10 Task 2.
|
||||
pub(crate) use watchdog::WatchdogState;
|
||||
|
||||
use crate::app_context::AppContext;
|
||||
|
||||
/// Entry point for `authforge --first-run`. Phase 10 fills this with the
|
||||
/// fullscreen modal + watchdog + enroll-then-clear-pending logic.
|
||||
/// Until then it exits cleanly so a developer who passes `--first-run`
|
||||
/// doesn't get a stuck process.
|
||||
pub(crate) fn run(_ctx: AppContext) {
|
||||
eprintln!("authforge: --first-run mode is a Phase 10 stub; exiting.");
|
||||
std::process::exit(0);
|
||||
}
|
||||
55
gui/src/firstrun/watchdog.rs
Normal file
55
gui/src/firstrun/watchdog.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! Tracks the last-activity timestamp; the GTK timer queries this each tick
|
||||
//! to decide whether 60s of idle has elapsed.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub(crate) struct WatchdogState {
|
||||
last_activity: Instant,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl WatchdogState {
|
||||
pub fn new(timeout: Duration) -> Self {
|
||||
Self {
|
||||
last_activity: Instant::now(),
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poke(&mut self) {
|
||||
self.last_activity = Instant::now();
|
||||
}
|
||||
|
||||
/// True iff `timeout` has passed since the last poke. Once expired,
|
||||
/// stays expired until the caller drops + recreates.
|
||||
pub fn expired(&self) -> bool {
|
||||
self.last_activity.elapsed() >= self.timeout
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fresh_state_is_not_expired() {
|
||||
let w = WatchdogState::new(Duration::from_secs(60));
|
||||
assert!(!w.expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_timeout_is_immediately_expired() {
|
||||
let w = WatchdogState::new(Duration::from_millis(0));
|
||||
std::thread::sleep(Duration::from_millis(2));
|
||||
assert!(w.expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_resets_the_clock() {
|
||||
let mut w = WatchdogState::new(Duration::from_millis(100));
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
assert!(w.expired());
|
||||
w.poke();
|
||||
assert!(!w.expired());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user