Skip to main content

chub_core/
annotations.rs

1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::config::chub_dir;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Annotation {
10    pub id: String,
11    pub note: String,
12    #[serde(rename = "updatedAt")]
13    pub updated_at: String,
14}
15
16fn annotations_dir() -> PathBuf {
17    chub_dir().join("annotations")
18}
19
20fn annotation_path(entry_id: &str) -> PathBuf {
21    let safe = entry_id.replace('/', "--");
22    annotations_dir().join(format!("{}.json", safe))
23}
24
25pub fn read_annotation(entry_id: &str) -> Option<Annotation> {
26    let path = annotation_path(entry_id);
27    fs::read_to_string(&path)
28        .ok()
29        .and_then(|s| serde_json::from_str(&s).ok())
30}
31
32pub fn write_annotation(entry_id: &str, note: &str) -> Annotation {
33    let dir = annotations_dir();
34    let _ = fs::create_dir_all(&dir);
35
36    let now = crate::build::builder::days_to_date(
37        std::time::SystemTime::now()
38            .duration_since(std::time::UNIX_EPOCH)
39            .unwrap_or_default()
40            .as_secs()
41            / 86400,
42    );
43    let secs = std::time::SystemTime::now()
44        .duration_since(std::time::UNIX_EPOCH)
45        .unwrap_or_default()
46        .as_secs();
47    let tod = secs % 86400;
48
49    let updated_at = format!(
50        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
51        now.0,
52        now.1,
53        now.2,
54        tod / 3600,
55        (tod % 3600) / 60,
56        tod % 60
57    );
58
59    let data = Annotation {
60        id: entry_id.to_string(),
61        note: note.to_string(),
62        updated_at,
63    };
64
65    let _ = fs::write(
66        annotation_path(entry_id),
67        serde_json::to_string_pretty(&data).unwrap_or_default(),
68    );
69
70    data
71}
72
73pub fn clear_annotation(entry_id: &str) -> bool {
74    fs::remove_file(annotation_path(entry_id)).is_ok()
75}
76
77pub fn list_annotations() -> Vec<Annotation> {
78    let dir = annotations_dir();
79    let files = match fs::read_dir(&dir) {
80        Ok(entries) => entries,
81        Err(_) => return vec![],
82    };
83
84    files
85        .filter_map(|e| e.ok())
86        .filter(|e| {
87            e.path()
88                .extension()
89                .map(|ext| ext == "json")
90                .unwrap_or(false)
91        })
92        .filter_map(|e| {
93            fs::read_to_string(e.path())
94                .ok()
95                .and_then(|s| serde_json::from_str::<Annotation>(&s).ok())
96        })
97        .collect()
98}