use std::{path::Path, sync::Arc};
use dlopen2::wrapper::WrapperApi;
use fast_able::vec::{SyncVec};
use notify::{Event, RecursiveMode, Watcher};
use crate::{
event::{EventNewDll, EventOldDll},
types::{get_file_name, Hotload, UA},
R,
};
pub const MAX_LOAD_INDEX: usize = 10;
pub struct HotloadBatch<API: WrapperApi> {
inner_batch: Arc<SyncVec<Hotload<API>>>,
dir: String,
pub watch: UA<notify::RecommendedWatcher>,
}
const HOTLOAD_CACHE_DIR: &str = ".hotload_cache_dir";
impl<API: WrapperApi + 'static + Send> HotloadBatch<API> {
pub fn new(dir_path: impl Into<String>) -> R<Self> {
let path = dir_path.into();
let mut batch_file = vec![];
let p = Path::new(&path).to_path_buf();
let dir_iter = std::fs::read_dir(p.clone())?;
for ele in dir_iter {
let ele = ele?;
if !ele.file_type()?.is_file() {
continue;
}
batch_file.push(ele.path());
}
let dir_cahe = Path::new(&path);
let mut dir_cahe = dir_cahe.to_path_buf();
dir_cahe.push(HOTLOAD_CACHE_DIR);
_ = std::fs::create_dir(dir_cahe.clone());
let r = SyncVec::new();
for ele in batch_file.iter() {
let p = ele.to_str().unwrap_or_else(|| "").to_string();
if p.is_empty() {
continue;
}
static PATHS: fast_able::vec::SyncVec<String> = fast_able::vec::SyncVec::new();
PATHS.push(p);
let item = Hotload::new(PATHS[PATHS.len() - 1].as_str());
r.push(item);
}
Ok(Self {
inner_batch: r.into(),
dir: path,
watch: UA::new(),
})
}
pub fn init_load<
F: FnMut(EventNewDll<API>, Option<EventOldDll<API>>) + Send + 'static + Clone,
>(
&self,
event_call: F,
) -> R<()> {
for ele in self.inner_batch.iter() {
ele.init_load(event_call.clone())?;
}
let inner_batch = self.inner_batch.clone();
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
let event = match res {
Ok(v) => v,
Err(e) => {
error!("watch error: {e:?}");
return;
}
};
let files = event
.paths
.iter()
.filter(|x| x.is_file())
.collect::<Vec<_>>();
if files.is_empty() {
return;
}
let mut is_action = false;
match &event.kind {
notify::EventKind::Create(_e) => is_action = true,
notify::EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::To,
)) => is_action = true,
_ => (),
};
if !is_action {
return;
}
for ele in files {
let full_path = ele.to_str().unwrap_or_else(|| "").to_string();
if full_path.is_empty() {
continue;
}
let change_file_name = ele
.as_path()
.file_name()
.and_then(|x| x.to_str())
.unwrap_or_else(|| "")
.to_string();
if change_file_name.is_empty() {
continue;
}
if inner_batch
.iter()
.find(|x| {
get_file_name(&x.inner.path).unwrap_or_else(|_| "".to_string())
== change_file_name
})
.is_some()
{
continue;
}
debug!("event: {:?}; load new dll", event);
static PATHS: fast_able::vec::SyncVec<String> = fast_able::vec::SyncVec::new();
PATHS.push(full_path);
let item = Hotload::<API>::new(PATHS[PATHS.len() - 1].as_str());
if let Err(e) = item.init_load(event_call.clone()) {
error!("{e:?}; item.init_load(event_call.clone()): {change_file_name}")
} else {
inner_batch.push(item);
}
}
})?;
let dir = &self.dir;
debug!("hoload watch path: {dir:?}");
watcher.watch(Path::new(&dir), RecursiveMode::NonRecursive)?;
self.watch.set(watcher);
Ok(())
}
pub fn iter(&self) -> fast_able::vec::Iter<'_, Hotload<API>> {
self.inner_batch.iter()
}
}