diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-02-05 22:35:05 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-02-05 22:35:05 +0800 |
| commit | 27f6414ad1ff451feb0044af62f37dc2a6255ffa (patch) | |
| tree | cb5693bc014cc8579dcf02a730fd4d2a5dfcf1a5 /legacy_actions/src/remote_actions.rs | |
| parent | ade2fcb9302a4ab759795820dbde3b2b269490ee (diff) | |
Remove examples and legacy code, update .gitignore
- Delete examples directory and its example action system
- Rename actions/ to legacy_actions/ and data/ to legacy_data/
- Update Cargo.toml license file reference
- Move setup scripts to scripts/dev/ directory
- Add todo.txt patterns to .gitignore
Diffstat (limited to 'legacy_actions/src/remote_actions.rs')
| -rw-r--r-- | legacy_actions/src/remote_actions.rs | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/legacy_actions/src/remote_actions.rs b/legacy_actions/src/remote_actions.rs new file mode 100644 index 0000000..d15edc9 --- /dev/null +++ b/legacy_actions/src/remote_actions.rs @@ -0,0 +1,288 @@ +use std::sync::Arc; + +use action_system::action::ActionContext; +use cfg_file::config::ConfigFile; +use tcp_connection::{error::TcpTargetError, instance::ConnectionInstance}; +use tokio::sync::{Mutex, mpsc::Sender}; +use vcs_data::{ + constants::{SERVER_PATH_MEMBER_PUB, VAULT_HOST_NAME}, + data::{ + local::{LocalWorkspace, latest_info::LatestInfo, workspace_config::LocalConfig}, + member::MemberId, + sheet::SheetName, + user::UserDirectory, + vault::Vault, + }, +}; + +pub mod content_manage; +pub mod edit_right_manage; +pub mod mapping_manage; +pub mod sheet_manage; +pub mod workspace_manage; + +/// Check if the connection instance is valid in the given context. +/// This function is used to verify the connection instance in actions that require remote calls. +pub fn check_connection_instance( + ctx: &ActionContext, +) -> Result<&Arc<Mutex<ConnectionInstance>>, TcpTargetError> { + let Some(instance) = ctx.instance() else { + return Err(TcpTargetError::NotFound( + "Connection instance lost.".to_string(), + )); + }; + Ok(instance) +} + +/// Try to get the Vault instance from the context. +pub fn try_get_vault(ctx: &ActionContext) -> Result<Arc<Vault>, TcpTargetError> { + let Some(vault) = ctx.get_arc::<Vault>() else { + return Err(TcpTargetError::NotFound( + "Vault instance not found".to_string(), + )); + }; + Ok(vault) +} + +/// Try to get the LocalWorkspace instance from the context. +pub fn try_get_local_workspace(ctx: &ActionContext) -> Result<Arc<LocalWorkspace>, TcpTargetError> { + let Some(local_workspace) = ctx.get_arc::<LocalWorkspace>() else { + return Err(TcpTargetError::NotFound( + "LocalWorkspace instance not found".to_string(), + )); + }; + Ok(local_workspace) +} + +/// Try to get the UserDirectory instance from the context. +pub fn try_get_user_directory(ctx: &ActionContext) -> Result<Arc<UserDirectory>, TcpTargetError> { + let Some(user_directory) = ctx.get_arc::<UserDirectory>() else { + return Err(TcpTargetError::NotFound( + "UserDirectory instance not found".to_string(), + )); + }; + Ok(user_directory) +} + +/// Try to get the LocalWorkspace instance from the context. +pub fn try_get_local_output(ctx: &ActionContext) -> Result<Arc<Sender<String>>, TcpTargetError> { + let Some(output) = ctx.get_arc::<Sender<String>>() else { + return Err(TcpTargetError::NotFound( + "Client sender not found".to_string(), + )); + }; + Ok(output) +} + +/// Authenticate member based on context and return MemberId +pub async fn auth_member( + ctx: &ActionContext, + instance: &Arc<Mutex<ConnectionInstance>>, +) -> Result<(MemberId, bool), TcpTargetError> { + // Window开服Linux连接 -> 此函数内产生 early eof + // ~ WS # jv update + // 身份认证失败:I/O error: early eof! + + // 分析相应流程: + // 1. 服务端发起挑战,客户端接受 + // 2. 服务端发送结果,客户端接受 + // 3. 推测此时发生 early eof ---> 无 ack,导致客户端尝试拿到结果时,服务端已经结束 + // 这很有可能是 Windows 和 Linux 对于连接处理的方案差异导致的问题,需要进一步排查 + + // Start Challenge (Remote) + if ctx.is_proc_on_remote() { + let mut mut_instance = instance.lock().await; + let vault = try_get_vault(ctx)?; + + let using_host_mode = mut_instance.read_msgpack::<bool>().await?; + + let result = mut_instance + .challenge(vault.vault_path().join(SERVER_PATH_MEMBER_PUB)) + .await; + + return match result { + Ok((pass, member_id)) => { + if !pass { + // Send false to inform the client that authentication failed + mut_instance.write(false).await?; + Err(TcpTargetError::Authentication( + "Authenticate failed.".to_string(), + )) + } else { + if using_host_mode { + if vault.config().vault_host_list().contains(&member_id) { + // Using Host mode authentication, and is indeed an administrator + mut_instance.write(true).await?; + Ok((member_id, true)) + } else { + // Using Host mode authentication, but not an administrator + mut_instance.write(false).await?; + Err(TcpTargetError::Authentication( + "Authenticate failed.".to_string(), + )) + } + } else { + // Not using Host mode authentication + mut_instance.write(true).await?; + Ok((member_id, false)) + } + } + } + Err(e) => Err(e), + }; + } + + // Accept Challenge (Local) + if ctx.is_proc_on_local() { + let mut mut_instance = instance.lock().await; + let local_workspace = try_get_local_workspace(ctx)?; + let (is_host_mode, member_name) = { + let cfg = local_workspace.config().lock_owned().await; + (cfg.is_host_mode(), cfg.current_account()) + }; + let user_directory = try_get_user_directory(ctx)?; + + // Inform remote whether to authenticate in Host mode + mut_instance.write_msgpack(is_host_mode).await?; + + // Member name & Private key + let private_key = user_directory.account_private_key_path(&member_name); + let _ = mut_instance + .accept_challenge(private_key, &member_name) + .await?; + + // Read result + let challenge_result = mut_instance.read::<bool>().await?; + if challenge_result { + return Ok((member_name.clone(), is_host_mode)); + } else { + return Err(TcpTargetError::Authentication( + "Authenticate failed.".to_string(), + )); + } + } + + Err(TcpTargetError::NoResult("Auth failed.".to_string())) +} + +/// Get the current sheet name based on the context (local or remote). +/// This function handles the communication between local and remote instances +/// to verify and retrieve the current sheet name and whether it's a reference sheet. +/// +/// On local: +/// - Reads the current sheet from local configuration +/// - Sends the sheet name to remote for verification +/// - Returns the sheet name and whether it's a reference sheet if remote confirms it exists +/// +/// On remote: +/// - Receives sheet name from local +/// - Verifies the sheet exists in the vault +/// - Checks if the sheet is a reference sheet +/// - If allow_ref is true, reference sheets are allowed to pass verification +/// - Sends confirmation and reference status back to local +/// +/// Returns a tuple of (SheetName, bool) where the bool indicates if it's a reference sheet, +/// or an error if the sheet doesn't exist or doesn't meet the verification criteria. +pub async fn get_current_sheet_name( + ctx: &ActionContext, + instance: &Arc<Mutex<ConnectionInstance>>, + member_id: &MemberId, + allow_ref: bool, +) -> Result<(SheetName, bool), TcpTargetError> { + let mut mut_instance = instance.lock().await; + if ctx.is_proc_on_local() { + let workspace = try_get_local_workspace(ctx)?; + let config = LocalConfig::read().await?; + let latest = LatestInfo::read_from(LatestInfo::latest_info_path( + workspace.local_path(), + member_id, + )) + .await?; + if let Some(sheet_name) = config.sheet_in_use() { + // Send sheet name + mut_instance.write_msgpack(sheet_name).await?; + + // Read result + if mut_instance.read_msgpack::<bool>().await? { + // Check if sheet is a reference sheet + let is_ref_sheet = latest.reference_sheets.contains(sheet_name); + if allow_ref { + // Allow reference sheets, directly return the determination result + return Ok((sheet_name.clone(), is_ref_sheet)); + } else if is_ref_sheet { + // Not allowed but it's a reference sheet, return an error + return Err(TcpTargetError::ReferenceSheetNotAllowed( + "Reference sheet not allowed".to_string(), + )); + } else { + // Not allowed but not a reference sheet, return normally + return Ok((sheet_name.clone(), false)); + } + } else { + return Err(TcpTargetError::NotFound("Sheet not found".to_string())); + } + } + // Send empty sheet_name + mut_instance.write_msgpack("".to_string()).await?; + + // Read result, since we know it's impossible to pass here, we just consume this result + let _ = mut_instance.read_msgpack::<bool>().await?; + + return Err(TcpTargetError::NotFound("Sheet not found".to_string())); + } + if ctx.is_proc_on_remote() { + let vault = try_get_vault(ctx)?; + + // Read sheet name + let sheet_name: SheetName = mut_instance.read_msgpack().await?; + + // Check if sheet exists + if let Ok(sheet) = vault.sheet(&sheet_name).await + && let Some(holder) = sheet.holder() + { + let is_ref_sheet = holder == VAULT_HOST_NAME; + if allow_ref { + // Allow reference sheets, directly return the determination result + if holder == member_id || holder == VAULT_HOST_NAME { + mut_instance.write_msgpack(true).await?; + return Ok((sheet.name().clone(), is_ref_sheet)); + } + } else if is_ref_sheet { + // Not allowed but it's a reference sheet, return an error + mut_instance.write_msgpack(true).await?; + return Err(TcpTargetError::ReferenceSheetNotAllowed( + "Reference sheet not allowed".to_string(), + )); + } else { + // Not allowed but not a reference sheet, return normally + if holder == member_id { + mut_instance.write_msgpack(true).await?; + return Ok((sheet_name.clone(), false)); + } + } + } + // Tell local the check is not passed + mut_instance.write_msgpack(false).await?; + return Err(TcpTargetError::NotFound("Sheet not found".to_string())); + } + Err(TcpTargetError::NoResult("NoResult".to_string())) +} + +/// The macro to write and return a result. +#[macro_export] +macro_rules! write_and_return { + ($instance:expr, $result:expr) => {{ + $instance.lock().await.write($result).await?; + return Ok($result); + }}; +} + +/// The macro to send formatted string to output channel. +/// Usage: local_println!(output, "format string", arg1, arg2, ...) +#[macro_export] +macro_rules! local_println { + ($output:expr, $($arg:tt)*) => {{ + let formatted = format!($($arg)*); + let _ = $output.send(formatted).await; + }}; +} |
