use super::CalendarStateError;
use crate::event::EventInstanceId;
use crate::utils::atomic_write;
use std::{collections::HashSet, path::Path};
pub(crate) const KNOWN_IDS_FILE_NAME: &str = "known_event_ids";
#[derive(Debug)]
pub(crate) struct KnownEventIds(HashSet<EventInstanceId>);
impl KnownEventIds {
pub fn new() -> Self {
Self(HashSet::new())
}
pub fn iter(&self) -> impl Iterator<Item = &EventInstanceId> {
self.0.iter()
}
pub fn load(path: &Path) -> Result<Self, CalendarStateError> {
if path.is_file() {
let contents = std::fs::read_to_string(path)?;
let ids = contents
.lines()
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.map(EventInstanceId::from)
.collect::<HashSet<_>>();
Ok(Self(ids))
} else {
Ok(Self::new())
}
}
#[cfg(test)]
pub fn write(&self, path: &Path) -> Result<(), CalendarStateError> {
Self::write_from(self.0.iter(), path)
}
pub fn write_from<'a>(
event_ids: impl IntoIterator<Item = &'a EventInstanceId>,
path: &Path,
) -> Result<(), CalendarStateError> {
let mut lines = event_ids
.into_iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
lines.sort_unstable();
let contents = lines.join("\n");
atomic_write(path, contents.as_bytes())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EventTime;
use crate::event::{EventUid, RecurrenceId};
use chrono::{NaiveDate, TimeZone, Utc};
use pretty_assertions::assert_eq;
fn sample_ids() -> HashSet<EventInstanceId> {
HashSet::from([
EventInstanceId::new(
EventUid::new("t5slp0vorqgoasogqkvadjt9jj@hooli.com".to_string()),
Some(RecurrenceId::from_event_time(EventTime::DateTimeUtc(
Utc.with_ymd_and_hms(2024, 6, 25, 17, 0, 0).unwrap(),
))),
),
EventInstanceId::new(
EventUid::new("t5slp0vorqgoasogqkvadjt9jj@hooli.com".to_string()),
Some(RecurrenceId::from_event_time(EventTime::DateTimeFloating(
NaiveDate::from_ymd_opt(2024, 6, 25)
.unwrap()
.and_hms_opt(18, 0, 0)
.unwrap(),
))),
),
EventInstanceId::new(
EventUid::new("t81pd0rkq8ujaughbrjhh87svo@hooli.com".to_string()),
None,
),
])
}
fn sample_lines() -> Vec<&'static str> {
vec![
"t5slp0vorqgoasogqkvadjt9jj@hooli.com__20240625T170000Z",
"t5slp0vorqgoasogqkvadjt9jj@hooli.com__20240625T180000",
"t81pd0rkq8ujaughbrjhh87svo@hooli.com",
]
}
#[test]
fn loads_ids_from_plaintext_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("known_event_ids");
std::fs::write(&path, sample_lines().join("\n")).unwrap();
let loaded = KnownEventIds::load(&path).unwrap();
assert_eq!(loaded.0, sample_ids());
}
#[test]
fn write_replaces_existing_file_contents() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("known_event_ids");
std::fs::write(&path, "stale-id@example.com").unwrap();
KnownEventIds(sample_ids()).write(&path).unwrap();
let got = std::fs::read_to_string(&path).unwrap();
assert!(!got.contains("stale-id@example.com"));
assert!(got.contains("t81pd0rkq8ujaughbrjhh87svo@hooli.com"));
}
#[test]
fn writes_ids_to_plaintext_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("known_event_ids");
let ids = KnownEventIds(sample_ids());
ids.write(&path).unwrap();
let got = std::fs::read_to_string(&path).unwrap();
assert_eq!(got, sample_lines().join("\n"));
}
}