From e861880f959b1175cd981bfbeb52138da61d9659 Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Sat, 2 Apr 2016 04:46:05 +0200 Subject: [PATCH 1/3] Implement Serve feature --- Cargo.toml | 8 +- src/bin/livereload.rs | 210 +++++++++++++++++++ src/bin/mdbook.rs | 192 +++++++++++------ src/book/mdbook.rs | 20 ++ src/renderer/html_handlebars/hbs_renderer.rs | 3 + src/theme/index.hbs | 3 + src/utils/fs.rs | 2 +- 7 files changed, 372 insertions(+), 66 deletions(-) create mode 100644 src/bin/livereload.rs diff --git a/Cargo.toml b/Cargo.toml index a2d8104b..504d042f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,11 @@ notify = { version = "2.5.4", optional = true } time = { version = "0.1.34", optional = true } crossbeam = { version = "0.2.8", optional = true } +# Serve feature +iron = { version = "0.3", optional = true } +staticfile = { version = "0.2", optional = true } +websocket = { version = "0.16.1", optional = true} + # Tests [dev-dependencies] @@ -32,11 +37,12 @@ tempdir = "0.3.4" [features] -default = ["output", "watch"] +default = ["output", "watch", "serve"] debug = [] output = [] regenerate-css = [] watch = ["notify", "time", "crossbeam"] +serve = ["iron", "staticfile", "websocket"] [[bin]] doc = false diff --git a/src/bin/livereload.rs b/src/bin/livereload.rs new file mode 100644 index 00000000..23b15b2d --- /dev/null +++ b/src/bin/livereload.rs @@ -0,0 +1,210 @@ +extern crate websocket; +extern crate crossbeam; + +use std::sync::mpsc::channel; +use std::sync::mpsc; +use std::io; +use std::thread; +use std::sync::{Arc, Mutex}; +use std::ops::Deref; +use std::marker::PhantomData; + +use self::websocket::header::WebSocketProtocol; +use self::websocket::ws::sender::Sender; +use self::websocket::ws::receiver::Receiver; +use self::websocket::message::Type; +use self::websocket::{Server, Message}; + +const WS_PROTOCOL: &'static str = "livereload"; +const RELOAD_COMMAND: &'static str = "reload"; + + +#[derive(Debug, Clone, PartialEq)] +enum MessageType { + Reload, + Close, +} + + +#[derive(Clone)] +struct ComparableSender { + sender: mpsc::Sender, + id: usize, +} + +impl PartialEq for ComparableSender { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Deref for ComparableSender { + type Target = mpsc::Sender; + + fn deref(&self) -> &mpsc::Sender { + &self.sender + } +} + + +struct ComparableSenderFactory { + next_id: usize, + sender_type: PhantomData, +} + +impl ComparableSenderFactory { + fn generate(&mut self, sender: mpsc::Sender) -> ComparableSender { + let tx = ComparableSender { + sender: sender, + id: self.next_id, + }; + self.next_id += 1; + tx + } + + fn new() -> ComparableSenderFactory { + ComparableSenderFactory { + next_id: 0, + sender_type: PhantomData, + } + } +} + + +pub struct LiveReload { + senders: Arc>>>, +} + +impl LiveReload { + pub fn new(address: &str) -> io::Result { + let server = try!(Server::bind(address)); + + let senders: Arc>>> = Arc::new(Mutex::new(vec![])); + let senders_clone = senders.clone(); + + let mut factory = ComparableSenderFactory::new(); + + let lr = LiveReload { senders: senders_clone }; + + // handle connection attempts on a separate thread + thread::spawn(move || { + for connection in server { + let mut senders = senders.clone(); + let (tx, rx) = channel(); + + let tx = factory.generate(tx); + + senders.lock().unwrap().push(tx.clone()); + + // each connection gets a separate thread + thread::spawn(move || { + let request = connection.unwrap().read_request().unwrap(); + let headers = request.headers.clone(); + + let mut valid = false; + if let Some(&WebSocketProtocol(ref protocols)) = headers.get() { + if protocols.contains(&(WS_PROTOCOL.to_owned())) { + valid = true; + } + } + + let client; + if valid { + let mut response = request.accept(); + response.headers.set(WebSocketProtocol(vec![WS_PROTOCOL.to_owned()])); + client = response.send().unwrap(); + } else { + request.fail().send().unwrap(); + println!("{:?}", "Rejecting invalid websocket request."); + return; + } + + let (mut ws_tx, mut ws_rx) = client.split(); + + // handle receiving and sending (websocket) in two separate threads + crossbeam::scope(|scope| { + let tx_clone = tx.clone(); + scope.spawn(move || { + let tx = tx_clone; + loop { + match rx.recv() { + Ok(msg) => { + match msg { + MessageType::Reload => { + let message: Message = Message::text(RELOAD_COMMAND.to_owned()); + let mut senders = senders.clone(); + if ws_tx.send_message(&message).is_err() { + // the receiver isn't available anymore + // remove the tx from senders and exit + LiveReload::remove_sender(&mut senders, &tx); + break; + } + }, + MessageType::Close => { + LiveReload::remove_sender(&mut senders, &tx); + break; + }, + } + }, + Err(e) => { + println!("{:?}", e); + break; + }, + } + } + }); + + for message in ws_rx.incoming_messages() { + match message { + Ok(message) => { + let message: Message = message; + match message.opcode { + Type::Close => { + tx.send(MessageType::Close).unwrap(); + break; + }, + // TODO ? + // Type::Ping => { + // let message = websocket::Message::pong(message.payload); + // ws_tx.send_message(&message).unwrap(); + // }, + _ => { + println!("{:?}", message.opcode); + unimplemented!() + }, + } + }, + Err(err) => { + println!("Error: {}", err); + break; + }, + } + } + }); + }); + } + }); + + Ok(lr) + } + + fn remove_sender(senders: &mut Arc>>>, el: &ComparableSender) { + let mut senders = senders.lock().unwrap(); + let mut index = 0; + for i in 0..senders.len() { + if &senders[i] == el { + index = i; + break; + } + } + senders.remove(index); + } + + pub fn trigger_reload(&self) { + let senders = self.senders.lock().unwrap(); + println!("Reloading {} client(s).", senders.len()); + for sender in senders.iter() { + sender.send(MessageType::Reload).unwrap(); + } + } +} diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 5f3c0605..5fde4750 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -10,6 +10,14 @@ extern crate notify; #[cfg(feature = "watch")] extern crate time; +// Dependencies for the Serve feature +#[cfg(feature = "serve")] +extern crate iron; +#[cfg(feature = "serve")] +extern crate staticfile; + +#[cfg(feature = "serve")] +mod livereload; use std::env; use std::error::Error; @@ -24,6 +32,10 @@ use notify::Watcher; #[cfg(feature = "watch")] use std::sync::mpsc::channel; +// Uses for the Serve feature +#[cfg(feature = "serve")] +use livereload::LiveReload; + use mdbook::MDBook; @@ -50,6 +62,11 @@ fn main() { .subcommand(SubCommand::with_name("watch") .about("Watch the files for changes") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")) + .subcommand(SubCommand::with_name("serve") + .about("Serve the book at http://localhost:3000. Rebuild and reload on change.") + .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'") + .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'") + .arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")) .subcommand(SubCommand::with_name("test") .about("Test that code samples compile")) .get_matches(); @@ -60,6 +77,8 @@ fn main() { ("build", Some(sub_matches)) => build(sub_matches), #[cfg(feature = "watch")] ("watch", Some(sub_matches)) => watch(sub_matches), + #[cfg(feature = "serve")] + ("serve", Some(sub_matches)) => serve(sub_matches), ("test", Some(sub_matches)) => test(sub_matches), (_, _) => unreachable!(), }; @@ -148,76 +167,70 @@ fn build(args: &ArgMatches) -> Result<(), Box> { #[cfg(feature = "watch")] fn watch(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config(); + let mut book = MDBook::new(&book_dir).read_config(); - // Create a channel to receive the events. - let (tx, rx) = channel(); - - let w: Result = notify::Watcher::new(tx); - - match w { - Ok(mut watcher) => { - - // Add the source directory to the watcher - if let Err(e) = watcher.watch(book.get_src()) { - println!("Error while watching {:?}:\n {:?}", book.get_src(), e); - ::std::process::exit(0); - }; - - // Add the book.json file to the watcher if it exists, because it's not - // located in the source directory - if let Err(_) = watcher.watch(book_dir.join("book.json")) { - // do nothing if book.json is not found + trigger_on_change(&mut book, |event, book| { + if let Some(path) = event.path { + println!("File changed: {:?}\nBuilding book...\n", path); + match book.build() { + Err(e) => println!("Error while building: {:?}", e), + _ => {}, } - - let mut previous_time = time::get_time(); - - crossbeam::scope(|scope| { - loop { - match rx.recv() { - Ok(event) => { - - // Skip the event if an event has already been issued in the last second - let time = time::get_time(); - if time - previous_time < time::Duration::seconds(1) { - continue; - } else { - previous_time = time; - } - - if let Some(path) = event.path { - // Trigger the build process in a new thread (to keep receiving events) - scope.spawn(move || { - println!("File changed: {:?}\nBuilding book...\n", path); - match build(args) { - Err(e) => println!("Error while building: {:?}", e), - _ => {}, - } - println!(""); - }); - - } else { - continue; - } - }, - Err(e) => { - println!("An error occured: {:?}", e); - }, - } - } - }); - - }, - Err(e) => { - println!("Error while trying to watch the files:\n\n\t{:?}", e); - ::std::process::exit(0); - }, - } + println!(""); + } + }); Ok(()) } +// Watch command implementation +#[cfg(feature = "serve")] +fn serve(args: &ArgMatches) -> Result<(), Box> { + let book_dir = get_book_dir(args); + let mut book = MDBook::new(&book_dir).read_config(); + let port = args.value_of("port").unwrap_or("3000"); + let ws_port = args.value_of("ws-port").unwrap_or("3001"); + + book.set_livereload(format!(r#" + + "#, ws_port).to_owned()); + + try!(book.build()); + + let staticfile = staticfile::Static::new(book.get_dest()); + let iron = iron::Iron::new(staticfile); + let _iron = iron.http(&*format!("localhost:{}", port)).unwrap(); + + let lr = LiveReload::new(&format!("localhost:{}", ws_port)).unwrap(); + + println!("{:?}", "Registering change trigger"); + trigger_on_change(&mut book, move |event, book| { + if let Some(path) = event.path { + println!("File changed: {:?}\nBuilding book...\n", path); + match book.build() { + Err(e) => println!("Error while building: {:?}", e), + _ => lr.trigger_reload(), + } + println!(""); + } + }); + + Ok(()) +} + fn test(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); @@ -229,7 +242,6 @@ fn test(args: &ArgMatches) -> Result<(), Box> { } - fn get_book_dir(args: &ArgMatches) -> PathBuf { if let Some(dir) = args.value_of("dir") { // Check if path is relative from current dir, or absolute... @@ -243,3 +255,55 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf { env::current_dir().unwrap() } } + + +// Calls the closure when a book source file is changed. This is blocking! +fn trigger_on_change(book: &mut MDBook, closure: F) -> () + where F: Fn(notify::Event, &mut MDBook) -> () +{ + // Create a channel to receive the events. + let (tx, rx) = channel(); + + let w: Result = notify::Watcher::new(tx); + + match w { + Ok(mut watcher) => { + // Add the source directory to the watcher + if let Err(e) = watcher.watch(book.get_src()) { + println!("Error while watching {:?}:\n {:?}", book.get_src(), e); + ::std::process::exit(0); + }; + + // Add the book.json file to the watcher if it exists, because it's not + // located in the source directory + if let Err(_) = watcher.watch(book.get_root().join("book.json")) { + // do nothing if book.json is not found + } + + let mut previous_time = time::get_time(); + + loop { + match rx.recv() { + Ok(event) => { + // Skip the event if an event has already been issued in the last second + let time = time::get_time(); + if time - previous_time < time::Duration::seconds(1) { + continue; + } else { + previous_time = time; + } + + closure(event, book); + }, + Err(e) => { + println!("An error occured: {:?}", e); + }, + } + } + }, + Err(e) => { + println!("Error while trying to watch the files:\n\n\t{:?}", e); + ::std::process::exit(0); + }, + } +} diff --git a/src/book/mdbook.rs b/src/book/mdbook.rs index 6d7945eb..07ef1140 100644 --- a/src/book/mdbook.rs +++ b/src/book/mdbook.rs @@ -15,6 +15,8 @@ pub struct MDBook { config: BookConfig, pub content: Vec, renderer: Box, + #[cfg(feature = "serve")] + livereload: Option, } impl MDBook { @@ -38,6 +40,7 @@ impl MDBook { .set_dest(&root.join("book")) .to_owned(), renderer: Box::new(HtmlHandlebars::new()), + livereload: None, } } @@ -398,6 +401,23 @@ impl MDBook { &self.config.description } + pub fn set_livereload(&mut self, livereload: String) -> &mut Self { + self.livereload = Some(livereload); + self + } + + pub fn unset_livereload(&mut self) -> &Self { + self.livereload = None; + self + } + + pub fn get_livereload(&self) -> Option<&String> { + match self.livereload { + Some(ref livereload) => Some(&livereload), + None => None, + } + } + // Construct book fn parse_summary(&mut self) -> Result<(), Box> { // When append becomes stable, use self.content.append() ... diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index ce6cf683..7178dc47 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -287,6 +287,9 @@ fn make_data(book: &MDBook) -> Result, Box> { data.insert("title".to_owned(), book.get_title().to_json()); data.insert("description".to_owned(), book.get_description().to_json()); data.insert("favicon".to_owned(), "favicon.png".to_json()); + if let Some(livereload) = book.get_livereload() { + data.insert("livereload".to_owned(), livereload.to_json()); + } let mut chapters = vec![]; diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 410fdb60..d0665675 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -107,6 +107,9 @@ } + + {{{livereload}}} + diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 189cca92..bb4aa7f0 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -150,7 +150,7 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl debug!("[*] creating path for file: {:?}", &to.join(entry.path().file_name().expect("a file should have a file name..."))); - output!("[*] copying file: {:?}\n to {:?}", + output!("[*] Copying file: {:?}\n to {:?}", entry.path(), &to.join(entry.path().file_name().expect("a file should have a file name..."))); try!(fs::copy(entry.path(), From 2f43167b759573290f4dceb14b6d812708c4702d Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Sat, 2 Apr 2016 05:38:53 +0200 Subject: [PATCH 2/3] Add documentation for Serve feature --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b54e7589..218aed2a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mdBook +# mdBook @@ -100,6 +100,10 @@ Here are the main commands you will want to run, for a more exhaustive explanati When you run this command, mdbook will watch your markdown files to rebuild the book on every change. This avoids having to come back to the terminal to type `mdbook build` over and over again. +- `mdbook serve` + + Does the same thing as `mdbook watch` but additionally serves the book at `http://localhost:3000` (port is changeable) and reloads the browser when a change occures. + ### As a library Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with and easy to use API and more! From c8051294b0a6556b99519eb93ff50266e1b63125 Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Sat, 2 Apr 2016 20:04:51 +0200 Subject: [PATCH 3/3] Switch from rust-websocket to ws-rs --- Cargo.toml | 4 +- src/bin/livereload.rs | 210 ------------------------------------------ src/bin/mdbook.rs | 37 +++++--- 3 files changed, 26 insertions(+), 225 deletions(-) delete mode 100644 src/bin/livereload.rs diff --git a/Cargo.toml b/Cargo.toml index 504d042f..43b186e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ crossbeam = { version = "0.2.8", optional = true } # Serve feature iron = { version = "0.3", optional = true } staticfile = { version = "0.2", optional = true } -websocket = { version = "0.16.1", optional = true} +ws = { version = "0.4.6", optional = true} # Tests @@ -42,7 +42,7 @@ debug = [] output = [] regenerate-css = [] watch = ["notify", "time", "crossbeam"] -serve = ["iron", "staticfile", "websocket"] +serve = ["iron", "staticfile", "ws"] [[bin]] doc = false diff --git a/src/bin/livereload.rs b/src/bin/livereload.rs deleted file mode 100644 index 23b15b2d..00000000 --- a/src/bin/livereload.rs +++ /dev/null @@ -1,210 +0,0 @@ -extern crate websocket; -extern crate crossbeam; - -use std::sync::mpsc::channel; -use std::sync::mpsc; -use std::io; -use std::thread; -use std::sync::{Arc, Mutex}; -use std::ops::Deref; -use std::marker::PhantomData; - -use self::websocket::header::WebSocketProtocol; -use self::websocket::ws::sender::Sender; -use self::websocket::ws::receiver::Receiver; -use self::websocket::message::Type; -use self::websocket::{Server, Message}; - -const WS_PROTOCOL: &'static str = "livereload"; -const RELOAD_COMMAND: &'static str = "reload"; - - -#[derive(Debug, Clone, PartialEq)] -enum MessageType { - Reload, - Close, -} - - -#[derive(Clone)] -struct ComparableSender { - sender: mpsc::Sender, - id: usize, -} - -impl PartialEq for ComparableSender { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Deref for ComparableSender { - type Target = mpsc::Sender; - - fn deref(&self) -> &mpsc::Sender { - &self.sender - } -} - - -struct ComparableSenderFactory { - next_id: usize, - sender_type: PhantomData, -} - -impl ComparableSenderFactory { - fn generate(&mut self, sender: mpsc::Sender) -> ComparableSender { - let tx = ComparableSender { - sender: sender, - id: self.next_id, - }; - self.next_id += 1; - tx - } - - fn new() -> ComparableSenderFactory { - ComparableSenderFactory { - next_id: 0, - sender_type: PhantomData, - } - } -} - - -pub struct LiveReload { - senders: Arc>>>, -} - -impl LiveReload { - pub fn new(address: &str) -> io::Result { - let server = try!(Server::bind(address)); - - let senders: Arc>>> = Arc::new(Mutex::new(vec![])); - let senders_clone = senders.clone(); - - let mut factory = ComparableSenderFactory::new(); - - let lr = LiveReload { senders: senders_clone }; - - // handle connection attempts on a separate thread - thread::spawn(move || { - for connection in server { - let mut senders = senders.clone(); - let (tx, rx) = channel(); - - let tx = factory.generate(tx); - - senders.lock().unwrap().push(tx.clone()); - - // each connection gets a separate thread - thread::spawn(move || { - let request = connection.unwrap().read_request().unwrap(); - let headers = request.headers.clone(); - - let mut valid = false; - if let Some(&WebSocketProtocol(ref protocols)) = headers.get() { - if protocols.contains(&(WS_PROTOCOL.to_owned())) { - valid = true; - } - } - - let client; - if valid { - let mut response = request.accept(); - response.headers.set(WebSocketProtocol(vec![WS_PROTOCOL.to_owned()])); - client = response.send().unwrap(); - } else { - request.fail().send().unwrap(); - println!("{:?}", "Rejecting invalid websocket request."); - return; - } - - let (mut ws_tx, mut ws_rx) = client.split(); - - // handle receiving and sending (websocket) in two separate threads - crossbeam::scope(|scope| { - let tx_clone = tx.clone(); - scope.spawn(move || { - let tx = tx_clone; - loop { - match rx.recv() { - Ok(msg) => { - match msg { - MessageType::Reload => { - let message: Message = Message::text(RELOAD_COMMAND.to_owned()); - let mut senders = senders.clone(); - if ws_tx.send_message(&message).is_err() { - // the receiver isn't available anymore - // remove the tx from senders and exit - LiveReload::remove_sender(&mut senders, &tx); - break; - } - }, - MessageType::Close => { - LiveReload::remove_sender(&mut senders, &tx); - break; - }, - } - }, - Err(e) => { - println!("{:?}", e); - break; - }, - } - } - }); - - for message in ws_rx.incoming_messages() { - match message { - Ok(message) => { - let message: Message = message; - match message.opcode { - Type::Close => { - tx.send(MessageType::Close).unwrap(); - break; - }, - // TODO ? - // Type::Ping => { - // let message = websocket::Message::pong(message.payload); - // ws_tx.send_message(&message).unwrap(); - // }, - _ => { - println!("{:?}", message.opcode); - unimplemented!() - }, - } - }, - Err(err) => { - println!("Error: {}", err); - break; - }, - } - } - }); - }); - } - }); - - Ok(lr) - } - - fn remove_sender(senders: &mut Arc>>>, el: &ComparableSender) { - let mut senders = senders.lock().unwrap(); - let mut index = 0; - for i in 0..senders.len() { - if &senders[i] == el { - index = i; - break; - } - } - senders.remove(index); - } - - pub fn trigger_reload(&self) { - let senders = self.senders.lock().unwrap(); - println!("Reloading {} client(s).", senders.len()); - for sender in senders.iter() { - sender.send(MessageType::Reload).unwrap(); - } - } -} diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 5fde4750..be69f8b4 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -15,9 +15,8 @@ extern crate time; extern crate iron; #[cfg(feature = "serve")] extern crate staticfile; - #[cfg(feature = "serve")] -mod livereload; +extern crate ws; use std::env; use std::error::Error; @@ -32,10 +31,6 @@ use notify::Watcher; #[cfg(feature = "watch")] use std::sync::mpsc::channel; -// Uses for the Serve feature -#[cfg(feature = "serve")] -use livereload::LiveReload; - use mdbook::MDBook; @@ -187,16 +182,21 @@ fn watch(args: &ArgMatches) -> Result<(), Box> { // Watch command implementation #[cfg(feature = "serve")] fn serve(args: &ArgMatches) -> Result<(), Box> { + const RELOAD_COMMAND: &'static str = "reload"; + let book_dir = get_book_dir(args); let mut book = MDBook::new(&book_dir).read_config(); let port = args.value_of("port").unwrap_or("3000"); let ws_port = args.value_of("ws-port").unwrap_or("3001"); + let address = format!("localhost:{}", port); + let ws_address = format!("localhost:{}", ws_port); + book.set_livereload(format!(r#" - "#, ws_port).to_owned()); + "#, ws_port, RELOAD_COMMAND).to_owned()); try!(book.build()); let staticfile = staticfile::Static::new(book.get_dest()); let iron = iron::Iron::new(staticfile); - let _iron = iron.http(&*format!("localhost:{}", port)).unwrap(); + let _iron = iron.http(&*address).unwrap(); - let lr = LiveReload::new(&format!("localhost:{}", ws_port)).unwrap(); + let ws_server = ws::WebSocket::new(|_| { + |_| { + Ok(()) + } + }).unwrap(); + + let broadcaster = ws_server.broadcaster(); + + std::thread::spawn(move || { + ws_server.listen(&*ws_address).unwrap(); + }); - println!("{:?}", "Registering change trigger"); trigger_on_change(&mut book, move |event, book| { if let Some(path) = event.path { println!("File changed: {:?}\nBuilding book...\n", path); match book.build() { Err(e) => println!("Error while building: {:?}", e), - _ => lr.trigger_reload(), + _ => broadcaster.send(RELOAD_COMMAND).unwrap(), } println!(""); } @@ -282,6 +291,8 @@ fn trigger_on_change(book: &mut MDBook, closure: F) -> () let mut previous_time = time::get_time(); + println!("\nListening for changes...\n"); + loop { match rx.recv() { Ok(event) => {