M src/cache.rs => src/cache.rs +5 -0
@@ 82,3 82,8 @@ where
pub type CacheFuture<Output> = Pin<Box<(dyn Future<Output = Result<Output, (StatusCode, &'static str)>> + Send + Sync)>>;
pub type CacheGetter<Output> = fn(&String) -> CacheFuture<Output>;
pub type Cache<Output> = Arc<RwLock<AsyncCache<String, Output, CacheGetter<Output>>>>;
+
+pub fn touch() {
+ user::touch();
+ font::touch();
+}
M src/cache/font.rs => src/cache/font.rs +4 -0
@@ 57,3 57,7 @@ static FONT_CACHE: LazyLock<FontCache> = LazyLock::new(|| {
pub async fn get_fontinfo(font: &String) -> Result<Arc<str>, (StatusCode, &'static str)> {
FONT_CACHE.write().await.get_owned(font).await
}
+
+pub fn touch() {
+ LazyLock::force(&FONT_CACHE);
+}
M src/cache/user.rs => src/cache/user.rs +4 -0
@@ 101,3 101,7 @@ pub async fn get_userinfo(user: &String) -> (Result<Arc<(User, Track, TrackStub)
}
}
}
+
+pub fn touch() {
+ LazyLock::force(&ALLOWLIST);
+}
M src/config.rs => src/config.rs +2 -2
@@ 23,7 23,7 @@ pub struct Config {
pub(crate) theme_dir: Option<Arc<str>>,
pub(crate) theme_ext_hbs: Arc<str>,
pub(crate) theme_ext_lua: Arc<str>,
- pub(crate) theme_debug: bool,
+ pub(crate) theme_dev: bool,
pub(crate) allowlist: BTreeSet<String>,
pub(crate) allowlist_mode: String,
@@ 48,7 48,7 @@ impl Config {
theme_dir: var("LFME_THEME_DIR").ok().map(Into::into),
theme_ext_hbs: var("LFME_THEME_EXT_HBS").unwrap_or_else(|_| ".hbs".into()).into(),
theme_ext_lua: var("LFME_THEME_EXT_LUA").unwrap_or_else(|_| ".lua".into()).into(),
- theme_debug: var("LFME_THEME_DEV").map(|h| &h == "1").unwrap_or(false),
+ theme_dev: var("LFME_THEME_DEV").map(|h| &h == "1").unwrap_or(false),
allowlist: var("LFME_WHITELIST").or_else(|_| var("LFME_ALLOWLIST")).ok().map(|w| w.split(',').map(|s| s.trim().to_string()).collect()).unwrap_or_default(),
allowlist_mode: var("LFME_WHITELIST_MODE").or_else(|_| var("LFME_ALLOWLIST_MODE")).map(|m| m.to_ascii_lowercase()).unwrap_or_else(|_| "open".into()),
M src/lib.rs => src/lib.rs +5 -0
@@ 10,3 10,8 @@ pub mod ctx;
pub mod theming;
pub use config::CONFIG;
+
+pub fn touch() {
+ cache::touch();
+ theming::touch();
+}
M src/main.rs => src/main.rs +2 -0
@@ 45,6 45,8 @@ async fn main() {
.unwrap_or(env_logger::Target::Stderr)
).init();
+ lfm_embed::touch();
+
let user = warp::path!("user" / String)
.and(warp::query::<UserQuery>())
.then(|s, q: UserQuery| async move {
M src/theming.rs => src/theming.rs +5 -0
@@ 24,3 24,8 @@ pub fn render_theme(name: Option<&str>, ctx: &crate::ctx::Ctx) -> (String, Resul
(theme.into(), res)
}
+
+pub fn touch() {
+ hbs::touch();
+ lua::touch();
+}
M src/theming/hbs.rs => src/theming/hbs.rs +5 -1
@@ 18,7 18,7 @@ static HANDLEBARS: LazyLock<Handlebars> = LazyLock::new(|| {
log::info!("Registering internal handlebars theme `{key}`");
hb.register_template_string(key, fulltext).unwrap();
}
- hb.set_dev_mode(CONFIG.theme_debug);
+ hb.set_dev_mode(CONFIG.theme_dev);
if let Some(themes_dir) = CONFIG.theme_dir.as_ref() {
log::info!("Registering handlebars theme dir `{themes_dir}` with extension `{}`.", CONFIG.theme_ext_hbs);
@@ 34,3 34,7 @@ pub fn render_theme(name: &str, ctx: &crate::ctx::Ctx) -> Option<Result<String,
let render = templ.renders(&HANDLEBARS, &ctx, &mut RenderContext::new(Some(&name.into()))).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
Some(render)
}
+
+pub fn touch() {
+ LazyLock::force(&HANDLEBARS);
+}
M src/theming/lua.rs => src/theming/lua.rs +35 -10
@@ 18,7 18,7 @@ static LUA: LazyLock<Mutex<Lua>> = LazyLock::new(|| {
lua.sandbox(true).expect("lua initialization");
lua.set_compiler(
- if CONFIG.theme_debug { Compiler::new().set_optimization_level(0).set_debug_level(2) }
+ if CONFIG.theme_dev { Compiler::new().set_optimization_level(0).set_debug_level(2) }
else { Compiler::new().set_optimization_level(2).set_debug_level(1) }
);
@@ 38,14 38,19 @@ static LUA: LazyLock<Mutex<Lua>> = LazyLock::new(|| {
for (k, v) in INTERNAL_THEMES {
log::info!("Registering compiled lua theme `{k}`.");
- let _ = themes.set(*k, lua.load(*v).into_function().expect("loading internal theme"));
+ let _ = themes.set(*k, lua.load(*v).eval::<Value>().expect("loading internal theme"));
}
if let Some(theme_dir) = CONFIG.theme_dir.as_ref() {
- log::info!("Registering lua theme dir `{theme_dir}` with extension `{}`.", CONFIG.theme_ext_lua);
- for (k, v) in walk_dir(theme_dir.as_ref().as_ref()).expect("walking theme dir") {
- log::info!("Registering runtime lua theme `{k}`.");
- let _ = themes.set(k, lua.load(v).eval::<Value>().expect("loading internal theme"));
+ if !CONFIG.theme_dev {
+ log::info!("Registering lua theme dir `{theme_dir}` with extension `{}`.", CONFIG.theme_ext_lua);
+ for (k, v) in walk_dir(theme_dir.as_ref().as_ref()).expect("walking theme dir") {
+ log::info!("Registering runtime lua theme `{k}`.");
+ let _ = themes.set(k, lua.load(v).eval::<Value>().expect("loading external theme"));
+ }
+ }
+ else {
+ log::info!("Ready to dev-load lua themes from `{theme_dir}` with extension `{}`.", CONFIG.theme_ext_lua);
}
}
@@ 86,13 91,29 @@ fn walk_dir(path: &Path) -> std::io::Result<Vec<(String, String)>> {
}
pub fn render_theme(name: &str, ctx: &crate::ctx::Ctx) -> Option<Result<String, StatusCode>> {
+ if name == ".." || name.starts_with("../") || name.ends_with("/..") || name.contains("/../") {
+ return Some(Err(StatusCode::BAD_REQUEST))
+ }
+
LUA.clear_poison();
let lua = LUA.lock().expect("FIXME: Mutex poisoning race condition.");
let themes: Table = lua.globals().get("__themes").unwrap();
- let Ok(Value::Function(theme)) = themes.get(name)
- else {
- return None;
- };
+ let theme;
+ match themes.get(name) {
+ Ok(Value::Function(themefn)) => { theme = themefn; },
+ Ok(Value::Nil) if !CONFIG.theme_dev || CONFIG.theme_dir.is_none() => { return None; },
+ Ok(Value::Nil) => {
+ let code = fs::read_to_string(CONFIG.theme_dir.as_ref().unwrap().to_string() + "/" + name + &CONFIG.theme_ext_lua).ok()?;
+ let res = lua.load(code).eval::<Value>();
+ match res {
+ Ok(Value::Function(themefn)) => { theme = themefn; },
+ Ok(v) => { log::error!("Got `{v:?}` instead of Function when dev-loading `{name}`."); return Some(Err(StatusCode::INTERNAL_SERVER_ERROR)); },
+ Err(e) => { log::error!("Error dev-loading `{name}`: {e}"); return Some(Err(StatusCode::INTERNAL_SERVER_ERROR)); },
+ }
+ },
+ Ok(v) => { log::error!("Got `{v:?}` instead of Function when loading `{name}`."); return Some(Err(StatusCode::INTERNAL_SERVER_ERROR)); }
+ Err(e) => { log::error!("Error loading `{name}`: {e}"); return Some(Err(StatusCode::INTERNAL_SERVER_ERROR)); }, //TODO: gate behind flag
+ }
let ctx = match lua.to_value(ctx) {
Ok(ok) => ok,
@@ 104,3 125,7 @@ pub fn render_theme(name: &str, ctx: &crate::ctx::Ctx) -> Option<Result<String,
Some(theme.call((ctx,)).map_err(|e| { log::error!("Lua theme execution error: {e}"); StatusCode::INTERNAL_SERVER_ERROR }))
}
+
+pub fn touch() {
+ LazyLock::force(&LUA);
+}