1use core::hash::{Hash, Hasher};
4
5use alloc::{
6 format,
7 string::{String, ToString},
8};
9
10use base64::{Engine, engine::general_purpose::URL_SAFE};
11use thiserror::Error;
12
13use crate::{entry::utils::write_checksum, m2dir::utils::extract_date, path::M2dirPath};
14
15const EPOCH_DATE: &str = "1970-01-01T00:00:00Z";
19
20pub const DOT_M2DIR: &str = ".m2dir";
22
23pub const META: &str = ".meta";
25
26#[derive(Clone, Debug, Error)]
28pub enum LoadM2dirError {
29 #[error("path {0} is not a directory")]
31 NotDir(M2dirPath),
32
33 #[error("no valid `.m2dir` marker found in directory {0}")]
35 NoDotM2dir(M2dirPath),
36}
37
38#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
43pub struct M2dir {
44 path: M2dirPath,
45}
46
47impl M2dir {
48 pub fn from_path(path: impl Into<M2dirPath>) -> Self {
50 Self { path: path.into() }
51 }
52
53 pub fn path(&self) -> &M2dirPath {
55 &self.path
56 }
57
58 pub fn marker_path(&self) -> M2dirPath {
60 self.path.join(DOT_M2DIR)
61 }
62
63 pub fn meta_dir(&self) -> M2dirPath {
65 self.path.join(META)
66 }
67
68 pub fn flags_path(&self, id: &str) -> M2dirPath {
71 self.meta_dir().join(&format!("{id}.flags"))
72 }
73
74 pub fn entry_path(&self, bytes: &[u8], nonce_bytes: &[u8]) -> (String, M2dirPath) {
81 let mut checksum = String::new();
82 write_checksum(bytes, &mut checksum).expect("base64 encoding to a string is always valid");
83
84 let dt = extract_date(bytes).unwrap_or_else(|| EPOCH_DATE.to_string());
85
86 let nonce = URL_SAFE.encode(nonce_bytes);
87
88 let id = format!("{checksum}.{nonce}");
89 let filename = format!("{dt},{id}");
90 let path = self.path.join(&filename);
91
92 (id, path)
93 }
94
95 pub fn tmp_path(&self, pid: u32, counter: u32) -> M2dirPath {
98 self.path.join(&format!(".m2dir.tmp.{pid:x}{counter:x}"))
99 }
100
101 pub fn parse_filename_id(filename: &str) -> Option<&str> {
104 let (_, id) = filename.rsplit_once(',')?;
105 Some(id)
106 }
107}
108
109impl Hash for M2dir {
110 fn hash<H: Hasher>(&self, state: &mut H) {
111 self.path.hash(state);
112 }
113}
114
115impl AsRef<M2dirPath> for M2dir {
116 fn as_ref(&self) -> &M2dirPath {
117 &self.path
118 }
119}
120
121impl AsRef<str> for M2dir {
122 fn as_ref(&self) -> &str {
123 self.path.as_str()
124 }
125}
126
127impl From<M2dirPath> for M2dir {
128 fn from(path: M2dirPath) -> Self {
129 Self { path }
130 }
131}
132
133impl From<String> for M2dir {
134 fn from(path: String) -> Self {
135 Self { path: path.into() }
136 }
137}
138
139impl From<&str> for M2dir {
140 fn from(path: &str) -> Self {
141 Self {
142 path: path.to_string().into(),
143 }
144 }
145}