1mod file_layer;
18mod http_layer;
19mod utils;
20
21pub use http_layer::server::Options;
22
23use file_layer::watcher::{create_poll_watcher, watch};
24use http_layer::{
25 listener::create_listener,
26 server::{AppState, create_server, serve},
27};
28use local_ip_address::local_ip;
29use notify::{PollWatcher, RecommendedWatcher, Watcher};
30use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache};
31use path_absolutize::Absolutize;
32use std::{
33 error::Error,
34 net::IpAddr,
35 path::{Path, PathBuf},
36 sync::Arc,
37};
38use tokio::{
39 net::TcpListener,
40 sync::{broadcast, mpsc::Receiver},
41};
42
43use crate::file_layer::watcher::create_recommended_watcher;
44
45pub struct Listener<W: Watcher> {
46 tcp_listener: TcpListener,
47 root_path: PathBuf,
48 debouncer: Debouncer<W, RecommendedCache>,
49 rx: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
50}
51
52impl<W: Watcher + Send + 'static> Listener<W> {
53 pub async fn start(self, options: Options) -> Result<(), Box<dyn Error>> {
63 let (tx, _) = broadcast::channel(16);
64
65 let arc_tx = Arc::new(tx);
66 let app_state = AppState {
67 hard_reload: options.hard_reload,
68 index_listing: options.index_listing,
69 auto_ignore: options.auto_ignore,
70 tx: arc_tx.clone(),
71 root: self.root_path.clone(),
72 };
73
74 let watcher_future = tokio::spawn(watch(
75 self.root_path,
76 self.debouncer,
77 self.rx,
78 arc_tx,
79 options.auto_ignore,
80 ));
81 let server_future = tokio::spawn(serve(self.tcp_listener, create_server(app_state)));
82
83 tokio::try_join!(watcher_future, server_future)?;
84
85 Ok(())
86 }
87
88 pub fn link(&self) -> Result<String, Box<dyn Error>> {
103 let addr = self.tcp_listener.local_addr()?;
104 let port = addr.port();
105 let host = addr.ip();
106 let host = match host.is_unspecified() {
107 true => local_ip()?,
108 false => host,
109 };
110
111 Ok(match host {
112 IpAddr::V4(host) => format!("http://{host}:{port}"),
113 IpAddr::V6(host) => format!("http://[{host}]:{port}"),
114 })
115 }
116}
117
118pub async fn listen(
128 addr: impl AsRef<str>,
129 root: impl AsRef<Path>,
130) -> Result<Listener<RecommendedWatcher>, String> {
131 let tcp_listener = create_listener(addr.as_ref()).await?;
132 let (debouncer, rx) = create_recommended_watcher().await?;
133
134 let abs_root = get_absolute_path(root.as_ref())?;
135 print_listening_on_path(&abs_root)?;
136
137 Ok(Listener {
138 tcp_listener,
139 debouncer,
140 root_path: abs_root,
141 rx,
142 })
143}
144
145pub async fn listen_poll(
159 addr: impl AsRef<str>,
160 root: impl AsRef<Path>,
161) -> Result<Listener<PollWatcher>, String> {
162 let tcp_listener = create_listener(addr.as_ref()).await?;
163 let (debouncer, rx) = create_poll_watcher().await?;
164
165 let abs_root = get_absolute_path(root.as_ref())?;
166 print_listening_on_path(&abs_root)?;
167
168 Ok(Listener {
169 tcp_listener,
170 debouncer,
171 root_path: abs_root,
172 rx,
173 })
174}
175
176fn get_absolute_path(path: &Path) -> Result<PathBuf, String> {
177 match path.absolutize() {
178 Ok(path) => Ok(path.to_path_buf()),
179 Err(err) => {
180 let err_msg = format!("Failed to get absolute path of {path:?}: {err}");
181 log::error!("{err_msg}");
182 Err(err_msg)
183 }
184 }
185}
186
187fn print_listening_on_path(path: &PathBuf) -> Result<(), String> {
188 match path.as_os_str().to_str() {
189 Some(path_str) => {
190 log::info!("Listening on {path_str}");
191 Ok(())
192 }
193 None => {
194 let err_msg = format!("Failed to parse path to string for `{path:?}`");
195 log::error!("{err_msg}");
196 Err(err_msg)
197 }
198 }
199}