You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
4.6KB

  1. use std::env;
  2. use std::path::Path;
  3. use std::sync::mpsc::channel;
  4. use std::time::Duration;
  5. use std::thread;
  6. use iron::{Iron, Request, IronResult, Response, status};
  7. use mount::Mount;
  8. use staticfile::Static;
  9. use notify::{Watcher, RecursiveMode, watcher};
  10. use ws::{WebSocket};
  11. use site::Site;
  12. use errors::{Result};
  13. const LIVE_RELOAD: &'static [u8; 37809] = include_bytes!("livereload.js");
  14. fn livereload_handler(_: &mut Request) -> IronResult<Response> {
  15. Ok(Response::with((status::Ok, String::from_utf8(LIVE_RELOAD.to_vec()).unwrap())))
  16. }
  17. // Most of it taken from mdbook
  18. pub fn serve(interface: &str, port: &str) -> Result<()> {
  19. let mut site = Site::new(true)?;
  20. site.build()?;
  21. let address = format!("{}:{}", interface, port);
  22. let ws_address = format!("{}:{}", interface, "1112");
  23. // Start a webserver that serves the `public` directory
  24. let mut mount = Mount::new();
  25. mount.mount("/", Static::new(Path::new("public/")));
  26. mount.mount("/livereload.js", livereload_handler);
  27. // Starts with a _ to not trigger the unused lint
  28. // we need to assign to a variable otherwise it will block
  29. let _iron = Iron::new(mount).http(address.clone()).unwrap();
  30. println!("Web server is available at http://{}", address);
  31. println!("Press CTRL+C to stop");
  32. // The websocket for livereload
  33. let ws_server = WebSocket::new(|_| {
  34. |_| {
  35. Ok(())
  36. }
  37. }).unwrap();
  38. let broadcaster = ws_server.broadcaster();
  39. thread::spawn(move || {
  40. ws_server.listen(&*ws_address).unwrap();
  41. });
  42. // And finally watching/reacting on file changes
  43. let (tx, rx) = channel();
  44. let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
  45. watcher.watch("content/", RecursiveMode::Recursive).unwrap();
  46. watcher.watch("static/", RecursiveMode::Recursive).unwrap();
  47. watcher.watch("templates/", RecursiveMode::Recursive).unwrap();
  48. let pwd = env::current_dir().unwrap();
  49. println!("Listening for changes in {}/{{content, static, templates}}", pwd.display());
  50. use notify::DebouncedEvent::*;
  51. loop {
  52. // See https://github.com/spf13/hugo/blob/master/commands/hugo.go
  53. // for a more complete version of that
  54. match rx.recv() {
  55. Ok(event) => match event {
  56. NoticeWrite(path) |
  57. NoticeRemove(path) |
  58. Create(path) |
  59. Write(path) |
  60. Remove(path) |
  61. Rename(_, path) => {
  62. if !is_temp_file(&path) {
  63. println!("Change detected in {}", path.display());
  64. match site.rebuild() {
  65. Ok(_) => {
  66. println!("Site rebuilt");
  67. broadcaster.send(r#"
  68. {
  69. "command": "reload",
  70. "path": "",
  71. "originalPath": "",
  72. "liveCSS": true,
  73. "liveImg": true,
  74. "protocol": ["http://livereload.com/protocols/official-7"]
  75. }"#).unwrap();
  76. },
  77. Err(e) => {
  78. println!("Failed to build the site");
  79. println!("Error: {}", e);
  80. for e in e.iter().skip(1) {
  81. println!("Reason: {}", e)
  82. }
  83. }
  84. }
  85. }
  86. }
  87. _ => {}
  88. },
  89. Err(e) => println!("Watch error: {:?}", e),
  90. };
  91. }
  92. }
  93. /// Returns whether the path we received corresponds to a temp file create
  94. /// by an editor
  95. fn is_temp_file(path: &Path) -> bool {
  96. let ext = path.extension();
  97. match ext {
  98. Some(ex) => match ex.to_str().unwrap() {
  99. "swp" | "swx" | "tmp" | ".DS_STORE" => true,
  100. // jetbrains IDE
  101. x if x.ends_with("jb_old___") => true,
  102. x if x.ends_with("jb_tmp___") => true,
  103. x if x.ends_with("jb_bak___") => true,
  104. // byword
  105. x if x.starts_with("sb-") => true,
  106. // gnome
  107. x if x.starts_with(".gooutputstream") => true,
  108. _ => {
  109. if let Some(filename) = path.file_stem() {
  110. // emacs
  111. filename.to_str().unwrap().starts_with("#")
  112. } else {
  113. false
  114. }
  115. }
  116. },
  117. None => false,
  118. }
  119. }