use std::path::PathBuf;
use std::sync::RwLock;
pub const SPOOL_FILE_PREFIX: &str = "webx-upload-";
const MAX_FILE_NAME_LEN: usize = 200;
static SPOOL_ROOT: RwLock<Option<PathBuf>> = RwLock::new(None);
pub fn set_spool_root(path: impl Into<PathBuf>) {
let mut guard = SPOOL_ROOT
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*guard = Some(path.into());
}
pub fn spool_root() -> PathBuf {
let guard = SPOOL_ROOT
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if let Some(root) = guard.as_ref() {
return root.clone();
}
std::env::temp_dir().join("webx-uploads")
}
pub fn sanitize_file_name(raw: &str) -> String {
let base = raw.rsplit(['/', '\\']).next().unwrap_or("");
let mut cleaned = String::with_capacity(base.len());
for ch in base.chars() {
if ch.is_control() {
continue;
}
match ch {
'<' | '>' | ':' | '"' | '|' | '?' | '*' => cleaned.push('_'),
_ => cleaned.push(ch),
}
}
let trimmed = cleaned.trim().trim_end_matches(['.', ' ']);
let candidate = if trimmed.is_empty() { "file" } else { trimmed };
truncate_preserving_extension(candidate, MAX_FILE_NAME_LEN)
}
fn truncate_preserving_extension(name: &str, max: usize) -> String {
if name.len() <= max {
return name.to_string();
}
match name.rfind('.') {
Some(idx) if idx > 0 && name.len() - idx <= 16 => {
let ext = &name[idx..];
let stem = truncate_chars(&name[..idx], max.saturating_sub(ext.len()));
format!("{stem}{ext}")
}
_ => truncate_chars(name, max),
}
}
fn truncate_chars(s: &str, max: usize) -> String {
if s.len() <= max {
return s.to_string();
}
let mut end = max;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
s[..end].to_string()
}