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}