From 8b3b92b405ebc96416ec300ad3f6260bd8354864 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 5 Nov 2025 16:45:30 +0800 Subject: Add sheet restoration and drop functionality - Add write_and_return macro for common result handling pattern - Extend make_sheet_action to restore sheets with no holder - Implement drop_sheet_action for releasing sheet ownership - Register new drop_sheet_action in client and server registries --- crates/vcs_actions/src/actions.rs | 9 ++ crates/vcs_actions/src/actions/sheet_actions.rs | 156 ++++++++++++++++++--- crates/vcs_actions/src/registry/client_registry.rs | 3 +- crates/vcs_actions/src/registry/server_registry.rs | 3 +- 4 files changed, 149 insertions(+), 22 deletions(-) diff --git a/crates/vcs_actions/src/actions.rs b/crates/vcs_actions/src/actions.rs index 795d2b0..51186fb 100644 --- a/crates/vcs_actions/src/actions.rs +++ b/crates/vcs_actions/src/actions.rs @@ -116,3 +116,12 @@ pub async fn auth_member( Err(TcpTargetError::NoResult("Auth failed.".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); + }}; +} diff --git a/crates/vcs_actions/src/actions/sheet_actions.rs b/crates/vcs_actions/src/actions/sheet_actions.rs index b6ea51d..b4e07a7 100644 --- a/crates/vcs_actions/src/actions/sheet_actions.rs +++ b/crates/vcs_actions/src/actions/sheet_actions.rs @@ -1,13 +1,19 @@ +use std::io::ErrorKind; + use action_system::{action::ActionContext, macros::action_gen}; use serde::{Deserialize, Serialize}; use tcp_connection::error::TcpTargetError; use vcs_data::data::sheet::SheetName; -use crate::actions::{auth_member, check_connection_instance, try_get_vault}; +use crate::{ + actions::{auth_member, check_connection_instance, try_get_local_workspace, try_get_vault}, + write_and_return, +}; #[derive(Default, Serialize, Deserialize)] pub enum MakeSheetActionResult { Success, + SuccessRestore, // Fail AuthorizeFailed(String), @@ -36,31 +42,35 @@ pub async fn make_sheet_action( let vault = try_get_vault(&ctx)?; // Check if the sheet already exists - if vault.sheet(&sheet_name).await.is_ok() { - instance - .lock() - .await - .write(MakeSheetActionResult::SheetAlreadyExists) - .await?; - return Ok(MakeSheetActionResult::SheetAlreadyExists); + if let Ok(mut sheet) = vault.sheet(&sheet_name).await { + // If the sheet has no holder, assign it to the current member (restore operation) + if sheet.holder().is_none() { + sheet.set_holder(member_id); + match sheet.persist().await { + Ok(_) => { + write_and_return!(instance, MakeSheetActionResult::SuccessRestore); + } + Err(e) => { + write_and_return!( + instance, + MakeSheetActionResult::SheetCreationFailed(e.to_string()) + ); + } + } + } else { + write_and_return!(instance, MakeSheetActionResult::SheetAlreadyExists); + } } else { // Create the sheet match vault.create_sheet(&sheet_name, &member_id).await { Ok(_) => { - instance - .lock() - .await - .write(MakeSheetActionResult::Success) - .await?; - return Ok(MakeSheetActionResult::Success); + write_and_return!(instance, MakeSheetActionResult::Success); } Err(e) => { - instance - .lock() - .await - .write(MakeSheetActionResult::SheetCreationFailed(e.to_string())) - .await?; - return Ok(MakeSheetActionResult::SheetCreationFailed(e.to_string())); + write_and_return!( + instance, + MakeSheetActionResult::SheetCreationFailed(e.to_string()) + ); } } } @@ -77,3 +87,109 @@ pub async fn make_sheet_action( Err(TcpTargetError::NoResult("No result.".to_string())) } + +#[derive(Default, Serialize, Deserialize)] +pub enum DropSheetActionResult { + Success, + + // Fail + SheetInUse, + AuthorizeFailed(String), + SheetNotExists, + SheetDropFailed(String), + NoHolder, + NotOwner, + + #[default] + Unknown, +} + +#[action_gen] +pub async fn drop_sheet_action( + ctx: ActionContext, + sheet_name: SheetName, +) -> Result { + let instance = check_connection_instance(&ctx)?; + + // Auth Member + let member_id = match auth_member(&ctx, instance).await { + Ok(id) => id, + Err(e) => { + return Ok(DropSheetActionResult::AuthorizeFailed(e.to_string())); + } + }; + + // Check sheet in use on local + if ctx.is_proc_on_local() { + let local_workspace = try_get_local_workspace(&ctx)?; + if let Some(sheet) = local_workspace.config().lock().await.sheet_in_use() { + if sheet == &sheet_name { + instance.lock().await.write(false).await?; + return Ok(DropSheetActionResult::SheetInUse); + } + instance.lock().await.write(true).await?; + } else { + instance.lock().await.write(true).await?; + } + } + + if ctx.is_proc_on_remote() { + // Check if client sheet is in use + let sheet_in_use = instance.lock().await.read::().await?; + if !sheet_in_use { + return Ok(DropSheetActionResult::SheetInUse); + } + + let vault = try_get_vault(&ctx)?; + + // Check if the sheet exists + let mut sheet = match vault.sheet(&sheet_name).await { + Ok(sheet) => sheet, + Err(e) => { + if e.kind() == ErrorKind::NotFound { + write_and_return!(instance, DropSheetActionResult::SheetNotExists); + } else { + write_and_return!( + instance, + DropSheetActionResult::SheetDropFailed(e.to_string()) + ); + } + } + }; + + // Get the sheet's holder + let Some(holder) = sheet.holder() else { + write_and_return!(instance, DropSheetActionResult::NoHolder); + }; + + // Verify the sheet's holder + if holder != &member_id { + write_and_return!(instance, DropSheetActionResult::NotOwner); + } + + // Drop the sheet + sheet.forget_holder(); + match sheet.persist().await { + Ok(_) => { + write_and_return!(instance, DropSheetActionResult::Success); + } + Err(e) => { + write_and_return!( + instance, + DropSheetActionResult::SheetDropFailed(e.to_string()) + ); + } + } + } + + if ctx.is_proc_on_local() { + let result = instance + .lock() + .await + .read::() + .await?; + return Ok(result); + } + + Err(TcpTargetError::NoResult("No result.".to_string())) +} diff --git a/crates/vcs_actions/src/registry/client_registry.rs b/crates/vcs_actions/src/registry/client_registry.rs index a0b87a6..95c0939 100644 --- a/crates/vcs_actions/src/registry/client_registry.rs +++ b/crates/vcs_actions/src/registry/client_registry.rs @@ -13,7 +13,7 @@ use crate::{ local_actions::{ register_set_upstream_vault_action, register_update_to_latest_info_action, }, - sheet_actions::register_make_sheet_action, + sheet_actions::{register_drop_sheet_action, register_make_sheet_action}, }, connection::protocol::RemoteActionInvoke, }; @@ -27,6 +27,7 @@ fn register_actions(pool: &mut ActionPool) { // Sheet Actions register_make_sheet_action(pool); + register_drop_sheet_action(pool); } pub fn client_action_pool() -> ActionPool { diff --git a/crates/vcs_actions/src/registry/server_registry.rs b/crates/vcs_actions/src/registry/server_registry.rs index eade391..b99d697 100644 --- a/crates/vcs_actions/src/registry/server_registry.rs +++ b/crates/vcs_actions/src/registry/server_registry.rs @@ -2,7 +2,7 @@ use action_system::action_pool::ActionPool; use crate::actions::{ local_actions::{register_set_upstream_vault_action, register_update_to_latest_info_action}, - sheet_actions::register_make_sheet_action, + sheet_actions::{register_drop_sheet_action, register_make_sheet_action}, }; pub fn server_action_pool() -> ActionPool { @@ -14,6 +14,7 @@ pub fn server_action_pool() -> ActionPool { // Sheet Actions register_make_sheet_action(&mut pool); + register_drop_sheet_action(&mut pool); pool } -- cgit From f34fd7e803f0e52f526b7a56c09fdb262ed9b8aa Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 5 Nov 2025 16:45:13 +0800 Subject: Update draft folder structure to include account name The draft folder path now includes the account name to prevent conflicts when multiple accounts work on the same sheet. This requires updating the draft_folder method signature and all call sites. Additionally, account switching is now restricted when a sheet is in use to maintain data integrity. --- crates/vcs_data/src/constants.rs | 2 +- crates/vcs_data/src/data/local/config.rs | 23 ++++++++++++++++++----- crates/vcs_data/src/data/sheet.rs | 10 ++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/vcs_data/src/constants.rs b/crates/vcs_data/src/constants.rs index 1d17927..55662e7 100644 --- a/crates/vcs_data/src/constants.rs +++ b/crates/vcs_data/src/constants.rs @@ -54,7 +54,7 @@ pub const CLIENT_FILE_LATEST_INFO: &str = "./.jv/latest.json"; pub const CLIENT_FILE_SHEET_COPY: &str = "./.jv/sheets/{sheet_name}.copy.json"; // Client - Local Draft -pub const CLIENT_PATH_LOCAL_DRAFT: &str = "./.jv/drafts/{sheet_name}/"; +pub const CLIENT_PATH_LOCAL_DRAFT: &str = "./.jv/drafts/{account}_{sheet_name}/"; // Client - Other pub const CLIENT_FILE_IGNOREFILES: &str = "IGNORE_RULES.toml"; diff --git a/crates/vcs_data/src/data/local/config.rs b/crates/vcs_data/src/data/local/config.rs index 3dc5248..fa3b607 100644 --- a/crates/vcs_data/src/data/local/config.rs +++ b/crates/vcs_data/src/data/local/config.rs @@ -1,6 +1,7 @@ use cfg_file::ConfigFile; use cfg_file::config::ConfigFile; use serde::{Deserialize, Serialize}; +use std::io::Error; use std::net::SocketAddr; use std::path::Path; use std::path::PathBuf; @@ -17,6 +18,7 @@ use crate::data::member::MemberId; use crate::data::sheet::SheetName; use crate::data::vault::config::VaultUuid; +const ACCOUNT: &str = "{account}"; const SHEET_NAME: &str = "{sheet_name}"; #[derive(Serialize, Deserialize, ConfigFile)] @@ -68,8 +70,15 @@ impl LocalConfig { } /// Set the currently used account - pub fn set_current_account(&mut self, account: MemberId) { + pub fn set_current_account(&mut self, account: MemberId) -> Result<(), std::io::Error> { + if self.sheet_in_use().is_some() { + return Err(Error::new( + std::io::ErrorKind::DirectoryNotEmpty, + "Please exit the current sheet before switching accounts", + )); + } self.using_account = account; + Ok(()) } /// Set the currently used sheet @@ -107,7 +116,7 @@ impl LocalConfig { self.check_local_path_empty(&local_path).await?; // Get the draft folder path - let draft_folder = self.draft_folder(&sheet, &local_path); + let draft_folder = self.draft_folder(&self.using_account, &sheet, &local_path); if draft_folder.exists() { // Exists @@ -135,7 +144,7 @@ impl LocalConfig { let sheet_name = self.sheet_in_use().as_ref().unwrap().clone(); // Get the draft folder path - let draft_folder = self.draft_folder(&sheet_name, &local_path); + let draft_folder = self.draft_folder(&self.using_account, &sheet_name, &local_path); // Create the draft folder if it doesn't exist if !draft_folder.exists() { @@ -312,11 +321,15 @@ impl LocalConfig { /// Get draft folder pub fn draft_folder( &self, + account: &MemberId, sheet_name: &SheetName, local_workspace_path: impl Into, ) -> PathBuf { + let account_str = snake_case!(account.as_str()); let sheet_name_str = snake_case!(sheet_name.as_str()); - let draft_path = CLIENT_PATH_LOCAL_DRAFT.replace(SHEET_NAME, &sheet_name_str); + let draft_path = CLIENT_PATH_LOCAL_DRAFT + .replace(ACCOUNT, &account_str) + .replace(SHEET_NAME, &sheet_name_str); local_workspace_path.into().join(draft_path) } @@ -330,7 +343,7 @@ impl LocalConfig { return None; }; - Some(self.draft_folder(sheet_name, current_dir)) + Some(self.draft_folder(&self.using_account, sheet_name, current_dir)) } } diff --git a/crates/vcs_data/src/data/sheet.rs b/crates/vcs_data/src/data/sheet.rs index ce450a6..69dc27d 100644 --- a/crates/vcs_data/src/data/sheet.rs +++ b/crates/vcs_data/src/data/sheet.rs @@ -89,6 +89,16 @@ impl<'a> Sheet<'a> { &self.data.mapping } + /// Forget the holder of this sheet + pub fn forget_holder(&mut self) { + self.data.holder = None; + } + + /// Set the holder of this sheet + pub fn set_holder(&mut self, holder: MemberId) { + self.data.holder = Some(holder); + } + /// Add an input package to the sheet pub fn add_input(&mut self, input_package: InputPackage) -> Result<(), std::io::Error> { if self.data.inputs.iter().any(|input| input == &input_package) { -- cgit