diff --git a/README.md b/README.md index 725d217..1a2d252 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,22 @@ You can also build an OpenWrt package to install in your images with the SDK: # Copy the generated .ipk from bin/packages/... to your router opkg install sqm-autorate-rust_*.ipk ``` + +## Reflector files and inline UCI reflectors + +Inline `config reflector` sections supplement the default +`/etc/sqm-autorate-rust/reflectors.csv`. To use only inline reflectors, explicitly +disable file loading: + +```uci +config reflectors + option files 'none' + +config reflector + option host 'global.irtt.lochnair.net' + list protocols 'irtt' +``` + +Use `list files '/path/to/reflectors.csv'` instead of `option files 'none'` to +combine CSV files with inline reflectors. Omitting `files` keeps the default CSV; +UCI discards empty string options, so `option files ''` does not disable it. diff --git a/src/settings/mod.rs b/src/settings/mod.rs index f80cf5e..5cfeb49 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -270,6 +270,7 @@ impl NetworkSettings { #[derive(Clone, Debug, Deserialize, UciSection)] #[serde(default)] pub(crate) struct ReflectorSettings { + #[serde(deserialize_with = "deserialize_reflector_files")] pub(crate) files: Vec, pub(crate) countries: Vec, @@ -472,3 +473,25 @@ where let value = String::deserialize(deserializer)?; value.parse().map_err(de::Error::custom) } + +// UCI drops empty string options; `option files 'none'` disables file loading. +fn deserialize_reflector_files<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum Files { + List(Vec), + String(String), + } + + match Files::deserialize(deserializer)? { + Files::List(files) => Ok(files), + Files::String(value) if value.trim().is_empty() || value.trim() == "none" => Ok(Vec::new()), + Files::String(_) => Err(de::Error::custom( + "reflector files must be a list, an empty string, or none", + )), + } +} +