use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use super::helpers::copy_path_resolve::copy_path_resolve;
pub use super::{AssetDeleteOnUnlink, AssetPlan};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AssetsManager {
copied_once: BTreeSet<(PathBuf, PathBuf)>,
action_in_progress: bool,
watchers: Vec<AssetWatchPlan>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssetAction {
pub action: AssetFileAction,
pub file_path: PathBuf,
pub source_root: PathBuf,
pub out_dir: PathBuf,
pub watch_assets_mode: bool,
pub item_watch_assets: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssetFileAction {
Change,
Unlink,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssetWatchPlan {
pub glob: String,
pub exclude: Option<String>,
pub out_dir: PathBuf,
}
impl AssetsManager {
pub fn copy_assets(&mut self, assets: &[AssetPlan], source_root: impl AsRef<Path>) {
self.watchers.extend(
assets
.iter()
.filter(|asset| asset.watch_assets)
.map(|asset| AssetWatchPlan {
glob: asset.glob.clone(),
exclude: asset.exclude.clone(),
out_dir: asset.out_dir.clone(),
}),
);
let source_root = source_root.as_ref();
for asset in assets.iter().filter(|asset| !asset.watch_assets) {
let include = asset.include.as_deref().unwrap_or_else(|| Path::new(""));
let path = source_root.join(include).join(&asset.glob);
let action = AssetAction {
action: AssetFileAction::Change,
file_path: path,
source_root: source_root.to_path_buf(),
out_dir: asset.out_dir.clone(),
watch_assets_mode: false,
item_watch_assets: false,
};
let _ = self.action_on_file(&action);
}
}
pub fn action_on_file(&mut self, option: &AssetAction) -> Result<Option<PathBuf>, String> {
let key = (option.file_path.clone(), option.out_dir.clone());
let is_watch_enabled = option.watch_assets_mode || option.item_watch_assets;
if !is_watch_enabled && self.copied_once.contains(&key) {
return Ok(None);
}
self.copied_once.insert(key);
self.action_in_progress = true;
let up = option.source_root.components().count();
let dest = copy_path_resolve(
&option.file_path.to_string_lossy(),
&option.out_dir.to_string_lossy(),
up,
)?;
match option.action {
AssetFileAction::Change => {
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).map_err(|error| {
format!("Failed to create \"{}\": {error}", parent.display())
})?;
}
fs::copy(&option.file_path, &dest).map_err(|error| {
format!(
"Failed to copy \"{}\" to \"{}\": {error}",
option.file_path.display(),
dest.display()
)
})?;
}
AssetFileAction::Unlink => match fs::remove_file(&dest) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => {
return Err(format!("Failed to remove \"{}\": {error}", dest.display()));
}
},
}
Ok(Some(dest))
}
pub fn close_watchers(&mut self) -> usize {
let count = self.watchers.len();
self.watchers.clear();
self.action_in_progress = false;
count
}
pub fn watchers(&self) -> &[AssetWatchPlan] {
&self.watchers
}
pub fn action_in_progress(&self) -> bool {
self.action_in_progress
}
}