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}