Changed config to a singleton pattern, updated state and config trait by adding resolver variant.
Updated README to reflect current structure approach.
This commit is contained in:
14
README.md
14
README.md
@@ -19,11 +19,14 @@ assets/
|
|||||||
generic/
|
generic/
|
||||||
intro/
|
intro/
|
||||||
Intro_MrNewVegas_N.mp3
|
Intro_MrNewVegas_N.mp3
|
||||||
|
shared/
|
||||||
|
Story_MrNewVegas_N.mp3
|
||||||
news/
|
news/
|
||||||
story_N/
|
story_N/
|
||||||
story.toml (ignored, metadata)
|
story.toml (optional, ignored)
|
||||||
Story_MrNewVegas_N.mp3
|
Story_MrNewVegas_N.mp3
|
||||||
Story_Guest_N.mp3
|
Story_Guest_N.mp3
|
||||||
|
Story_MrNewVegas_N.mp3 -> symlink shared/Story_MrNewVegas_N.mp3
|
||||||
.cache/ (automatically generated)
|
.cache/ (automatically generated)
|
||||||
songs/
|
songs/
|
||||||
my_song/
|
my_song/
|
||||||
@@ -32,11 +35,16 @@ assets/
|
|||||||
cache.json
|
cache.json
|
||||||
generic/
|
generic/
|
||||||
intro/
|
intro/
|
||||||
Intro_MrNewVegas_N.pcm (f32 pcm)
|
intro_N.pcm (f32 pcm)
|
||||||
Intro_MrNewVegas_N.cache.json
|
intro_N.cache.json
|
||||||
|
shared/
|
||||||
|
story_N.pcm (f32 pcm)
|
||||||
|
story_N.cache.json
|
||||||
news/
|
news/
|
||||||
story_N/
|
story_N/
|
||||||
part_N.pcm (f32 pcm)
|
part_N.pcm (f32 pcm)
|
||||||
|
part_N.pcm (f32 pcm)
|
||||||
|
part_N.pcm (f32 pcm) -> symlink shared/story_N.pcm
|
||||||
cache.json
|
cache.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
use std::{ env, path::PathBuf };
|
use std::{
|
||||||
|
env,
|
||||||
|
sync::{ Arc, LazyLock },
|
||||||
|
path::{ Path, PathBuf, Component },
|
||||||
|
};
|
||||||
use serde::{ Serialize, Deserialize };
|
use serde::{ Serialize, Deserialize };
|
||||||
|
|
||||||
use crate::app::parser::FromConfig;
|
use crate::app::parser::FromConfig;
|
||||||
@@ -12,9 +16,33 @@ pub struct Config {
|
|||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
|
static CFG: LazyLock<Arc<Config>> = LazyLock::new(|| {
|
||||||
|
Arc::new(<Config as FromConfig>::load())
|
||||||
|
});
|
||||||
|
|
||||||
impl Config
|
impl Config
|
||||||
{
|
{
|
||||||
pub fn load() -> Self { FromConfig::load() }
|
/// Arc to a Singleton Instance of Config.
|
||||||
|
pub fn load() -> Arc<Self> { CFG.clone() }
|
||||||
|
|
||||||
|
fn resolve(&mut self, root: impl AsRef<Path>)
|
||||||
|
{
|
||||||
|
let d = &mut self.defaults;
|
||||||
|
let r = root.as_ref();
|
||||||
|
|
||||||
|
d.songs_directory = Self::normalize(r.join(&d.songs_directory));
|
||||||
|
d.generic_directory = Self::normalize(r.join(&d.generic_directory));
|
||||||
|
d.cache_directory = Self::normalize(r.join(&d.cache_directory));
|
||||||
|
d.log_directory = Self::normalize(r.join(&d.log_directory));
|
||||||
|
d.temp_directory = Self::normalize(r.join(&d.temp_directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(path: impl AsRef<Path>) -> PathBuf
|
||||||
|
{
|
||||||
|
path.as_ref().components().fold(PathBuf::new(), |mut acc, c| {
|
||||||
|
match c { Component::ParentDir => { acc.pop(); acc }, _ => { acc.push(c); acc } }
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromConfig for Config
|
impl FromConfig for Config
|
||||||
@@ -37,13 +65,14 @@ impl FromConfig for Config
|
|||||||
.map(|p: PathBuf| root.join(p))
|
.map(|p: PathBuf| root.join(p))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self::deserialize_config(
|
Self::deserialize_config_with_resolver(
|
||||||
assets_directory.join("config"),
|
assets_directory.join("config"),
|
||||||
&[
|
&[
|
||||||
("ron", Self::from_ron),
|
("ron", Self::from_ron),
|
||||||
("toml", Self::from_toml),
|
("toml", Self::from_toml),
|
||||||
("json", Self::from_json),
|
("json", Self::from_json),
|
||||||
],
|
],
|
||||||
|
|cfg: &mut Self| cfg.resolve(&assets_directory)
|
||||||
).expect("Failed to deserialize config.")
|
).expect("Failed to deserialize config.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,11 +81,12 @@ impl FromConfig for Config
|
|||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
pub struct Defaults {
|
pub struct Defaults {
|
||||||
pub songs_directory: String,
|
pub songs_directory: PathBuf,
|
||||||
pub generic_directory: String,
|
pub generic_directory: PathBuf,
|
||||||
|
pub cache_directory: PathBuf,
|
||||||
|
|
||||||
pub log_directory: String,
|
pub log_directory: PathBuf,
|
||||||
pub temp_directory: String,
|
pub temp_directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use thiserror::Error;
|
|||||||
///
|
///
|
||||||
/// Implementors must define [`load`](FromConfig::load), which is responsible for
|
/// Implementors must define [`load`](FromConfig::load), which is responsible for
|
||||||
/// resolving the config path and calling [`deserialize_config`](FromConfig::deserialize_config).
|
/// resolving the config path and calling [`deserialize_config`](FromConfig::deserialize_config).
|
||||||
/// All parsing methods have default implementations supporting RON, TOML, and JSON,
|
/// All parsing methods have default implementations supporting [`RON`](FromConfig::from_ron),
|
||||||
|
/// [`TOML`](FromConfig::from_toml), and [`JSON`](FromConfig::from_json),
|
||||||
/// and can be overridden if custom parsing behaviour is needed.
|
/// and can be overridden if custom parsing behaviour is needed.
|
||||||
pub trait FromConfig
|
pub trait FromConfig
|
||||||
{
|
{
|
||||||
@@ -18,10 +19,14 @@ pub trait FromConfig
|
|||||||
|
|
||||||
/// Deserializes a config file into a rust struct.
|
/// Deserializes a config file into a rust struct.
|
||||||
///
|
///
|
||||||
|
/// Returns [`ConfigError::NotFound`] if no file matching any of the provided extensions
|
||||||
|
/// exists, [`ConfigError::Io`] if the file cannot be read, or the appropriate parse
|
||||||
|
/// error variant if deserialisation fails.
|
||||||
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```
|
/// ```
|
||||||
/// let cfg: Config = Config::deserialize_config(
|
/// let cfg: Config = Config::deserialize_config(
|
||||||
/// "path/to/config",
|
/// "path/to/config/file",
|
||||||
/// &[
|
/// &[
|
||||||
/// ("ron", Config::from_ron),
|
/// ("ron", Config::from_ron),
|
||||||
/// ("toml", Config::from_toml),
|
/// ("toml", Config::from_toml),
|
||||||
@@ -49,6 +54,50 @@ pub trait FromConfig
|
|||||||
Err(ConfigError::NotFound { path: file.as_ref().to_string_lossy().into() })
|
Err(ConfigError::NotFound { path: file.as_ref().to_string_lossy().into() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deserializes a config file into a Rust struct, then applies a post-processing
|
||||||
|
/// resolver before returning.
|
||||||
|
///
|
||||||
|
/// This is identical to [`deserialize_config`](FromConfig::deserialize_config), except
|
||||||
|
/// that after a successful parse, `resolver` is called with a mutable reference to the
|
||||||
|
/// deserialized value. Use it to fill in derived fields, apply default overrides, or
|
||||||
|
/// validate and normalise values that cannot be expressed in the raw config format.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// let cfg: Config = Config::deserialize_config(
|
||||||
|
/// "path/to/config/file",
|
||||||
|
/// &[
|
||||||
|
/// ("ron", Config::from_ron),
|
||||||
|
/// ("toml", Config::from_toml),
|
||||||
|
/// ("json", Config::from_json),
|
||||||
|
/// ],
|
||||||
|
/// |cfg: &mut Self| cfg.resolve(&assets_directory)
|
||||||
|
/// )?;
|
||||||
|
/// ```
|
||||||
|
fn deserialize_config_with_resolver<T>(file: impl AsRef<Path>, formats: &[(&str, ParseFn<T>)], resolver: impl Fn(&mut T)) -> Result<T, ConfigError>
|
||||||
|
where
|
||||||
|
T: for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
let base = file.as_ref();
|
||||||
|
|
||||||
|
for (ext, parse) in formats
|
||||||
|
{
|
||||||
|
let path = base.with_extension(ext);
|
||||||
|
if path.exists()
|
||||||
|
{
|
||||||
|
let raw = std::fs::read_to_string(&path)
|
||||||
|
.map_err(ConfigError::Io)?;
|
||||||
|
|
||||||
|
let mut cfg = parse(&raw)?;
|
||||||
|
|
||||||
|
resolver(&mut cfg);
|
||||||
|
return Ok(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ConfigError::NotFound { path: file.as_ref().to_string_lossy().into() })
|
||||||
|
}
|
||||||
|
|
||||||
fn from_ron<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
fn from_ron<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
||||||
ron::from_str(s).map_err(ConfigError::Ron)
|
ron::from_str(s).map_err(ConfigError::Ron)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
EnvFilter,
|
EnvFilter,
|
||||||
fmt::{
|
fmt::{
|
||||||
@@ -12,13 +13,13 @@ use crate::app::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub config: Config,
|
pub config: Arc<Config>,
|
||||||
pub scheduler: Scheduler
|
pub scheduler: Scheduler
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState
|
impl AppState
|
||||||
{
|
{
|
||||||
pub fn new(config: Config, scheduler: Scheduler) -> Self
|
pub fn new(config: Arc<Config>, scheduler: Scheduler) -> Self
|
||||||
{
|
{
|
||||||
Self { config, scheduler }
|
Self { config, scheduler }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user