cli/lib/compiler/
assets_manager.rs1use std::collections::BTreeSet;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7use super::helpers::copy_path_resolve::copy_path_resolve;
8pub use super::{AssetDeleteOnUnlink, AssetPlan};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct AssetsManager {
12 copied_once: BTreeSet<(PathBuf, PathBuf)>,
13 action_in_progress: bool,
14 watchers: Vec<AssetWatchPlan>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct AssetAction {
19 pub action: AssetFileAction,
20 pub file_path: PathBuf,
21 pub source_root: PathBuf,
22 pub out_dir: PathBuf,
23 pub watch_assets_mode: bool,
24 pub item_watch_assets: bool,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum AssetFileAction {
29 Change,
30 Unlink,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct AssetWatchPlan {
35 pub glob: String,
36 pub exclude: Option<String>,
37 pub out_dir: PathBuf,
38}
39
40impl AssetsManager {
41 pub fn copy_assets(&mut self, assets: &[AssetPlan], source_root: impl AsRef<Path>) {
42 self.watchers.extend(
43 assets
44 .iter()
45 .filter(|asset| asset.watch_assets)
46 .map(|asset| AssetWatchPlan {
47 glob: asset.glob.clone(),
48 exclude: asset.exclude.clone(),
49 out_dir: asset.out_dir.clone(),
50 }),
51 );
52 let source_root = source_root.as_ref();
53 for asset in assets.iter().filter(|asset| !asset.watch_assets) {
54 let include = asset.include.as_deref().unwrap_or_else(|| Path::new(""));
55 let path = source_root.join(include).join(&asset.glob);
56 let action = AssetAction {
57 action: AssetFileAction::Change,
58 file_path: path,
59 source_root: source_root.to_path_buf(),
60 out_dir: asset.out_dir.clone(),
61 watch_assets_mode: false,
62 item_watch_assets: false,
63 };
64 let _ = self.action_on_file(&action);
65 }
66 }
67
68 pub fn action_on_file(&mut self, option: &AssetAction) -> Result<Option<PathBuf>, String> {
69 let key = (option.file_path.clone(), option.out_dir.clone());
70 let is_watch_enabled = option.watch_assets_mode || option.item_watch_assets;
71 if !is_watch_enabled && self.copied_once.contains(&key) {
72 return Ok(None);
73 }
74 self.copied_once.insert(key);
75 self.action_in_progress = true;
76
77 let up = option.source_root.components().count();
78 let dest = copy_path_resolve(
79 &option.file_path.to_string_lossy(),
80 &option.out_dir.to_string_lossy(),
81 up,
82 )?;
83
84 match option.action {
85 AssetFileAction::Change => {
86 if let Some(parent) = dest.parent() {
87 fs::create_dir_all(parent).map_err(|error| {
88 format!("Failed to create \"{}\": {error}", parent.display())
89 })?;
90 }
91 fs::copy(&option.file_path, &dest).map_err(|error| {
92 format!(
93 "Failed to copy \"{}\" to \"{}\": {error}",
94 option.file_path.display(),
95 dest.display()
96 )
97 })?;
98 }
99 AssetFileAction::Unlink => match fs::remove_file(&dest) {
100 Ok(()) => {}
101 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
102 Err(error) => {
103 return Err(format!("Failed to remove \"{}\": {error}", dest.display()));
104 }
105 },
106 }
107 Ok(Some(dest))
108 }
109
110 pub fn close_watchers(&mut self) -> usize {
111 let count = self.watchers.len();
112 self.watchers.clear();
113 self.action_in_progress = false;
114 count
115 }
116
117 pub fn watchers(&self) -> &[AssetWatchPlan] {
118 &self.watchers
119 }
120
121 pub fn action_in_progress(&self) -> bool {
122 self.action_in_progress
123 }
124}