use std::cell::RefCell;
use std::collections::HashSet;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::{Path, PathBuf};
use std::sync::{OnceLock, RwLock};
thread_local! {
static THREAD_RESOURCE_ROOTS: RefCell<Vec<PathBuf>> = const { RefCell::new(Vec::new()) };
}
static REGISTERED_CLASSES: OnceLock<RwLock<HashSet<String>>> = OnceLock::new();
pub struct ResourceLoaderUtils;
impl ResourceLoaderUtils {
pub fn with_thread_resource_roots<T>(
resource_roots: Vec<PathBuf>,
operation: impl FnOnce() -> T,
) -> T {
struct RestoreThreadRoots(Option<Vec<PathBuf>>);
impl Drop for RestoreThreadRoots {
fn drop(&mut self) {
if let Some(previous) = self.0.take() {
THREAD_RESOURCE_ROOTS.with(|roots| {
roots.replace(previous);
});
}
}
}
let previous = THREAD_RESOURCE_ROOTS.with(|roots| roots.replace(resource_roots));
let _restore = RestoreThreadRoots(Some(previous));
operation()
}
#[must_use]
pub fn get_resource_roots() -> Vec<PathBuf> {
let mut roots = Vec::new();
THREAD_RESOURCE_ROOTS.with(|thread_roots| {
for root in thread_roots.borrow().iter() {
push_unique(&mut roots, root);
}
});
push_unique(&mut roots, Path::new(env!("CARGO_MANIFEST_DIR")));
if let Ok(executable) = std::env::current_exe()
&& let Some(parent) = executable.parent()
{
push_unique(&mut roots, parent);
}
if let Ok(current_directory) = std::env::current_dir() {
push_unique(&mut roots, ¤t_directory);
}
roots
}
pub fn register_class(class_name: impl Into<String>) {
let mut classes = registered_classes()
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
classes.insert(class_name.into());
}
pub fn load_class(class_name: &str) -> io::Result<String> {
Self::find_class(class_name).ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!("Could not locate runtime class '{class_name}'"),
)
})
}
#[must_use]
pub fn find_class(class_name: &str) -> Option<String> {
let classes = registered_classes()
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
classes.get(class_name).cloned()
}
#[must_use]
pub fn is_class_present(class_name: &str) -> bool {
Self::find_class(class_name).is_some()
}
#[must_use]
pub fn find_resource(resource_name: &str) -> Option<PathBuf> {
let resource_name = resource_name.strip_prefix('/').unwrap_or(resource_name);
Self::get_resource_roots()
.into_iter()
.map(|root| root.join(resource_name))
.find(|candidate| candidate.is_file())
}
#[must_use]
pub fn is_resource_present(resource_name: &str) -> bool {
Self::find_resource(resource_name).is_some()
}
pub fn load_resource_as_stream(resource_name: &str) -> io::Result<Box<dyn Read>> {
Self::find_resource_as_stream(resource_name)?.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"Could not locate resource '{resource_name}' in the application's resource path"
),
)
})
}
pub fn find_resource_as_stream(resource_name: &str) -> io::Result<Option<Box<dyn Read>>> {
let Some(resource_path) = Self::find_resource(resource_name) else {
return Ok(None);
};
let reader = BufReader::new(File::open(resource_path)?);
Ok(Some(Box::new(reader)))
}
}
fn registered_classes() -> &'static RwLock<HashSet<String>> {
REGISTERED_CLASSES.get_or_init(|| RwLock::new(HashSet::new()))
}
fn push_unique(roots: &mut Vec<PathBuf>, root: &Path) {
let root = root.to_path_buf();
if !roots.contains(&root) {
roots.push(root);
}
}