1use std::{path::Path, sync::Arc};
2
3use dlopen2::wrapper::WrapperApi;
4use fast_able::vec::{ReadGuard, SyncVec};
5use notify::{Event, RecursiveMode, Watcher};
6
7use crate::{
8 event::{EventNewDll, EventOldDll},
9 types::{get_file_name, Hotload, UA},
10 R,
11};
12
13pub const MAX_LOAD_INDEX: usize = 10;
14
15pub struct HotloadBatch<API: WrapperApi> {
16 inner_batch: Arc<SyncVec<Hotload<API>>>,
17 dir: String,
18 pub watch: UA<notify::RecommendedWatcher>,
19}
20
21const HOTLOAD_CACHE_DIR: &str = ".hotload_cache_dir";
40
41impl<API: WrapperApi + 'static + Send> HotloadBatch<API> {
42 pub fn new(dir_path: impl Into<String>) -> R<Self> {
43 let path = dir_path.into();
44
45 let mut batch_file = vec![];
47 let p = Path::new(&path).to_path_buf();
48 let dir_iter = std::fs::read_dir(p.clone())?;
49 for ele in dir_iter {
50 let ele = ele?;
51 if !ele.file_type()?.is_file() {
52 continue;
53 }
54 batch_file.push(ele.path());
55 }
56
57 let dir_cahe = Path::new(&path);
58 let mut dir_cahe = dir_cahe.to_path_buf();
59 dir_cahe.push(HOTLOAD_CACHE_DIR);
60 _ = std::fs::create_dir(dir_cahe.clone());
61
62 let r = SyncVec::new();
63 for ele in batch_file.iter() {
64 let p = ele.to_str().unwrap_or_else(|| "").to_string();
65 if p.is_empty() {
66 continue;
67 }
68
69 static PATHS: fast_able::vec::SyncVec<String> = fast_able::vec::SyncVec::new();
70 PATHS.push(p);
71
72 let item = Hotload::new(PATHS[PATHS.len() - 1].as_str());
73 r.push(item);
74 }
75
76 Ok(Self {
77 inner_batch: r.into(),
78 dir: path,
79 watch: UA::new(),
80 })
81 }
82
83 pub fn init_load<
87 F: FnMut(EventNewDll<API>, Option<EventOldDll<API>>) + Send + 'static + Clone,
88 >(
89 &self,
90 event_call: F,
91 ) -> R<()> {
92 for ele in self.inner_batch.iter() {
93 ele.init_load(event_call.clone())?;
94 }
95
96 let inner_batch = self.inner_batch.clone();
97 let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
98 let event = match res {
99 Ok(v) => v,
100 Err(e) => {
101 error!("watch error: {e:?}");
102 return;
103 }
104 };
105
106 let files = event
108 .paths
109 .iter()
110 .filter(|x| x.is_file())
111 .collect::<Vec<_>>();
112 if files.is_empty() {
113 return;
114 }
115
116 let mut is_action = false;
117 match &event.kind {
118 notify::EventKind::Create(_e) => is_action = true,
119 notify::EventKind::Modify(notify::event::ModifyKind::Name(
120 notify::event::RenameMode::To,
121 )) => is_action = true,
122 _ => (),
123 };
124
125 if !is_action {
126 return;
127 }
128
129 for ele in files {
130 let full_path = ele.to_str().unwrap_or_else(|| "").to_string();
131 if full_path.is_empty() {
132 continue;
133 }
134
135 let change_file_name = ele
136 .as_path()
137 .file_name()
138 .and_then(|x| x.to_str())
139 .unwrap_or_else(|| "")
140 .to_string();
141 if change_file_name.is_empty() {
142 continue;
143 }
144
145 if inner_batch
146 .iter()
147 .find(|x| {
148 get_file_name(&x.inner.path).unwrap_or_else(|_| "".to_string())
149 == change_file_name
150 })
151 .is_some()
152 {
153 continue;
154 }
155
156 debug!("event: {:?}; load new dll", event);
157
158 static PATHS: fast_able::vec::SyncVec<String> = fast_able::vec::SyncVec::new();
159 PATHS.push(full_path);
160
161 let item = Hotload::<API>::new(PATHS[PATHS.len() - 1].as_str());
162 if let Err(e) = item.init_load(event_call.clone()) {
163 error!("{e:?}; item.init_load(event_call.clone()): {change_file_name}")
164 } else {
165 inner_batch.push(item);
166 }
167 }
168 })?;
169
170 let dir = &self.dir;
172 debug!("hoload watch path: {dir:?}");
173 watcher.watch(Path::new(&dir), RecursiveMode::NonRecursive)?;
174 self.watch.set(watcher);
175
176 Ok(())
177 }
178
179 pub fn get(&self, index: usize) -> Option<ReadGuard<Hotload<API>>> {
180 self.inner_batch.get(index)
181 }
182
183 pub fn iter(&self) -> fast_able::vec::Iter<'_, Hotload<API>> {
184 self.inner_batch.iter()
185 }
186}
187
188