xdg_thumbnail/
namespace.rs1use std::fmt;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7
8use crate::{Result, ThumbnailError};
9
10static THUMBNAIL_SIZES: [ThumbnailSize; 4] = [
11 ThumbnailSize::Normal,
12 ThumbnailSize::Large,
13 ThumbnailSize::XLarge,
14 ThumbnailSize::XxLarge,
15];
16
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19#[non_exhaustive]
20pub enum CacheNamespace {
21 Size(ThumbnailSize),
23 Failure(FailureNamespace),
25}
26
27impl CacheNamespace {
28 pub(crate) fn join_under(&self, root: &Path, filename: &str) -> PathBuf {
29 match self {
30 Self::Size(size) => root.join(size.directory_name()).join(filename),
31 Self::Failure(namespace) => root.join("fail").join(namespace.as_str()).join(filename),
32 }
33 }
34
35 #[must_use]
37 pub fn relative_directory(&self) -> String {
38 match self {
39 Self::Size(size) => size.directory_name().to_owned(),
40 Self::Failure(namespace) => format!("fail/{}", namespace.as_str()),
41 }
42 }
43}
44
45impl fmt::Display for CacheNamespace {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.write_str(&self.relative_directory())
48 }
49}
50
51#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
53pub struct FailureNamespace {
54 value: String,
55}
56
57impl FailureNamespace {
58 pub fn new(value: impl Into<String>) -> Result<Self> {
65 let value = value.into();
66 if value.is_empty() || value == "." || value == ".." {
67 return Err(ThumbnailError::invalid_namespace(
68 "failure namespace must be a non-empty direct name",
69 ));
70 }
71 if !value
72 .bytes()
73 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'+' | b'-'))
74 {
75 return Err(ThumbnailError::invalid_namespace(
76 "failure namespace contains an invalid character",
77 ));
78 }
79 Ok(Self { value })
80 }
81
82 #[must_use]
84 pub fn as_str(&self) -> &str {
85 &self.value
86 }
87}
88
89impl fmt::Display for FailureNamespace {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 f.write_str(&self.value)
92 }
93}
94
95impl AsRef<str> for FailureNamespace {
96 fn as_ref(&self) -> &str {
97 self.as_str()
98 }
99}
100
101impl FromStr for FailureNamespace {
102 type Err = ThumbnailError;
103
104 fn from_str(value: &str) -> Result<Self> {
105 Self::new(value)
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
111#[non_exhaustive]
112pub enum ThumbnailSize {
113 Normal,
115 Large,
117 XLarge,
119 XxLarge,
121}
122
123impl ThumbnailSize {
124 #[must_use]
126 pub const fn directory_name(self) -> &'static str {
127 match self {
128 Self::Normal => "normal",
129 Self::Large => "large",
130 Self::XLarge => "x-large",
131 Self::XxLarge => "xx-large",
132 }
133 }
134
135 #[must_use]
137 pub const fn max_dimension(self) -> u32 {
138 match self {
139 Self::Normal => 128,
140 Self::Large => 256,
141 Self::XLarge => 512,
142 Self::XxLarge => 1024,
143 }
144 }
145
146 #[must_use]
148 pub const fn all() -> &'static [Self] {
149 &THUMBNAIL_SIZES
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::{CacheNamespace, FailureNamespace, ThumbnailSize};
156
157 #[test]
158 fn thumbnail_size_directory_names_match_standard() {
159 assert_eq!(ThumbnailSize::Normal.directory_name(), "normal");
160 assert_eq!(ThumbnailSize::Large.directory_name(), "large");
161 assert_eq!(ThumbnailSize::XLarge.directory_name(), "x-large");
162 assert_eq!(ThumbnailSize::XxLarge.directory_name(), "xx-large");
163 }
164
165 #[test]
166 fn all_thumbnail_sizes_are_in_scan_order() {
167 let names = ThumbnailSize::all()
168 .iter()
169 .copied()
170 .map(ThumbnailSize::directory_name)
171 .collect::<Vec<_>>();
172
173 assert_eq!(names, ["normal", "large", "x-large", "xx-large"]);
174 }
175
176 #[test]
177 fn cache_namespace_strings_are_relative_directories() {
178 let size = CacheNamespace::Size(ThumbnailSize::Normal);
179 let failure = CacheNamespace::Failure(FailureNamespace::new("app-1").unwrap());
180
181 assert_eq!(size.relative_directory(), "normal");
182 assert_eq!(size.to_string(), "normal");
183 assert_eq!(failure.relative_directory(), "fail/app-1");
184 assert_eq!(failure.to_string(), "fail/app-1");
185 }
186}