use core::hash::{Hash, Hasher};
use alloc::{
format,
string::{String, ToString},
};
use base64::{Engine, engine::general_purpose::URL_SAFE};
use thiserror::Error;
use crate::{entry::utils::write_checksum, m2dir::utils::extract_date, path::M2dirPath};
const EPOCH_DATE: &str = "1970-01-01T00:00:00Z";
pub const DOT_M2DIR: &str = ".m2dir";
pub const META: &str = ".meta";
#[derive(Clone, Debug, Error)]
pub enum LoadM2dirError {
#[error("path {0} is not a directory")]
NotDir(M2dirPath),
#[error("no valid `.m2dir` marker found in directory {0}")]
NoDotM2dir(M2dirPath),
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct M2dir {
path: M2dirPath,
}
impl M2dir {
pub fn from_path(path: impl Into<M2dirPath>) -> Self {
Self { path: path.into() }
}
pub fn path(&self) -> &M2dirPath {
&self.path
}
pub fn marker_path(&self) -> M2dirPath {
self.path.join(DOT_M2DIR)
}
pub fn meta_dir(&self) -> M2dirPath {
self.path.join(META)
}
pub fn flags_path(&self, id: &str) -> M2dirPath {
self.meta_dir().join(&format!("{id}.flags"))
}
pub fn entry_path(&self, bytes: &[u8], nonce_bytes: &[u8]) -> (String, M2dirPath) {
let mut checksum = String::new();
write_checksum(bytes, &mut checksum).expect("base64 encoding to a string is always valid");
let dt = extract_date(bytes).unwrap_or_else(|| EPOCH_DATE.to_string());
let nonce = URL_SAFE.encode(nonce_bytes);
let id = format!("{checksum}.{nonce}");
let filename = format!("{dt},{id}");
let path = self.path.join(&filename);
(id, path)
}
pub fn tmp_path(&self, pid: u32, counter: u32) -> M2dirPath {
self.path.join(&format!(".m2dir.tmp.{pid:x}{counter:x}"))
}
pub fn parse_filename_id(filename: &str) -> Option<&str> {
let (_, id) = filename.rsplit_once(',')?;
Some(id)
}
}
impl Hash for M2dir {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
impl AsRef<M2dirPath> for M2dir {
fn as_ref(&self) -> &M2dirPath {
&self.path
}
}
impl AsRef<str> for M2dir {
fn as_ref(&self) -> &str {
self.path.as_str()
}
}
impl From<M2dirPath> for M2dir {
fn from(path: M2dirPath) -> Self {
Self { path }
}
}
impl From<String> for M2dir {
fn from(path: String) -> Self {
Self { path: path.into() }
}
}
impl From<&str> for M2dir {
fn from(path: &str) -> Self {
Self {
path: path.to_string().into(),
}
}
}