summaryrefslogtreecommitdiff
path: root/crates/vcs_actions
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-01-02 02:22:01 +0800
committer魏曹先生 <1992414357@qq.com>2026-01-02 02:22:01 +0800
commitf6a918848b499b9ec6fab8124d714d64af8afae2 (patch)
treee7dab039d511d5ec17310af916fa941da4a9374f /crates/vcs_actions
parent8644ba2ea292ef2aa3d49976f4f3916b7ecde938 (diff)
Add host mode authentication and reference sheet handling
- Return host mode status from auth_member to determine admin privileges - Add reference sheet detection to get_current_sheet_name with allow_ref parameter - Prevent modifications to reference sheets unless in host mode - Use VAULT_HOST_NAME as sheet holder for host mode operations - Add share/merge share action registrations
Diffstat (limited to 'crates/vcs_actions')
-rw-r--r--crates/vcs_actions/src/actions.rs122
-rw-r--r--crates/vcs_actions/src/actions/local_actions.rs90
-rw-r--r--crates/vcs_actions/src/actions/sheet_actions.rs258
-rw-r--r--crates/vcs_actions/src/actions/track_action.rs180
-rw-r--r--crates/vcs_actions/src/actions/user_actions.rs9
-rw-r--r--crates/vcs_actions/src/actions/vault_actions.rs1
-rw-r--r--crates/vcs_actions/src/registry/client_registry.rs5
-rw-r--r--crates/vcs_actions/src/registry/server_registry.rs5
8 files changed, 518 insertions, 152 deletions
diff --git a/crates/vcs_actions/src/actions.rs b/crates/vcs_actions/src/actions.rs
index 260a6be..50281c9 100644
--- a/crates/vcs_actions/src/actions.rs
+++ b/crates/vcs_actions/src/actions.rs
@@ -5,9 +5,9 @@ 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,
+ constants::{SERVER_PATH_MEMBER_PUB, VAULT_HOST_NAME},
data::{
- local::{LocalWorkspace, config::LocalConfig},
+ local::{LocalWorkspace, config::LocalConfig, latest_info::LatestInfo},
member::MemberId,
sheet::SheetName,
user::UserDirectory,
@@ -78,13 +78,25 @@ pub fn try_get_local_output(ctx: &ActionContext) -> Result<Arc<Sender<String>>,
pub async fn auth_member(
ctx: &ActionContext,
instance: &Arc<Mutex<ConnectionInstance>>,
-) -> Result<MemberId, TcpTargetError> {
+) -> 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 result = instance
- .lock()
- .await
+
+ let using_host_mode = mut_instance.read_msgpack::<bool>().await?;
+
+ let result = mut_instance
.challenge(vault.vault_path().join(SERVER_PATH_MEMBER_PUB))
.await;
@@ -92,14 +104,28 @@ pub async fn auth_member(
Ok((pass, member_id)) => {
if !pass {
// Send false to inform the client that authentication failed
- instance.lock().await.write(false).await?;
+ mut_instance.write(false).await?;
Err(TcpTargetError::Authentication(
"Authenticate failed.".to_string(),
))
} else {
- // Send true to inform the client that authentication was successful
- instance.lock().await.write(true).await?;
- Ok(member_id)
+ if using_host_mode {
+ if vault.config().vault_admin_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),
@@ -108,22 +134,27 @@ pub async fn auth_member(
// 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 member_name = local_workspace.config().lock().await.current_account();
let private_key = user_directory.account_private_key_path(&member_name);
- let _ = instance
- .lock()
- .await
+ let _ = mut_instance
.accept_challenge(private_key, &member_name)
.await?;
// Read result
- let challenge_result = instance.lock().await.read::<bool>().await?;
+ let challenge_result = mut_instance.read::<bool>().await?;
if challenge_result {
- return Ok(member_name.clone());
+ return Ok((member_name.clone(), is_host_mode));
} else {
return Err(TcpTargetError::Authentication(
"Authenticate failed.".to_string(),
@@ -136,34 +167,57 @@ pub async fn auth_member(
/// 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.
+/// 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 if remote confirms it exists
+/// - 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
-/// - Sends confirmation back to local
+/// - 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 the verified sheet name or an error if the sheet doesn't exist
+/// 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,
-) -> Result<SheetName, TcpTargetError> {
+ 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? {
- return Ok(sheet_name.clone());
+ // 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()));
}
@@ -185,11 +239,27 @@ pub async fn get_current_sheet_name(
// Check if sheet exists
if let Ok(sheet) = vault.sheet(&sheet_name).await
&& let Some(holder) = sheet.holder()
- && holder == member_id
{
- // Tell local the check is passed
- mut_instance.write_msgpack(true).await?;
- return Ok(sheet_name.clone());
+ 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?;
diff --git a/crates/vcs_actions/src/actions/local_actions.rs b/crates/vcs_actions/src/actions/local_actions.rs
index 7eee64d..d3f718d 100644
--- a/crates/vcs_actions/src/actions/local_actions.rs
+++ b/crates/vcs_actions/src/actions/local_actions.rs
@@ -1,4 +1,10 @@
-use std::{collections::HashMap, io::ErrorKind, net::SocketAddr, path::PathBuf, time::SystemTime};
+use std::{
+ collections::{HashMap, HashSet},
+ io::ErrorKind,
+ net::SocketAddr,
+ path::PathBuf,
+ time::SystemTime,
+};
use action_system::{action::ActionContext, macros::action_gen};
use cfg_file::config::ConfigFile;
@@ -6,7 +12,9 @@ use log::info;
use serde::{Deserialize, Serialize};
use tcp_connection::error::TcpTargetError;
use vcs_data::{
- constants::{CLIENT_PATH_CACHED_SHEET, CLIENT_PATH_LOCAL_SHEET},
+ constants::{
+ CLIENT_PATH_CACHED_SHEET, CLIENT_PATH_LOCAL_SHEET, REF_SHEET_NAME, VAULT_HOST_NAME,
+ },
data::{
local::{
cached_sheet::CachedSheet,
@@ -19,6 +27,7 @@ use vcs_data::{
sheet::{SheetData, SheetName},
vault::{
config::VaultUuid,
+ sheet_share::{Share, SheetShareId},
virtual_file::{VirtualFileId, VirtualFileVersion},
},
},
@@ -138,7 +147,7 @@ pub async fn update_to_latest_info_action(
) -> Result<UpdateToLatestInfoResult, TcpTargetError> {
let instance = check_connection_instance(&ctx)?;
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, _is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => return Ok(UpdateToLatestInfoResult::AuthorizeFailed(e.to_string())),
};
@@ -153,13 +162,43 @@ pub async fn update_to_latest_info_action(
// Build latest info
let mut latest_info = LatestInfo::default();
- // Sheet
+ // Sheet & Share
+ let mut shares_in_my_sheets: HashMap<SheetName, HashMap<SheetShareId, Share>> =
+ HashMap::new();
let mut member_owned = Vec::new();
let mut member_visible = Vec::new();
+ let mut ref_sheets = HashSet::new();
for sheet in vault.sheets().await? {
- if sheet.holder().is_some() && sheet.holder().unwrap() == &member_id {
+ // Build share parts
+ if let Some(holder) = sheet.holder() {
+ if holder == &member_id {
+ let mut sheet_shares: HashMap<SheetShareId, Share> = HashMap::new();
+ for share in sheet.get_shares().await? {
+ // Get SharePath
+ let Some(share_path) = share.path.clone() else {
+ continue;
+ };
+ // Get ShareId from SharePath
+ let Some(share_id) = share_path.file_name() else {
+ continue;
+ };
+ sheet_shares.insert(share_id.display().to_string(), share);
+ }
+ shares_in_my_sheets.insert(sheet.name().clone(), sheet_shares);
+ }
+ }
+
+ // Build sheet parts
+ let holder_is_host =
+ sheet.holder().unwrap_or(&String::default()) == &VAULT_HOST_NAME;
+ if sheet.holder().is_some()
+ && (sheet.holder().unwrap() == &member_id || holder_is_host)
+ {
member_owned.push(sheet.name().clone());
+ if holder_is_host {
+ ref_sheets.insert(sheet.name().clone());
+ }
} else {
member_visible.push(SheetInfo {
sheet_name: sheet.name().clone(),
@@ -168,12 +207,15 @@ pub async fn update_to_latest_info_action(
}
}
- latest_info.my_sheets = member_owned;
- latest_info.other_sheets = member_visible;
+ // Record Share & Sheet
+ latest_info.visible_sheets = member_owned;
+ latest_info.invisible_sheets = member_visible;
+ latest_info.shares_in_my_sheets = shares_in_my_sheets;
// RefSheet
- let ref_sheet_data = vault.sheet(&"ref".to_string()).await?.to_data();
+ let ref_sheet_data = vault.sheet(&REF_SHEET_NAME.to_string()).await?.to_data();
latest_info.ref_sheet_content = ref_sheet_data;
+ latest_info.reference_sheets = ref_sheets;
// Members
let members = vault.members().await?;
@@ -222,7 +264,7 @@ pub async fn update_to_latest_info_action(
// Collect all local versions
let mut local_versions = vec![];
- for request_sheet in latest_info.my_sheets {
+ for request_sheet in latest_info.visible_sheets {
let Ok(data) = CachedSheet::cached_sheet_data(&request_sheet).await else {
// For newly created sheets, the version is 0.
// Send -1 to distinguish from 0, ensuring the upstream will definitely send the sheet information
@@ -244,29 +286,6 @@ pub async fn update_to_latest_info_action(
let _: bool = mut_instance.read_msgpack().await?;
}
} else {
- // Send data to local
- if ctx.is_proc_on_remote() {
- let vault = try_get_vault(&ctx)?;
- let mut mut_instance = instance.lock().await;
-
- let local_versions =
- mut_instance.read_msgpack::<Vec<(SheetName, i32)>>().await?;
-
- for (sheet_name, local_write_count) in local_versions.iter() {
- let sheet = vault.sheet(sheet_name).await?;
- if let Some(holder) = sheet.holder()
- && holder == &member_id
- && &sheet.write_count() != local_write_count
- {
- mut_instance.write_msgpack(true).await?;
- mut_instance
- .write_large_msgpack((sheet_name, sheet.to_data()), 1024u16)
- .await?;
- }
- }
- mut_instance.write_msgpack(false).await?;
- }
-
// Receive data
if ctx.is_proc_on_local() {
let mut mut_instance = instance.lock().await;
@@ -289,7 +308,8 @@ pub async fn update_to_latest_info_action(
}
}
}
- } else if ctx.is_proc_on_remote() {
+ }
+ if ctx.is_proc_on_remote() {
let vault = try_get_vault(&ctx)?;
let mut mut_instance = instance.lock().await;
@@ -298,7 +318,7 @@ pub async fn update_to_latest_info_action(
for (sheet_name, version) in local_versions.iter() {
let sheet = vault.sheet(sheet_name).await?;
if let Some(holder) = sheet.holder()
- && holder == &member_id
+ && (holder == &member_id || holder == VAULT_HOST_NAME)
&& &sheet.write_count() != version
{
mut_instance.write_msgpack(true).await?;
@@ -331,7 +351,7 @@ pub async fn update_to_latest_info_action(
// Collect files that need to know the holder
let mut holder_wants_know = Vec::new();
- for sheet_name in &latest_info.my_sheets {
+ for sheet_name in &latest_info.visible_sheets {
if let Ok(sheet_data) = CachedSheet::cached_sheet_data(sheet_name).await {
holder_wants_know
.extend(sheet_data.mapping().values().map(|value| value.id.clone()));
diff --git a/crates/vcs_actions/src/actions/sheet_actions.rs b/crates/vcs_actions/src/actions/sheet_actions.rs
index aff06f5..759c275 100644
--- a/crates/vcs_actions/src/actions/sheet_actions.rs
+++ b/crates/vcs_actions/src/actions/sheet_actions.rs
@@ -3,12 +3,16 @@ use std::{collections::HashMap, io::ErrorKind};
use action_system::{action::ActionContext, macros::action_gen};
use serde::{Deserialize, Serialize};
use tcp_connection::error::TcpTargetError;
-use vcs_data::data::{
- local::{
- vault_modified::sign_vault_modified,
- workspace_analyzer::{FromRelativePathBuf, ToRelativePathBuf},
+use vcs_data::{
+ constants::VAULT_HOST_NAME,
+ data::{
+ local::{
+ vault_modified::sign_vault_modified,
+ workspace_analyzer::{FromRelativePathBuf, ToRelativePathBuf},
+ },
+ sheet::SheetName,
+ vault::sheet_share::{ShareMergeMode, SheetShareId},
},
- sheet::SheetName,
};
use crate::{
@@ -42,7 +46,7 @@ pub async fn make_sheet_action(
let instance = check_connection_instance(&ctx)?;
// Auth Member
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => return Ok(MakeSheetActionResult::AuthorizeFailed(e.to_string())),
};
@@ -54,7 +58,11 @@ pub async fn make_sheet_action(
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);
+ sheet.set_holder(if is_host_mode {
+ VAULT_HOST_NAME.to_string()
+ } else {
+ member_id
+ });
match sheet.persist().await {
Ok(_) => {
write_and_return!(instance, MakeSheetActionResult::SuccessRestore);
@@ -124,7 +132,7 @@ pub async fn drop_sheet_action(
let instance = check_connection_instance(&ctx)?;
// Auth Member
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => {
return Ok(DropSheetActionResult::AuthorizeFailed(e.to_string()));
@@ -174,8 +182,9 @@ pub async fn drop_sheet_action(
write_and_return!(instance, DropSheetActionResult::NoHolder);
};
- // Verify the sheet's holder
- if holder != &member_id {
+ // Verify that the sheet holder is either the current user or the host
+ // All sheets belong to the host
+ if holder != &member_id && !is_host_mode {
write_and_return!(instance, DropSheetActionResult::NotOwner);
}
@@ -223,6 +232,7 @@ pub enum EditMappingActionResult {
// Fail
AuthorizeFailed(String),
+ EditNotAllowed,
MappingNotFound(FromRelativePathBuf),
InvalidMove(InvalidMoveReason),
@@ -251,7 +261,7 @@ pub async fn edit_mapping_action(
let instance = check_connection_instance(&ctx)?;
// Auth Member
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => {
return Ok(EditMappingActionResult::AuthorizeFailed(e.to_string()));
@@ -259,7 +269,15 @@ pub async fn edit_mapping_action(
};
// Check sheet
- let sheet_name = get_current_sheet_name(&ctx, instance, &member_id).await?;
+ let (sheet_name, is_ref_sheet) =
+ get_current_sheet_name(&ctx, instance, &member_id, true).await?;
+
+ // Can modify Sheet when not in reference sheet or in Host mode
+ let can_modify_sheet = !is_ref_sheet || is_host_mode;
+
+ if !can_modify_sheet {
+ return Ok(EditMappingActionResult::EditNotAllowed);
+ }
if ctx.is_proc_on_remote() {
let vault = try_get_vault(&ctx)?;
@@ -340,3 +358,219 @@ pub async fn edit_mapping_action(
Ok(EditMappingActionResult::Success)
}
+
+#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
+pub struct ShareMappingArguments {
+ pub mappings: Vec<FromRelativePathBuf>,
+ pub description: String,
+ // None = current sheet,
+ // Some(sheet_name) = other ref(public) sheet
+ pub from_sheet: Option<SheetName>,
+ pub to_sheet: SheetName,
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub enum ShareMappingActionResult {
+ Success,
+
+ // Fail
+ AuthorizeFailed(String),
+ TargetSheetNotFound(SheetName),
+ TargetIsSelf,
+ MappingNotFound(FromRelativePathBuf),
+
+ #[default]
+ Unknown,
+}
+
+#[action_gen]
+pub async fn share_mapping_action(
+ ctx: ActionContext,
+ args: ShareMappingArguments,
+) -> Result<ShareMappingActionResult, TcpTargetError> {
+ let instance = check_connection_instance(&ctx)?;
+
+ // Auth Member
+ let (member_id, _is_host_mode) = match auth_member(&ctx, instance).await {
+ Ok(id) => id,
+ Err(e) => {
+ return Ok(ShareMappingActionResult::AuthorizeFailed(e.to_string()));
+ }
+ };
+
+ // Check sheet
+ let sheet_name = args.from_sheet.unwrap_or(
+ get_current_sheet_name(&ctx, instance, &member_id, false)
+ .await?
+ .0,
+ );
+
+ if ctx.is_proc_on_remote() {
+ let vault = try_get_vault(&ctx)?;
+ let sheet = vault.sheet(&sheet_name).await?;
+
+ // Tip: Because sheet_name may specify a sheet that does not belong to the user,
+ // a secondary verification is required.
+
+ // Check if the sheet holder is Some and matches the member_id or is the host
+ let Some(holder) = sheet.holder() else {
+ // If there's no holder, the sheet cannot be shared from
+ write_and_return!(
+ instance,
+ ShareMappingActionResult::AuthorizeFailed("Sheet has no holder".to_string())
+ );
+ };
+
+ // Verify the holder is either the current member or the host
+ if holder != &member_id && holder != VAULT_HOST_NAME {
+ write_and_return!(
+ instance,
+ ShareMappingActionResult::AuthorizeFailed(
+ "Not sheet holder or ref sheet".to_string()
+ )
+ );
+ }
+
+ let to_sheet_name = args.to_sheet;
+
+ // Verify target sheet exists
+ if !vault.sheet_names()?.contains(&to_sheet_name) {
+ // Does not exist
+ write_and_return!(
+ instance,
+ ShareMappingActionResult::TargetSheetNotFound(to_sheet_name.clone())
+ );
+ }
+
+ // Verify sheet is not self
+ if sheet_name == to_sheet_name {
+ // Is self
+ write_and_return!(instance, ShareMappingActionResult::TargetIsSelf);
+ }
+
+ // Verify all mappings are correct
+ for mapping in args.mappings.iter() {
+ if !sheet.mapping().contains_key(mapping) {
+ // If any mapping is invalid, indicate failure
+ write_and_return!(
+ instance,
+ ShareMappingActionResult::MappingNotFound(mapping.clone())
+ );
+ }
+ }
+
+ // Execute sharing logic
+ sheet
+ .share_mappings(&to_sheet_name, args.mappings, &member_id, args.description)
+ .await?;
+
+ // Sharing successful
+ write_and_return!(instance, ShareMappingActionResult::Success);
+ }
+
+ if ctx.is_proc_on_local() {
+ let result = instance
+ .lock()
+ .await
+ .read::<ShareMappingActionResult>()
+ .await?;
+ return Ok(result);
+ }
+
+ Ok(ShareMappingActionResult::Success)
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
+pub struct MergeShareMappingArguments {
+ pub share_id: SheetShareId,
+ pub share_merge_mode: ShareMergeMode,
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub enum MergeShareMappingActionResult {
+ Success,
+
+ // Fail
+ HasConflicts,
+ AuthorizeFailed(String),
+ EditNotAllowed,
+ ShareIdNotFound(SheetShareId),
+ MergeFails(String),
+
+ #[default]
+ Unknown,
+}
+
+#[action_gen]
+pub async fn merge_share_mapping_action(
+ ctx: ActionContext,
+ args: MergeShareMappingArguments,
+) -> Result<MergeShareMappingActionResult, TcpTargetError> {
+ let instance = check_connection_instance(&ctx)?;
+
+ // Auth Member
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
+ Ok(id) => id,
+ Err(e) => {
+ return Ok(MergeShareMappingActionResult::AuthorizeFailed(
+ e.to_string(),
+ ));
+ }
+ };
+
+ // Check sheet
+ let (sheet_name, is_ref_sheet) =
+ get_current_sheet_name(&ctx, instance, &member_id, true).await?;
+
+ // Can modify Sheet when not in reference sheet or in Host mode
+ let can_modify_sheet = !is_ref_sheet || is_host_mode;
+
+ if !can_modify_sheet {
+ return Ok(MergeShareMappingActionResult::EditNotAllowed);
+ }
+
+ if ctx.is_proc_on_remote() {
+ let vault = try_get_vault(&ctx)?;
+ let share_id = args.share_id;
+
+ // Get the share and sheet
+ let (sheet, share) = if vault.share_file_path(&sheet_name, &share_id).exists() {
+ let sheet = vault.sheet(&sheet_name).await?;
+ let share = sheet.get_share(&share_id).await?;
+ (sheet, share)
+ } else {
+ // Share does not exist
+ write_and_return!(
+ instance,
+ MergeShareMappingActionResult::ShareIdNotFound(share_id.clone())
+ );
+ };
+
+ // Perform the merge
+ match sheet.merge_share(share, args.share_merge_mode).await {
+ Ok(_) => write_and_return!(instance, MergeShareMappingActionResult::Success),
+ Err(e) => match e.kind() {
+ ErrorKind::AlreadyExists => {
+ write_and_return!(instance, MergeShareMappingActionResult::HasConflicts);
+ }
+ _ => {
+ write_and_return!(
+ instance,
+ MergeShareMappingActionResult::MergeFails(e.to_string())
+ );
+ }
+ },
+ }
+ }
+
+ if ctx.is_proc_on_local() {
+ let result = instance
+ .lock()
+ .await
+ .read::<MergeShareMappingActionResult>()
+ .await?;
+ return Ok(result);
+ }
+
+ Ok(MergeShareMappingActionResult::Success)
+}
diff --git a/crates/vcs_actions/src/actions/track_action.rs b/crates/vcs_actions/src/actions/track_action.rs
index 63c1b67..e5f96b3 100644
--- a/crates/vcs_actions/src/actions/track_action.rs
+++ b/crates/vcs_actions/src/actions/track_action.rs
@@ -123,13 +123,17 @@ pub async fn track_file_action(
let instance = check_connection_instance(&ctx)?;
// Auth Member
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => return Ok(TrackFileActionResult::AuthorizeFailed(e.to_string())),
};
// Check sheet
- let sheet_name = get_current_sheet_name(&ctx, instance, &member_id).await?;
+ let (sheet_name, is_ref_sheet) =
+ get_current_sheet_name(&ctx, instance, &member_id, true).await?;
+
+ // Can modify Sheet when not in reference sheet or in Host mode
+ let can_modify_sheet = !is_ref_sheet || is_host_mode;
if ctx.is_proc_on_local() {
let workspace = try_get_local_workspace(&ctx)?;
@@ -164,7 +168,7 @@ pub async fn track_file_action(
.collect::<Vec<_>>();
// Filter out modified files that need to be updated
- let update_task: Vec<PathBuf> = {
+ let mut update_task: Vec<PathBuf> = {
let result = modified.iter().filter_map(|p| {
if let Ok(local_data) = local_sheet.mapping_data(p) {
let id = local_data.mapping_vfid();
@@ -187,7 +191,7 @@ pub async fn track_file_action(
let mut skipped_task: Vec<PathBuf> = Vec::new();
// Filter out files that do not exist locally or have version inconsistencies and need to be synchronized
- let sync_task: Vec<PathBuf> = {
+ let mut sync_task: Vec<PathBuf> = {
let other: Vec<PathBuf> = relative_pathes
.iter()
.filter(|p| !created_task.contains(p) && !update_task.contains(p))
@@ -242,6 +246,18 @@ pub async fn track_file_action(
result.collect()
};
+ // If the sheet cannot be modified,
+ // the update_task here should be considered invalid and changed to sync rollback
+ if !can_modify_sheet {
+ if arguments.allow_overwrite_modified {
+ sync_task.append(&mut update_task);
+ update_task.clear();
+ } else {
+ skipped_task.append(&mut update_task);
+ update_task.clear();
+ }
+ }
+
// Package tasks
let tasks: (Vec<PathBuf>, Vec<PathBuf>, Vec<PathBuf>) =
(created_task, update_task, sync_task);
@@ -256,45 +272,51 @@ pub async fn track_file_action(
}
// Process create tasks
- let success_create = match proc_create_tasks_local(
- &ctx,
- instance.clone(),
- &member_id,
- &sheet_name,
- tasks.0,
- arguments.print_infos,
- )
- .await
- {
- Ok(r) => match r {
- CreateTaskResult::Success(relative_pathes) => relative_pathes,
- _ => {
- return Ok(TrackFileActionResult::CreateTaskFailed(r));
- }
- },
- Err(e) => return Err(e),
- };
+ let mut success_create = Vec::<PathBuf>::new();
+ if can_modify_sheet {
+ success_create = match proc_create_tasks_local(
+ &ctx,
+ instance.clone(),
+ &member_id,
+ &sheet_name,
+ tasks.0,
+ arguments.print_infos,
+ )
+ .await
+ {
+ Ok(r) => match r {
+ CreateTaskResult::Success(relative_pathes) => relative_pathes,
+ _ => {
+ return Ok(TrackFileActionResult::CreateTaskFailed(r));
+ }
+ },
+ Err(e) => return Err(e),
+ };
+ }
// Process update tasks
- let success_update = match proc_update_tasks_local(
- &ctx,
- instance.clone(),
- &member_id,
- &sheet_name,
- tasks.1,
- arguments.print_infos,
- arguments.file_update_info,
- )
- .await
- {
- Ok(r) => match r {
- UpdateTaskResult::Success(relative_pathes) => relative_pathes,
- _ => {
- return Ok(TrackFileActionResult::UpdateTaskFailed(r));
- }
- },
- Err(e) => return Err(e),
- };
+ let mut success_update = Vec::<PathBuf>::new();
+ if can_modify_sheet {
+ success_update = match proc_update_tasks_local(
+ &ctx,
+ instance.clone(),
+ &member_id,
+ &sheet_name,
+ tasks.1,
+ arguments.print_infos,
+ arguments.file_update_info,
+ )
+ .await
+ {
+ Ok(r) => match r {
+ UpdateTaskResult::Success(relative_pathes) => relative_pathes,
+ _ => {
+ return Ok(TrackFileActionResult::UpdateTaskFailed(r));
+ }
+ },
+ Err(e) => return Err(e),
+ };
+ }
// Process sync tasks
let success_sync = match proc_sync_tasks_local(
@@ -333,43 +355,49 @@ pub async fn track_file_action(
};
// Process create tasks
- let success_create = match proc_create_tasks_remote(
- &ctx,
- instance.clone(),
- &member_id,
- &sheet_name,
- created_task,
- )
- .await
- {
- Ok(r) => match r {
- CreateTaskResult::Success(relative_pathes) => relative_pathes,
- _ => {
- return Ok(TrackFileActionResult::CreateTaskFailed(r));
- }
- },
- Err(e) => return Err(e),
- };
+ let mut success_create = Vec::<PathBuf>::new();
+ if can_modify_sheet {
+ success_create = match proc_create_tasks_remote(
+ &ctx,
+ instance.clone(),
+ &member_id,
+ &sheet_name,
+ created_task,
+ )
+ .await
+ {
+ Ok(r) => match r {
+ CreateTaskResult::Success(relative_pathes) => relative_pathes,
+ _ => {
+ return Ok(TrackFileActionResult::CreateTaskFailed(r));
+ }
+ },
+ Err(e) => return Err(e),
+ };
+ }
// Process update tasks
- let success_update = match proc_update_tasks_remote(
- &ctx,
- instance.clone(),
- &member_id,
- &sheet_name,
- update_task,
- arguments.file_update_info,
- )
- .await
- {
- Ok(r) => match r {
- UpdateTaskResult::Success(relative_pathes) => relative_pathes,
- _ => {
- return Ok(TrackFileActionResult::UpdateTaskFailed(r));
- }
- },
- Err(e) => return Err(e),
- };
+ let mut success_update = Vec::<PathBuf>::new();
+ if can_modify_sheet {
+ success_update = match proc_update_tasks_remote(
+ &ctx,
+ instance.clone(),
+ &member_id,
+ &sheet_name,
+ update_task,
+ arguments.file_update_info,
+ )
+ .await
+ {
+ Ok(r) => match r {
+ UpdateTaskResult::Success(relative_pathes) => relative_pathes,
+ _ => {
+ return Ok(TrackFileActionResult::UpdateTaskFailed(r));
+ }
+ },
+ Err(e) => return Err(e),
+ };
+ }
// Process sync tasks
let success_sync = match proc_sync_tasks_remote(
diff --git a/crates/vcs_actions/src/actions/user_actions.rs b/crates/vcs_actions/src/actions/user_actions.rs
index febfeeb..dc0f71a 100644
--- a/crates/vcs_actions/src/actions/user_actions.rs
+++ b/crates/vcs_actions/src/actions/user_actions.rs
@@ -43,7 +43,7 @@ pub async fn change_virtual_file_edit_right_action(
let (relative_paths, print_info) = arguments;
// Auth Member
- let member_id = match auth_member(&ctx, instance).await {
+ let (member_id, is_host_mode) = match auth_member(&ctx, instance).await {
Ok(id) => id,
Err(e) => {
return Ok(ChangeVirtualFileEditRightResult::AuthorizeFailed(
@@ -53,7 +53,8 @@ pub async fn change_virtual_file_edit_right_action(
};
// Check sheet
- let sheet_name = get_current_sheet_name(&ctx, instance, &member_id).await?;
+ let (sheet_name, _is_ref_sheet) =
+ get_current_sheet_name(&ctx, instance, &member_id, true).await?;
if ctx.is_proc_on_remote() {
let mut mut_instance = instance.lock().await;
@@ -87,7 +88,9 @@ pub async fn change_virtual_file_edit_right_action(
}
} else
// Throw file
- if has_edit_right && behaviour == EditRightChangeBehaviour::Throw {
+ if (has_edit_right || is_host_mode)
+ && behaviour == EditRightChangeBehaviour::Throw
+ {
match vault.revoke_virtual_file_edit_right(&mapping.id).await {
Ok(_) => {
success_throw.push(path.clone());
diff --git a/crates/vcs_actions/src/actions/vault_actions.rs b/crates/vcs_actions/src/actions/vault_actions.rs
index e69de29..8b13789 100644
--- a/crates/vcs_actions/src/actions/vault_actions.rs
+++ b/crates/vcs_actions/src/actions/vault_actions.rs
@@ -0,0 +1 @@
+
diff --git a/crates/vcs_actions/src/registry/client_registry.rs b/crates/vcs_actions/src/registry/client_registry.rs
index 6667a74..05cb7f1 100644
--- a/crates/vcs_actions/src/registry/client_registry.rs
+++ b/crates/vcs_actions/src/registry/client_registry.rs
@@ -15,6 +15,7 @@ use crate::{
},
sheet_actions::{
register_drop_sheet_action, register_edit_mapping_action, register_make_sheet_action,
+ register_merge_share_mapping_action, register_share_mapping_action,
},
track_action::register_track_file_action,
user_actions::register_change_virtual_file_edit_right_action,
@@ -34,6 +35,10 @@ fn register_actions(pool: &mut ActionPool) {
register_drop_sheet_action(pool);
register_edit_mapping_action(pool);
+ // Share / Merge Share Actions
+ register_share_mapping_action(pool);
+ register_merge_share_mapping_action(pool);
+
// Track Action
register_track_file_action(pool);
diff --git a/crates/vcs_actions/src/registry/server_registry.rs b/crates/vcs_actions/src/registry/server_registry.rs
index 404625c..356e640 100644
--- a/crates/vcs_actions/src/registry/server_registry.rs
+++ b/crates/vcs_actions/src/registry/server_registry.rs
@@ -4,6 +4,7 @@ use crate::actions::{
local_actions::{register_set_upstream_vault_action, register_update_to_latest_info_action},
sheet_actions::{
register_drop_sheet_action, register_edit_mapping_action, register_make_sheet_action,
+ register_merge_share_mapping_action, register_share_mapping_action,
},
track_action::register_track_file_action,
user_actions::register_change_virtual_file_edit_right_action,
@@ -21,6 +22,10 @@ pub fn server_action_pool() -> ActionPool {
register_drop_sheet_action(&mut pool);
register_edit_mapping_action(&mut pool);
+ // Share / Merge Share Actions
+ register_share_mapping_action(&mut pool);
+ register_merge_share_mapping_action(&mut pool);
+
// Track Action
register_track_file_action(&mut pool);