hotload 1.2.0

Zero cost hot update dynamic library; supporting DLL, SO
Documentation
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>,
}

// impl<API: WrapperApi + 'static> Deref for HotloadBatch<API> {
//     type Target = Container<API>;
//     fn deref(&self) -> &Self::Target {
//         let inner = &*self.inner;
//         let inner = &inner.container[inner.load_index];
//         inner.as_ref().expect("container not loaded")
//     }
// }

// // inpl DerefMut
// impl<API: WrapperApi + 'static> DerefMut for HotloadBatch<API> {
//     fn deref_mut(&mut self) -> &mut Self::Target {
//         let inner = self.inner.get_mut();
//         let inner = &mut inner.container[inner.load_index];
//         inner.as_mut().expect("container not loaded")
//     }
// }

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(),
        })
    }

    /// "dll created" or "file rename to" is actively loaded
    /// frist time load EventOldDll is None
    /// load all "file_name-xxx" dll
    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;
                }
            };

            // The file in the incident is not a monitored file and will not be processed
            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);
                }
            }
        })?;

        // watch directory
        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()
    }
}

// IntoIterator
// impl<API: WrapperApi, 'a> IntoIterator for HotloadBatch<API> {
//     type IntoIter = std::slice::Iter<'a, Self::Item>;
//     type Item = Hotload<API>;
//     fn into_iter(self) -> Self::IntoIter {
//         self.inner_batch.into_iter()
//     }
// }

// impl<API: WrapperApi> Iterator for Hotload<API> {
//     type Item =  Hotload<API>;
//     fn next(&mut self) -> Option<Self::Item> {
//         todo!()
//     }
// }