Skip to main content

draw_core/
storage.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5
6use crate::document::Document;
7
8pub fn storage_dir() -> Result<PathBuf> {
9    let dir = dirs::config_dir()
10        .context("could not determine config directory")?
11        .join("draw")
12        .join("drawings");
13    fs::create_dir_all(&dir)?;
14    Ok(dir)
15}
16
17pub fn save(doc: &Document, path: &Path) -> Result<()> {
18    let json = serde_json::to_string_pretty(doc)?;
19    if let Some(parent) = path.parent() {
20        fs::create_dir_all(parent)?;
21    }
22    fs::write(path, json)?;
23    Ok(())
24}
25
26pub fn save_to_storage(doc: &Document) -> Result<PathBuf> {
27    let dir = storage_dir()?;
28    let path = dir.join(format!("{}.draw.json", doc.id));
29    save(doc, &path)?;
30    Ok(path)
31}
32
33pub fn load(path: &Path) -> Result<Document> {
34    let json = fs::read_to_string(path).context("could not read drawing file")?;
35    let doc: Document = serde_json::from_str(&json).context("invalid drawing file")?;
36    Ok(doc)
37}
38
39pub fn list_drawings() -> Result<Vec<(String, PathBuf)>> {
40    let dir = storage_dir()?;
41    let mut drawings = vec![];
42
43    if !dir.exists() {
44        return Ok(drawings);
45    }
46
47    for entry in fs::read_dir(&dir)? {
48        let entry = entry?;
49        let path = entry.path();
50        if path.extension().and_then(|e| e.to_str()) == Some("json")
51            && path
52                .file_name()
53                .and_then(|n| n.to_str())
54                .is_some_and(|n| n.ends_with(".draw.json"))
55            && let Ok(doc) = load(&path)
56        {
57            drawings.push((doc.name, path));
58        }
59    }
60
61    drawings.sort_by(|a, b| a.0.cmp(&b.0));
62    Ok(drawings)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::document::Document;
69
70    #[test]
71    fn test_save_load_roundtrip() {
72        let dir = tempfile::tempdir().unwrap();
73        let path = dir.path().join("test.draw.json");
74
75        let doc = Document::new("test drawing".to_string());
76        save(&doc, &path).unwrap();
77
78        let loaded = load(&path).unwrap();
79        assert_eq!(doc.id, loaded.id);
80        assert_eq!(doc.name, loaded.name);
81    }
82
83    #[test]
84    fn test_save_load_with_elements() {
85        use crate::element::{Element, ShapeElement, TextElement};
86
87        let dir = tempfile::tempdir().unwrap();
88        let path = dir.path().join("elements.draw.json");
89
90        let mut doc = Document::new("with elements".to_string());
91        doc.add_element(Element::Rectangle(ShapeElement::new(
92            "r1".to_string(),
93            10.0,
94            20.0,
95            100.0,
96            50.0,
97        )));
98        doc.add_element(Element::Text(TextElement::new(
99            "t1".to_string(),
100            5.0,
101            5.0,
102            "hello\nworld".to_string(),
103        )));
104        save(&doc, &path).unwrap();
105
106        let loaded = load(&path).unwrap();
107        assert_eq!(loaded.id, doc.id);
108        assert_eq!(loaded.elements.len(), 2);
109        assert_eq!(loaded.elements[0].id(), "r1");
110        assert_eq!(loaded.elements[1].id(), "t1");
111    }
112}