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

227 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 配置管理模块
///
/// 采用分层配置设计:
/// 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");
}
}