1#![allow(clippy::doc_lazy_continuation)]
2
3use std::collections::BTreeMap;
4use std::path::Path;
5
6use mime::Mime;
7use serde::{Deserialize, Serialize};
8
9use crate::crypto::Secret;
10use crate::linked_data::{BlockEncoded, DagCborCodec, Link, LinkedData};
11
12use super::maybe_mime::MaybeMime;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct Data {
31 mime: MaybeMime,
36 metadata: Option<BTreeMap<String, LinkedData>>,
39}
40
41impl Default for Data {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl Data {
48 pub fn new() -> Self {
50 Self {
51 mime: MaybeMime(None),
52 metadata: None,
53 }
54 }
55
56 pub fn from_path(path: &Path) -> Self {
58 let mime = Self::detect_mime_from_path(path);
59 let metadata = BTreeMap::new();
60
61 Self {
62 mime: MaybeMime(mime),
63 metadata: if metadata.is_empty() {
64 None
65 } else {
66 Some(metadata)
67 },
68 }
69 }
70
71 fn detect_mime_from_path(path: &Path) -> Option<Mime> {
73 let extension = path.extension()?.to_str()?.to_lowercase();
74
75 let mime_str = match extension.as_str() {
77 "txt" => "text/plain",
79 "html" | "htm" => "text/html",
80 "css" => "text/css",
81 "js" | "mjs" => "application/javascript",
82 "json" => "application/json",
83 "xml" => "application/xml",
84 "md" | "markdown" => "text/markdown",
85 "csv" => "text/csv",
86 "yaml" | "yml" => "application/x-yaml",
87 "toml" => "application/toml",
88
89 "jpg" | "jpeg" => "image/jpeg",
91 "png" => "image/png",
92 "gif" => "image/gif",
93 "bmp" => "image/bmp",
94 "svg" => "image/svg+xml",
95 "webp" => "image/webp",
96 "ico" => "image/x-icon",
97
98 "mp4" => "video/mp4",
100 "webm" => "video/webm",
101 "avi" => "video/x-msvideo",
102 "mkv" => "video/x-matroska",
103 "mov" => "video/quicktime",
104
105 "mp3" => "audio/mpeg",
107 "wav" => "audio/wav",
108 "ogg" => "audio/ogg",
109 "flac" => "audio/flac",
110 "aac" => "audio/aac",
111
112 "zip" => "application/zip",
114 "tar" => "application/x-tar",
115 "gz" | "gzip" => "application/gzip",
116 "7z" => "application/x-7z-compressed",
117 "rar" => "application/x-rar-compressed",
118
119 "pdf" => "application/pdf",
121 "doc" => "application/msword",
122 "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
123 "xls" => "application/vnd.ms-excel",
124 "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
125 "ppt" => "application/vnd.ms-powerpoint",
126 "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
127
128 "rs" => "text/rust",
130 "py" => "text/x-python",
131 "c" => "text/x-c",
132 "cpp" | "cc" | "cxx" => "text/x-c++",
133 "h" | "hpp" => "text/x-c-header",
134 "java" => "text/x-java",
135 "go" => "text/x-go",
136 "sh" => "application/x-sh",
137
138 "ttf" => "font/ttf",
140 "otf" => "font/otf",
141 "woff" => "font/woff",
142 "woff2" => "font/woff2",
143
144 _ => "application/octet-stream",
145 };
146
147 mime_str.parse().ok()
148 }
149
150 pub fn set_metadata(&mut self, key: String, value: LinkedData) {
152 if let Some(ref mut metadata) = self.metadata {
153 metadata.insert(key, value);
154 } else {
155 let mut metadata = BTreeMap::new();
156 metadata.insert(key, value);
157 self.metadata = Some(metadata);
158 }
159 }
160
161 pub fn mime(&self) -> Option<&Mime> {
163 self.mime.0.as_ref()
164 }
165
166 pub fn metadata(&self) -> Option<&BTreeMap<String, LinkedData>> {
168 self.metadata.as_ref()
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175pub enum NodeLink {
176 Data(Link, Secret, Data),
177 Dir(Link, Secret),
178}
179
180impl NodeLink {
181 pub fn new_data_from_path(link: Link, secret: Secret, path: &Path) -> Self {
183 let data = Data::from_path(path);
184 NodeLink::Data(link, secret, data)
185 }
186
187 pub fn new_data(link: Link, secret: Secret) -> Self {
189 NodeLink::Data(link, secret, Data::new())
190 }
191
192 pub fn new_dir(link: Link, secret: Secret) -> Self {
194 NodeLink::Dir(link, secret)
195 }
196
197 pub fn link(&self) -> &Link {
198 match self {
199 NodeLink::Data(link, _, _) => link,
200 NodeLink::Dir(link, _) => link,
201 }
202 }
203
204 pub fn secret(&self) -> &Secret {
205 match self {
206 NodeLink::Data(_, secret, _) => secret,
207 NodeLink::Dir(_, secret) => secret,
208 }
209 }
210
211 pub fn data(&self) -> Option<&Data> {
213 match self {
214 NodeLink::Data(_, _, data) => Some(data),
215 NodeLink::Dir(_, _) => None,
216 }
217 }
218
219 pub fn is_dir(&self) -> bool {
221 matches!(self, NodeLink::Dir(_, _))
222 }
223
224 pub fn is_data(&self) -> bool {
226 matches!(self, NodeLink::Data(_, _, _))
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
234pub struct Node {
235 links: BTreeMap<String, NodeLink>,
236}
237
238#[derive(Debug, thiserror::Error)]
239pub enum NodeError {
240 #[error("link not found")]
241 LinkNotFound(String),
242}
243
244impl BlockEncoded<DagCborCodec> for Node {}
245
246impl Node {
247 pub fn new() -> Self {
248 Node {
249 links: BTreeMap::new(),
250 }
251 }
252
253 pub fn get_link(&self, name: &str) -> Option<&NodeLink> {
254 self.links.get(name)
255 }
256
257 pub fn insert(&mut self, name: String, link: NodeLink) -> Option<NodeLink> {
258 self.links.insert(name, link)
259 }
260
261 pub fn get_links(&self) -> &BTreeMap<String, NodeLink> {
262 &self.links
263 }
264
265 pub fn del(&mut self, name: &str) -> Option<NodeLink> {
266 self.links.remove(name)
268 }
269
270 pub fn size(&self) -> usize {
271 self.links.len()
272 }
273}
274
275#[cfg(test)]
276mod test {
277 use super::*;
278
279 #[test]
280 fn test_node_encode_decode() {
281 let mut node = Node::default();
282 node.links.insert(
283 "example".to_string(),
284 NodeLink::Data(
285 Link::default(),
286 Secret::default(),
287 Data {
288 metadata: None,
289 mime: MaybeMime(None),
290 },
291 ),
292 );
293
294 let encoded = node.encode().unwrap();
295 let decoded = Node::decode(&encoded).unwrap();
296
297 assert_eq!(node, decoded);
298 }
299
300 #[test]
301 fn test_data_from_path() {
302 use std::path::PathBuf;
303
304 let path = PathBuf::from("/test/file.json");
306 let data = Data::from_path(&path);
307 assert_eq!(data.mime().map(|m| m.as_ref()), Some("application/json"));
308
309 let path = PathBuf::from("/src/main.rs");
311 let data = Data::from_path(&path);
312 assert_eq!(data.mime().map(|m| m.as_ref()), Some("text/rust"));
313
314 let path = PathBuf::from("/test/file.xyz");
316 let data = Data::from_path(&path);
317 assert_eq!(
318 data.mime().map(|m| m.as_ref()),
319 Some("application/octet-stream")
320 );
321
322 let path = PathBuf::from("/test/README");
324 let data = Data::from_path(&path);
325 assert_eq!(data.mime(), None);
326 }
327
328 #[test]
329 fn test_node_link_constructors() {
330 use std::path::PathBuf;
331
332 let link = Link::default();
333 let secret = Secret::default();
334 let path = PathBuf::from("/test/image.png");
335
336 let node_link = NodeLink::new_data_from_path(link.clone(), secret.clone(), &path);
338 assert!(node_link.is_data());
339 assert!(!node_link.is_dir());
340 let data = node_link.data().unwrap();
341 assert_eq!(data.mime().map(|m| m.as_ref()), Some("image/png"));
342
343 let node_link = NodeLink::new_data(link.clone(), secret.clone());
345 assert!(node_link.is_data());
346 let data = node_link.data().unwrap();
347 assert_eq!(data.mime(), None);
348 assert_eq!(data.metadata(), None);
349
350 let node_link = NodeLink::new_dir(link.clone(), secret.clone());
352 assert!(node_link.is_dir());
353 assert!(!node_link.is_data());
354 assert!(node_link.data().is_none());
355 }
356}