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> {
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
22pub 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
35pub 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
46pub 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
56pub 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}