1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! Launch a local network server with live reload feature for static pages.
//!
//! ## Create live server
//! ```
//! use live_server::listen;
//!
//! async fn serve() -> Result<(), Box<dyn std::error::Error>> {
//!     listen("127.0.0.1:8080", "./").await?.start().await
//! }
//! ```
//!
//! ## Enable logs (Optional)
//! ```rust
//! env_logger::init();
//! ```

mod server;
mod watcher;

use std::{error::Error, net::IpAddr, path::PathBuf};

use axum::Router;
use local_ip_address::local_ip;
use notify::RecommendedWatcher;
use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap};
use server::{create_listener, create_server};
use tokio::{
    net::TcpListener,
    sync::{broadcast, mpsc::Receiver, OnceCell},
};
use watcher::create_watcher;

static ADDR: OnceCell<String> = OnceCell::const_new();
static ROOT: OnceCell<PathBuf> = OnceCell::const_new();
static TX: OnceCell<broadcast::Sender<()>> = OnceCell::const_new();

pub struct Listener {
    tcp_listener: TcpListener,
    router: Router,
    root_path: PathBuf,
    debouncer: Debouncer<RecommendedWatcher, FileIdMap>,
    rx: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
}

impl Listener {
    /// Start live-server
    ///
    /// ```
    /// use live_server::listen;
    ///
    /// async fn serve() -> Result<(), Box<dyn std::error::Error>> {
    ///     listen("127.0.0.1:8080", "./").await?.start().await
    /// }
    /// ```
    pub async fn start(self) -> Result<(), Box<dyn Error>> {
        ROOT.set(self.root_path.clone())?;
        let (tx, _) = broadcast::channel(16);
        TX.set(tx)?;

        let watcher_future = tokio::spawn(watcher::watch(self.root_path, self.debouncer, self.rx));
        let server_future = tokio::spawn(server::serve(self.tcp_listener, self.router));

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

/// Create live-server listener
///
/// ```
/// use live_server::listen;
///
/// async fn serve() -> Result<(), Box<dyn std::error::Error>> {
///     listen("127.0.0.1:8080", "./").await?.start().await
/// }
/// ```
pub async fn listen<A: Into<String>, R: Into<PathBuf>>(
    addr: A,
    root: R,
) -> Result<Listener, String> {
    let tcp_listener = create_listener(addr.into()).await?;
    let router = create_server();
    let (debouncer, root_path, rx) = create_watcher(root.into()).await?;

    Ok(Listener {
        tcp_listener,
        router,
        debouncer,
        root_path,
        rx,
    })
}