Files
haibao-tts-cli/src/config.rs

227 lines
6.7 KiB
Rust
Raw Normal View History

/// 配置管理模块
///
/// 采用分层配置设计:
/// 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");
}
}