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
8/// Return the directory where drawings are stored, creating it if needed.
9///
10/// # Errors
11/// Returns an error if the platform config directory cannot be determined or
12/// the directory cannot be created.
13pub fn storage_dir() -> Result<PathBuf> {
14    let dir = dirs::config_dir()
15        .context("could not determine config directory")?
16        .join("draw")
17        .join("drawings");
18    fs::create_dir_all(&dir)?;
19    Ok(dir)
20}
21
22/// Serialize a document to `path` as pretty-printed JSON.
23///
24/// # Errors
25/// Returns an error if JSON serialization fails or the file cannot be written.
26pub fn save(doc: &Document, path: &Path) -> Result<()> {
27    let json = serde_json::to_string_pretty(doc)?;
28    if let Some(parent) = path.parent() {
29        fs::create_dir_all(parent)?;
30    }
31    fs::write(path, json)?;
32    Ok(())
33}
34
35/// Save a document under [`storage_dir`] as `<doc.id>.draw.json`.
36///
37/// # Errors
38/// Returns an error if the storage directory cannot be resolved or writing fails.
39pub fn save_to_storage(doc: &Document) -> Result<PathBuf> {
40    let dir = storage_dir()?;
41    let path = dir.join(format!("{}.draw.json", doc.id));
42    save(doc, &path)?;
43    Ok(path)
44}
45
46/// Load a document from a `.draw.json` file.
47///
48/// # Errors
49/// Returns an error if the file cannot be read or the JSON is malformed.
50pub fn load(path: &Path) -> Result<Document> {
51    let json = fs::read_to_string(path).context("could not read drawing file")?;
52    let doc: Document = serde_json::from_str(&json).context("invalid drawing file")?;
53    Ok(doc)
54}
55
56/// List all drawings in [`storage_dir`] as `(name, path)` tuples, sorted by name.
57///
58/// # Errors
59/// Returns an error if the storage directory cannot be resolved or read.
60pub fn list_drawings() -> Result<Vec<(String, PathBuf)>> {
61    let dir = storage_dir()?;
62    let mut drawings = vec![];
63
64    if !dir.exists() {
65        return Ok(drawings);
66    }
67
68    for entry in fs::read_dir(&dir)? {
69        let entry = entry?;
70        let path = entry.path();
71        if path.extension().and_then(|e| e.to_str()) == Some("json")
72            && path
73                .file_name()
74                .and_then(|n| n.to_str())
75                .is_some_and(|n| n.ends_with(".draw.json"))
76            && let Ok(doc) = load(&path)
77        {
78            drawings.push((doc.name, path));
79        }
80    }
81
82    drawings.sort_by(|a, b| a.0.cmp(&b.0));
83    Ok(drawings)
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::document::Document;
90
91    #[test]
92    fn test_save_load_roundtrip() {
93        let dir = tempfile::tempdir().unwrap();
94        let path = dir.path().join("test.draw.json");
95
96        let doc = Document::new("test drawing".to_string());
97        save(&doc, &path).unwrap();
98
99        let loaded = load(&path).unwrap();
100        assert_eq!(doc.id, loaded.id);
101        assert_eq!(doc.name, loaded.name);
102    }
103
104    #[test]
105    fn test_save_load_with_elements() {
106        use crate::element::{Element, ShapeElement, TextElement};
107
108        let dir = tempfile::tempdir().unwrap();
109        let path = dir.path().join("elements.draw.json");
110
111        let mut doc = Document::new("with elements".to_string());
112        doc.add_element(Element::Rectangle(ShapeElement::new(
113            "r1".to_string(),
114            10.0,
115            20.0,
116            100.0,
117            50.0,
118        )));
119        doc.add_element(Element::Text(TextElement::new(
120            "t1".to_string(),
121            5.0,
122            5.0,
123            "hello\nworld".to_string(),
124        )));
125        save(&doc, &path).unwrap();
126
127        let loaded = load(&path).unwrap();
128        assert_eq!(loaded.id, doc.id);
129        assert_eq!(loaded.elements.len(), 2);
130        assert_eq!(loaded.elements[0].id(), "r1");
131        assert_eq!(loaded.elements[1].id(), "t1");
132    }
133}