prefix_file_tree/scheme/
encoding.rs1use crate::scheme::{Case, Error, Scheme};
2use data_encoding::BASE32;
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::ffi::OsStr;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub struct Base32<const N: usize> {
12 pub case: Case,
13}
14
15impl<const N: usize> Base32<N> {
16 const VALID: () = assert!(N.is_multiple_of(5), "N must be a multiple of 5 for Base32 encoding");
17
18 #[must_use]
19 pub const fn new(case: Case) -> Self {
20 let () = Self::VALID;
21 Self { case }
22 }
23}
24
25impl<const N: usize> Scheme for Base32<N> {
26 type Name = [u8; N];
27 type NameRef<'a> = [u8; N];
28
29 fn fixed_length() -> Option<usize> {
30 Some(N / 5 * 8)
31 }
32
33 fn name_to_string<'a>(&self, name: Self::NameRef<'a>) -> Cow<'a, str> {
34 BASE32.encode(&name).into()
35 }
36
37 fn cmp_prefix_part(&self, a: &OsStr, b: &OsStr) -> Result<Ordering, Error> {
38 let a_chars = a
39 .as_encoded_bytes()
40 .iter()
41 .map(|byte| Base32Char::try_from(*byte))
42 .collect::<Result<Vec<_>, _>>()?;
43
44 let b_chars = b
45 .as_encoded_bytes()
46 .iter()
47 .map(|byte| Base32Char::try_from(*byte))
48 .collect::<Result<Vec<_>, _>>()?;
49
50 Ok(a_chars.cmp(&b_chars))
51 }
52
53 fn name_from_file_stem(&self, file_stem: &OsStr) -> Result<Self::Name, Error> {
54 let () = Self::VALID;
55 let as_bytes = file_stem.as_encoded_bytes();
56
57 if as_bytes.len() == N / 5 * 8 {
58 let decoded = BASE32
59 .decode(as_bytes)
60 .map_err(|error| Error::InvalidByte(as_bytes[error.position]))?;
61
62 Ok(decoded.try_into().expect("Invalid decoded bytes length"))
63 } else {
64 Err(Error::InvalidLength(as_bytes.len()))
65 }
66 }
67}
68
69#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
70enum Base32Char {
71 Alphabetic(u8),
72 Numeric(u8),
73}
74
75impl TryFrom<u8> for Base32Char {
76 type Error = Error;
77
78 fn try_from(value: u8) -> Result<Self, Self::Error> {
79 if value.is_ascii_uppercase() {
80 Ok(Self::Alphabetic(value))
81 } else if (b'2'..=b'7').contains(&value) {
82 Ok(Self::Numeric(value))
83 } else {
84 Err(Error::InvalidByte(value))
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::Tree;
92 use std::io::Write;
93
94 #[test]
95 fn test_base32() -> Result<(), Box<dyn std::error::Error>> {
96 let base = tempfile::tempdir()?;
97 let prefix_part_lengths = vec![3, 2];
98
99 let name_1 = b"abcd_abcd_abcd_abcd_";
100 let name_2 = &[255u8; 20];
102 let name_3 = b"abcd_abcd_abcd_efgh_";
103
104 let tree = Tree::builder(base)
105 .with_scheme(crate::scheme::encoding::Base32::<20>::new(
106 crate::scheme::Case::Lower,
107 ))
108 .with_prefix_part_lengths(prefix_part_lengths)
109 .build()?;
110
111 let mut file = tree.create_file(*name_1)?.expect("Unexpected file");
112
113 file.write_all(b"foo")?;
114
115 let file = tree.create_file(*name_1)?;
116
117 assert!(file.is_none());
118
119 let mut file = tree.create_file(*name_2)?.expect("Unexpected file");
120
121 file.write_all(b"bar")?;
122
123 let mut file = tree.create_file(*name_3)?.expect("Unexpected file");
124
125 file.write_all(b"qux")?;
126
127 let entries = tree.entries().collect::<Result<Vec<_>, _>>()?;
128
129 assert!(
130 entries[0]
131 .path
132 .to_string_lossy()
133 .ends_with("/MFR/GG/MFRGGZC7MFRGGZC7MFRGGZC7MFRGGZC7")
134 );
135
136 assert_eq!(
137 entries
138 .into_iter()
139 .map(|entry| entry.name)
140 .collect::<Vec<_>>(),
141 vec![*name_1, *name_3, *name_2]
142 );
143
144 Ok(())
145 }
146}