Skip to main content

worktree_io/ttl/
mod.rs

1use std::time::{Duration, SystemTime};
2
3use serde::{Deserialize, Serialize};
4
5/// Workspace record and registry persistence.
6pub mod registry;
7pub use registry::{WorkspaceRecord, WorkspaceRegistry};
8
9/// A time-to-live duration controlling how long a workspace remains active.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub struct Ttl(#[serde(with = "humantime_serde")] Duration);
12
13impl Ttl {
14    /// Create a new [`Ttl`] wrapping the given duration.
15    #[must_use]
16    pub const fn new(duration: Duration) -> Self {
17        Self(duration)
18    }
19
20    /// Return the inner [`Duration`].
21    #[must_use]
22    pub const fn duration(self) -> Duration {
23        self.0
24    }
25}
26
27impl std::fmt::Display for Ttl {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", humantime::format_duration(self.0))
30    }
31}
32
33impl std::str::FromStr for Ttl {
34    type Err = humantime::DurationError;
35
36    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
37        humantime::parse_duration(s).map(Self)
38    }
39}
40
41/// Returns `true` if the workspace has exceeded its TTL at the given instant.
42///
43/// Returns `false` when `created_at` is in the future relative to `now`.
44#[must_use]
45pub fn is_expired(record: &WorkspaceRecord, ttl: &Ttl, now: SystemTime) -> bool {
46    now.duration_since(record.created_at)
47        .map(|age| age >= ttl.0)
48        .unwrap_or(false)
49}
50
51/// Returns workspaces that are both present on disk and have exceeded the TTL.
52///
53/// Entries whose [`WorkspaceRecord::path`] no longer exists are silently
54/// skipped, making the registry self-healing on the next prune call.
55#[must_use]
56pub fn prune<'a>(
57    records: &'a [WorkspaceRecord],
58    ttl: &Ttl,
59    now: SystemTime,
60) -> Vec<&'a WorkspaceRecord> {
61    records
62        .iter()
63        .filter(|r| r.path.exists() && is_expired(r, ttl, now))
64        .collect()
65}
66
67#[cfg(test)]
68#[path = "ttl_tests.rs"]
69mod ttl_tests;
70
71#[cfg(test)]
72#[path = "prune_tests.rs"]
73mod prune_tests;
74
75#[cfg(test)]
76#[path = "serde_tests.rs"]
77mod serde_tests;