hydrate_base/
uuid_path.rs

1use std::path::{Path, PathBuf};
2use uuid::Uuid;
3
4/// Converts a UUID to a path starting at the given root and with the given extension
5/// Example: /2/d/2d4154f72b3c422387677e8d1fa70447.af
6pub fn uuid_to_path(
7    root: &Path,
8    uuid: Uuid,
9    extension: &str,
10) -> PathBuf {
11    // Convert UUID to a 32-character hex string (no hyphens)
12    // example: 8cf25195abd839981ea3c93c8fd2843f
13    let mut buffer = [0; 32];
14    let encoded = uuid.to_simple().encode_lower(&mut buffer).to_string();
15    // Produce path like [root]/8/c/8cf25195abd839981ea3c93c8fd2843f
16    root.join(&encoded[0..1]).join(&encoded[1..2]).join(format!(
17        "{}.{}",
18        &encoded[0..32],
19        extension
20    ))
21}
22
23/// Converts a UUID to a path starting at the given root and with the given extension
24/// and appends a u64 hash of the contents
25/// Example: /2/d/2d41f453d6224b2fab9bc8021a6c7dde-45647afbadf0c93d.bf
26pub fn uuid_and_hash_to_path(
27    root: &Path,
28    uuid: Uuid,
29    hash: u64,
30    extension: &str,
31) -> PathBuf {
32    // Convert UUID to a 32-character hex string (no hyphens)
33    // example: 8cf25195abd839981ea3c93c8fd2843f
34    let mut buffer = [0; 32];
35    let uuid_encoded = uuid.to_simple().encode_lower(&mut buffer).to_string();
36
37    // Produce path like [root]/8/c/8cf25195abd839981ea3c93c8fd2843f
38    root.join(&uuid_encoded[0..1])
39        .join(&uuid_encoded[1..2])
40        .join(format!("{}-{:x}.{}", &uuid_encoded[0..32], hash, extension))
41}
42
43/// Converts a path within a root to a UUID
44pub fn path_to_uuid(
45    root: &Path,
46    file_path: &Path,
47) -> Option<Uuid> {
48    // Remove root from the path
49    let relative_path_from_root = file_path.strip_prefix(root).ok()?;
50
51    // Split the path by directory paths
52    let components: Vec<_> = relative_path_from_root.components().collect();
53
54    let mut filename = components[2].as_os_str().to_str().unwrap();
55
56    if components.len() != 3 {
57        return None;
58    }
59
60    if components[0].as_os_str().to_str().unwrap().as_bytes()[0] != filename.as_bytes()[0] {
61        return None;
62    }
63
64    if components[1].as_os_str().to_str().unwrap().as_bytes()[0] != filename.as_bytes()[1] {
65        return None;
66    }
67
68    // Remove the extension
69    if let Some(extension_begin) = filename.find('.') {
70        filename = filename.strip_suffix(&filename[extension_begin..]).unwrap();
71    }
72
73    u128::from_str_radix(&filename, 16)
74        .ok()
75        .map(|x| Uuid::from_u128(x))
76}
77
78/// Converts a path within a root to a UUID + u64 hash
79pub fn path_to_uuid_and_hash(
80    root: &Path,
81    file_path: &Path,
82) -> Option<(Uuid, u64)> {
83    // Remove root from the path
84    let relative_path_from_root = file_path.strip_prefix(root).ok()?;
85
86    // Split the path by directory paths
87    let components: Vec<_> = relative_path_from_root.components().collect();
88
89    let mut filename = components[2].as_os_str().to_str().unwrap();
90
91    if components.len() != 3 {
92        return None;
93    }
94
95    if components[0].as_os_str().to_str().unwrap().as_bytes()[0] != filename.as_bytes()[0] {
96        return None;
97    }
98
99    if components[1].as_os_str().to_str().unwrap().as_bytes()[0] != filename.as_bytes()[1] {
100        return None;
101    }
102
103    // Remove the extension
104    if let Some(extension_begin) = filename.find('.') {
105        filename = filename.strip_suffix(&filename[extension_begin..]).unwrap();
106    }
107
108    if let Some(hash_begin) = filename.find('-') {
109        let hash = u64::from_str_radix(&filename[(hash_begin + 1)..], 16).ok()?;
110
111        let uuid = u128::from_str_radix(&filename[0..hash_begin], 16)
112            .ok()
113            .map(|x| Uuid::from_u128(x))?;
114
115        Some((uuid, hash))
116    } else {
117        None
118    }
119}