1use std::time::{Duration, SystemTime};
2
3use serde::{Deserialize, Serialize};
4
5pub mod registry;
7pub use registry::{WorkspaceRecord, WorkspaceRegistry};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub struct Ttl(#[serde(with = "humantime_serde")] Duration);
12
13impl Ttl {
14 #[must_use]
16 pub const fn new(duration: Duration) -> Self {
17 Self(duration)
18 }
19
20 #[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#[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#[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;