reminder_cli/
storage.rs

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    /// Export all reminders to a JSON file
107    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    /// Import reminders from a JSON file
121    /// Returns (imported_count, skipped_count)
122    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}