refactor: 提取 proxycast-terminal 独立 crate

将终端模块 (~13k 行) 从主 crate 迁移至 crates/terminal/:
- 设计 TerminalEventEmit/TerminalEventEmitter 两层 trait 抽象
- DynEmitter newtype 包装 Arc<dyn TerminalEventEmit>
- 主 crate 保留 TauriEmitter 桥接层和重导出
- 187 个单元测试全部通过
This commit is contained in:
coso
2026-02-08 16:41:23 +08:00
parent 46b2303acc
commit ccca4cd479
38 changed files with 533 additions and 933 deletions
+28
View File
@@ -6673,6 +6673,7 @@ dependencies = [
"proxycast-infra",
"proxycast-providers",
"proxycast-services",
"proxycast-terminal",
"rand 0.8.5",
"regex",
"reqwest 0.12.28",
@@ -6847,6 +6848,33 @@ dependencies = [
"zip",
]
[[package]]
name = "proxycast-terminal"
version = "0.60.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"chrono",
"dashmap 5.5.3",
"dirs 5.0.1",
"parking_lot",
"portable-pty",
"proptest",
"proxycast-core",
"rusqlite",
"scopeguard",
"serde",
"serde_json",
"sha2",
"ssh2",
"tempfile",
"thiserror 1.0.69",
"tokio",
"tracing",
"uuid",
"whoami",
]
[[package]]
name = "psl-types"
version = "2.0.11"
+2
View File
@@ -15,6 +15,7 @@ proxycast-core = { path = "crates/core" }
proxycast-infra = { path = "crates/infra" }
proxycast-providers = { path = "crates/providers" }
proxycast-services = { path = "crates/services" }
proxycast-terminal = { path = "crates/terminal" }
voice-core = { path = "crates/voice-core" }
# 序列化
@@ -189,6 +190,7 @@ proxycast-core.workspace = true
proxycast-infra.workspace = true
proxycast-providers.workspace = true
proxycast-services.workspace = true
proxycast-terminal.workspace = true
voice-core.workspace = true
# Tauri
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "proxycast-terminal"
version.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
[dependencies]
# 项目内 crate
proxycast-core = { path = "../core" }
# 序列化
serde.workspace = true
serde_json.workspace = true
# 异步运行时
tokio.workspace = true
async-trait.workspace = true
# 错误处理
thiserror.workspace = true
# 日志
tracing.workspace = true
# 数据库
rusqlite.workspace = true
# 时间和 UUID
chrono.workspace = true
uuid.workspace = true
# 工具库
dirs.workspace = true
parking_lot.workspace = true
base64.workspace = true
sha2.workspace = true
dashmap.workspace = true
whoami.workspace = true
scopeguard.workspace = true
# 终端
portable-pty.workspace = true
# SSH
ssh2.workspace = true
[dev-dependencies]
proptest.workspace = true
tempfile.workspace = true
+41
View File
@@ -0,0 +1,41 @@
# proxycast-terminal
<!-- 一旦我所属的文件夹有所变化,请更新我 -->
## 架构说明
终端核心模块独立 crate,采用**后端预创建 PTY**架构(参考 WaveTerm)。
通过 `TerminalEventEmit` trait 抽象事件发射,不直接依赖 Tauri。
**核心原则:**
- 后端是会话的唯一真相来源
- PTY 使用默认大小 (24x80) 预创建
- 前端连接后通过 resize 同步实际大小
- 统一的 BlockController 抽象层支持多种连接类型
- 通过 trait 抽象与 Tauri 解耦
## Trait 设计(两层抽象)
- `TerminalEventEmit`:基础 trait(dyn 兼容,不要求 Clone)
- `TerminalEventEmitter`:扩展 trait = `TerminalEventEmit + Clone`(blanket impl)
- `DynEmitter`:`Arc<dyn TerminalEventEmit>` newtype,自动获得 `TerminalEventEmitter`
- `NoOpEmitter`:空实现,用于测试
## 文件索引
- `src/lib.rs` - 模块声明和类型重导出
- `src/emitter.rs` - 事件发射器 trait 定义
- `src/emit_helper.rs` - 事件发射辅助函数
- `src/error.rs` - 错误类型定义
- `src/events.rs` - 事件定义
- `src/pty_session.rs` - PTY 会话封装
- `src/session_manager.rs` - 会话管理器
- `src/tests.rs` - 单元测试(187 个)
- `src/block_controller/` - 块控制器模块
- `src/connections/` - 连接模块(本地 PTY、SSH、WSL)
- `src/integration/` - 集成模块(OSC 解析、Shell 集成、状态重同步)
- `src/persistence/` - 持久化存储模块
## 更新提醒
任何文件变更后,请更新此文档和相关的上级文档。
@@ -129,10 +129,10 @@ impl ControllerRegistry {
#[cfg(test)]
mod tests {
use super::*;
use crate::terminal::block_controller::traits::{
use crate::block_controller::traits::{
BlockControllerRuntimeStatus, BlockInputUnion, BlockMeta, RuntimeOpts,
};
use crate::terminal::TerminalError;
use crate::error::TerminalError;
use async_trait::async_trait;
/// 测试用的 Mock 控制器
@@ -17,15 +17,16 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::Arc;
use async_trait::async_trait;
use tauri::Emitter;
use tokio::sync::{mpsc, RwLock};
use super::traits::{
BlockController, BlockControllerRuntimeStatus, BlockInputUnion, BlockMeta, RuntimeOpts,
};
use crate::terminal::connections::ShellProc;
use crate::terminal::error::TerminalError;
use crate::terminal::persistence::BlockFile;
use crate::connections::ShellProc;
use crate::emit_helper;
use crate::emitter::TerminalEventEmitter;
use crate::error::TerminalError;
use crate::persistence::BlockFile;
/// 控制器状态事件名称
pub const CONTROLLER_STATUS_EVENT: &str = "controller:status";
@@ -67,7 +68,7 @@ impl From<BlockControllerRuntimeStatus> for ControllerStatusEvent {
/// - `cmd_run_once`: 仅运行一次(不自动重启)
/// - `cmd_clear_on_start`: 启动前清空输出
/// - `cmd_close_on_exit`: 退出后自动关闭
pub struct ShellController {
pub struct ShellController<E: TerminalEventEmitter> {
/// 控制器类型: "shell" | "cmd"
controller_type: String,
/// Tab ID
@@ -85,11 +86,11 @@ pub struct ShellController {
/// 连接名称(用于 SSH/WSL)
conn_name: RwLock<Option<String>>,
/// Shell 进程
shell_proc: RwLock<Option<ShellProc>>,
shell_proc: RwLock<Option<ShellProc<E>>>,
/// Shell 输入发送器
shell_input_tx: RwLock<Option<mpsc::Sender<BlockInputUnion>>>,
/// Tauri 应用句柄
app_handle: tauri::AppHandle,
/// 事件发射器
app_handle: E,
/// 块文件存储
block_file: Option<Arc<BlockFile>>,
/// 是否已运行过(用于 cmd:runonce)
@@ -98,25 +99,20 @@ pub struct ShellController {
current_meta: RwLock<Option<BlockMeta>>,
}
impl ShellController {
impl<E: TerminalEventEmitter> ShellController<E> {
/// 创建新的 ShellController
///
/// # 参数
/// - `tab_id`: Tab ID
/// - `block_id`: Block ID
/// - `controller_type`: 控制器类型 ("shell" | "cmd")
/// - `app_handle`: Tauri 应用句柄
/// - `app_handle`: 事件发射器
///
/// # 返回
/// 新的 ShellController 实例
///
/// _Requirements: 1.2, 1.3_
pub fn new(
tab_id: String,
block_id: String,
controller_type: String,
app_handle: tauri::AppHandle,
) -> Self {
pub fn new(tab_id: String, block_id: String, controller_type: String, app_handle: E) -> Self {
tracing::info!(
"[ShellController] 创建控制器: block_id={}, type={}",
block_id,
@@ -147,13 +143,13 @@ impl ShellController {
/// - `tab_id`: Tab ID
/// - `block_id`: Block ID
/// - `controller_type`: 控制器类型
/// - `app_handle`: Tauri 应用句柄
/// - `app_handle`: 事件发射器
/// - `block_file`: 块文件存储
pub fn with_block_file(
tab_id: String,
block_id: String,
controller_type: String,
app_handle: tauri::AppHandle,
app_handle: E,
block_file: Arc<BlockFile>,
) -> Self {
let mut controller = Self::new(tab_id, block_id, controller_type, app_handle);
@@ -224,7 +220,7 @@ impl ShellController {
let status = self.get_runtime_status();
let event = ControllerStatusEvent::from(status);
if let Err(e) = self.app_handle.emit(CONTROLLER_STATUS_EVENT, &event) {
if let Err(e) = emit_helper::emit(&self.app_handle, CONTROLLER_STATUS_EVENT, &event) {
tracing::error!(
"[ShellController] 发送状态更新事件失败: block_id={}, error={}",
self.block_id,
@@ -347,7 +343,7 @@ impl ShellController {
}
#[async_trait]
impl BlockController for ShellController {
impl<E: TerminalEventEmitter> BlockController for ShellController<E> {
/// 启动控制器
///
/// 根据 block_meta 配置启动 Shell 或命令执行进程。
@@ -15,7 +15,7 @@ use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::terminal::TerminalError;
use crate::error::TerminalError;
/// 终端大小
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
@@ -14,7 +14,7 @@
use serde::{Deserialize, Serialize};
use super::{is_local_conn_name, is_ssh_conn_name, is_wsl_conn_name};
use crate::terminal::error::TerminalError;
use crate::error::TerminalError;
// ============================================================================
// 连接类型枚举
@@ -27,22 +27,21 @@ use std::sync::Arc;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use parking_lot::Mutex;
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use tauri::Emitter;
use tauri::Manager;
use tokio::sync::mpsc;
use crate::terminal::block_controller::{BlockInputUnion, BlockMeta};
use crate::terminal::error::TerminalError;
use crate::terminal::events::{
event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent,
};
use crate::terminal::integration::{ShellLaunchBuilder, ShellType};
use crate::terminal::persistence::BlockFile;
use crate::emit_helper;
use crate::emitter::TerminalEventEmitter;
use crate::block_controller::{BlockInputUnion, BlockMeta};
use crate::error::TerminalError;
use crate::events::{event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
use crate::integration::{ShellLaunchBuilder, ShellType};
use crate::persistence::BlockFile;
/// Shell 进程封装
///
/// 封装 PTY 进程,提供输入输出和生命周期管理。
pub struct ShellProc {
pub struct ShellProc<E: TerminalEventEmitter> {
/// Block ID
block_id: String,
/// 控制器类型
@@ -57,9 +56,11 @@ pub struct ShellProc {
exit_code: Arc<AtomicI32>,
/// 是否已退出
exited: Arc<AtomicBool>,
/// 泛型标记
_emitter: std::marker::PhantomData<E>,
}
impl ShellProc {
impl<E: TerminalEventEmitter> ShellProc<E> {
/// 创建新的 Shell 进程
///
/// # 参数
@@ -67,7 +68,7 @@ impl ShellProc {
/// - `controller_type`: 控制器类型 ("shell" | "cmd")
/// - `rows`: 终端行数
/// - `cols`: 终端列数
/// - `app_handle`: Tauri 应用句柄
/// - `app_handle`: 事件发射器
/// - `block_meta`: 块元数据配置
/// - `input_rx`: 输入接收器
/// - `block_file`: 块文件存储(可选)
@@ -82,7 +83,7 @@ impl ShellProc {
controller_type: String,
rows: u16,
cols: u16,
app_handle: tauri::AppHandle,
app_handle: E,
block_meta: BlockMeta,
input_rx: mpsc::Receiver<BlockInputUnion>,
block_file: Option<Arc<BlockFile>>,
@@ -165,6 +166,7 @@ impl ShellProc {
shutdown_flag,
exit_code,
exited,
_emitter: std::marker::PhantomData,
})
}
@@ -175,7 +177,7 @@ impl ShellProc {
/// # 参数
/// - `controller_type`: 控制器类型
/// - `block_meta`: 块元数据
/// - `app_handle`: Tauri 应用句柄
/// - `app_handle`: 事件发射器
/// - `block_id`: Block ID
///
/// # 返回
@@ -185,7 +187,7 @@ impl ShellProc {
fn build_command(
controller_type: &str,
block_meta: &BlockMeta,
app_handle: &tauri::AppHandle,
app_handle: &E,
block_id: &str,
) -> Result<CommandBuilder, TerminalError> {
let mut cmd = if controller_type == "cmd" {
@@ -213,7 +215,7 @@ impl ShellProc {
/// _Requirements: 17.5, 17.7, 17.8, 17.9, 17.10_
fn build_shell_command(
block_meta: &BlockMeta,
app_handle: &tauri::AppHandle,
app_handle: &E,
block_id: &str,
) -> Result<CommandBuilder, TerminalError> {
// 获取用户默认 shell
@@ -222,7 +224,6 @@ impl ShellProc {
// 获取应用数据目录
let app_data_dir = app_handle
.path()
.app_data_dir()
.map_err(|e| TerminalError::Internal(format!("获取应用数据目录失败: {e}")))?;
@@ -299,7 +300,7 @@ impl ShellProc {
fn spawn_output_reader(
block_id: String,
mut reader: Box<dyn Read + Send>,
app_handle: tauri::AppHandle,
app_handle: E,
shutdown_flag: Arc<AtomicBool>,
exit_code: Arc<AtomicI32>,
exited: Arc<AtomicBool>,
@@ -323,9 +324,10 @@ impl ShellProc {
exited.store(true, Ordering::SeqCst);
// 发送状态事件
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Done,
exit_code: Some(exit_code.load(Ordering::SeqCst)),
@@ -350,9 +352,10 @@ impl ShellProc {
// 发送输出事件
let data = BASE64.encode(output_data);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
&TerminalOutputEvent {
session_id: block_id.clone(),
data,
},
@@ -367,9 +370,10 @@ impl ShellProc {
tracing::error!("[ShellProc] 读取错误: block_id={}, error={}", block_id, e);
exited.store(true, Ordering::SeqCst);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Error,
exit_code: None,
@@ -545,7 +549,7 @@ impl ShellProc {
}
}
impl Drop for ShellProc {
impl<E: TerminalEventEmitter> Drop for ShellProc<E> {
fn drop(&mut self) {
// 确保关闭标志被设置
self.shutdown_flag.store(true, Ordering::SeqCst);
@@ -9,14 +9,6 @@
//! - `wsl_connection` - WSL 连接(仅 Windows)
//! - `connection_router` - 连接类型路由
//! - `connection_config` - 连接配置持久化
//!
//! ## 功能
//! - 本地 PTY 进程管理
//! - SSH 远程连接和认证
//! - SSH 远程 PTY 创建和数据转发
//! - WSL 发行版连接
//! - 连接类型自动路由
//! - 连接配置存储和管理
pub mod connection_config;
pub mod connection_router;
@@ -26,12 +26,15 @@ use std::fmt;
use std::net::TcpStream;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering};
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use ssh2::{KeyboardInteractivePrompt as SshKeyboardInteractivePrompt, Session};
use crate::terminal::error::TerminalError;
use crate::emit_helper;
use crate::emitter::TerminalEventEmit;
use crate::error::TerminalError;
/// 默认 SSH 端口
pub const DEFAULT_SSH_PORT: u16 = 22;
@@ -574,8 +577,8 @@ pub struct SSHConn {
wsh_error: RwLock<Option<String>>,
/// 不使用 wsh 的原因
no_wsh_reason: RwLock<Option<String>>,
/// Tauri 应用句柄(用于事件广播)
app_handle: RwLock<Option<tauri::AppHandle>>,
/// 事件发射器(用于事件广播)
app_handle: RwLock<Option<Arc<dyn TerminalEventEmit>>>,
}
impl SSHConn {
@@ -598,26 +601,25 @@ impl SSHConn {
}
}
/// 创建带有 Tauri 应用句柄的 SSH 连接管理器
/// 创建带有事件发射器的 SSH 连接管理器
///
/// 启用事件广播功能。
pub fn with_app_handle(opts: SSHOpts, app_handle: tauri::AppHandle) -> Self {
pub fn with_app_handle(opts: SSHOpts, app_handle: impl TerminalEventEmit) -> Self {
let conn = Self::new(opts);
*conn.app_handle.write() = Some(app_handle);
*conn.app_handle.write() = Some(Arc::new(app_handle));
conn
}
/// 设置 Tauri 应用句柄
pub fn set_app_handle(&self, app_handle: tauri::AppHandle) {
*self.app_handle.write() = Some(app_handle);
/// 设置事件发射器
pub fn set_app_handle(&self, app_handle: impl TerminalEventEmit) {
*self.app_handle.write() = Some(Arc::new(app_handle));
}
/// 广播连接状态变更事件
///
/// _Requirements: 7.3_
fn broadcast_conn_change(&self) {
use crate::terminal::events::{event_names, ConnChangeEvent};
use tauri::Emitter;
use crate::events::{event_names, ConnChangeEvent};
if let Some(ref app_handle) = *self.app_handle.read() {
let status = self.derive_conn_status();
@@ -626,7 +628,8 @@ impl SSHConn {
status,
};
if let Err(e) = app_handle.emit(event_names::CONN_CHANGE, event) {
if let Err(e) = emit_helper::emit(app_handle.as_ref(), event_names::CONN_CHANGE, &event)
{
tracing::warn!("[SSHConn] 广播连接状态变更事件失败: {}", e);
}
}
@@ -21,15 +21,15 @@ use std::sync::Arc;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use parking_lot::Mutex;
use ssh2::{Channel, Session};
use tauri::Emitter;
use tokio::sync::mpsc;
use crate::terminal::block_controller::{BlockInputUnion, BlockMeta, TermSize};
use crate::terminal::error::TerminalError;
use crate::terminal::events::{
event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent,
};
use crate::terminal::persistence::BlockFile;
use crate::emit_helper;
use crate::emitter::TerminalEventEmitter;
use crate::block_controller::{BlockInputUnion, BlockMeta, TermSize};
use crate::error::TerminalError;
use crate::events::{event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
use crate::persistence::BlockFile;
use super::ssh_connection::SSHConn;
@@ -80,7 +80,7 @@ impl SSHShellProc {
session: &Session,
rows: u16,
cols: u16,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_meta: BlockMeta,
input_rx: mpsc::Receiver<BlockInputUnion>,
block_file: Option<Arc<BlockFile>>,
@@ -190,7 +190,7 @@ impl SSHShellProc {
ssh_conn: &SSHConn,
rows: u16,
cols: u16,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_meta: BlockMeta,
input_rx: mpsc::Receiver<BlockInputUnion>,
block_file: Option<Arc<BlockFile>>,
@@ -258,7 +258,7 @@ impl SSHShellProc {
fn spawn_output_reader(
block_id: String,
channel: Arc<Mutex<Channel>>,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
shutdown_flag: Arc<AtomicBool>,
exit_code: Arc<AtomicI32>,
exited: Arc<AtomicBool>,
@@ -294,9 +294,10 @@ impl SSHShellProc {
);
// 发送状态事件
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Done,
exit_code: Some(code),
@@ -321,9 +322,10 @@ impl SSHShellProc {
exit_code.store(code, Ordering::SeqCst);
exited.store(true, Ordering::SeqCst);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Done,
exit_code: Some(code),
@@ -353,9 +355,10 @@ impl SSHShellProc {
// 发送输出事件
let data = BASE64.encode(output_data);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
&TerminalOutputEvent {
session_id: block_id.clone(),
data,
},
@@ -379,9 +382,10 @@ impl SSHShellProc {
);
exited.store(true, Ordering::SeqCst);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Error,
exit_code: None,
@@ -25,15 +25,19 @@ use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use crate::terminal::block_controller::{BlockInputUnion, BlockMeta};
use crate::terminal::connections::{ConnStatus, ConnectionState};
use crate::terminal::error::TerminalError;
use crate::terminal::events::event_names;
use crate::block_controller::{BlockInputUnion, BlockMeta};
use crate::connections::{ConnStatus, ConnectionState};
use crate::emit_helper;
use crate::emitter::TerminalEventEmit;
#[cfg(target_os = "windows")]
use crate::terminal::events::{TerminalOutputEvent, TerminalStatusEvent};
use crate::terminal::persistence::BlockFile;
use crate::emitter::TerminalEventEmitter;
use crate::error::TerminalError;
use crate::events::event_names;
#[cfg(target_os = "windows")]
use crate::terminal::SessionStatus;
use crate::events::{TerminalOutputEvent, TerminalStatusEvent};
use crate::persistence::BlockFile;
#[cfg(target_os = "windows")]
use crate::SessionStatus;
#[cfg(target_os = "windows")]
use base64::engine::general_purpose::STANDARD as BASE64;
@@ -286,8 +290,8 @@ pub struct WSLConn {
wsh_error: RwLock<Option<String>>,
/// 不使用 wsh 的原因
no_wsh_reason: RwLock<Option<String>>,
/// Tauri 应用句柄(用于事件广播)
app_handle: RwLock<Option<tauri::AppHandle>>,
/// 事件发射器(用于事件广播)
app_handle: RwLock<Option<Arc<dyn TerminalEventEmit>>>,
}
impl WSLConn {
@@ -308,16 +312,16 @@ impl WSLConn {
}
}
/// 创建带有 Tauri 应用句柄的 WSL 连接管理器
pub fn with_app_handle(opts: WSLOpts, app_handle: tauri::AppHandle) -> Self {
/// 创建带有事件发射器的 WSL 连接管理器
pub fn with_app_handle(opts: WSLOpts, app_handle: impl TerminalEventEmit) -> Self {
let conn = Self::new(opts);
*conn.app_handle.write() = Some(app_handle);
*conn.app_handle.write() = Some(Arc::new(app_handle));
conn
}
/// 设置 Tauri 应用句柄
pub fn set_app_handle(&self, app_handle: tauri::AppHandle) {
*self.app_handle.write() = Some(app_handle);
/// 设置事件发射器
pub fn set_app_handle(&self, app_handle: impl TerminalEventEmit) {
*self.app_handle.write() = Some(Arc::new(app_handle));
}
/// 从连接字符串创建
@@ -362,8 +366,7 @@ impl WSLConn {
///
/// _Requirements: 7.3_
fn broadcast_conn_change(&self) {
use crate::terminal::events::ConnChangeEvent;
use tauri::Emitter;
use crate::events::ConnChangeEvent;
if let Some(ref app_handle) = *self.app_handle.read() {
let status = self.derive_conn_status();
@@ -372,7 +375,8 @@ impl WSLConn {
status,
};
if let Err(e) = app_handle.emit(event_names::CONN_CHANGE, event) {
if let Err(e) = emit_helper::emit(app_handle.as_ref(), event_names::CONN_CHANGE, &event)
{
tracing::warn!("[WSLConn] 广播连接状态变更事件失败: {}", e);
}
}
@@ -617,7 +621,7 @@ impl WSLShellProc {
opts: WSLOpts,
rows: u16,
cols: u16,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_meta: BlockMeta,
input_rx: mpsc::Receiver<BlockInputUnion>,
block_file: Option<Arc<BlockFile>>,
@@ -712,7 +716,7 @@ impl WSLShellProc {
opts: WSLOpts,
_rows: u16,
_cols: u16,
_app_handle: tauri::AppHandle,
_app_handle: impl TerminalEventEmit,
_block_meta: BlockMeta,
_input_rx: mpsc::Receiver<BlockInputUnion>,
_block_file: Option<Arc<BlockFile>>,
@@ -790,14 +794,12 @@ impl WSLShellProc {
fn spawn_output_reader(
block_id: String,
mut reader: Box<dyn Read + Send>,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmit,
shutdown_flag: Arc<AtomicBool>,
exit_code: Arc<AtomicI32>,
exited: Arc<AtomicBool>,
block_file: Option<Arc<BlockFile>>,
) {
use tauri::Emitter;
std::thread::spawn(move || {
let mut buffer = [0u8; 4096];
@@ -812,9 +814,10 @@ impl WSLShellProc {
tracing::info!("[WSLShellProc] WSL 进程已退出: block_id={}", block_id);
exited.store(true, Ordering::SeqCst);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Done,
exit_code: Some(exit_code.load(Ordering::SeqCst)),
@@ -837,9 +840,10 @@ impl WSLShellProc {
}
let data = BASE64.encode(output_data);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
&TerminalOutputEvent {
session_id: block_id.clone(),
data,
},
@@ -857,9 +861,10 @@ impl WSLShellProc {
);
exited.store(true, Ordering::SeqCst);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: block_id.clone(),
status: SessionStatus::Error,
exit_code: None,
@@ -0,0 +1,19 @@
//! 事件发射辅助函数
//!
//! 提供类型安全的事件发射便捷方法,封装序列化逻辑。
use serde::Serialize;
use crate::emitter::TerminalEventEmit;
/// 发射序列化事件
///
/// 将 payload 序列化为 JSON 后通过 emitter 发射。
pub fn emit<E: TerminalEventEmit + ?Sized, T: Serialize>(
emitter: &E,
event: &str,
payload: &T,
) -> Result<(), String> {
let value = serde_json::to_value(payload).map_err(|e| format!("序列化事件数据失败: {e}"))?;
emitter.emit_event(event, &value)
}
+73
View File
@@ -0,0 +1,73 @@
//! 事件发射器抽象
//!
//! 定义事件发射器 trait 层次,用于将 Tauri 事件发射功能抽象化,
//! 使终端模块不直接依赖 Tauri。
//!
//! ## 设计
//! - `TerminalEventEmit`:基础 trait(dyn 兼容,不要求 Clone)
//! - `TerminalEventEmitter`:扩展 trait(要求 Clone,用于泛型参数)
//! - `DynEmitter`:`Arc<dyn TerminalEventEmit>` 的 newtype,实现 `TerminalEventEmitter`
use std::path::PathBuf;
use std::sync::Arc;
/// 基础事件发射 trait(dyn 兼容)
///
/// 不要求 `Clone`,可以用作 `dyn TerminalEventEmit`。
/// 主 crate 中为 `tauri::AppHandle` 实现此 trait。
pub trait TerminalEventEmit: Send + Sync + 'static {
/// 发射事件到前端
fn emit_event(&self, event: &str, payload: &serde_json::Value) -> Result<(), String>;
/// 获取应用数据目录
fn app_data_dir(&self) -> Result<PathBuf, String>;
}
/// 扩展事件发射器 trait(要求 Clone)
///
/// 用于泛型参数场景(如 `ShellController<E>`、`ShellProc<E>`)。
/// 所有实现了 `TerminalEventEmit + Clone` 的类型自动实现此 trait。
pub trait TerminalEventEmitter: TerminalEventEmit + Clone {}
/// 自动实现:任何 `TerminalEventEmit + Clone` 的类型都是 `TerminalEventEmitter`
impl<T: TerminalEventEmit + Clone> TerminalEventEmitter for T {}
/// 动态事件发射器包装
///
/// 使用 `Arc<dyn TerminalEventEmit>` 包装,实现 `Clone` + `TerminalEventEmit`,
/// 从而自动获得 `TerminalEventEmitter`。
///
/// 用于需要存储和传递发射器但不想泛型化的场景(如 `TerminalSessionManager`)。
#[derive(Clone)]
pub struct DynEmitter(pub Arc<dyn TerminalEventEmit>);
impl DynEmitter {
/// 从实现了 TerminalEventEmit 的类型创建
pub fn new(emitter: impl TerminalEventEmit) -> Self {
Self(Arc::new(emitter))
}
}
impl TerminalEventEmit for DynEmitter {
fn emit_event(&self, event: &str, payload: &serde_json::Value) -> Result<(), String> {
self.0.emit_event(event, payload)
}
fn app_data_dir(&self) -> Result<PathBuf, String> {
self.0.app_data_dir()
}
}
/// 空事件发射器(用于测试)
#[derive(Debug, Clone)]
pub struct NoOpEmitter;
impl TerminalEventEmit for NoOpEmitter {
fn emit_event(&self, _event: &str, _payload: &serde_json::Value) -> Result<(), String> {
Ok(())
}
fn app_data_dir(&self) -> Result<PathBuf, String> {
Ok(PathBuf::from(".proxycast"))
}
}
@@ -11,7 +11,7 @@
use serde::{Deserialize, Serialize};
use crate::terminal::connections::ConnStatus;
use crate::connections::ConnStatus;
/// 会话状态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -19,15 +19,16 @@
use std::sync::Arc;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use tauri::Emitter;
use crate::terminal::block_controller::{
use crate::block_controller::{
BlockController, BlockControllerRuntimeStatus, BlockMeta, ControllerRegistry, RuntimeOpts,
ShellController,
};
use crate::terminal::error::TerminalError;
use crate::terminal::events::{event_names, TerminalOutputEvent};
use crate::terminal::persistence::BlockFile;
use crate::emit_helper;
use crate::emitter::TerminalEventEmitter;
use crate::error::TerminalError;
use crate::events::{event_names, TerminalOutputEvent};
use crate::persistence::BlockFile;
/// 终端重置序列
///
@@ -134,7 +135,7 @@ impl ResyncController {
block_id: &str,
block_meta: BlockMeta,
rt_opts: Option<RuntimeOpts>,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_file: Option<Arc<BlockFile>>,
options: ResyncOptions,
) -> Result<ResyncResult, TerminalError> {
@@ -338,7 +339,7 @@ impl ResyncController {
controller_name: &str,
tab_id: &str,
block_id: &str,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_file: Option<Arc<BlockFile>>,
) -> Result<Box<dyn BlockController>, TerminalError> {
match controller_name {
@@ -378,7 +379,7 @@ impl ResyncController {
///
/// _Requirements: 2.3_
fn send_reset_sequence(
app_handle: &tauri::AppHandle,
app_handle: &impl TerminalEventEmitter,
block_id: &str,
full_reset: bool,
) -> Result<(), TerminalError> {
@@ -390,15 +391,15 @@ impl ResyncController {
let data = BASE64.encode(reset_data);
app_handle
.emit(
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
session_id: block_id.to_string(),
data,
},
)
.map_err(|e| TerminalError::Internal(format!("发送重置序列失败: {e}")))?;
emit_helper::emit(
app_handle,
event_names::TERMINAL_OUTPUT,
&TerminalOutputEvent {
session_id: block_id.to_string(),
data,
},
)
.map_err(|e| TerminalError::Internal(format!("发送重置序列失败: {e}")))?;
tracing::debug!(
"[ResyncController] 发送重置序列: block_id={}, full={}",
@@ -423,7 +424,7 @@ impl ResyncController {
///
/// _Requirements: 2.4_
fn restore_history(
app_handle: &tauri::AppHandle,
app_handle: &impl TerminalEventEmitter,
block_id: &str,
block_file: &BlockFile,
) -> Result<usize, TerminalError> {
@@ -440,15 +441,15 @@ impl ResyncController {
let data_size = history_data.len();
let data = BASE64.encode(&history_data);
app_handle
.emit(
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
session_id: block_id.to_string(),
data,
},
)
.map_err(|e| TerminalError::Internal(format!("发送历史数据失败: {e}")))?;
emit_helper::emit(
app_handle,
event_names::TERMINAL_OUTPUT,
&TerminalOutputEvent {
session_id: block_id.to_string(),
data,
},
)
.map_err(|e| TerminalError::Internal(format!("发送历史数据失败: {e}")))?;
tracing::info!(
"[ResyncController] 恢复历史数据: block_id={}, size={} bytes",
@@ -556,7 +557,7 @@ pub async fn resync_controller(
block_id: &str,
block_meta: BlockMeta,
rt_opts: Option<RuntimeOpts>,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmitter,
block_file: Option<Arc<BlockFile>>,
force: bool,
) -> Result<ResyncResult, TerminalError> {
@@ -22,11 +22,12 @@ use std::sync::RwLock;
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use tauri::Emitter;
use super::osc_parser::{OSCParser, OSCSequence, PromptMarkType};
use crate::terminal::error::TerminalError;
use crate::terminal::events::event_names;
use crate::emit_helper;
use crate::emitter::TerminalEventEmit;
use crate::error::TerminalError;
use crate::events::event_names;
/// Shell 类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -161,15 +162,12 @@ pub struct ShellIntegration {
current_command: RwLock<Option<CommandInfo>>,
/// 上次命令开始时间
last_command_start: AtomicI64,
/// Tauri 应用句柄(可选)
app_handle: Option<tauri::AppHandle>,
/// 事件发射器(可选)
app_handle: Option<std::sync::Arc<dyn TerminalEventEmit>>,
}
impl ShellIntegration {
/// 创建新的 Shell 集成处理器
///
/// # 参数
/// - `block_id`: Block ID
pub fn new(block_id: String) -> Self {
Self {
block_id,
@@ -182,12 +180,8 @@ impl ShellIntegration {
}
}
/// 创建带有 Tauri 应用句柄的 Shell 集成处理器
///
/// # 参数
/// - `block_id`: Block ID
/// - `app_handle`: Tauri 应用句柄
pub fn with_app_handle(block_id: String, app_handle: tauri::AppHandle) -> Self {
/// 创建带有事件发射器的 Shell 集成处理器
pub fn with_app_handle(block_id: String, app_handle: impl TerminalEventEmit) -> Self {
Self {
block_id,
shell_type: RwLock::new(ShellType::Unknown),
@@ -195,7 +189,7 @@ impl ShellIntegration {
status: RwLock::new(ShellIntegrationStatus::Unknown),
current_command: RwLock::new(None),
last_command_start: AtomicI64::new(0),
app_handle: Some(app_handle),
app_handle: Some(std::sync::Arc::new(app_handle)),
}
}
@@ -343,9 +337,10 @@ impl ShellIntegration {
// 发送剪贴板事件到前端
if let Some(ref app_handle) = self.app_handle {
let _ = app_handle.emit(
let _ = emit_helper::emit(
app_handle.as_ref(),
event_names::CLIPBOARD_WRITE,
serde_json::json!({
&serde_json::json!({
"block_id": self.block_id,
"selection": selection,
"content": content,
@@ -497,7 +492,11 @@ impl ShellIntegration {
command_info,
};
if let Err(e) = app_handle.emit(event_names::SHELL_INTEGRATION_STATUS, &event) {
if let Err(e) = emit_helper::emit(
app_handle.as_ref(),
event_names::SHELL_INTEGRATION_STATUS,
&event,
) {
tracing::warn!(
"[ShellIntegration] 发送状态事件失败: block_id={}, error={}",
self.block_id,
@@ -25,8 +25,8 @@ use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use crate::terminal::error::TerminalError;
use crate::terminal::integration::shell_integration::ShellType;
use crate::error::TerminalError;
use crate::integration::shell_integration::ShellType;
/// Shell 集成脚本目录名
const SHELL_INTEGRATION_DIR: &str = "shell-integration";
+60
View File
@@ -0,0 +1,60 @@
//! ProxyCast 终端模块
//!
//! 提供 PTY 管理和会话管理能力,独立于 Tauri 框架。
//!
//! ## 模块结构
//! - `emitter` - 事件发射器抽象 trait
//! - `emit_helper` - 事件发射辅助函数
//! - `error` - 错误类型定义
//! - `events` - 事件定义
//! - `pty_session` - PTY 会话封装
//! - `session_manager` - 会话管理器
//! - `persistence` - 持久化存储(块文件、会话元数据)
//! - `block_controller` - 块控制器抽象层
//! - `connections` - 连接模块(本地 PTY、SSH、WSL)
//! - `integration` - 集成模块(Shell 集成、OSC 解析、状态重同步)
// 核心抽象
pub mod emit_helper;
pub mod emitter;
// 基础类型
pub mod error;
pub mod events;
// 会话管理
pub mod pty_session;
pub mod session_manager;
// 持久化
pub mod persistence;
// 块控制器
pub mod block_controller;
// 连接
pub mod connections;
// 集成
pub mod integration;
#[cfg(test)]
mod tests;
// 重新导出常用类型
pub use emitter::{DynEmitter, NoOpEmitter, TerminalEventEmitter};
pub use error::TerminalError;
pub use events::{SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
pub use pty_session::{PtySession, DEFAULT_COLS, DEFAULT_ROWS};
pub use session_manager::{SessionMetadata, TerminalSessionManager};
pub use block_controller::{
BlockController, BlockControllerRuntimeStatus, BlockInputUnion, BlockMeta, ControllerRegistry,
ControllerStatusEvent, RuntimeOpts, ShellController, TermSize, CONTROLLER_STATUS_EVENT,
};
pub use connections::ShellProc;
pub use integration::{
resync_controller, ResyncController, ResyncOptions, ResyncResult, TERMINAL_RESET_SEQUENCE,
TERMINAL_SOFT_RESET_SEQUENCE,
};
pub use persistence::{BlockFile, SessionMetadataStore, SessionRecord};
@@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use parking_lot::RwLock;
use crate::terminal::error::TerminalError;
use crate::error::TerminalError;
/// 默认终端块文件最大大小 (256KB)
pub const DEFAULT_TERM_MAX_FILE_SIZE: usize = 256 * 1024;
@@ -13,8 +13,8 @@ use chrono::Utc;
use rusqlite::{params, OptionalExtension};
use serde::{Deserialize, Serialize};
use crate::database::DbConnection;
use crate::terminal::error::TerminalError;
use crate::error::TerminalError;
use proxycast_core::database::DbConnection;
/// 会话记录(存储在 SQLite)
///
@@ -20,11 +20,12 @@ use std::sync::Arc;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use parking_lot::Mutex;
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use tauri::Emitter;
use tokio::sync::RwLock;
use super::error::TerminalError;
use super::events::{event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
use crate::emit_helper;
use crate::emitter::TerminalEventEmit;
use crate::error::TerminalError;
use crate::events::{event_names, SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
/// 默认终端行数
pub const DEFAULT_ROWS: u16 = 24;
@@ -99,7 +100,7 @@ impl PtySession {
/// # 返回
/// - `Ok(PtySession)`: 创建成功
/// - `Err(TerminalError)`: 创建失败
pub fn new(id: String, app_handle: tauri::AppHandle) -> Result<Self, TerminalError> {
pub fn new(id: String, app_handle: impl TerminalEventEmit) -> Result<Self, TerminalError> {
Self::with_size(id, DEFAULT_ROWS, DEFAULT_COLS, app_handle)
}
@@ -118,7 +119,7 @@ impl PtySession {
id: String,
rows: u16,
cols: u16,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmit,
) -> Result<Self, TerminalError> {
Self::with_size_and_cwd(id, rows, cols, None, app_handle)
}
@@ -140,7 +141,7 @@ impl PtySession {
rows: u16,
cols: u16,
cwd: Option<String>,
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmit,
) -> Result<Self, TerminalError> {
tracing::info!(
"[终端] 创建 PTY 会话 {}, 大小: {}x{}, cwd: {:?}",
@@ -255,9 +256,10 @@ impl PtySession {
});
// 发送状态事件
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: id_clone.clone(),
status: SessionStatus::Done,
exit_code: Some(0),
@@ -274,9 +276,10 @@ impl PtySession {
// 发送输出事件
let data = BASE64.encode(output_data);
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_OUTPUT,
TerminalOutputEvent {
&TerminalOutputEvent {
session_id: id_clone.clone(),
data,
},
@@ -293,9 +296,10 @@ impl PtySession {
*status_clone.write().await = SessionStatus::Error;
});
let _ = app_handle.emit(
let _ = emit_helper::emit(
&app_handle,
event_names::TERMINAL_STATUS,
TerminalStatusEvent {
&TerminalStatusEvent {
session_id: id_clone.clone(),
status: SessionStatus::Error,
exit_code: None,
@@ -26,13 +26,14 @@ use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::database::DbConnection;
use proxycast_core::database::DbConnection;
use super::block_controller::ControllerRegistry;
use super::error::TerminalError;
use super::events::SessionStatus;
use super::persistence::{BlockFile, SessionMetadataStore, SessionRecord};
use super::pty_session::{PtySession, DEFAULT_COLS, DEFAULT_ROWS};
use crate::block_controller::ControllerRegistry;
use crate::emitter::{DynEmitter, TerminalEventEmit};
use crate::error::TerminalError;
use crate::events::SessionStatus;
use crate::persistence::{BlockFile, SessionMetadataStore, SessionRecord};
use crate::pty_session::{PtySession, DEFAULT_COLS, DEFAULT_ROWS};
/// 会话元数据(用于前端展示)
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -114,16 +115,16 @@ pub struct TerminalSessionManager {
session_store: Option<Arc<SessionMetadataStore>>,
/// 块文件基础目录
block_file_base_dir: PathBuf,
/// Tauri 应用句柄
app_handle: tauri::AppHandle,
/// Tauri 应用句柄(抽象为事件发射器)
app_handle: DynEmitter,
}
impl TerminalSessionManager {
/// 创建新的会话管理器
///
/// # 参数
/// - `app_handle`: Tauri 应用句柄
pub fn new(app_handle: tauri::AppHandle) -> Self {
/// - `app_handle`: 事件发射器(实现 TerminalEventEmit trait)
pub fn new(app_handle: impl TerminalEventEmit) -> Self {
let block_file_base_dir = BlockFile::default_base_dir()
.unwrap_or_else(|_| PathBuf::from(".proxycast/terminal_blocks"));
@@ -137,19 +138,19 @@ impl TerminalSessionManager {
controller_registry: Arc::new(ControllerRegistry::new()),
session_store: None,
block_file_base_dir,
app_handle,
app_handle: DynEmitter::new(app_handle),
}
}
/// 创建带数据库连接的会话管理器
///
/// # 参数
/// - `app_handle`: Tauri 应用句柄
/// - `app_handle`: 事件发射器(实现 TerminalEventEmit trait)
/// - `db`: 数据库连接
///
/// _Requirements: 3.5_
pub fn with_database(
app_handle: tauri::AppHandle,
app_handle: impl TerminalEventEmit,
db: DbConnection,
) -> Result<Self, TerminalError> {
let mut manager = Self::new(app_handle);
+1 -1
View File
@@ -338,7 +338,7 @@ pub fn run() {
// 初始化终端会话管理器
{
let app_handle = app.handle().clone();
let terminal_manager = crate::terminal::TerminalSessionManager::new(app_handle.clone());
let terminal_manager = crate::terminal::TerminalSessionManager::new(crate::terminal::TauriEmitter(app_handle.clone()));
if let Some(state) = app_handle.try_state::<crate::commands::terminal_cmd::TerminalManagerState>() {
let mut guard = state.inner().0.blocking_write();
*guard = Some(terminal_manager);
+8 -76
View File
@@ -1,90 +1,22 @@
# terminal
# terminal(重导出层)
<!-- 一旦我所属的文件夹有所变化,请更新我 -->
## 架构说明
终端核心模块,采用**后端预创建 PTY**架构(参考 WaveTerm)。
本目录是终端模块的**重导出层**,实际实现已迁移至独立 crate `proxycast-terminal`(位于 `crates/terminal/`)。
**核心原则:**
- 后端是会话的唯一真相来源
- PTY 使用默认大小 (24x80) 预创建
- 前端连接后通过 resize 同步实际大小
- 通过 Tauri Commands 和 Events 暴露给前端
- 统一的 BlockController 抽象层支持多种连接类型
## 核心功能
- **PTY 管理**: 创建和管理伪终端进程(默认大小预创建)
- **会话管理**: 多会话支持,生命周期管理
- **实时输出**: 通过 Tauri Events 推送终端输出
- **状态通知**: 会话状态变化事件
- **持久化存储**: 块文件循环缓冲存储、会话元数据 SQLite 存储
- **块控制器**: 统一的控制器抽象层(Shell、Cmd、SSH、WSL)
- **连接管理**: 本地 PTY、SSH、WSL 连接支持
- **Shell 集成**: OSC 序列解析、状态重同步、命令跟踪
本层职责:
- 提供 `TauriEmitter` newtype,桥接 Tauri `AppHandle` 与终端 crate 的 `TerminalEventEmit` trait
- 重导出 `proxycast-terminal` 的所有公共模块和类型,保持 `crate::terminal::xxx` 路径兼容
## 文件索引
- `mod.rs` - 模块入口和类型导出
- `error.rs` - 错误类型定义
- `events.rs` - Tauri 事件定义(terminal:output, terminal:status, terminal:shell-integration)
- `pty_session.rs` - PTY 会话封装(支持默认大小创建)
- `session_manager.rs` - 会话管理器
- `tests.rs` - 单元测试
- `block_controller/` - 块控制器模块
- `mod.rs` - 模块入口
- `traits.rs` - BlockController trait 定义
- `registry.rs` - 控制器注册表
- `shell_controller.rs` - Shell/Cmd 控制器实现
- `connections/` - 连接模块
- `mod.rs` - 模块入口
- `local_pty.rs` - 本地 PTY 连接(ShellProc)
- `ssh_connection.rs` - SSH 远程连接(待实现)
- `wsl_connection.rs` - WSL 连接(待实现)
- `integration/` - 集成模块
- `mod.rs` - 模块入口
- `resync.rs` - 状态重同步控制器
- `osc_parser.rs` - OSC 序列解析器(OSC 7/52/133/16162)
- `shell_integration.rs` - Shell 集成处理器(状态管理、命令跟踪)
- `persistence/` - 持久化存储模块
- `mod.rs` - 模块入口
- `block_file.rs` - 块文件循环缓冲存储
- `session_store.rs` - 会话元数据 SQLite 存储
- `mod.rs` - TauriEmitter 定义 + proxycast-terminal 重导出
## 命令接口
## 实际实现
| 命令 | 描述 | 参数 |
|------|------|------|
| `terminal_create_session` | 创建终端会话(默认大小) | 无 |
| `terminal_write` | 向终端发送输入 | `session_id`, `data` |
| `terminal_resize` | 调整终端大小 | `session_id`, `rows`, `cols` |
| `terminal_close` | 关闭终端会话 | `session_id` |
| `terminal_list_sessions` | 获取所有会话列表 | 无 |
| `terminal_get_session` | 获取单个会话信息 | `session_id` |
## 事件定义
| 事件名 | 描述 | 数据结构 |
|--------|------|----------|
| `terminal:output` | 终端输出数据 | `{ session_id, data }` |
| `terminal:status` | 会话状态变化 | `{ session_id, status, exit_code?, error? }` |
| `terminal:shell-integration` | Shell 集成状态变化 | `{ block_id, status, current_dir?, command_info? }` |
| `terminal:clipboard-write` | 剪贴板写入请求 | `{ block_id, selection, content }` |
| `controller:status` | 控制器状态变化 | `{ block_id, version, shell_proc_status, ... }` |
## 常量
- `DEFAULT_ROWS`: 默认终端行数 (24)
- `DEFAULT_COLS`: 默认终端列数 (80)
- `DEFAULT_TERM_MAX_FILE_SIZE`: 默认块文件最大大小 (256KB)
- `TERMINAL_RESET_SEQUENCE`: 终端完全重置序列
- `TERMINAL_SOFT_RESET_SEQUENCE`: 终端软重置序列
## 依赖
- `portable-pty` - 跨平台 PTY 支持
- `rusqlite` - SQLite 数据库支持
详见 `crates/terminal/` 目录及其 README。
## 更新提醒
@@ -1,123 +0,0 @@
# block_controller
<!-- 一旦我所属的文件夹有所变化,请更新我 -->
## 架构说明
块控制器模块,提供统一的控制器抽象层,支持不同类型的终端连接。
**核心原则:**
- 所有控制器类型实现统一的 BlockController trait
- 通过注册表管理控制器生命周期
- 支持 Shell、Cmd、SSH、WSL 等多种控制器类型
## 核心功能
- **BlockController trait**: 统一的控制器接口(start、stop、send_input、get_runtime_status)
- **ShellController**: Shell/Cmd 控制器实现,管理本地和远程 Shell 进程
- **控制器注册表**: 按 block_id 管理控制器实例
- **运行时状态**: 提供控制器状态查询
- **状态事件广播**: 通过 Tauri 事件系统广播状态更新
## 文件索引
- `mod.rs` - 模块入口和类型导出
- `traits.rs` - BlockController trait 定义、BlockControllerRuntimeStatus、BlockInputUnion、BlockMeta
- `registry.rs` - 控制器注册表(HashMap + RwLock)
- `shell_controller.rs` - ShellController 实现,支持 shell 和 cmd 两种模式
## 数据结构
### BlockControllerRuntimeStatus
```rust
pub struct BlockControllerRuntimeStatus {
pub block_id: String,
pub version: i32,
pub shell_proc_status: String, // "init" | "running" | "done"
pub shell_proc_conn_name: Option<String>,
pub shell_proc_exit_code: i32,
}
```
### BlockInputUnion
```rust
pub struct BlockInputUnion {
pub input_data: Option<Vec<u8>>,
pub sig_name: Option<String>,
pub term_size: Option<TermSize>,
}
```
### BlockMeta
```rust
pub struct BlockMeta {
pub controller: Option<String>, // "shell" | "cmd"
pub connection: Option<String>, // SSH/WSL 连接名称
pub cmd: Option<String>, // 命令字符串
pub cmd_args: Option<Vec<String>>, // 命令参数
pub cmd_cwd: Option<String>, // 工作目录
pub cmd_env: Option<HashMap<String, String>>, // 环境变量
pub cmd_run_on_start: Option<bool>, // 启动时自动运行
pub cmd_run_once: Option<bool>, // 仅运行一次
pub cmd_clear_on_start: Option<bool>, // 启动前清空输出
pub cmd_close_on_exit: Option<bool>, // 退出后自动关闭
// ... 其他终端配置
}
```
## ShellController 功能
### 支持的控制器类型
- **shell**: 交互式 Shell 模式,启动用户默认 Shell
- **cmd**: 命令执行模式,执行指定命令
### Cmd 模式配置选项
- `cmd_run_on_start`: 启动时自动运行命令(默认 true)
- `cmd_run_once`: 仅运行一次,不自动重启
- `cmd_clear_on_start`: 启动前清空输出历史
- `cmd_close_on_exit`: 命令退出后自动关闭
### 状态事件
通过 `controller:status` 事件广播状态更新:
```rust
pub struct ControllerStatusEvent {
pub block_id: String,
pub version: i32,
pub shell_proc_status: String,
pub shell_proc_conn_name: Option<String>,
pub shell_proc_exit_code: i32,
}
```
## 依赖
- `async-trait` - 异步 trait 支持
- `tokio` - 异步运行时(RwLock、mpsc)
- `serde` - 序列化支持
- `scopeguard` - 作用域守卫
- `tauri` - 事件系统
## Requirements 覆盖
- 1.1: BlockController trait 定义
- 1.2: Shell 控制器创建
- 1.3: Cmd 控制器创建
- 1.6: 控制器注册表
- 1.8: get_runtime_status 方法
- 2.7: 状态事件广播
- 16.5: cmd:runonstart 配置
- 16.6: cmd:runonce 配置
- 16.7: cmd:clearonstart 配置
- 16.8: cmd:closeonexit 配置
- 16.9: 重启按钮支持
## 更新提醒
任何文件变更后,请更新此文档和相关的上级文档。
@@ -1,356 +0,0 @@
# connections
<!-- 一旦我所属的文件夹有所变化,请更新我 -->
## 架构说明
连接模块,提供不同类型的终端连接实现。
**核心原则:**
- 封装 PTY 进程管理
- 支持本地、SSH、WSL 多种连接类型
- 异步输入输出处理
## 核心功能
- **ShellProc**: 本地 PTY 进程封装,支持 shell 和 cmd 模式
- **SSHConn**: SSH 远程连接管理器,支持多种认证方式
- **SSHShellProc**: SSH 远程 Shell 进程封装,支持远程 PTY 创建和数据转发
- **WSLConn**: WSL 连接管理器(仅 Windows),支持发行版列表和 PTY 创建
- **输出读取**: 异步读取 PTY 输出并通过 Tauri 事件推送
- **输入处理**: 处理键盘输入、信号和终端大小调整
- **块文件集成**: 自动保存输出到块文件
## 文件索引
- `mod.rs` - 模块入口和类型导出
- `local_pty.rs` - 本地 PTY 连接实现(ShellProc)
- `ssh_connection.rs` - SSH 远程连接实现
- `ssh_shell_proc.rs` - SSH 远程 Shell 进程实现
- `wsl_connection.rs` - WSL 连接实现(仅 Windows)
- `connection_router.rs` - 连接类型路由和工厂模式
## ShellProc 功能
### 创建进程
```rust
let shell_proc = ShellProc::new(
block_id,
controller_type, // "shell" | "cmd"
rows,
cols,
app_handle,
block_meta,
input_rx,
block_file,
).await?;
```
### 支持的命令模式
- **shell 模式**: 启动用户默认 Shell($SHELL 或 /bin/bash)
- **cmd 模式**: 执行指定命令,支持参数和环境变量
### 环境变量
自动设置以下环境变量:
- `TERM=xterm-256color`
- `COLORTERM=truecolor`
- 自定义环境变量(通过 `cmd_env`)
### 进程控制
- `terminate()`: 优雅终止(SIGTERM)
- `kill()`: 强制终止(SIGKILL)
- `resize()`: 调整终端大小
- `write()`: 写入数据到 PTY
## SSH 连接功能
### 连接字符串解析
```rust
// 支持多种格式
let opts = SSHOpts::parse("user@host:port")?;
let opts = SSHOpts::parse("ssh://user@host")?;
let opts = SSHOpts::parse("[::1]:22")?; // IPv6
```
### 连接状态管理
```rust
let conn = SSHConn::new(opts);
conn.connect(&conn_flags).await?;
conn.authenticate(&auth_methods).await?;
let status = conn.derive_conn_status();
```
### 支持的认证方式
- 公钥认证(密钥文件)
- SSH Agent 认证
- 密码认证
- 键盘交互认证(待完善)
### SSH 配置文件解析
支持从 `~/.ssh/config` 读取连接配置:
```rust
// 获取主机配置(自动合并通配符配置)
let config = SSHConfigParser::get_host_config("myserver")?;
// 从指定文件获取配置
let config = SSHConfigParser::get_host_config_from_file(&path, "myserver")?;
// 解析配置内容
let hosts = SSHConfigParser::parse_config_content(content)?;
```
#### 支持的配置选项
- **基础选项**: HostName, User, Port, IdentityFile
- **认证选项**: PubkeyAuthentication, PasswordAuthentication, KbdInteractiveAuthentication, PreferredAuthentications
- **连接选项**: ConnectTimeout, ServerAliveInterval, ServerAliveCountMax, Compression
- **代理选项**: ProxyJump, ProxyCommand
- **转发选项**: LocalForward, RemoteForward, DynamicForward, ForwardAgent
- **其他选项**: BatchMode, StrictHostKeyChecking, RequestTTY, RemoteCommand, SendEnv, SetEnv
#### 配置合并语义
遵循 SSH 配置文件的 "first match wins" 语义:
- 按顺序遍历所有 Host 块
- 如果模式匹配,合并配置(第一个匹配的值优先)
- 通配符 `*` 匹配所有主机
- IdentityFile 是累加的(不覆盖)
#### 通配符模式匹配
支持以下模式:
- `*` - 匹配所有
- `*.example.com` - 后缀匹配
- `server*` - 前缀匹配
- `?` - 匹配单个字符
- `!pattern` - 否定匹配(排除)
### ProxyJump 支持
支持跳板机配置,最大深度为 10:
```rust
// 解析 ProxyJump 链
let chain = SSHConfigParser::parse_proxy_jump_chain("jump1.com, user@jump2.com:2222");
// 解析单个跳板机
let opts = SSHConfigParser::parse_proxy_jump_host("user@jump.example.com:2222")?;
// 解析完整的 ProxyJump 链(递归解析每个跳板机的配置)
let chain = SSHConfigParser::resolve_proxy_jump_chain("bastion@jump.example.com", 0)?;
```
## SSH 远程 Shell 进程功能
### 创建远程 Shell 进程
```rust
// 从 SSH 会话创建
let ssh_proc = SSHShellProc::new(
block_id,
controller_type, // "shell" | "cmd"
&session,
rows,
cols,
app_handle,
block_meta,
input_rx,
block_file,
).await?;
// 从 SSHConn 创建(便捷方法)
let ssh_proc = SSHShellProc::from_ssh_conn(
block_id,
controller_type,
&ssh_conn,
rows,
cols,
app_handle,
block_meta,
input_rx,
block_file,
).await?;
```
### 远程 PTY 功能
- **PTY 请求**: 使用 xterm-256color 终端类型
- **Shell 模式**: 启动远程交互式 Shell
- **Cmd 模式**: 执行远程命令,支持工作目录和环境变量
- **数据转发**: 异步读取远程输出并推送到前端
- **终端大小同步**: 自动同步终端大小到远程 PTY
### 进程控制
- `terminate()`: 优雅终止(发送 Ctrl+C 并关闭 Channel)
- `kill()`: 强制终止(直接关闭 Channel)
- `resize()`: 调整远程 PTY 大小
- `write()`: 写入数据到远程 PTY
- `send_eof()`: 发送 EOF 到远程
### 信号处理
由于 ssh2 crate 限制,信号通过控制字符发送:
- `SIGINT`: 发送 Ctrl+C (0x03)
- `SIGQUIT`: 发送 Ctrl+\ (0x1C)
## WSL 连接功能(仅 Windows)
### 连接字符串解析
```rust
// 支持多种格式
let opts = WSLOpts::parse("wsl://")?; // 默认发行版
let opts = WSLOpts::parse("wsl://Ubuntu")?; // 指定发行版
let opts = WSLOpts::parse("wsl://Ubuntu/home/user")?; // 指定路径
```
### 发行版管理
```rust
// 列出所有可用的 WSL 发行版
let distros = WSLConn::list_distros()?;
// 获取默认发行版
let default = WSLConn::get_default_distro()?;
// 检查 WSL 是否可用
let available = WSLConn::is_wsl_available();
```
### 连接状态管理
```rust
let conn = WSLConn::new(opts);
conn.connect().await?;
let status = conn.derive_conn_status();
```
### WSL Shell 进程
```rust
let wsl_proc = WSLShellProc::new(
block_id,
opts,
rows,
cols,
app_handle,
block_meta,
input_rx,
block_file,
).await?;
```
### 连接类型检测
```rust
// 检测连接类型
is_local_conn_name("local"); // true
is_ssh_conn_name("user@host"); // true
is_wsl_conn_name("wsl://Ubuntu"); // true
```
## 连接类型路由
### 自动路由
根据连接名称自动选择连接类型:
```rust
use crate::terminal::connections::{ConnectionRouter, ConnectionType};
// 自动路由
let conn_type = ConnectionRouter::route(""); // Local
let conn_type = ConnectionRouter::route("local"); // Local
let conn_type = ConnectionRouter::route("user@host"); // SSH
let conn_type = ConnectionRouter::route("wsl://Ubuntu"); // WSL
```
### 路由规则
1. 空字符串或 "local" → `ConnectionType::Local`
2. 以 "wsl://" 开头或等于 "wsl" → `ConnectionType::WSL`
3. 以 "ssh://" 开头、包含 "@" 或其他非本地/WSL 格式 → `ConnectionType::SSH`
### 连接验证
```rust
// 验证连接名称格式
let conn_type = ConnectionRouter::validate("user@host:22")?;
// 检查连接类型是否在当前平台可用
let available = ConnectionRouter::is_available(ConnectionType::WSL);
```
### 连接信息
```rust
// 获取完整的连接信息
let info = ConnectionInfo::from_conn_name("user@host");
println!("类型: {}", info.conn_type); // ssh
println!("可用: {}", info.available); // true
println!("描述: {}", info.description); // SSH 远程连接
```
## 事件
通过 Tauri 事件系统发送:
- `terminal:output` - 终端输出数据(Base64 编码)
- `terminal:status` - 终端状态变化
- `terminal:conn-change` - 连接状态变化
## 依赖
- `portable-pty` - 跨平台 PTY 支持
- `ssh2` - SSH 协议支持
- `tokio` - 异步运行时
- `base64` - 数据编码
- `parking_lot` - 高性能锁
## Requirements 覆盖
### 连接类型路由 (connection_router.rs)
- 1.4: 创建 SSH 终端时使用 SSH_Connection 建立远程连接
- 1.5: 创建 WSL 终端时使用 WSL_Connection 建立连接
### 本地 PTY (local_pty.rs)
- 16.1: cmd 命令字符串配置
- 16.2: cmd_args 参数配置
- 16.3: cmd_cwd 工作目录配置
- 17.1: Shell 进程生命周期管理
- 17.2: 环境变量设置
- 17.3: 优雅终止和强制终止
- 17.4: 退出码记录
### SSH 连接 (ssh_connection.rs)
- 4.1: SSH 连接字符串解析
- 4.3-4.6: 多种认证方式
- 4.7: ProxyJump 跳板机配置支持
- 4.10: 连接断开处理
- 4.12: SSH 配置文件解析(~/.ssh/config)
- 7.1-7.7: 连接状态管理
### SSH 远程 Shell 进程 (ssh_shell_proc.rs)
- 4.2: SSH 连接建立成功时创建远程 PTY 会话
- 4.7: 支持 ProxyJump 配置(通过 SSHConn)
- 4.11: 用户调整终端大小时同步调整远程 PTY 大小
### WSL 连接 (wsl_connection.rs)
- 5.1: 连接到指定的 WSL 发行版
- 5.2: 创建 PTY 会话
- 5.3: 列出所有可用的 WSL 发行版
- 5.4: 连接断开处理和重连
- 5.6: 终端大小同步
## 更新提醒
任何文件变更后,请更新此文档和相关的上级文档。
@@ -1,78 +0,0 @@
# integration
<!-- 一旦我所属的文件夹有所变化,请更新我 -->
## 架构说明
集成模块,提供 Shell 集成、OSC 序列解析、状态重同步、Shell 集成脚本管理等功能。
**核心原则:**
- OSC 序列解析和处理
- Shell 集成状态管理
- Shell 集成脚本安装和管理
- 终端状态重同步机制
## 核心功能
- **OSC 解析器**: 解析 OSC 7/52/133/16162 序列
- **Shell 集成**: 目录同步、命令时间记录、状态管理
- **Shell 脚本**: 各种 Shell 的集成脚本安装和启动配置
- **状态重同步**: 连接恢复时重建终端状态
## 文件索引
- `mod.rs` - 模块入口,导出公共类型
- `resync.rs` - 状态重同步控制器,实现终端状态重建
- `osc_parser.rs` - OSC 序列解析器,支持 OSC 7/52/133/16162
- `shell_integration.rs` - Shell 集成处理器,管理 Shell 状态和命令跟踪
- `shell_scripts.rs` - Shell 集成脚本管理,支持 Bash/Zsh/Fish/PowerShell
## 已实现功能
### 任务 6: 状态重同步控制器 ✅
- `ResyncController` - 状态重同步控制器
- `resync_controller` - 便捷重同步函数
- `ResyncOptions` - 重同步选项配置
- `ResyncResult` - 重同步结果
- 终端重置序列发送(完全重置和软重置)
- 历史数据恢复(从 BlockFile 读取)
### 任务 7.1: OSC 序列解析器 ✅
- `OSCParser` - OSC 序列解析器
- `OSCSequence` - OSC 序列类型枚举
- `PromptMarkType` - 命令提示符标记类型
- `ParsedOSC` - 解析结果结构
- `strip_osc_sequences` - 过滤 OSC 序列工具函数
- 支持 OSC 7(当前目录)、OSC 52(剪贴板)、OSC 133(命令标记)、OSC 16162(Wave 命令)
### 任务 7.3: ShellIntegration 处理器 ✅
- `ShellIntegration` - Shell 集成处理器
- `ShellIntegrationStatus` - 集成状态枚举(Ready、RunningCommand、Unknown)
- `ShellType` - Shell 类型枚举(Bash、Zsh、Fish、Pwsh)
- `CommandInfo` - 命令执行信息(开始时间、结束时间、持续时间)
- `ShellIntegrationEvent` - 状态变更事件
- 当前目录跟踪(OSC 7)
- 命令时间记录(OSC 133)
- Wave 命令处理(OSC 16162)
### 任务 21.1: Shell 集成脚本安装 ✅
- `ShellScripts` - Shell 集成脚本管理器
- `ShellLaunchConfig` - Shell 启动配置
- `ShellLaunchBuilder` - Shell 启动配置构建器
- Bash 集成(--rcfile 参数)
- Zsh 集成(ZDOTDIR 环境变量)
- Fish 集成(-C source 参数)
- PowerShell 集成(-NoExit -Command)
- 自动安装集成脚本到应用数据目录
### 任务 21.2: 环境变量配置 ✅
- `TerminalEnvConfig` - 终端环境变量配置管理器
- 标准环境变量设置(TERM、COLORTERM)
- 块标识环境变量(PROXYCAST_BLOCKID、WAVETERM_BLOCKID)
- 版本信息环境变量(PROXYCAST_VERSION)
- 语言设置环境变量(LANG、LC_ALL)
- 自定义环境变量合并支持
## 更新提醒
任何文件变更后,请更新此文档和相关的上级文档。
+48 -44
View File
@@ -1,50 +1,54 @@
//! 终端核心模块
//! 终端核心模块(重导出层)
//!
//! 提供 PTY 管理和会话管理能力,通过 Tauri Commands 和 Events 暴露给前端。
//!
//! ## 模块结构
//! - `error` - 错误类型定义
//! - `events` - Tauri 事件定义
//! - `pty_session` - PTY 会话封装
//! - `session_manager` - 会话管理器
//! - `persistence` - 持久化存储(块文件、会话元数据)
//! - `block_controller` - 块控制器抽象层
//! - `connections` - 连接模块(本地 PTY、SSH、WSL)
//! - `integration` - 集成模块(Shell 集成、OSC 解析、状态重同步)
//!
//! ## 使用示例
//! ```ignore
//! use proxycast_lib::terminal::{TerminalSessionManager, SessionStatus};
//!
//! let manager = TerminalSessionManager::new(app_handle);
//! let session_id = manager.create_session(24, 80).await?;
//! manager.write_to_session(&session_id, b"ls -la\n").await?;
//! ```
//! 实际实现位于 `proxycast-terminal` crate。
//! 本模块提供 `TauriEmitter` newtype 桥接 Tauri 与终端 crate。
pub mod block_controller;
pub mod connections;
pub mod error;
pub mod events;
pub mod integration;
pub mod persistence;
pub mod pty_session;
pub mod session_manager;
use std::path::PathBuf;
#[cfg(test)]
mod tests;
use tauri::{Emitter, Manager};
use proxycast_terminal::emitter::TerminalEventEmit;
/// Tauri AppHandle 的 newtype 包装
///
/// 实现 `TerminalEventEmit` trait,桥接 Tauri 框架与终端 crate。
#[derive(Clone)]
pub struct TauriEmitter(pub tauri::AppHandle);
impl TerminalEventEmit for TauriEmitter {
fn emit_event(&self, event: &str, payload: &serde_json::Value) -> Result<(), String> {
self.0
.emit(event, payload.clone())
.map_err(|e| format!("Tauri emit 失败: {e}"))
}
fn app_data_dir(&self) -> Result<PathBuf, String> {
self.0
.path()
.app_data_dir()
.map_err(|e| format!("获取应用数据目录失败: {e}"))
}
}
// 重新导出 proxycast-terminal 的所有公共类型
pub use proxycast_terminal::block_controller;
pub use proxycast_terminal::connections;
pub use proxycast_terminal::emit_helper;
pub use proxycast_terminal::emitter;
pub use proxycast_terminal::error;
pub use proxycast_terminal::events;
pub use proxycast_terminal::integration;
pub use proxycast_terminal::persistence;
pub use proxycast_terminal::pty_session;
pub use proxycast_terminal::session_manager;
// 重新导出常用类型
pub use block_controller::{
BlockController, BlockControllerRuntimeStatus, BlockInputUnion, BlockMeta, ControllerRegistry,
ControllerStatusEvent, RuntimeOpts, ShellController, TermSize, CONTROLLER_STATUS_EVENT,
pub use proxycast_terminal::{
resync_controller, BlockController, BlockControllerRuntimeStatus, BlockFile, BlockInputUnion,
BlockMeta, ControllerRegistry, ControllerStatusEvent, DynEmitter, NoOpEmitter, PtySession,
ResyncController, ResyncOptions, ResyncResult, RuntimeOpts, SessionMetadata,
SessionMetadataStore, SessionRecord, SessionStatus, ShellController, ShellProc, TermSize,
TerminalError, TerminalEventEmitter, TerminalOutputEvent, TerminalSessionManager,
TerminalStatusEvent, CONTROLLER_STATUS_EVENT, DEFAULT_COLS, DEFAULT_ROWS,
TERMINAL_RESET_SEQUENCE, TERMINAL_SOFT_RESET_SEQUENCE,
};
pub use connections::ShellProc;
pub use error::TerminalError;
pub use events::{SessionStatus, TerminalOutputEvent, TerminalStatusEvent};
pub use integration::{
resync_controller, ResyncController, ResyncOptions, ResyncResult, TERMINAL_RESET_SEQUENCE,
TERMINAL_SOFT_RESET_SEQUENCE,
};
pub use persistence::{BlockFile, SessionMetadataStore, SessionRecord};
pub use pty_session::{PtySession, DEFAULT_COLS, DEFAULT_ROWS};
pub use session_manager::{SessionMetadata, TerminalSessionManager};
@@ -1,62 +0,0 @@
# 终端持久化模块
提供终端会话数据的持久化存储能力。
## 模块结构
| 文件 | 说明 |
|------|------|
| `mod.rs` | 模块入口,导出公共类型 |
| `block_file.rs` | 块文件循环缓冲存储 |
| `session_store.rs` | 会话元数据 SQLite 存储 |
## 功能
### BlockFile - 块文件存储
- 终端输出历史的文件存储
- 循环缓冲策略(超过最大大小时覆盖旧数据)
- 默认最大大小 256KB
- 支持读取、追加、截断操作
### SessionMetadataStore - 会话元数据存储
- 会话元数据的 SQLite 存储
- 支持 CRUD 操作
- 支持按状态、标签页查询
- 支持会话恢复
## 使用示例
```rust
use proxycast_lib::terminal::persistence::{BlockFile, SessionMetadataStore, SessionRecord};
// 创建块文件
let base_dir = BlockFile::default_base_dir()?;
let block_file = BlockFile::with_default_size("session-123", &base_dir)?;
// 追加数据
block_file.append_data(b"Hello, World!")?;
// 读取数据
let data = block_file.read_all()?;
// 创建会话存储
let store = SessionMetadataStore::new(db_connection);
store.init_tables()?;
// 保存会话记录
let record = SessionRecord::new(
"session-123".to_string(),
"block-123".to_string(),
"tab-1".to_string(),
"shell".to_string(),
None,
);
store.save(&record)?;
```
## 相关需求
- Requirements 3.1, 3.2, 3.3, 3.4, 3.7 - 块文件存储
- Requirements 3.5, 3.9 - 会话元数据存储