Skip to main content

purple_ssh/app/
reload_state.rs

1//! Auto-reload mtime tracking and form conflict mtimes.
2
3use std::path::{Path, PathBuf};
4use std::time::SystemTime;
5
6use crate::ssh_config::model::SshConfigFile;
7
8/// Auto-reload mtime tracking.
9#[derive(Default)]
10pub struct ReloadState {
11    pub config_path: PathBuf,
12    pub last_modified: Option<SystemTime>,
13    pub include_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
14    pub include_dir_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
15    /// mtime of `~/.ssh/` itself. Changes when a key file is created,
16    /// renamed or removed; combined with `key_file_mtimes` this gives a
17    /// full add/remove/modify signal without needing a real watcher.
18    pub keys_dir_mtime: Option<SystemTime>,
19    /// mtime per known `*.pub` (or private) key path. Touch-only edits
20    /// (re-encrypt, passphrase change) move the file mtime without
21    /// touching the parent directory, so we track both.
22    pub key_file_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
23}
24
25/// Form conflict detection mtimes.
26#[derive(Default)]
27pub struct ConflictState {
28    pub form_mtime: Option<SystemTime>,
29    pub form_include_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
30    pub form_include_dir_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
31    pub provider_form_mtime: Option<SystemTime>,
32}
33
34impl ReloadState {
35    /// Build from a loaded config: captures initial mtimes for the main file
36    /// and every Include'd file and directory.
37    pub fn from_config(config: &SshConfigFile) -> Self {
38        let config_path = config.path.clone();
39        let last_modified = get_mtime(&config_path);
40        let include_mtimes = snapshot_include_mtimes(config);
41        let include_dir_mtimes = snapshot_include_dir_mtimes(config);
42        Self {
43            config_path,
44            last_modified,
45            include_mtimes,
46            include_dir_mtimes,
47            keys_dir_mtime: None,
48            key_file_mtimes: Vec::new(),
49        }
50    }
51}
52
53/// Get the modification time of a file.
54pub fn get_mtime(path: &Path) -> Option<SystemTime> {
55    std::fs::metadata(path).ok()?.modified().ok()
56}
57
58/// Snapshot mtimes of all resolved Include files.
59pub fn snapshot_include_mtimes(config: &SshConfigFile) -> Vec<(PathBuf, Option<SystemTime>)> {
60    config
61        .include_paths()
62        .into_iter()
63        .map(|p| {
64            let mtime = get_mtime(&p);
65            (p, mtime)
66        })
67        .collect()
68}
69
70/// Snapshot mtimes of parent directories of Include glob patterns.
71pub fn snapshot_include_dir_mtimes(config: &SshConfigFile) -> Vec<(PathBuf, Option<SystemTime>)> {
72    config
73        .include_glob_dirs()
74        .into_iter()
75        .map(|p| {
76            let mtime = get_mtime(&p);
77            (p, mtime)
78        })
79        .collect()
80}
81
82/// Snapshot the mtime of every discovered key's public-key file. The
83/// caller passes the live `discover_keys` result; we resolve each
84/// `display_path` (with the leading `~` expanded) back to an absolute
85/// path under `ssh_dir` so we can stat it cheaply on each tick.
86pub fn snapshot_key_mtimes(
87    ssh_dir: &Path,
88    keys: &[crate::ssh_keys::SshKeyInfo],
89) -> Vec<(PathBuf, Option<SystemTime>)> {
90    keys.iter()
91        .map(|k| {
92            let pub_path = ssh_dir.join(format!("{}.pub", k.name));
93            let mtime = get_mtime(&pub_path);
94            (pub_path, mtime)
95        })
96        .collect()
97}