mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
refactor: 提取 proxycast-terminal 独立 crate
将终端模块 (~13k 行) 从主 crate 迁移至 crates/terminal/: - 设计 TerminalEventEmit/TerminalEventEmitter 两层 trait 抽象 - DynEmitter newtype 包装 Arc<dyn TerminalEventEmit> - 主 crate 保留 TauriEmitter 桥接层和重导出 - 187 个单元测试全部通过
This commit is contained in:
Generated
+28
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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/` - 持久化存储模块
|
||||
|
||||
## 更新提醒
|
||||
|
||||
任何文件变更后,请更新此文档和相关的上级文档。
|
||||
+2
-2
@@ -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 控制器
|
||||
+16
-20
@@ -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 或命令执行进程。
|
||||
+1
-1
@@ -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)]
|
||||
+1
-1
@@ -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;
|
||||
|
||||
// ============================================================================
|
||||
// 连接类型枚举
|
||||
+29
-25
@@ -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);
|
||||
-8
@@ -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;
|
||||
+15
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
-18
@@ -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,
|
||||
+34
-29
@@ -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)
|
||||
}
|
||||
@@ -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)]
|
||||
+29
-28
@@ -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> {
|
||||
+17
-18
@@ -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,
|
||||
+2
-2
@@ -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";
|
||||
@@ -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};
|
||||
+1
-1
@@ -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;
|
||||
+2
-2
@@ -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)
|
||||
///
|
||||
+16
-12
@@ -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,
|
||||
+14
-13
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
- 自定义环境变量合并支持
|
||||
|
||||
## 更新提醒
|
||||
|
||||
任何文件变更后,请更新此文档和相关的上级文档。
|
||||
@@ -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 - 会话元数据存储
|
||||
Reference in New Issue
Block a user