-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reload templates when they have been changed on disk #537
Closed
Closed
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
6f1ca6e
template reload: Store template initialization callback inside Contex…
jebrosen 06a356f
template reload: Before each request in development mode, reload temp…
jebrosen d9ca78f
template reload: properly handle failure case when instantiating a fi…
jebrosen 34cca3f
template reload: split TemplateFairing and ManagedContext into a sepa…
jebrosen 7c4d09c
template reload: clarify and document definition of ManagedContext
jebrosen 5771e94
template reload: Keep previous templates and engines if there is a fa…
jebrosen 10b0c33
mostly revert Context: move the callback into the fairing, move the T…
jebrosen 91a4e38
rename ManagedContext to ContextManager
jebrosen 1a517cc
remove Send+Sync+'static bounds where they are not needed
jebrosen ef6159d
register the on_request part of TemplateFairing only in debug_assertions
jebrosen c301c13
refactor based on feedback
jebrosen 8b54c86
move TemplateWatcher functionality directly into ManagedContext
jebrosen 5340c5a
merge watcher and corresponding receive queue into same Option field
jebrosen 4d3a0ff
minor style fixes
jebrosen 9807844
split the long warning lines
jebrosen 970056c
template reload: simplest test of a single template reload cycle
jebrosen d94c4ba
switch watcher to raw_watcher
jebrosen 0f3daa1
explicitly sync writes to files in the test; likely necessary on some…
jebrosen 8b28f80
unbreak #676 when combined with ContextManager
jebrosen 91204ae
fully qualify Data, Request to avoid unused warnings in release mode
jebrosen d37ed0f
template_reload: attempt several times to observe filesystem changes
jebrosen f4176df
refactor loop based on feedback. don't delete template file becuase i…
jebrosen 2585897
template reload tweaks and documentation
jebrosen d75d1ec
style changes, documentation additions/updates
jebrosen e13036e
update note about reload being disabled in release & add to guide
jebrosen 85f2a5d
include template reload test file so it does not appear as an untrack…
jebrosen dc34ab6
missing 'of'
jebrosen 3ecc02a
remove an unnecessary clone()
jebrosen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
use super::DEFAULT_TEMPLATE_DIR; | ||
use super::context::Context; | ||
use super::engine::Engines; | ||
|
||
use rocket::Rocket; | ||
use rocket::config::ConfigError; | ||
use rocket::fairing::{Fairing, Info, Kind}; | ||
|
||
#[cfg(not(debug_assertions))] | ||
mod context { | ||
use std::ops::Deref; | ||
use super::Context; | ||
|
||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure | ||
/// additionally provides a method to reload the context at runtime. | ||
pub struct ContextManager(Context); | ||
|
||
impl ContextManager { | ||
pub fn new(ctxt: Context) -> ContextManager { | ||
ContextManager(ctxt) | ||
} | ||
|
||
pub fn context<'a>(&'a self) -> impl Deref<Target=Context> + 'a { | ||
&self.0 | ||
} | ||
} | ||
} | ||
|
||
#[cfg(debug_assertions)] | ||
mod context { | ||
extern crate notify; | ||
|
||
use std::ops::{Deref, DerefMut}; | ||
use std::sync::{RwLock, Mutex}; | ||
use std::sync::mpsc::{channel, Receiver}; | ||
|
||
use super::{Context, Engines}; | ||
|
||
use self::notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher}; | ||
|
||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure | ||
/// additionally provides a method to reload the context at runtime. | ||
pub struct ContextManager { | ||
/// The current template context, inside an RwLock so it can be updated | ||
context: RwLock<Context>, | ||
/// A filesystem watcher and the receive queue for its events | ||
watcher: Option<(RecommendedWatcher, Mutex<Receiver<RawEvent>>)>, | ||
} | ||
|
||
impl ContextManager { | ||
pub fn new(ctxt: Context) -> ContextManager { | ||
let (tx, rx) = channel(); | ||
|
||
let watcher = if let Ok(mut watcher) = raw_watcher(tx) { | ||
if watcher.watch(ctxt.root.clone(), RecursiveMode::Recursive).is_ok() { | ||
Some((watcher, Mutex::new(rx))) | ||
} else { | ||
warn!("Could not monitor the templates directory for changes."); | ||
warn_!("Live template reload will be unavailable"); | ||
None | ||
} | ||
} else { | ||
warn!("Could not instantiate a filesystem watcher."); | ||
warn_!("Live template reload will be unavailable"); | ||
None | ||
}; | ||
|
||
ContextManager { | ||
watcher, | ||
context: RwLock::new(ctxt), | ||
} | ||
} | ||
|
||
pub fn context<'a>(&'a self) -> impl Deref<Target=Context> + 'a { | ||
self.context.read().unwrap() | ||
} | ||
|
||
fn context_mut<'a>(&'a self) -> impl DerefMut<Target=Context> + 'a { | ||
self.context.write().unwrap() | ||
} | ||
|
||
/// Checks whether any template files have changed on disk. If there | ||
/// have been changes since the last reload, all templates are | ||
/// reinitialized from disk and the user's customization callback is run | ||
/// again. | ||
pub fn reload_if_needed<F: Fn(&mut Engines)>(&self, custom_callback: F) { | ||
self.watcher.as_ref().map(|w| { | ||
let rx = w.1.lock().expect("receive queue"); | ||
let mut changed = false; | ||
while let Ok(_) = rx.try_recv() { | ||
changed = true; | ||
} | ||
|
||
if changed { | ||
info!("Change detected, reloading templates"); | ||
let mut ctxt = self.context_mut(); | ||
if let Some(mut new_ctxt) = Context::initialize(ctxt.root.clone()) { | ||
custom_callback(&mut new_ctxt.engines); | ||
*ctxt = new_ctxt; | ||
} else { | ||
warn_!("An error occurred while reloading templates."); | ||
warn_!("The previous templates will remain active."); | ||
}; | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
pub use self::context::ContextManager; | ||
|
||
/// The TemplateFairing initializes the template system on attach, running | ||
/// custom_callback after templates have been loaded. In debug mode, the | ||
/// fairing checks for modifications to templates before every request and | ||
/// reloads them if necessary. | ||
pub struct TemplateFairing { | ||
/// The user-provided customization callback, allowing the use of | ||
/// functionality specific to individual template engines. In debug mode, | ||
/// this callback might be run multiple times as templates are reloaded. | ||
pub(crate) custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>, | ||
} | ||
|
||
impl Fairing for TemplateFairing { | ||
fn info(&self) -> Info { | ||
// The on_request part of this fairing only applies in debug | ||
// mode, so only register it in debug mode for better performance. | ||
Info { | ||
name: "Templates", | ||
#[cfg(debug_assertions)] | ||
kind: Kind::Attach | Kind::Request, | ||
#[cfg(not(debug_assertions))] | ||
kind: Kind::Attach, | ||
} | ||
} | ||
|
||
/// Initializes the template context. Templates will be searched for in the | ||
/// `template_dir` config variable or the default ([DEFAULT_TEMPLATE_DIR]). | ||
/// The user's callback, if any was supplied, is called to customize the | ||
/// template engines. In debug mode, the `ContextManager::new` method | ||
/// initializes a directory watcher for auto-reloading of templates. | ||
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { | ||
let mut template_root = rocket.config().root_relative(DEFAULT_TEMPLATE_DIR); | ||
match rocket.config().get_str("template_dir") { | ||
Ok(dir) => template_root = rocket.config().root_relative(dir), | ||
Err(ConfigError::NotFound) => { /* ignore missing configs */ } | ||
Err(e) => { | ||
e.pretty_print(); | ||
warn_!("Using default templates directory '{:?}'", template_root); | ||
} | ||
}; | ||
|
||
match Context::initialize(template_root) { | ||
Some(mut ctxt) => { | ||
(self.custom_callback)(&mut ctxt.engines); | ||
Ok(rocket.manage(ContextManager::new(ctxt))) | ||
} | ||
None => Err(rocket), | ||
} | ||
} | ||
|
||
#[cfg(debug_assertions)] | ||
fn on_request(&self, req: &mut ::rocket::Request, _data: &::rocket::Data) { | ||
let cm = req.guard::<::rocket::State<ContextManager>>() | ||
.expect("Template ContextManager registered in on_attach"); | ||
cm.reload_if_needed(&*self.custom_callback); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
reload |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what to do about this file. Two things I thought of were to add it to a gitignore, or to commit what the file should contain after a successful test run (in this case "reload").