227 lines
6.7 KiB
Rust
227 lines
6.7 KiB
Rust
/// 配置管理模块
|
||
///
|
||
/// 采用分层配置设计:
|
||
/// 1. project.config.toml - 项目默认配置(base_url、default_format)
|
||
/// 2. ~/.config/tts/config.toml - 用户配置(api_key、default_voice)
|
||
/// 用户配置会覆盖项目默认配置中的对应项
|
||
|
||
use anyhow::{Context, Result};
|
||
use home::home_dir;
|
||
use serde::{Deserialize, Serialize};
|
||
use std::fs;
|
||
use std::path::PathBuf;
|
||
|
||
/// 项目配置(从项目根目录的 project.config.toml 读取)
|
||
///
|
||
/// 这是项目的默认配置,包含 API 地址和默认格式
|
||
#[derive(Debug, Deserialize, Clone)]
|
||
pub struct ProjectConfig {
|
||
/// 项目版本号
|
||
#[allow(dead_code)]
|
||
pub version: String,
|
||
/// API 基础 URL 地址
|
||
#[serde(default = "default_base_url")]
|
||
pub base_url: String,
|
||
/// 默认音频格式
|
||
#[serde(default = "default_format")]
|
||
pub default_format: String,
|
||
}
|
||
|
||
impl Default for ProjectConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
version: "0.1.0".to_string(),
|
||
base_url: default_base_url(),
|
||
default_format: default_format(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 用户配置(从 ~/.config/tts/config.toml 读取)
|
||
///
|
||
/// 只包含用户需要设置的项,会覆盖项目默认配置中的对应项
|
||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
pub struct UserConfig {
|
||
/// Mimo-TTS API 密钥
|
||
pub api_key: String,
|
||
/// 默认音色
|
||
#[serde(default = "default_voice")]
|
||
pub default_voice: String,
|
||
}
|
||
|
||
impl Default for UserConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
api_key: String::new(),
|
||
default_voice: default_voice(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 合并后的应用配置
|
||
///
|
||
/// 结合了项目默认配置和用户配置
|
||
#[derive(Debug, Clone)]
|
||
pub struct Config {
|
||
/// Mimo-TTS API 密钥(来自用户配置)
|
||
pub api_key: String,
|
||
/// 默认音色(来自用户配置,覆盖项目默认值)
|
||
pub default_voice: String,
|
||
/// API 基础 URL 地址(来自项目配置)
|
||
pub base_url: String,
|
||
/// 默认音频格式(来自项目配置)
|
||
#[allow(dead_code)]
|
||
pub default_format: String,
|
||
}
|
||
|
||
impl Config {
|
||
/// 从项目配置和用户配置创建合并配置
|
||
pub fn from_configs(project: ProjectConfig, user: UserConfig) -> Self {
|
||
Self {
|
||
api_key: user.api_key,
|
||
default_voice: user.default_voice,
|
||
base_url: project.base_url,
|
||
default_format: project.default_format,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 返回默认的 API 基础 URL
|
||
fn default_base_url() -> String {
|
||
"https://api.xiaomimimo.com/v1/chat/completions".to_string()
|
||
}
|
||
|
||
/// 返回默认的语音音色
|
||
fn default_voice() -> String {
|
||
"mimo_default".to_string()
|
||
}
|
||
|
||
/// 返回默认的音频格式
|
||
fn default_format() -> String {
|
||
"wav".to_string()
|
||
}
|
||
|
||
/// 配置管理器(Singleton 模式)
|
||
///
|
||
/// 负责加载项目配置和用户配置,并合并为应用配置
|
||
pub struct ConfigManager {
|
||
/// 合并后的配置
|
||
config: Config,
|
||
/// 用户配置文件路径
|
||
user_config_path: PathBuf,
|
||
}
|
||
|
||
impl ConfigManager {
|
||
/// 创建配置管理器并加载配置
|
||
pub fn new() -> Result<Self> {
|
||
// 1. 加载项目配置(从项目根目录的 project.config.toml)
|
||
let project_config = Self::load_project_config()?;
|
||
|
||
// 2. 加载用户配置(从 ~/.config/tts/config.toml)
|
||
let (user_config, user_config_path) = Self::load_user_config()?;
|
||
|
||
// 3. 合并配置
|
||
let config = Config::from_configs(project_config, user_config);
|
||
|
||
Ok(Self {
|
||
config,
|
||
user_config_path,
|
||
})
|
||
}
|
||
|
||
/// 加载项目配置
|
||
fn load_project_config() -> Result<ProjectConfig> {
|
||
let project_config_path = std::env::current_dir()
|
||
.context("无法获取当前目录")?
|
||
.join("project.config.toml");
|
||
|
||
if project_config_path.exists() {
|
||
let content = fs::read_to_string(&project_config_path)
|
||
.with_context(|| format!("无法读取项目配置文件: {:?}", project_config_path))?;
|
||
let config: ProjectConfig = toml::from_str(&content)
|
||
.with_context(|| format!("无法解析项目配置文件: {:?}", project_config_path))?;
|
||
Ok(config)
|
||
} else {
|
||
// 如果项目配置文件不存在,使用默认配置
|
||
Ok(ProjectConfig::default())
|
||
}
|
||
}
|
||
|
||
/// 加载用户配置
|
||
fn load_user_config() -> Result<(UserConfig, PathBuf)> {
|
||
// 所有平台统一使用 ~/.config/tts/config.toml
|
||
let home = home_dir().ok_or_else(|| anyhow::anyhow!("无法获取用户家目录"))?;
|
||
let config_dir = home.join(".config").join("tts");
|
||
let config_path = config_dir.join("config.toml");
|
||
|
||
// 确保配置目录存在
|
||
fs::create_dir_all(&config_dir)
|
||
.with_context(|| format!("无法创建配置目录: {:?}", config_dir))?;
|
||
|
||
let config = if config_path.exists() {
|
||
let content = fs::read_to_string(&config_path)
|
||
.with_context(|| format!("无法读取用户配置文件: {:?}", config_path))?;
|
||
let config: UserConfig = toml::from_str(&content)
|
||
.with_context(|| format!("无法解析用户配置文件: {:?}", config_path))?;
|
||
config
|
||
} else {
|
||
UserConfig::default()
|
||
};
|
||
|
||
Ok((config, config_path))
|
||
}
|
||
|
||
/// 获取合并后的配置引用
|
||
pub fn get_config(&self) -> &Config {
|
||
&self.config
|
||
}
|
||
|
||
/// 更新 API Key
|
||
pub fn set_api_key(&mut self, api_key: String) {
|
||
self.config.api_key = api_key;
|
||
}
|
||
|
||
/// 更新默认音色
|
||
pub fn set_default_voice(&mut self, voice: String) {
|
||
self.config.default_voice = voice;
|
||
}
|
||
|
||
/// 获取用户配置文件路径
|
||
pub fn get_config_path(&self) -> &PathBuf {
|
||
&self.user_config_path
|
||
}
|
||
|
||
/// 保存用户配置到文件
|
||
///
|
||
/// 只保存用户配置项(api_key、default_voice)
|
||
pub fn save(&self) -> Result<()> {
|
||
let user_config = UserConfig {
|
||
api_key: self.config.api_key.clone(),
|
||
default_voice: self.config.default_voice.clone(),
|
||
};
|
||
|
||
let content = toml::to_string_pretty(&user_config)
|
||
.context("无法序列化用户配置")?;
|
||
|
||
fs::write(&self.user_config_path, content)
|
||
.with_context(|| format!("无法写入用户配置文件: {:?}", self.user_config_path))?;
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_default_voice() {
|
||
assert_eq!(default_voice(), "mimo_default");
|
||
}
|
||
|
||
#[test]
|
||
fn test_default_base_url() {
|
||
assert_eq!(default_base_url(), "https://api.xiaomimimo.com/v1/chat/completions");
|
||
}
|
||
}
|