mod file_layer;
mod http_layer;
mod utils;
pub use http_layer::server::Options;
use file_layer::watcher::{create_poll_watcher, watch};
use http_layer::{
listener::create_listener,
server::{AppState, create_server, serve},
};
use local_ip_address::local_ip;
use notify::{PollWatcher, RecommendedWatcher, Watcher};
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache};
use path_absolutize::Absolutize;
use std::{
error::Error,
net::IpAddr,
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{
net::TcpListener,
sync::{broadcast, mpsc::Receiver},
};
use crate::file_layer::watcher::create_recommended_watcher;
pub struct Listener<W: Watcher> {
tcp_listener: TcpListener,
root_path: PathBuf,
debouncer: Debouncer<W, RecommendedCache>,
rx: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
}
impl<W: Watcher + Send + 'static> Listener<W> {
pub async fn start(self, options: Options) -> Result<(), Box<dyn Error>> {
let (tx, _) = broadcast::channel(16);
let arc_tx = Arc::new(tx);
let app_state = AppState {
hard_reload: options.hard_reload,
index_listing: options.index_listing,
auto_ignore: options.auto_ignore,
tx: arc_tx.clone(),
root: self.root_path.clone(),
};
let watcher_future = tokio::spawn(watch(
self.root_path,
self.debouncer,
self.rx,
arc_tx,
options.auto_ignore,
));
let server_future = tokio::spawn(serve(self.tcp_listener, create_server(app_state)));
tokio::try_join!(watcher_future, server_future)?;
Ok(())
}
pub fn link(&self) -> Result<String, Box<dyn Error>> {
let addr = self.tcp_listener.local_addr()?;
let port = addr.port();
let host = addr.ip();
let host = match host.is_unspecified() {
true => local_ip()?,
false => host,
};
Ok(match host {
IpAddr::V4(host) => format!("http://{host}:{port}"),
IpAddr::V6(host) => format!("http://[{host}]:{port}"),
})
}
}
pub async fn listen(
addr: impl AsRef<str>,
root: impl AsRef<Path>,
) -> Result<Listener<RecommendedWatcher>, String> {
let tcp_listener = create_listener(addr.as_ref()).await?;
let (debouncer, rx) = create_recommended_watcher().await?;
let abs_root = get_absolute_path(root.as_ref())?;
print_listening_on_path(&abs_root)?;
Ok(Listener {
tcp_listener,
debouncer,
root_path: abs_root,
rx,
})
}
pub async fn listen_poll(
addr: impl AsRef<str>,
root: impl AsRef<Path>,
) -> Result<Listener<PollWatcher>, String> {
let tcp_listener = create_listener(addr.as_ref()).await?;
let (debouncer, rx) = create_poll_watcher().await?;
let abs_root = get_absolute_path(root.as_ref())?;
print_listening_on_path(&abs_root)?;
Ok(Listener {
tcp_listener,
debouncer,
root_path: abs_root,
rx,
})
}
fn get_absolute_path(path: &Path) -> Result<PathBuf, String> {
match path.absolutize() {
Ok(path) => Ok(path.to_path_buf()),
Err(err) => {
let err_msg = format!("Failed to get absolute path of {path:?}: {err}");
log::error!("{err_msg}");
Err(err_msg)
}
}
}
fn print_listening_on_path(path: &PathBuf) -> Result<(), String> {
match path.as_os_str().to_str() {
Some(path_str) => {
log::info!("Listening on {path_str}");
Ok(())
}
None => {
let err_msg = format!("Failed to parse path to string for `{path:?}`");
log::error!("{err_msg}");
Err(err_msg)
}
}
}