1use crate::reminder::Reminder;
2use anyhow::{Context, Result};
3use std::fs;
4use std::path::{Path, PathBuf};
5use uuid::Uuid;
6
7pub struct Storage {
8 path: PathBuf,
9}
10
11impl Storage {
12 pub fn new() -> Result<Self> {
13 let data_dir = dirs::data_local_dir()
14 .context("Failed to get local data directory")?
15 .join("reminder-cli");
16
17 fs::create_dir_all(&data_dir)?;
18
19 Ok(Self {
20 path: data_dir.join("reminders.json"),
21 })
22 }
23
24 pub fn load(&self) -> Result<Vec<Reminder>> {
25 if !self.path.exists() {
26 return Ok(Vec::new());
27 }
28
29 let content = fs::read_to_string(&self.path)
30 .context("Failed to read reminders file")?;
31
32 if content.trim().is_empty() {
33 return Ok(Vec::new());
34 }
35
36 let reminders: Vec<Reminder> = serde_json::from_str(&content)
37 .context("Failed to parse reminders JSON")?;
38
39 Ok(reminders)
40 }
41
42 pub fn save(&self, reminders: &[Reminder]) -> Result<()> {
43 let content = serde_json::to_string_pretty(reminders)
44 .context("Failed to serialize reminders")?;
45
46 fs::write(&self.path, content)
47 .context("Failed to write reminders file")?;
48
49 Ok(())
50 }
51
52 pub fn add(&self, reminder: Reminder) -> Result<()> {
53 let mut reminders = self.load()?;
54 reminders.push(reminder);
55 self.save(&reminders)
56 }
57
58 pub fn delete(&self, id: Uuid) -> Result<bool> {
59 let mut reminders = self.load()?;
60 let initial_len = reminders.len();
61 reminders.retain(|r| r.id != id);
62
63 if reminders.len() == initial_len {
64 return Ok(false);
65 }
66
67 self.save(&reminders)?;
68 Ok(true)
69 }
70
71 pub fn update(&self, id: Uuid, updater: impl FnOnce(&mut Reminder)) -> Result<bool> {
72 let mut reminders = self.load()?;
73
74 if let Some(reminder) = reminders.iter_mut().find(|r| r.id == id) {
75 updater(reminder);
76 self.save(&reminders)?;
77 Ok(true)
78 } else {
79 Ok(false)
80 }
81 }
82
83 pub fn get(&self, id: Uuid) -> Result<Option<Reminder>> {
84 let reminders = self.load()?;
85 Ok(reminders.into_iter().find(|r| r.id == id))
86 }
87
88 pub fn pid_file_path() -> Result<PathBuf> {
89 let data_dir = dirs::data_local_dir()
90 .context("Failed to get local data directory")?
91 .join("reminder-cli");
92
93 fs::create_dir_all(&data_dir)?;
94 Ok(data_dir.join("daemon.pid"))
95 }
96
97 pub fn log_file_path() -> Result<PathBuf> {
98 let data_dir = dirs::data_local_dir()
99 .context("Failed to get local data directory")?
100 .join("reminder-cli");
101
102 fs::create_dir_all(&data_dir)?;
103 Ok(data_dir.join("daemon.log"))
104 }
105
106 pub fn export_to_file(&self, path: &Path) -> Result<usize> {
108 let reminders = self.load()?;
109 let count = reminders.len();
110
111 let content = serde_json::to_string_pretty(&reminders)
112 .context("Failed to serialize reminders for export")?;
113
114 fs::write(path, content)
115 .context("Failed to write export file")?;
116
117 Ok(count)
118 }
119
120 pub fn import_from_file(&self, path: &Path, overwrite: bool) -> Result<(usize, usize)> {
123 let content = fs::read_to_string(path)
124 .context("Failed to read import file")?;
125
126 let imported: Vec<Reminder> = serde_json::from_str(&content)
127 .context("Failed to parse import JSON")?;
128
129 let mut existing = self.load()?;
130 let existing_ids: std::collections::HashSet<Uuid> =
131 existing.iter().map(|r| r.id).collect();
132
133 let mut imported_count = 0;
134 let mut skipped_count = 0;
135
136 for reminder in imported {
137 if existing_ids.contains(&reminder.id) {
138 if overwrite {
139 existing.retain(|r| r.id != reminder.id);
140 existing.push(reminder);
141 imported_count += 1;
142 } else {
143 skipped_count += 1;
144 }
145 } else {
146 existing.push(reminder);
147 imported_count += 1;
148 }
149 }
150
151 self.save(&existing)?;
152 Ok((imported_count, skipped_count))
153 }
154}