Initial release v0.1.0

This commit is contained in:
Lihatoo 2026-08-21 18:34:18 +08:00
commit b997bd6078
62 changed files with 56224 additions and 0 deletions

158
src/domain/settings.rs Normal file
View file

@ -0,0 +1,158 @@
use serde::{Deserialize, Serialize};
use std::{fs, io, path::Path};
const SCHEMA_VERSION: u32 = 1;
/// settings.tomltz 软件自身的全局策略,与 core 运行关系不大。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Settings {
pub schema_version: u32,
#[serde(default)]
pub bypass: BypassConfig,
#[serde(default)]
pub log: LogConfig,
#[serde(default)]
pub update: UpdateConfig,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BypassConfig {
pub enabled: bool,
/// 除 bypass.list 外,直接内联的补充条目。
pub inline: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogConfig {
pub level: String,
/// tz.log 超过该大小直接清除重建,不保留归档。
pub max_size_mb: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdateConfig {
pub profiles: UpdateSection,
pub cores: UpdateSection,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdateSection {
pub auto_update: bool,
pub interval_minutes: u32,
}
impl Default for Settings {
fn default() -> Self {
Self {
schema_version: SCHEMA_VERSION,
bypass: BypassConfig::default(),
log: LogConfig::default(),
update: UpdateConfig::default(),
}
}
}
impl Default for BypassConfig {
fn default() -> Self {
Self {
enabled: true,
inline: vec!["localhost".into(), "127.0.0.0/8".into()],
}
}
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: "warn".into(),
max_size_mb: 10,
}
}
}
impl Default for UpdateConfig {
fn default() -> Self {
Self {
profiles: UpdateSection {
auto_update: false,
interval_minutes: 4320,
},
cores: UpdateSection {
auto_update: false,
interval_minutes: 14400,
},
}
}
}
impl Settings {
pub fn load(path: &Path) -> Result<Self, io::Error> {
let content = fs::read_to_string(path)?;
let settings: Self = toml::from_str(&content).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid settings.toml: {error}"),
)
})?;
if settings.schema_version != SCHEMA_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"unsupported settings.toml schema_version {}; expected {SCHEMA_VERSION}",
settings.schema_version
),
));
}
Ok(settings)
}
pub fn save(&self, path: &Path) -> Result<(), io::Error> {
let content = toml::to_string_pretty(self)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error.to_string()))?;
crate::platform::atomic_write(path, content.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::Settings;
#[test]
fn default_settings_write_log_and_bypass() {
let settings = Settings::default();
let text = toml::to_string_pretty(&settings).expect("serialize");
assert!(text.contains("[bypass]"));
assert!(text.contains("max_size_mb = 10"));
assert!(!text.contains("keep"));
}
#[test]
fn default_settings_contain_update_sections() {
let settings = Settings::default();
let text = toml::to_string_pretty(&settings).expect("serialize");
assert!(text.contains("[update.profiles]"));
assert!(text.contains("[update.cores]"));
}
#[test]
fn rejects_unknown_fields_and_schema_versions() {
let file = std::env::temp_dir().join(format!("tz-settings-{}", std::process::id()));
let mut text = toml::to_string_pretty(&Settings::default()).unwrap();
text.insert_str(text.find("[bypass]").unwrap(), "unknown = true\n\n");
std::fs::write(&file, text).unwrap();
assert!(Settings::load(&file).is_err());
let settings = Settings {
schema_version: 2,
..Default::default()
};
std::fs::write(&file, toml::to_string(&settings).unwrap()).unwrap();
assert!(Settings::load(&file).is_err());
std::fs::remove_file(file).unwrap();
}
}