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
109
110
111
112
113
114
115
use std::{
    io::{BufRead, BufReader},
    path::PathBuf,
};

use dioxus_core::Template;
#[cfg(feature = "file_watcher")]
pub use dioxus_html::HtmlCtx;
use interprocess::local_socket::LocalSocketStream;
use serde::{Deserialize, Serialize};

#[cfg(feature = "custom_file_watcher")]
mod file_watcher;
#[cfg(feature = "custom_file_watcher")]
pub use file_watcher::*;

/// A message the hot reloading server sends to the client
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(bound(deserialize = "'de: 'static"))]
pub enum HotReloadMsg {
    /// A template has been updated
    UpdateTemplate(Template),

    /// An asset discovered by rsx! has been updated
    UpdateAsset(PathBuf),

    /// The program needs to be recompiled, and the client should shut down
    Shutdown,
}

/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
pub fn connect(callback: impl FnMut(HotReloadMsg) + Send + 'static) {
    if cfg!(windows) {
        connect_at(PathBuf::from("@dioxusin"), callback);
    } else {
        // FIXME: this is falling back onto the current directory when not running under cargo, which is how the CLI runs this.
        // This needs to be fixed.
        let _manifest_dir = std::env::var("CARGO_MANIFEST_DIR");

        // get the cargo manifest directory, where the target dir lives
        let mut path = match _manifest_dir {
            Ok(manifest_dir) => PathBuf::from(manifest_dir),
            Err(_) => std::env::current_dir().unwrap(),
        };

        // walk the path until we a find a socket named `dioxusin` inside that folder's target directory
        loop {
            let maybe = path.join("target").join("dioxusin");

            if maybe.exists() {
                path = maybe;
                break;
            }

            // It's likely we're running under just cargo and not dx
            path = match path.parent() {
                Some(parent) => parent.to_path_buf(),
                None => return,
            };
        }

        println!("connecting to {:?}", path);
        connect_at(path, callback);
    }
}

pub fn connect_at(socket: PathBuf, mut callback: impl FnMut(HotReloadMsg) + Send + 'static) {
    std::thread::spawn(move || {
        // There might be a socket since the we're not running under the hot reloading server
        let stream = if cfg!(windows) {
            LocalSocketStream::connect("@dioxusin")
        } else {
            LocalSocketStream::connect(socket.clone())
        };
        let Ok(socket) = stream else {
            println!(
                "could not find hot reloading server at {:?}, make sure it's running",
                socket
            );
            return;
        };

        let mut buf_reader = BufReader::new(socket);

        loop {
            let mut buf = String::new();

            if let Err(err) = buf_reader.read_line(&mut buf) {
                if err.kind() != std::io::ErrorKind::WouldBlock {
                    break;
                }
            }

            let Ok(template) = serde_json::from_str(Box::leak(buf.into_boxed_str())) else {
                continue;
            };

            callback(template);
        }
    });
}

/// Start the hot reloading server with the current directory as the root
#[macro_export]
macro_rules! hot_reload_init {
    () => {
        #[cfg(debug_assertions)]
        dioxus_hot_reload::init(dioxus_hot_reload::Config::new().root(env!("CARGO_MANIFEST_DIR")));
    };

    ($cfg: expr) => {
        #[cfg(debug_assertions)]
        dioxus_hot_reload::init($cfg.root(env!("CARGO_MANIFEST_DIR")));
    };
}