From 635ded4f6815d738dd9b9b711aa4c7cf302d340b Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 12 Oct 2025 18:17:08 +0800 Subject: feat: Add connection infrastructure and documentation - Implement action service for connection handling - Add error types for connection operations - Create todo.md for project tracking --- crates/vcs_actions/src/connection.rs | 2 + .../vcs_actions/src/connection/action_service.rs | 129 +++++++++++++++++++++ crates/vcs_actions/src/connection/error.rs | 14 +++ crates/vcs_data/todo.md | 31 +++++ 4 files changed, 176 insertions(+) create mode 100644 crates/vcs_actions/src/connection.rs create mode 100644 crates/vcs_actions/src/connection/action_service.rs create mode 100644 crates/vcs_actions/src/connection/error.rs create mode 100644 crates/vcs_data/todo.md (limited to 'crates') diff --git a/crates/vcs_actions/src/connection.rs b/crates/vcs_actions/src/connection.rs new file mode 100644 index 0000000..dabbd44 --- /dev/null +++ b/crates/vcs_actions/src/connection.rs @@ -0,0 +1,2 @@ +pub mod action_service; +pub mod error; diff --git a/crates/vcs_actions/src/connection/action_service.rs b/crates/vcs_actions/src/connection/action_service.rs new file mode 100644 index 0000000..8d3a03d --- /dev/null +++ b/crates/vcs_actions/src/connection/action_service.rs @@ -0,0 +1,129 @@ +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; + +use action_system::action_pool::ActionPool; +use cfg_file::config::ConfigFile; +use tcp_connection::{error::TcpTargetError, instance::ConnectionInstance}; +use tokio::{ + net::{TcpListener, TcpStream}, + select, signal, spawn, + sync::mpsc, +}; +use vcs_data::data::vault::{Vault, config::VaultConfig}; + +use crate::registry::server_registry::server_action_pool; + +// Start the server with a Vault using the specified directory +pub async fn server_entry(path: impl Into) -> Result<(), TcpTargetError> { + // Read the vault cfg + let vault_cfg = VaultConfig::read().await?; + + // Create TCPListener + let listener = create_tcp_listener(&vault_cfg).await?; + + // Initialize the vault + let vault: Arc = init_vault(vault_cfg, path.into()).await?; + + // Create ActionPool + let action_pool: Arc = Arc::new(server_action_pool()); + + // Start the server + let (_shutdown_rx, future) = build_server_future(vault.clone(), action_pool.clone(), listener); + let _ = future.await?; // Start and block until shutdown + + Ok(()) +} + +async fn create_tcp_listener(cfg: &VaultConfig) -> Result { + let local_bind_addr = cfg.server_config().local_bind(); + let bind_port = cfg.server_config().port(); + let sock_addr = SocketAddr::new(local_bind_addr.clone(), bind_port); + let listener = TcpListener::bind(sock_addr).await?; + + Ok(listener) +} + +async fn init_vault(cfg: VaultConfig, path: PathBuf) -> Result, TcpTargetError> { + // Init and create the vault + let Some(vault) = Vault::init(cfg, path) else { + return Err(TcpTargetError::NotFound("Vault not found".to_string())); + }; + let vault: Arc = Arc::new(vault); + + Ok(vault) +} + +fn build_server_future( + vault: Arc, + action_pool: Arc, + listener: TcpListener, +) -> ( + mpsc::Sender<()>, + impl std::future::Future>, +) { + let (tx, mut rx) = mpsc::channel::(100); + let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1); + let mut active_connections = 0; + let mut shutdown_requested = false; + + // Spawn task to handle Ctrl+C + let shutdown_tx_clone = shutdown_tx.clone(); + spawn(async move { + if let Ok(()) = signal::ctrl_c().await { + let _ = shutdown_tx_clone.send(()).await; + } + }); + + let future = async move { + loop { + select! { + // Accept new connections + accept_result = listener.accept(), if !shutdown_requested => { + match accept_result { + Ok((stream, _addr)) => { + active_connections += 1; + let _ = tx.send(1).await; + + let vault_clone = vault.clone(); + let action_pool_clone = action_pool.clone(); + let tx_clone = tx.clone(); + spawn(async move { + process_connection(stream, vault_clone, action_pool_clone).await; + let _ = tx_clone.send(-1).await; + }); + } + Err(_) => { + continue; + } + } + } + + // Handle connection count updates + Some(count_change) = rx.recv() => { + active_connections = (active_connections as i32 + count_change) as usize; + + // Check if we should shutdown after all connections are done + if shutdown_requested && active_connections == 0 { + break; + } + } + + // Handle shutdown signal + _ = shutdown_rx.recv() => { + shutdown_requested = true; + // If no active connections, break immediately + if active_connections == 0 { + break; + } + } + } + } + + Ok(()) + }; + + (shutdown_tx, future) +} + +async fn process_connection(stream: TcpStream, vault: Arc, action_pool: Arc) { + let instance = ConnectionInstance::from(stream); +} diff --git a/crates/vcs_actions/src/connection/error.rs b/crates/vcs_actions/src/connection/error.rs new file mode 100644 index 0000000..241c16e --- /dev/null +++ b/crates/vcs_actions/src/connection/error.rs @@ -0,0 +1,14 @@ +use std::io; +use thiserror::Error; + +#[derive(Error, Debug, Clone)] +pub enum ConnectionError { + #[error("I/O error: {0}")] + Io(String), +} + +impl From for ConnectionError { + fn from(error: io::Error) -> Self { + ConnectionError::Io(error.to_string()) + } +} diff --git a/crates/vcs_data/todo.md b/crates/vcs_data/todo.md new file mode 100644 index 0000000..3c7e0c0 --- /dev/null +++ b/crates/vcs_data/todo.md @@ -0,0 +1,31 @@ +| 类别 | 项 | 可完成性 | 已完成 | +|----------|----|----------|--------| +| 本地文件 | 设置上游服务器(仅设置,不会连接和修改染色标识) | y | | +| 本地文件 | 验证连接、权限,并为当前工作区染色(若已染色,则无法连接不同标识的服务器) | y | | +| 本地文件 | 进入表 (否则无法做任何操作) | | | +| 本地文件 | 退出表 (文件将会从当前目录移出,等待下次进入时还原) | | | +| 本地文件 | 去色 - 断开与上游服务器的关联 | y | | +| 本地文件 | 跟踪本地文件的移动、重命名,立刻同步至表 | | | +| 本地文件 | 扫描本地文件结构,标记变化 | | | +| 本地文件 | 通过本地暂存的表索引搜索文件 | | | +| 本地文件 | 查询本地某个文件的状态 | | | +| 本地文件 | 查询当前目录的状态 | | | +| 本地文件 | 查询工作区状态 | | | +| 本地文件 | 将本地所有文件更新到最新状态 | | | +| 本地文件 | 提交所有产生变化的自身所属文件 | | | +| 表 | 表查看 - 指定表并查看结构 | | | +| 表 | 从参照表拉入文件项目 | | | +| 表 | 将文件项目(或多个)导出到指定表 | | | +| 表 | 查看导入请求 | | | +| 表 | 在某个本地地址同意并导入文件 | | | +| 表 | 拒绝某个、某些或所有导入请求 | | | +| 表 | 删除表中的映射,但要确保实际文件已被移除 (忽略文件) | | | +| 表 | 放弃表,所有者消失,下一个切换至表的人获得(放弃需要确保表中没有任何文件是所有者持有的)(替代目前的安全删除) | | | +| 虚拟文件 | 跟踪本地某些文件,并将其创建为虚拟文件,然后添加到自己的表 | | | +| 虚拟文件 | 根据本地文件的目录查找虚拟文件,并为自己获得所有权(需要确保版本和上游同步才可) | | | +| 虚拟文件 | 根据本地文件的目录查找虚拟文件,并放弃所有权(需要确保和上游同步才可) | | | +| 虚拟文件 | 根据本地文件的目录查找虚拟文件,并定向到指定的存在的老版本 | | | + + +?为什么虚拟文件不能删除:虚拟文件的唯一删除方式就是,没有人再用他 +?为什么没有删除表:同理,表权限可以转移,但是删除只能等待定期清除无主人的表 -- cgit