1pub mod create;
9pub mod delete;
10pub mod list;
11
12mod date;
13#[cfg(not(feature = "client"))]
14mod parse;
15
16use core::hash::{Hash, Hasher};
17
18use alloc::{
19 format,
20 string::{String, ToString},
21};
22
23use base64::{Engine, engine::general_purpose::URL_SAFE};
24use thiserror::Error;
25
26use crate::{checksum::write_checksum, m2dir::date::extract_date, path::M2dirPath};
27
28const EPOCH_DATE: &str = "1970-01-01T00:00:00Z";
32
33pub(crate) const DOT_M2DIR: &str = ".m2dir";
35
36pub(crate) const META: &str = ".meta";
38
39#[derive(Clone, Debug, Error)]
41pub enum M2dirLoadError {
42 #[error("path {0} is not a directory")]
44 NotDir(M2dirPath),
45 #[error("no valid `.m2dir` marker found in directory {0}")]
47 NoDotM2dir(M2dirPath),
48}
49
50#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
55pub struct M2dir {
56 path: M2dirPath,
57}
58
59impl M2dir {
60 pub fn from_path(path: impl Into<M2dirPath>) -> Self {
62 Self { path: path.into() }
63 }
64
65 pub fn path(&self) -> &M2dirPath {
67 &self.path
68 }
69
70 pub fn marker_path(&self) -> M2dirPath {
72 self.path.join(DOT_M2DIR)
73 }
74
75 pub fn meta_dir(&self) -> M2dirPath {
77 self.path.join(META)
78 }
79
80 pub fn flags_path(&self, id: &str) -> M2dirPath {
83 self.meta_dir().join(&format!("{id}.flags"))
84 }
85
86 pub fn entry_path(&self, bytes: &[u8], nonce_bytes: &[u8]) -> (String, M2dirPath) {
93 let mut checksum = String::new();
94 write_checksum(bytes, &mut checksum).expect("base64 encoding to a string is always valid");
95
96 let dt = extract_date(bytes).unwrap_or_else(|| EPOCH_DATE.to_string());
97
98 let nonce = URL_SAFE.encode(nonce_bytes);
99
100 let id = format!("{checksum}.{nonce}");
101 let filename = format!("{dt},{id}");
102 let path = self.path.join(&filename);
103
104 (id, path)
105 }
106
107 pub fn tmp_path(&self, pid: u32, counter: u32) -> M2dirPath {
110 self.path.join(&format!(".m2dir.tmp.{pid:x}{counter:x}"))
111 }
112
113 pub fn parse_filename_id(filename: &str) -> Option<&str> {
116 let (_, id) = filename.rsplit_once(',')?;
117 Some(id)
118 }
119}
120
121impl Hash for M2dir {
122 fn hash<H: Hasher>(&self, state: &mut H) {
123 self.path.hash(state);
124 }
125}
126
127impl AsRef<M2dirPath> for M2dir {
128 fn as_ref(&self) -> &M2dirPath {
129 &self.path
130 }
131}
132
133impl AsRef<str> for M2dir {
134 fn as_ref(&self) -> &str {
135 self.path.as_str()
136 }
137}
138
139impl From<M2dirPath> for M2dir {
140 fn from(path: M2dirPath) -> Self {
141 Self { path }
142 }
143}
144
145impl From<String> for M2dir {
146 fn from(path: String) -> Self {
147 Self { path: path.into() }
148 }
149}
150
151impl From<&str> for M2dir {
152 fn from(path: &str) -> Self {
153 Self {
154 path: path.to_string().into(),
155 }
156 }
157}