feat: 添加守护进程(daemon)模式

- 实现 TCP Socket 守护进程,支持后台运行
- 添加 daemon 子命令:start/stop/status/logs
- 添加 send 子命令:发送文本到守护进程播放
- 添加日志级别自动检测(INFO/WARN/ERROR)
- 新日志格式:[时间戳] [级别] [PID] 消息
- 支持跨平台 ~/.config/tts/ 配置目录
This commit is contained in:
2026-04-25 05:50:28 +08:00
parent f81d1ab979
commit 19eb313bef
10 changed files with 1202 additions and 5 deletions

View File

@@ -3,6 +3,46 @@ mod config;
mod api;
mod ui;
mod tone;
mod daemon;
mod client;
/// 启动守护进程(后台运行)
///
/// 通过启动新进程来实现后台运行
fn spawn_daemon_process(port: u16) -> Result<()> {
// 获取当前可执行文件路径
let exe_path = std::env::current_exe()
.context("无法获取当前可执行文件路径")?;
// 启动新进程,执行 ttsd 命令
// 使用 nohup 实现后台运行Unix
#[cfg(unix)]
{
std::process::Command::new("nohup")
.arg(&exe_path)
.arg("ttsd")
.arg("--port")
.arg(port.to_string())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null())
.spawn()
.context("无法启动守护进程")?;
}
#[cfg(windows)]
{
std::process::Command::new("cmd")
.args(["/C", "start", "", &exe_path.to_string_lossy(), "ttsd", "--port", &port.to_string()])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null())
.spawn()
.context("无法启动守护进程")?;
}
Ok(())
}
use anyhow::{Context, Result};
use clap::Parser;
@@ -81,6 +121,49 @@ async fn run(cli: Cli) -> Result<()> {
// 引导式配置初始化
onboard().await
}
Some(Commands::Daemon(daemon_cmd)) => {
// 处理守护进程命令
match daemon_cmd.action {
cli::DaemonAction::Start { port, daemonize } => {
if daemonize {
// 后台运行:启动新进程执行 tt sd 命令
spawn_daemon_process(port)?;
println!("守护进程已在后台启动");
Ok(())
} else {
// 前台运行
daemon::start_daemon(port).await
}
}
cli::DaemonAction::Stop => {
daemon::stop_daemon()
}
cli::DaemonAction::Status => {
daemon::show_status()
}
cli::DaemonAction::Logs { lines } => {
daemon::show_logs(lines)
}
}
}
Some(Commands::DaemonMode { port }) => {
// 守护进程模式(由 daemon start -d 自动调用)
daemon::start_daemon(port).await
}
Some(Commands::Send { text, voice, format, style, port }) => {
// 发送文本到守护进程
client::send_to_daemon(
&text,
voice.as_deref(),
format.as_deref(),
style.as_deref(),
port,
)
.await
.map(|msg| {
println!("{}", msg);
})
}
// 没有子命令时,执行语音合成
None => {
// 检查参数组合