summaryrefslogtreecommitdiff
path: root/crates/vcs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-10-06 04:11:58 +0800
committer魏曹先生 <1992414357@qq.com>2025-10-06 04:11:58 +0800
commitee7cba690582b9c47e8c856bf0bd331eedda7908 (patch)
tree21218393cd14b4465296b15ad999fc89be97757c /crates/vcs
parent03a5ff8a1629cde933120faf47963fcb59118261 (diff)
Remove old vcs directory after migration to vcs_data
- Delete entire crates/vcs directory and its contents - Remove test files and configuration from old structure - Complete transition to new vcs_data and vcs_actions architecture
Diffstat (limited to 'crates/vcs')
-rw-r--r--crates/vcs/Cargo.toml22
-rw-r--r--crates/vcs/src/constants.rs54
-rw-r--r--crates/vcs/src/current.rs78
-rw-r--r--crates/vcs/src/data.rs5
-rw-r--r--crates/vcs/src/data/local.rs100
-rw-r--r--crates/vcs/src/data/local/config.rs53
-rw-r--r--crates/vcs/src/data/member.rs69
-rw-r--r--crates/vcs/src/data/sheet.rs347
-rw-r--r--crates/vcs/src/data/user.rs28
-rw-r--r--crates/vcs/src/data/user/accounts.rs164
-rw-r--r--crates/vcs/src/data/vault.rs146
-rw-r--r--crates/vcs/src/data/vault/config.rs77
-rw-r--r--crates/vcs/src/data/vault/member.rs140
-rw-r--r--crates/vcs/src/data/vault/sheets.rs268
-rw-r--r--crates/vcs/src/data/vault/virtual_file.rs473
-rw-r--r--crates/vcs/src/lib.rs5
-rw-r--r--crates/vcs/todo.txt36
-rw-r--r--crates/vcs/vcs_test/Cargo.toml13
-rw-r--r--crates/vcs/vcs_test/lib.rs11
-rw-r--r--crates/vcs/vcs_test/src/lib.rs27
-rw-r--r--crates/vcs/vcs_test/src/test_local_workspace_setup_and_account_management.rs248
-rw-r--r--crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs307
-rw-r--r--crates/vcs/vcs_test/src/test_vault_setup_and_member_register.rs67
-rw-r--r--crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs162
24 files changed, 0 insertions, 2900 deletions
diff --git a/crates/vcs/Cargo.toml b/crates/vcs/Cargo.toml
deleted file mode 100644
index 888e18d..0000000
--- a/crates/vcs/Cargo.toml
+++ /dev/null
@@ -1,22 +0,0 @@
-[package]
-name = "vcs"
-edition = "2024"
-version.workspace = true
-
-[dependencies]
-tcp_connection = { path = "../utils/tcp_connection" }
-cfg_file = { path = "../utils/cfg_file", features = ["default"] }
-string_proc = { path = "../utils/string_proc" }
-action_system = { path = "../system_action" }
-
-# Identity
-uuid = { version = "1.18.1", features = ["v4", "serde"] }
-
-# Serialization
-serde = { version = "1.0.219", features = ["derive"] }
-
-# Async & Networking
-tokio = { version = "1.46.1", features = ["full"] }
-
-# Filesystem
-dirs = "6.0.0"
diff --git a/crates/vcs/src/constants.rs b/crates/vcs/src/constants.rs
deleted file mode 100644
index 5e147c4..0000000
--- a/crates/vcs/src/constants.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-// -------------------------------------------------------------------------------------
-//
-
-// Project
-pub const PATH_TEMP: &str = "./.temp/";
-
-// Default Port
-pub const PORT: u16 = 25331;
-
-// Vault Host Name
-pub const VAULT_HOST_NAME: &str = "host";
-
-// Server
-// Server - Vault (Main)
-pub const SERVER_FILE_VAULT: &str = "./vault.toml"; // crates::env::vault::vault_config
-
-// Server - Sheets
-pub const REF_SHEET_NAME: &str = "ref";
-pub const SERVER_PATH_SHEETS: &str = "./sheets/";
-pub const SERVER_FILE_SHEET: &str = "./sheets/{sheet-name}.yaml";
-
-// Server - Members
-pub const SERVER_PATH_MEMBERS: &str = "./members/";
-pub const SERVER_PATH_MEMBER_PUB: &str = "./key/";
-pub const SERVER_FILE_MEMBER_INFO: &str = "./members/{member_id}.toml"; // crates::env::member::manager
-pub const SERVER_FILE_MEMBER_PUB: &str = "./key/{member_id}.pem"; // crates::utils::tcp_connection::instance
-
-// Server - Virtual File Storage
-pub const SERVER_PATH_VF_TEMP: &str = "./.temp/{temp_name}";
-pub const SERVER_PATH_VF_ROOT: &str = "./storage/";
-pub const SERVER_PATH_VF_STORAGE: &str = "./storage/{vf_index}/{vf_id}/";
-pub const SERVER_FILE_VF_VERSION_INSTANCE: &str = "./storage/{vf_index}/{vf_id}/{vf_version}.rf";
-pub const SERVER_FILE_VF_META: &str = "./storage/{vf_index}/{vf_id}/meta.yaml";
-
-pub const SERVER_FILE_README: &str = "./README.md";
-
-// -------------------------------------------------------------------------------------
-
-// Client
-pub const CLIENT_PATH_WORKSPACE_ROOT: &str = "./.jv/";
-
-// Client - Workspace (Main)
-pub const CLIENT_FILE_WORKSPACE: &str = "./.jv/workspace.toml"; // crates::env::local::local_config
-
-// Client - Other
-pub const CLIENT_FILE_IGNOREFILES: &str = ".jgnore .gitignore"; // Support gitignore file.
-pub const CLIENT_FILE_README: &str = "./README.md";
-
-// -------------------------------------------------------------------------------------
-
-// User - Verify (Documents path)
-pub const USER_FILE_ACCOUNTS: &str = "./accounts/";
-pub const USER_FILE_KEY: &str = "./accounts/{self_id}_private.pem";
-pub const USER_FILE_MEMBER: &str = "./accounts/{self_id}.toml";
diff --git a/crates/vcs/src/current.rs b/crates/vcs/src/current.rs
deleted file mode 100644
index 97b5058..0000000
--- a/crates/vcs/src/current.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use crate::constants::*;
-use std::io::{self, Error};
-use std::{env::set_current_dir, path::PathBuf};
-
-/// Find the nearest vault or local workspace and correct the `current_dir` to it
-pub fn correct_current_dir() -> Result<(), io::Error> {
- if let Some(local_workspace) = current_local_path() {
- set_current_dir(local_workspace)?;
- return Ok(());
- }
- if let Some(vault) = current_vault_path() {
- set_current_dir(vault)?;
- return Ok(());
- }
- Err(Error::new(
- io::ErrorKind::NotFound,
- "Could not find any vault or local workspace!",
- ))
-}
-
-/// Get the nearest Vault directory from `current_dir`
-pub fn current_vault_path() -> Option<PathBuf> {
- let current_dir = std::env::current_dir().ok()?;
- find_vault_path(current_dir)
-}
-
-/// Get the nearest local workspace from `current_dir`
-pub fn current_local_path() -> Option<PathBuf> {
- let current_dir = std::env::current_dir().ok()?;
- find_local_path(current_dir)
-}
-
-/// Get the nearest Vault directory from the specified path
-pub fn find_vault_path(path: impl Into<PathBuf>) -> Option<PathBuf> {
- let mut current_path = path.into();
- let vault_file = SERVER_FILE_VAULT;
-
- loop {
- let vault_toml_path = current_path.join(vault_file);
- if vault_toml_path.exists() {
- return Some(current_path);
- }
-
- if let Some(parent) = current_path.parent() {
- current_path = parent.to_path_buf();
- } else {
- break;
- }
- }
-
- None
-}
-
-/// Get the nearest local workspace from the specified path
-pub fn find_local_path(path: impl Into<PathBuf>) -> Option<PathBuf> {
- let mut current_path = path.into();
- let workspace_dir = CLIENT_PATH_WORKSPACE_ROOT;
-
- loop {
- let jvc_path = current_path.join(workspace_dir);
- if jvc_path.exists() {
- return Some(current_path);
- }
-
- if let Some(parent) = current_path.parent() {
- current_path = parent.to_path_buf();
- } else {
- break;
- }
- }
-
- None
-}
-
-/// Get the system's document directory and join with .just_enough_vcs
-pub fn current_doc_dir() -> Option<PathBuf> {
- dirs::document_dir().map(|path| path.join(".just_enough_vcs"))
-}
diff --git a/crates/vcs/src/data.rs b/crates/vcs/src/data.rs
deleted file mode 100644
index ed9383a..0000000
--- a/crates/vcs/src/data.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub mod local;
-pub mod member;
-pub mod sheet;
-pub mod user;
-pub mod vault;
diff --git a/crates/vcs/src/data/local.rs b/crates/vcs/src/data/local.rs
deleted file mode 100644
index 1c99832..0000000
--- a/crates/vcs/src/data/local.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use std::{env::current_dir, path::PathBuf};
-
-use cfg_file::config::ConfigFile;
-use tokio::fs;
-
-use crate::{
- constants::{CLIENT_FILE_README, CLIENT_FILE_WORKSPACE},
- current::{current_local_path, find_local_path},
- data::local::config::LocalConfig,
-};
-
-pub mod config;
-
-pub struct LocalWorkspace {
- config: LocalConfig,
- local_path: PathBuf,
-}
-
-impl LocalWorkspace {
- /// Get the path of the local workspace.
- pub fn local_path(&self) -> &PathBuf {
- &self.local_path
- }
-
- /// Initialize local workspace.
- pub fn init(config: LocalConfig, local_path: impl Into<PathBuf>) -> Option<Self> {
- let local_path = find_local_path(local_path)?;
- Some(Self { config, local_path })
- }
-
- /// Initialize local workspace in the current directory.
- pub fn init_current_dir(config: LocalConfig) -> Option<Self> {
- let local_path = current_local_path()?;
- Some(Self { config, local_path })
- }
-
- /// Setup local workspace
- pub async fn setup_local_workspace(
- local_path: impl Into<PathBuf>,
- ) -> Result<(), std::io::Error> {
- let local_path: PathBuf = local_path.into();
-
- // Ensure directory is empty
- if local_path.exists() && local_path.read_dir()?.next().is_some() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::DirectoryNotEmpty,
- "DirectoryNotEmpty",
- ));
- }
-
- // 1. Setup config
- let config = LocalConfig::default();
- LocalConfig::write_to(&config, local_path.join(CLIENT_FILE_WORKSPACE)).await?;
-
- // 2. Setup README.md
- let readme_content = "\
-# JustEnoughVCS Local Workspace
-
-This directory is a **Local Workspace** managed by `JustEnoughVCS`. All files and subdirectories within this scope can be version-controlled using the `JustEnoughVCS` CLI or GUI tools, with the following exceptions:
-
-- The `.jv` directory
-- Any files or directories excluded via `.jgnore` or `.gitignore`
-
-> ⚠️ **Warning**
->
-> Files in this workspace will be uploaded to the upstream server. Please ensure you fully trust this server before proceeding.
-
-## Access Requirements
-
-To use `JustEnoughVCS` with this workspace, you must have:
-
-- **A registered user ID** with the upstream server
-- **Your private key** properly configured locally
-- **Your public key** stored in the server's public key directory
-
-Without these credentials, the server will reject all access requests.
-
-## Support
-
-- **Permission or access issues?** → Contact your server administrator
-- **Tooling problems or bugs?** → Reach out to the development team via [GitHub Issues](https://github.com/JustEnoughVCS/VersionControl/issues)
-- **Documentation**: Visit our repository for full documentation
-
-------
-
-*Thank you for using JustEnoughVCS!*
-".to_string()
- .trim()
- .to_string();
- fs::write(local_path.join(CLIENT_FILE_README), readme_content).await?;
-
- Ok(())
- }
-
- /// Setup local workspace in current directory
- pub async fn setup_local_workspacecurrent_dir() -> Result<(), std::io::Error> {
- Self::setup_local_workspace(current_dir()?).await?;
- Ok(())
- }
-}
diff --git a/crates/vcs/src/data/local/config.rs b/crates/vcs/src/data/local/config.rs
deleted file mode 100644
index 5444047..0000000
--- a/crates/vcs/src/data/local/config.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-use std::net::SocketAddr;
-
-use crate::constants::CLIENT_FILE_WORKSPACE;
-use crate::constants::PORT;
-use crate::data::member::MemberId;
-
-#[derive(Serialize, Deserialize, ConfigFile)]
-#[cfg_file(path = CLIENT_FILE_WORKSPACE)]
-pub struct LocalConfig {
- /// The upstream address, representing the upstream address of the local workspace,
- /// to facilitate timely retrieval of new updates from the upstream source.
- upstream_addr: SocketAddr,
-
- /// The member ID used by the current local workspace.
- /// This ID will be used to verify access permissions when connecting to the upstream server.
- using_account: MemberId,
-}
-
-impl Default for LocalConfig {
- fn default() -> Self {
- Self {
- upstream_addr: SocketAddr::V4(std::net::SocketAddrV4::new(
- std::net::Ipv4Addr::new(127, 0, 0, 1),
- PORT,
- )),
- using_account: "unknown".to_string(),
- }
- }
-}
-
-impl LocalConfig {
- /// Set the vault address.
- pub fn set_vault_addr(&mut self, addr: SocketAddr) {
- self.upstream_addr = addr;
- }
-
- /// Get the vault address.
- pub fn vault_addr(&self) -> SocketAddr {
- self.upstream_addr
- }
-
- /// Set the currently used account
- pub fn set_current_account(&mut self, account: MemberId) {
- self.using_account = account;
- }
-
- /// Get the currently used account
- pub fn current_account(&self) -> MemberId {
- self.using_account.clone()
- }
-}
diff --git a/crates/vcs/src/data/member.rs b/crates/vcs/src/data/member.rs
deleted file mode 100644
index b5136a1..0000000
--- a/crates/vcs/src/data/member.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use std::collections::HashMap;
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-use string_proc::snake_case;
-
-pub type MemberId = String;
-
-#[derive(Debug, Eq, Clone, ConfigFile, Serialize, Deserialize)]
-pub struct Member {
- /// Member ID, the unique identifier of the member
- id: String,
-
- /// Member metadata
- metadata: HashMap<String, String>,
-}
-
-impl Default for Member {
- fn default() -> Self {
- Self::new("default_user")
- }
-}
-
-impl PartialEq for Member {
- fn eq(&self, other: &Self) -> bool {
- self.id == other.id
- }
-}
-
-impl std::fmt::Display for Member {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.id)
- }
-}
-
-impl std::convert::AsRef<str> for Member {
- fn as_ref(&self) -> &str {
- &self.id
- }
-}
-
-impl Member {
- /// Create member struct by id
- pub fn new(new_id: impl Into<String>) -> Self {
- Self {
- id: snake_case!(new_id.into()),
- metadata: HashMap::new(),
- }
- }
-
- /// Get member id
- pub fn id(&self) -> String {
- self.id.clone()
- }
-
- /// Get metadata
- pub fn metadata(&self, key: impl Into<String>) -> Option<&String> {
- self.metadata.get(&key.into())
- }
-
- /// Set metadata
- pub fn set_metadata(
- &mut self,
- key: impl AsRef<str>,
- value: impl Into<String>,
- ) -> Option<String> {
- self.metadata.insert(key.as_ref().to_string(), value.into())
- }
-}
diff --git a/crates/vcs/src/data/sheet.rs b/crates/vcs/src/data/sheet.rs
deleted file mode 100644
index a6220c9..0000000
--- a/crates/vcs/src/data/sheet.rs
+++ /dev/null
@@ -1,347 +0,0 @@
-use std::{collections::HashMap, path::PathBuf};
-
-use cfg_file::{ConfigFile, config::ConfigFile};
-use serde::{Deserialize, Serialize};
-use string_proc::simple_processer::sanitize_file_path;
-
-use crate::{
- constants::SERVER_FILE_SHEET,
- data::{
- member::MemberId,
- vault::{Vault, virtual_file::VirtualFileId},
- },
-};
-
-pub type SheetName = String;
-pub type SheetPathBuf = PathBuf;
-pub type InputName = String;
-pub type InputRelativePathBuf = PathBuf;
-
-#[derive(Debug, Clone, Serialize, Deserialize, Eq)]
-pub struct InputPackage {
- /// Name of the input package
- pub name: InputName,
-
- /// The sheet from which this input package was created
- pub from: SheetName,
-
- /// Files in this input package with their relative paths and virtual file IDs
- pub files: Vec<(InputRelativePathBuf, VirtualFileId)>,
-}
-
-impl PartialEq for InputPackage {
- fn eq(&self, other: &Self) -> bool {
- self.name == other.name
- }
-}
-
-const SHEET_NAME: &str = "{sheet-name}";
-
-pub struct Sheet<'a> {
- /// The name of the current sheet
- pub(crate) name: SheetName,
-
- /// Sheet data
- pub(crate) data: SheetData,
-
- /// Sheet path
- pub(crate) vault_reference: &'a Vault,
-}
-
-#[derive(Default, Serialize, Deserialize, ConfigFile)]
-pub struct SheetData {
- /// The holder of the current sheet, who has full operation rights to the sheet mapping
- pub(crate) holder: MemberId,
-
- /// Inputs
- pub(crate) inputs: Vec<InputPackage>,
-
- /// Mapping of sheet paths to virtual file IDs
- pub(crate) mapping: HashMap<SheetPathBuf, VirtualFileId>,
-}
-
-impl<'a> Sheet<'a> {
- /// Get the holder of this sheet
- pub fn holder(&self) -> &MemberId {
- &self.data.holder
- }
-
- /// Get the inputs of this sheet
- pub fn inputs(&self) -> &Vec<InputPackage> {
- &self.data.inputs
- }
-
- /// Get the names of the inputs of this sheet
- pub fn input_names(&self) -> Vec<String> {
- self.data
- .inputs
- .iter()
- .map(|input| input.name.clone())
- .collect()
- }
-
- /// Get the mapping of this sheet
- pub fn mapping(&self) -> &HashMap<SheetPathBuf, VirtualFileId> {
- &self.data.mapping
- }
-
- /// 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) {
- return Err(std::io::Error::new(
- std::io::ErrorKind::AlreadyExists,
- format!("Input package '{}' already exists", input_package.name),
- ));
- }
- self.data.inputs.push(input_package);
- Ok(())
- }
-
- /// Deny and remove an input package from the sheet
- pub fn deny_input(&mut self, input_name: &InputName) -> Option<InputPackage> {
- self.data
- .inputs
- .iter()
- .position(|input| input.name == *input_name)
- .map(|pos| self.data.inputs.remove(pos))
- }
-
- /// Accept an input package and insert to the sheet
- pub fn accept_import(
- &mut self,
- input_name: &InputName,
- insert_to: &SheetPathBuf,
- ) -> Result<(), std::io::Error> {
- // Remove inputs
- let input = self
- .inputs()
- .iter()
- .position(|input| input.name == *input_name)
- .map(|pos| self.data.inputs.remove(pos));
-
- // Ensure input is not empty
- let Some(input) = input else {
- return Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "Empty inputs.",
- ));
- };
-
- // Insert to sheet
- for (relative_path, virtual_file_id) in input.files {
- let _ = self.add_mapping(insert_to.join(relative_path), virtual_file_id);
- }
-
- Ok(())
- }
-
- /// Add (or Edit) a mapping entry to the sheet
- ///
- /// This operation performs safety checks to ensure the member has the right to add the mapping:
- /// 1. If the virtual file ID doesn't exist in the vault, the mapping is added directly
- /// 2. If the virtual file exists, check if the member has edit rights to the virtual file
- /// 3. If member has edit rights, the mapping is not allowed to be modified and returns an error
- /// 4. If member doesn't have edit rights, the mapping is allowed (member is giving up the file)
- ///
- /// Note: Full validation adds overhead - avoid frequent calls
- pub async fn add_mapping(
- &mut self,
- sheet_path: SheetPathBuf,
- virtual_file_id: VirtualFileId,
- ) -> Result<(), std::io::Error> {
- // Check if the virtual file exists in the vault
- if self.vault_reference.virtual_file(&virtual_file_id).is_err() {
- // Virtual file doesn't exist, add the mapping directly
- self.data.mapping.insert(sheet_path, virtual_file_id);
- return Ok(());
- }
-
- // Check if the holder has edit rights to the virtual file
- match self
- .vault_reference
- .has_virtual_file_edit_right(self.holder(), &virtual_file_id)
- .await
- {
- Ok(false) => {
- // Holder doesn't have rights, add the mapping (member is giving up the file)
- self.data.mapping.insert(sheet_path, virtual_file_id);
- Ok(())
- }
- Ok(true) => {
- // Holder has edit rights, don't allow modifying the mapping
- Err(std::io::Error::new(
- std::io::ErrorKind::PermissionDenied,
- "Member has edit rights to the virtual file, cannot modify mapping",
- ))
- }
- Err(_) => {
- // Error checking rights, don't allow modifying the mapping
- Err(std::io::Error::new(
- std::io::ErrorKind::Other,
- "Failed to check virtual file edit rights",
- ))
- }
- }
- }
-
- /// Remove a mapping entry from the sheet
- ///
- /// This operation performs safety checks to ensure the member has the right to remove the mapping:
- /// 1. Member must NOT have edit rights to the virtual file to release it (ensuring clear ownership)
- /// 2. If the virtual file doesn't exist, the mapping is removed but no ID is returned
- /// 3. If member has no edit rights and the file exists, returns the removed virtual file ID
- ///
- /// Note: Full validation adds overhead - avoid frequent calls
- pub async fn remove_mapping(&mut self, sheet_path: &SheetPathBuf) -> Option<VirtualFileId> {
- let virtual_file_id = match self.data.mapping.get(sheet_path) {
- Some(id) => id,
- None => {
- // The mapping entry doesn't exist, nothing to remove
- return None;
- }
- };
-
- // Check if the virtual file exists in the vault
- if self.vault_reference.virtual_file(virtual_file_id).is_err() {
- // Virtual file doesn't exist, remove the mapping and return None
- self.data.mapping.remove(sheet_path);
- return None;
- }
-
- // Check if the holder has edit rights to the virtual file
- match self
- .vault_reference
- .has_virtual_file_edit_right(self.holder(), virtual_file_id)
- .await
- {
- Ok(false) => {
- // Holder doesn't have rights, remove and return the virtual file ID
- self.data.mapping.remove(sheet_path)
- }
- Ok(true) => {
- // Holder has edit rights, don't remove the mapping
- None
- }
- Err(_) => {
- // Error checking rights, don't remove the mapping
- None
- }
- }
- }
-
- /// Persist the sheet to disk
- ///
- /// Why not use a reference?
- /// Because I don't want a second instance of the sheet to be kept in memory.
- /// If needed, please deserialize and reload it.
- pub async fn persist(self) -> Result<(), std::io::Error> {
- SheetData::write_to(&self.data, self.sheet_path()).await
- }
-
- /// Get the path to the sheet file
- pub fn sheet_path(&self) -> PathBuf {
- Sheet::sheet_path_with_name(self.vault_reference, &self.name)
- }
-
- /// Get the path to the sheet file with the given name
- pub fn sheet_path_with_name(vault: &Vault, name: impl AsRef<str>) -> PathBuf {
- vault
- .vault_path()
- .join(SERVER_FILE_SHEET.replace(SHEET_NAME, name.as_ref()))
- }
-
- /// Export files from the current sheet as an InputPackage for importing into other sheets
- ///
- /// This is the recommended way to create InputPackages. It takes a list of sheet paths
- /// and generates an InputPackage with optimized relative paths by removing the longest
- /// common prefix from all provided paths, then placing the files under a directory
- /// named with the output_name.
- ///
- /// # Example
- /// Given paths:
- /// - `MyProject/Art/Character/Model/final.fbx`
- /// - `MyProject/Art/Character/Texture/final.png`
- /// - `MyProject/Art/Character/README.md`
- ///
- /// With output_name = "MyExport", the resulting package will contain:
- /// - `MyExport/Model/final.fbx`
- /// - `MyExport/Texture/final.png`
- /// - `MyExport/README.md`
- ///
- /// # Arguments
- /// * `output_name` - Name of the output package (will be used as the root directory)
- /// * `paths` - List of sheet paths to include in the package
- ///
- /// # Returns
- /// Returns an InputPackage containing the exported files with optimized paths,
- /// or an error if paths are empty or files are not found in the sheet mapping
- pub fn output_mappings(
- &self,
- output_name: InputName,
- paths: &[SheetPathBuf],
- ) -> Result<InputPackage, std::io::Error> {
- let output_name = sanitize_file_path(output_name);
-
- // Return error for empty paths since there's no need to generate an empty package
- if paths.is_empty() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Cannot generate output package with empty paths",
- ));
- }
-
- // Find the longest common prefix among all paths
- let common_prefix = Self::find_longest_common_prefix(paths);
-
- // Create output files with optimized relative paths
- let files = paths
- .iter()
- .map(|path| {
- let relative_path = path.strip_prefix(&common_prefix).unwrap_or(path);
- let output_path = PathBuf::from(&output_name).join(relative_path);
-
- self.data
- .mapping
- .get(path)
- .map(|vfid| (output_path, vfid.clone()))
- .ok_or_else(|| {
- std::io::Error::new(
- std::io::ErrorKind::NotFound,
- format!("File not found: {:?}", path),
- )
- })
- })
- .collect::<Result<Vec<_>, _>>()?;
-
- Ok(InputPackage {
- name: output_name,
- from: self.name.clone(),
- files,
- })
- }
-
- /// Helper function to find the longest common prefix among all paths
- fn find_longest_common_prefix(paths: &[SheetPathBuf]) -> PathBuf {
- if paths.is_empty() {
- return PathBuf::new();
- }
-
- let first_path = &paths[0];
- let mut common_components = Vec::new();
-
- for (component_idx, first_component) in first_path.components().enumerate() {
- for path in paths.iter().skip(1) {
- if let Some(component) = path.components().nth(component_idx) {
- if component != first_component {
- return common_components.into_iter().collect();
- }
- } else {
- return common_components.into_iter().collect();
- }
- }
- common_components.push(first_component);
- }
-
- common_components.into_iter().collect()
- }
-}
diff --git a/crates/vcs/src/data/user.rs b/crates/vcs/src/data/user.rs
deleted file mode 100644
index 0abd098..0000000
--- a/crates/vcs/src/data/user.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use crate::current::current_doc_dir;
-use std::path::PathBuf;
-
-pub mod accounts;
-
-pub struct UserDirectory {
- local_path: PathBuf,
-}
-
-impl UserDirectory {
- /// Create a user ditectory struct from the current system's document directory
- pub fn current_doc_dir() -> Option<Self> {
- Some(UserDirectory {
- local_path: current_doc_dir()?,
- })
- }
-
- /// Create a user directory struct from a specified directory path
- /// Returns None if the directory does not exist
- pub fn from_path<P: Into<PathBuf>>(path: P) -> Option<Self> {
- let local_path = path.into();
- if local_path.exists() {
- Some(UserDirectory { local_path })
- } else {
- None
- }
- }
-}
diff --git a/crates/vcs/src/data/user/accounts.rs b/crates/vcs/src/data/user/accounts.rs
deleted file mode 100644
index d77bc02..0000000
--- a/crates/vcs/src/data/user/accounts.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use std::{
- fs,
- io::{Error, ErrorKind},
- path::PathBuf,
-};
-
-use cfg_file::config::ConfigFile;
-
-use crate::{
- constants::{USER_FILE_ACCOUNTS, USER_FILE_KEY, USER_FILE_MEMBER},
- data::{
- member::{Member, MemberId},
- user::UserDirectory,
- },
-};
-
-const SELF_ID: &str = "{self_id}";
-
-/// Account Management
-impl UserDirectory {
- /// Read account from configuration file
- pub async fn account(&self, id: &MemberId) -> Result<Member, std::io::Error> {
- if let Some(cfg_file) = self.account_cfg(id) {
- let member = Member::read_from(cfg_file).await?;
- return Ok(member);
- }
-
- Err(Error::new(ErrorKind::NotFound, "Account not found!"))
- }
-
- /// List all account IDs in the user directory
- pub fn account_ids(&self) -> Result<Vec<MemberId>, std::io::Error> {
- let accounts_path = self
- .local_path
- .join(USER_FILE_ACCOUNTS.replace(SELF_ID, ""));
-
- if !accounts_path.exists() {
- return Ok(Vec::new());
- }
-
- let mut account_ids = Vec::new();
-
- for entry in fs::read_dir(accounts_path)? {
- let entry = entry?;
- let path = entry.path();
-
- if path.is_file()
- && let Some(file_name) = path.file_stem().and_then(|s| s.to_str())
- && path.extension().and_then(|s| s.to_str()) == Some("toml")
- {
- // Remove the "_private" suffix from key files if present
- let account_id = file_name.replace("_private", "");
- account_ids.push(account_id);
- }
- }
-
- Ok(account_ids)
- }
-
- /// Get all accounts
- /// This method will read and deserialize account information, please pay attention to performance issues
- pub async fn accounts(&self) -> Result<Vec<Member>, std::io::Error> {
- let mut accounts = Vec::new();
-
- for account_id in self.account_ids()? {
- if let Ok(account) = self.account(&account_id).await {
- accounts.push(account);
- }
- }
-
- Ok(accounts)
- }
-
- /// Update account info
- pub async fn update_account(&self, member: Member) -> Result<(), std::io::Error> {
- // Ensure account exist
- if self.account_cfg(&member.id()).is_some() {
- let account_cfg_path = self.account_cfg_path(&member.id());
- Member::write_to(&member, account_cfg_path).await?;
- return Ok(());
- }
-
- Err(Error::new(ErrorKind::NotFound, "Account not found!"))
- }
-
- /// Register an account to user directory
- pub async fn register_account(&self, member: Member) -> Result<(), std::io::Error> {
- // Ensure account not exist
- if self.account_cfg(&member.id()).is_some() {
- return Err(Error::new(
- ErrorKind::DirectoryNotEmpty,
- format!("Account `{}` already registered!", member.id()),
- ));
- }
-
- // Ensure accounts directory exists
- let accounts_dir = self
- .local_path
- .join(USER_FILE_ACCOUNTS.replace(SELF_ID, ""));
- if !accounts_dir.exists() {
- fs::create_dir_all(&accounts_dir)?;
- }
-
- // Write config file to accounts dir
- let account_cfg_path = self.account_cfg_path(&member.id());
- Member::write_to(&member, account_cfg_path).await?;
-
- Ok(())
- }
-
- /// Remove account from user directory
- pub fn remove_account(&self, id: &MemberId) -> Result<(), std::io::Error> {
- // Remove config file if exists
- if let Some(account_cfg_path) = self.account_cfg(id) {
- fs::remove_file(account_cfg_path)?;
- }
-
- // Remove private key file if exists
- if let Some(private_key_path) = self.account_private_key(id)
- && private_key_path.exists()
- {
- fs::remove_file(private_key_path)?;
- }
-
- Ok(())
- }
-
- /// Try to get the account's configuration file to determine if the account exists
- pub fn account_cfg(&self, id: &MemberId) -> Option<PathBuf> {
- let cfg_file = self.account_cfg_path(id);
- if cfg_file.exists() {
- Some(cfg_file)
- } else {
- None
- }
- }
-
- /// Try to get the account's private key file to determine if the account has a private key
- pub fn account_private_key(&self, id: &MemberId) -> Option<PathBuf> {
- let key_file = self.account_private_key_path(id);
- if key_file.exists() {
- Some(key_file)
- } else {
- None
- }
- }
-
- /// Check if account has private key
- pub fn has_private_key(&self, id: &MemberId) -> bool {
- self.account_private_key(id).is_some()
- }
-
- /// Get the account's configuration file path, but do not check if the file exists
- pub fn account_cfg_path(&self, id: &MemberId) -> PathBuf {
- self.local_path
- .join(USER_FILE_MEMBER.replace(SELF_ID, id.to_string().as_str()))
- }
-
- /// Get the account's private key file path, but do not check if the file exists
- pub fn account_private_key_path(&self, id: &MemberId) -> PathBuf {
- self.local_path
- .join(USER_FILE_KEY.replace(SELF_ID, id.to_string().as_str()))
- }
-}
diff --git a/crates/vcs/src/data/vault.rs b/crates/vcs/src/data/vault.rs
deleted file mode 100644
index 5d17a81..0000000
--- a/crates/vcs/src/data/vault.rs
+++ /dev/null
@@ -1,146 +0,0 @@
-use std::{
- env::current_dir,
- fs::{self, create_dir_all},
- path::PathBuf,
-};
-
-use cfg_file::config::ConfigFile;
-
-use crate::{
- constants::{
- SERVER_FILE_README, SERVER_FILE_VAULT, SERVER_PATH_MEMBER_PUB, SERVER_PATH_MEMBERS,
- SERVER_PATH_SHEETS, SERVER_PATH_VF_ROOT, VAULT_HOST_NAME,
- },
- current::{current_vault_path, find_vault_path},
- data::{member::Member, vault::config::VaultConfig},
-};
-
-pub mod config;
-pub mod member;
-pub mod sheets;
-pub mod virtual_file;
-
-pub struct Vault {
- config: VaultConfig,
- vault_path: PathBuf,
-}
-
-impl Vault {
- /// Get vault path
- pub fn vault_path(&self) -> &PathBuf {
- &self.vault_path
- }
-
- /// Initialize vault
- pub fn init(config: VaultConfig, vault_path: impl Into<PathBuf>) -> Option<Self> {
- let vault_path = find_vault_path(vault_path)?;
- Some(Self { config, vault_path })
- }
-
- /// Initialize vault
- pub fn init_current_dir(config: VaultConfig) -> Option<Self> {
- let vault_path = current_vault_path()?;
- Some(Self { config, vault_path })
- }
-
- /// Setup vault
- pub async fn setup_vault(vault_path: impl Into<PathBuf>) -> Result<(), std::io::Error> {
- let vault_path: PathBuf = vault_path.into();
-
- // Ensure directory is empty
- if vault_path.exists() && vault_path.read_dir()?.next().is_some() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::DirectoryNotEmpty,
- "DirectoryNotEmpty",
- ));
- }
-
- // 1. Setup main config
- let config = VaultConfig::default();
- VaultConfig::write_to(&config, vault_path.join(SERVER_FILE_VAULT)).await?;
-
- // 2. Setup sheets directory
- create_dir_all(vault_path.join(SERVER_PATH_SHEETS))?;
-
- // 3. Setup key directory
- create_dir_all(vault_path.join(SERVER_PATH_MEMBER_PUB))?;
-
- // 4. Setup member directory
- create_dir_all(vault_path.join(SERVER_PATH_MEMBERS))?;
-
- // 5. Setup storage directory
- create_dir_all(vault_path.join(SERVER_PATH_VF_ROOT))?;
-
- let Some(vault) = Vault::init(config, &vault_path) else {
- return Err(std::io::Error::other("Failed to initialize vault"));
- };
-
- // 6. Create host member
- vault
- .register_member_to_vault(Member::new(VAULT_HOST_NAME))
- .await?;
-
- // 7. Setup reference sheet
- vault
- .create_sheet(&"ref".to_string(), &VAULT_HOST_NAME.to_string())
- .await?;
-
- // Final, generate README.md
- let readme_content = format!(
- "\
-# JustEnoughVCS Server Setup
-
-This directory contains the server configuration and data for `JustEnoughVCS`.
-
-## User Authentication
-To allow users to connect to this server, place their public keys in the `{}` directory.
-Each public key file should be named `{{member_id}}.pem` (e.g., `juliet.pem`), and contain the user's public key in PEM format.
-
-**ECDSA:**
-```bash
-openssl genpkey -algorithm ed25519 -out your_name_private.pem
-openssl pkey -in your_name_private.pem -pubout -out your_name.pem
-```
-
-**RSA:**
-```bash
-openssl genpkey -algorithm RSA -out your_name_private.pem -pkeyopt rsa_keygen_bits:2048
-openssl pkey -in your_name_private.pem -pubout -out your_name.pem
-```
-
-**DSA:**
-```bash
-openssl genpkey -algorithm DSA -out your_name_private.pem -pkeyopt dsa_paramgen_bits:2048
-openssl pkey -in your_name_private.pem -pubout -out your_name.pem
-```
-
-Place only the `your_name.pem` file in the server's `./key/` directory, renamed to match the user's member ID.
-
-## File Storage
-All version-controlled files (Virtual File) are stored in the `{}` directory.
-
-## License
-This software is distributed under the MIT License. For complete license details, please see the main repository homepage.
-
-## Support
-Repository: `https://github.com/JustEnoughVCS/VersionControl`
-Please report any issues or questions on the GitHub issue tracker.
-
-## Thanks :)
-Thank you for using `JustEnoughVCS!`
- ",
- SERVER_PATH_MEMBER_PUB, SERVER_PATH_VF_ROOT
- )
- .trim()
- .to_string();
- fs::write(vault_path.join(SERVER_FILE_README), readme_content)?;
-
- Ok(())
- }
-
- /// Setup vault in current directory
- pub async fn setup_vault_current_dir() -> Result<(), std::io::Error> {
- Self::setup_vault(current_dir()?).await?;
- Ok(())
- }
-}
diff --git a/crates/vcs/src/data/vault/config.rs b/crates/vcs/src/data/vault/config.rs
deleted file mode 100644
index 1cfc8ef..0000000
--- a/crates/vcs/src/data/vault/config.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use std::net::{IpAddr, Ipv4Addr};
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-
-use crate::constants::{PORT, SERVER_FILE_VAULT};
-use crate::data::member::{Member, MemberId};
-
-#[derive(Serialize, Deserialize, ConfigFile)]
-#[cfg_file(path = SERVER_FILE_VAULT)]
-pub struct VaultConfig {
- /// Vault name, which can be used as the project name and generally serves as a hint
- vault_name: String,
-
- /// Vault admin id, a list of member id representing administrator identities
- vault_admin_list: Vec<MemberId>,
-
- /// Vault server configuration, which will be loaded when connecting to the server
- server_config: VaultServerConfig,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct VaultServerConfig {
- /// Local IP address to bind to when the server starts
- local_bind: IpAddr,
-
- /// TCP port to bind to when the server starts
- port: u16,
-
- /// Whether to enable LAN discovery, allowing members on the same LAN to more easily find the upstream server
- lan_discovery: bool,
-
- /// Authentication strength level
- /// 0: Weakest - Anyone can claim any identity, fastest speed
- /// 1: Basic - Any device can claim any registered identity, slightly faster
- /// 2: Advanced - Uses asymmetric encryption, multiple devices can use key authentication to log in simultaneously, slightly slower
- /// 3: Secure - Uses asymmetric encryption, only one device can use key for authentication at a time, much slower
- /// Default is "Advanced", if using a lower security policy, ensure your server is only accessible by trusted devices
- auth_strength: u8,
-}
-
-impl Default for VaultConfig {
- fn default() -> Self {
- Self {
- vault_name: "JustEnoughVault".to_string(),
- vault_admin_list: Vec::new(),
- server_config: VaultServerConfig {
- local_bind: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
- port: PORT,
- lan_discovery: false,
- auth_strength: 2,
- },
- }
- }
-}
-
-/// Vault Management
-impl VaultConfig {
- // Change name of the vault.
- pub fn change_name(&mut self, name: impl Into<String>) {
- self.vault_name = name.into()
- }
-
- // Add admin
- pub fn add_admin(&mut self, member: &Member) {
- let uuid = member.id();
- if !self.vault_admin_list.contains(&uuid) {
- self.vault_admin_list.push(uuid);
- }
- }
-
- // Remove admin
- pub fn remove_admin(&mut self, member: &Member) {
- let id = member.id();
- self.vault_admin_list.retain(|x| x != &id);
- }
-}
diff --git a/crates/vcs/src/data/vault/member.rs b/crates/vcs/src/data/vault/member.rs
deleted file mode 100644
index aebd92d..0000000
--- a/crates/vcs/src/data/vault/member.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-use std::{
- fs,
- io::{Error, ErrorKind},
- path::PathBuf,
-};
-
-use cfg_file::config::ConfigFile;
-
-use crate::{
- constants::{SERVER_FILE_MEMBER_INFO, SERVER_FILE_MEMBER_PUB, SERVER_PATH_MEMBERS},
- data::{
- member::{Member, MemberId},
- vault::Vault,
- },
-};
-
-const ID_PARAM: &str = "{member_id}";
-
-/// Member Manage
-impl Vault {
- /// Read member from configuration file
- pub async fn member(&self, id: &MemberId) -> Result<Member, std::io::Error> {
- if let Some(cfg_file) = self.member_cfg(id) {
- let member = Member::read_from(cfg_file).await?;
- return Ok(member);
- }
-
- Err(Error::new(ErrorKind::NotFound, "Member not found!"))
- }
-
- /// List all member IDs in the vault
- pub fn member_ids(&self) -> Result<Vec<MemberId>, std::io::Error> {
- let members_path = self.vault_path.join(SERVER_PATH_MEMBERS);
-
- if !members_path.exists() {
- return Ok(Vec::new());
- }
-
- let mut member_ids = Vec::new();
-
- for entry in fs::read_dir(members_path)? {
- let entry = entry?;
- let path = entry.path();
-
- if path.is_file()
- && let Some(file_name) = path.file_stem().and_then(|s| s.to_str())
- && path.extension().and_then(|s| s.to_str()) == Some("toml")
- {
- member_ids.push(file_name.to_string());
- }
- }
-
- Ok(member_ids)
- }
-
- /// Get all members
- /// This method will read and deserialize member information, please pay attention to performance issues
- pub async fn members(&self) -> Result<Vec<Member>, std::io::Error> {
- let mut members = Vec::new();
-
- for member_id in self.member_ids()? {
- if let Ok(member) = self.member(&member_id).await {
- members.push(member);
- }
- }
-
- Ok(members)
- }
-
- /// Update member info
- pub async fn update_member(&self, member: Member) -> Result<(), std::io::Error> {
- // Ensure member exist
- if self.member_cfg(&member.id()).is_some() {
- let member_cfg_path = self.member_cfg_path(&member.id());
- Member::write_to(&member, member_cfg_path).await?;
- return Ok(());
- }
-
- Err(Error::new(ErrorKind::NotFound, "Member not found!"))
- }
-
- /// Register a member to vault
- pub async fn register_member_to_vault(&self, member: Member) -> Result<(), std::io::Error> {
- // Ensure member not exist
- if self.member_cfg(&member.id()).is_some() {
- return Err(Error::new(
- ErrorKind::DirectoryNotEmpty,
- format!("Member `{}` already registered!", member.id()),
- ));
- }
-
- // Wrtie config file to member dir
- let member_cfg_path = self.member_cfg_path(&member.id());
- Member::write_to(&member, member_cfg_path).await?;
-
- Ok(())
- }
-
- /// Remove member from vault
- pub fn remove_member_from_vault(&self, id: &MemberId) -> Result<(), std::io::Error> {
- // Ensure member exist
- if let Some(member_cfg_path) = self.member_cfg(id) {
- fs::remove_file(member_cfg_path)?;
- }
-
- Ok(())
- }
-
- /// Try to get the member's configuration file to determine if the member exists
- pub fn member_cfg(&self, id: &MemberId) -> Option<PathBuf> {
- let cfg_file = self.member_cfg_path(id);
- if cfg_file.exists() {
- Some(cfg_file)
- } else {
- None
- }
- }
-
- /// Try to get the member's public key file to determine if the member has login permission
- pub fn member_key(&self, id: &MemberId) -> Option<PathBuf> {
- let key_file = self.member_key_path(id);
- if key_file.exists() {
- Some(key_file)
- } else {
- None
- }
- }
-
- /// Get the member's configuration file path, but do not check if the file exists
- pub fn member_cfg_path(&self, id: &MemberId) -> PathBuf {
- self.vault_path
- .join(SERVER_FILE_MEMBER_INFO.replace(ID_PARAM, id.to_string().as_str()))
- }
-
- /// Get the member's public key file path, but do not check if the file exists
- pub fn member_key_path(&self, id: &MemberId) -> PathBuf {
- self.vault_path
- .join(SERVER_FILE_MEMBER_PUB.replace(ID_PARAM, id.to_string().as_str()))
- }
-}
diff --git a/crates/vcs/src/data/vault/sheets.rs b/crates/vcs/src/data/vault/sheets.rs
deleted file mode 100644
index 0bba4f5..0000000
--- a/crates/vcs/src/data/vault/sheets.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use std::{collections::HashMap, io::Error};
-
-use cfg_file::config::ConfigFile;
-use string_proc::snake_case;
-use tokio::fs;
-
-use crate::{
- constants::SERVER_PATH_SHEETS,
- data::{
- member::MemberId,
- sheet::{Sheet, SheetData, SheetName},
- vault::Vault,
- },
-};
-
-/// Vault Sheets Management
-impl Vault {
- /// Load all sheets in the vault
- ///
- /// It is generally not recommended to call this function frequently.
- /// Although a vault typically won't contain too many sheets,
- /// if individual sheet contents are large, this operation may cause
- /// significant performance bottlenecks.
- pub async fn sheets<'a>(&'a self) -> Result<Vec<Sheet<'a>>, std::io::Error> {
- let sheet_names = self.sheet_names()?;
- let mut sheets = Vec::new();
-
- for sheet_name in sheet_names {
- let sheet = self.sheet(&sheet_name).await?;
- sheets.push(sheet);
- }
-
- Ok(sheets)
- }
-
- /// Search for all sheet names in the vault
- ///
- /// The complexity of this operation is proportional to the number of sheets,
- /// but generally there won't be too many sheets in a Vault
- pub fn sheet_names(&self) -> Result<Vec<SheetName>, std::io::Error> {
- // Get the sheets directory path
- let sheets_dir = self.vault_path.join(SERVER_PATH_SHEETS);
-
- // If the directory doesn't exist, return an empty list
- if !sheets_dir.exists() {
- return Ok(vec![]);
- }
-
- let mut sheet_names = Vec::new();
-
- // Iterate through all files in the sheets directory
- for entry in std::fs::read_dir(sheets_dir)? {
- let entry = entry?;
- let path = entry.path();
-
- // Check if it's a YAML file
- if path.is_file()
- && path.extension().is_some_and(|ext| ext == "yaml")
- && let Some(file_stem) = path.file_stem().and_then(|s| s.to_str())
- {
- // Create a new SheetName and add it to the result list
- sheet_names.push(file_stem.to_string());
- }
- }
-
- Ok(sheet_names)
- }
-
- /// Read a sheet from its name
- ///
- /// If the sheet information is successfully found in the vault,
- /// it will be deserialized and read as a sheet.
- /// This is the only correct way to obtain a sheet instance.
- pub async fn sheet<'a>(&'a self, sheet_name: &SheetName) -> Result<Sheet<'a>, std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- // Get the path to the sheet file
- let sheet_path = Sheet::sheet_path_with_name(self, &sheet_name);
-
- // Ensure the sheet file exists
- if !sheet_path.exists() {
- // If the sheet does not exist, try to restore it from the trash
- if self.restore_sheet(&sheet_name).await.is_err() {
- // If restoration fails, return an error
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Sheet `{}` not found!", sheet_name),
- ));
- }
- }
-
- // Read the sheet data from the file
- let data = SheetData::read_from(sheet_path).await?;
-
- Ok(Sheet {
- name: sheet_name.clone(),
- data,
- vault_reference: self,
- })
- }
-
- /// Create a sheet locally and return the sheet instance
- ///
- /// This method creates a new sheet in the vault with the given name and holder.
- /// It will verify that the member exists and that the sheet doesn't already exist
- /// before creating the sheet file with default empty data.
- pub async fn create_sheet<'a>(
- &'a self,
- sheet_name: &SheetName,
- holder: &MemberId,
- ) -> Result<Sheet<'a>, std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- // Ensure member exists
- if !self.member_cfg_path(holder).exists() {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Member `{}` not found!", &holder),
- ));
- }
-
- // Ensure sheet does not already exist
- let sheet_file_path = Sheet::sheet_path_with_name(self, &sheet_name);
- if sheet_file_path.exists() {
- return Err(Error::new(
- std::io::ErrorKind::AlreadyExists,
- format!("Sheet `{}` already exists!", &sheet_name),
- ));
- }
-
- // Create the sheet file
- let sheet_data = SheetData {
- holder: holder.clone(),
- inputs: Vec::new(),
- mapping: HashMap::new(),
- };
- SheetData::write_to(&sheet_data, sheet_file_path).await?;
-
- Ok(Sheet {
- name: sheet_name,
- data: sheet_data,
- vault_reference: self,
- })
- }
-
- /// Delete the sheet file from local disk by name
- ///
- /// This method will remove the sheet file with the given name from the vault.
- /// It will verify that the sheet exists before attempting to delete it.
- /// If the sheet is successfully deleted, it will return Ok(()).
- ///
- /// Warning: This operation is dangerous. Deleting a sheet will cause local workspaces
- /// using this sheet to become invalid. Please ensure the sheet is not currently in use
- /// and will not be used in the future.
- ///
- /// For a safer deletion method, consider using `delete_sheet_safety`.
- ///
- /// Note: This function is intended for server-side use only and should not be
- /// arbitrarily called by other members to prevent unauthorized data deletion.
- pub async fn delete_sheet(&self, sheet_name: &SheetName) -> Result<(), std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- // Ensure sheet exists
- let sheet_file_path = Sheet::sheet_path_with_name(self, &sheet_name);
- if !sheet_file_path.exists() {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Sheet `{}` not found!", &sheet_name),
- ));
- }
-
- // Delete the sheet file
- fs::remove_file(sheet_file_path).await?;
-
- Ok(())
- }
-
- /// Safely delete the sheet
- ///
- /// The sheet will be moved to the trash directory, ensuring it does not appear in the
- /// results of `sheets` and `sheet_names` methods.
- /// However, if the sheet's holder attempts to access the sheet through the `sheet` method,
- /// the system will automatically restore it from the trash directory.
- /// This means: the sheet will only permanently remain in the trash directory,
- /// waiting for manual cleanup by an administrator, when it is truly no longer in use.
- ///
- /// This is a safer deletion method because it provides the possibility of recovery,
- /// avoiding irreversible data loss caused by accidental deletion.
- ///
- /// Note: This function is intended for server-side use only and should not be
- /// arbitrarily called by other members to prevent unauthorized data deletion.
- pub async fn delete_sheet_safely(&self, sheet_name: &SheetName) -> Result<(), std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- // Ensure the sheet exists
- let sheet_file_path = Sheet::sheet_path_with_name(self, &sheet_name);
- if !sheet_file_path.exists() {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Sheet `{}` not found!", &sheet_name),
- ));
- }
-
- // Create the trash directory
- let trash_dir = self.vault_path.join(".trash");
- if !trash_dir.exists() {
- fs::create_dir_all(&trash_dir).await?;
- }
-
- // Generate a unique filename in the trash
- let timestamp = std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .unwrap()
- .as_millis();
- let trash_file_name = format!("{}_{}.yaml", sheet_name, timestamp);
- let trash_path = trash_dir.join(trash_file_name);
-
- // Move the sheet file to the trash
- fs::rename(&sheet_file_path, &trash_path).await?;
-
- Ok(())
- }
-
- /// Restore the sheet from the trash
- ///
- /// Restore the specified sheet from the trash to its original location, making it accessible normally.
- pub async fn restore_sheet(&self, sheet_name: &SheetName) -> Result<(), std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- // Search for matching files in the trash
- let trash_dir = self.vault_path.join(".trash");
- if !trash_dir.exists() {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Trash directory does not exist!".to_string(),
- ));
- }
-
- let mut found_path = None;
- for entry in std::fs::read_dir(&trash_dir)? {
- let entry = entry?;
- let path = entry.path();
-
- if path.is_file()
- && let Some(file_name) = path.file_stem().and_then(|s| s.to_str())
- {
- // Check if the filename starts with the sheet name
- if file_name.starts_with(&sheet_name) {
- found_path = Some(path);
- break;
- }
- }
- }
-
- let trash_path = found_path.ok_or_else(|| {
- Error::new(
- std::io::ErrorKind::NotFound,
- format!("Sheet `{}` not found in trash!", &sheet_name),
- )
- })?;
-
- // Restore the sheet to its original location
- let original_path = Sheet::sheet_path_with_name(self, &sheet_name);
- fs::rename(&trash_path, &original_path).await?;
-
- Ok(())
- }
-}
diff --git a/crates/vcs/src/data/vault/virtual_file.rs b/crates/vcs/src/data/vault/virtual_file.rs
deleted file mode 100644
index fe83594..0000000
--- a/crates/vcs/src/data/vault/virtual_file.rs
+++ /dev/null
@@ -1,473 +0,0 @@
-use std::{
- collections::HashMap,
- io::{Error, ErrorKind},
- path::PathBuf,
-};
-
-use cfg_file::{ConfigFile, config::ConfigFile};
-use serde::{Deserialize, Serialize};
-use string_proc::snake_case;
-use tcp_connection::instance::ConnectionInstance;
-use tokio::fs;
-use uuid::Uuid;
-
-use crate::{
- constants::{
- SERVER_FILE_VF_META, SERVER_FILE_VF_VERSION_INSTANCE, SERVER_PATH_VF_ROOT,
- SERVER_PATH_VF_STORAGE, SERVER_PATH_VF_TEMP,
- },
- data::{member::MemberId, vault::Vault},
-};
-
-pub type VirtualFileId = String;
-pub type VirtualFileVersion = String;
-
-const VF_PREFIX: &str = "vf_";
-const ID_PARAM: &str = "{vf_id}";
-const ID_INDEX: &str = "{vf_index}";
-const VERSION_PARAM: &str = "{vf_version}";
-const TEMP_NAME: &str = "{temp_name}";
-
-pub struct VirtualFile<'a> {
- /// Unique identifier for the virtual file
- id: VirtualFileId,
-
- /// Reference of Vault
- current_vault: &'a Vault,
-}
-
-#[derive(Default, Clone, Serialize, Deserialize, ConfigFile)]
-pub struct VirtualFileMeta {
- /// Current version of the virtual file
- current_version: VirtualFileVersion,
-
- /// The member who holds the edit right of the file
- hold_member: MemberId,
-
- /// Description of each version
- version_description: HashMap<VirtualFileVersion, VirtualFileVersionDescription>,
-
- /// Histories
- histories: Vec<VirtualFileVersion>,
-}
-
-#[derive(Default, Clone, Serialize, Deserialize)]
-pub struct VirtualFileVersionDescription {
- /// The member who created this version
- pub creator: MemberId,
-
- /// The description of this version
- pub description: String,
-}
-
-impl VirtualFileVersionDescription {
- /// Create a new version description
- pub fn new(creator: MemberId, description: String) -> Self {
- Self {
- creator,
- description,
- }
- }
-}
-
-/// Virtual File Operations
-impl Vault {
- /// Generate a temporary path for receiving
- pub fn virtual_file_temp_path(&self) -> PathBuf {
- let random_receive_name = format!("{}", uuid::Uuid::new_v4());
- self.vault_path
- .join(SERVER_PATH_VF_TEMP.replace(TEMP_NAME, &random_receive_name))
- }
-
- /// Get the directory where virtual files are stored
- pub fn virtual_file_storage_dir(&self) -> PathBuf {
- self.vault_path().join(SERVER_PATH_VF_ROOT)
- }
-
- /// Get the directory where a specific virtual file is stored
- pub fn virtual_file_dir(&self, id: &VirtualFileId) -> Result<PathBuf, std::io::Error> {
- Ok(self.vault_path().join(
- SERVER_PATH_VF_STORAGE
- .replace(ID_PARAM, &id.to_string())
- .replace(ID_INDEX, &Self::vf_index(id)?),
- ))
- }
-
- // Generate index path of virtual file
- fn vf_index(id: &VirtualFileId) -> Result<String, std::io::Error> {
- // Remove VF_PREFIX if present
- let id_str = if let Some(stripped) = id.strip_prefix(VF_PREFIX) {
- stripped
- } else {
- id
- };
-
- // Extract the first part before the first hyphen
- let first_part = id_str.split('-').next().ok_or_else(|| {
- std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Invalid virtual file ID format: no hyphen found",
- )
- })?;
-
- // Ensure the first part has exactly 8 characters
- if first_part.len() != 8 {
- return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Invalid virtual file ID format: first part must be 8 characters",
- ))?;
- }
-
- // Split into 2-character chunks and join with path separator
- let mut path = String::new();
- for i in (0..first_part.len()).step_by(2) {
- if i > 0 {
- path.push('/');
- }
- path.push_str(&first_part[i..i + 2]);
- }
-
- Ok(path)
- }
-
- /// Get the directory where a specific virtual file's metadata is stored
- pub fn virtual_file_real_path(
- &self,
- id: &VirtualFileId,
- version: &VirtualFileVersion,
- ) -> PathBuf {
- self.vault_path().join(
- SERVER_FILE_VF_VERSION_INSTANCE
- .replace(ID_PARAM, &id.to_string())
- .replace(ID_INDEX, &Self::vf_index(id).unwrap_or_default())
- .replace(VERSION_PARAM, &version.to_string()),
- )
- }
-
- /// Get the directory where a specific virtual file's metadata is stored
- pub fn virtual_file_meta_path(&self, id: &VirtualFileId) -> PathBuf {
- self.vault_path().join(
- SERVER_FILE_VF_META
- .replace(ID_PARAM, &id.to_string())
- .replace(ID_INDEX, &Self::vf_index(id).unwrap_or_default()),
- )
- }
-
- /// Get the virtual file with the given ID
- pub fn virtual_file(&self, id: &VirtualFileId) -> Result<VirtualFile<'_>, std::io::Error> {
- let dir = self.virtual_file_dir(id);
- if dir?.exists() {
- Ok(VirtualFile {
- id: id.clone(),
- current_vault: self,
- })
- } else {
- Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "Cannot found virtual file!",
- ))
- }
- }
-
- /// Get the meta data of the virtual file with the given ID
- pub async fn virtual_file_meta(
- &self,
- id: &VirtualFileId,
- ) -> Result<VirtualFileMeta, std::io::Error> {
- let dir = self.virtual_file_meta_path(id);
- let metadata = VirtualFileMeta::read_from(dir).await?;
- Ok(metadata)
- }
-
- /// Write the meta data of the virtual file with the given ID
- pub async fn write_virtual_file_meta(
- &self,
- id: &VirtualFileId,
- meta: &VirtualFileMeta,
- ) -> Result<(), std::io::Error> {
- let dir = self.virtual_file_meta_path(id);
- VirtualFileMeta::write_to(meta, dir).await?;
- Ok(())
- }
-
- /// Create a virtual file from a connection instance
- ///
- /// It's the only way to create virtual files!
- ///
- /// When the target machine executes `write_file`, use this function instead of `read_file`,
- /// and provide the member ID of the transmitting member.
- ///
- /// The system will automatically receive the file and
- /// create the virtual file.
- pub async fn create_virtual_file_from_connection(
- &self,
- instance: &mut ConnectionInstance,
- member_id: &MemberId,
- ) -> Result<VirtualFileId, std::io::Error> {
- const FIRST_VERSION: &str = "0";
- let receive_path = self.virtual_file_temp_path();
- let new_id = format!("{}{}", VF_PREFIX, Uuid::new_v4());
- let move_path = self.virtual_file_real_path(&new_id, &FIRST_VERSION.to_string());
-
- match instance.read_file(receive_path.clone()).await {
- Ok(_) => {
- // Read successful, create virtual file
- // Create default version description
- let mut version_description =
- HashMap::<VirtualFileVersion, VirtualFileVersionDescription>::new();
- version_description.insert(
- FIRST_VERSION.to_string(),
- VirtualFileVersionDescription {
- creator: member_id.clone(),
- description: "Track".to_string(),
- },
- );
- // Create metadata
- let mut meta = VirtualFileMeta {
- current_version: FIRST_VERSION.to_string(),
- hold_member: member_id.clone(), // The holder of the newly created virtual file is the creator by default
- version_description,
- histories: Vec::default(),
- };
-
- // Add first version
- meta.histories.push(FIRST_VERSION.to_string());
-
- // Write metadata to file
- VirtualFileMeta::write_to(&meta, self.virtual_file_meta_path(&new_id)).await?;
-
- // Move temp file to virtual file directory
- if let Some(parent) = move_path.parent()
- && !parent.exists()
- {
- fs::create_dir_all(parent).await?;
- }
- fs::rename(receive_path, move_path).await?;
-
- //
-
- Ok(new_id)
- }
- Err(e) => {
- // Read failed, remove temp file.
- if receive_path.exists() {
- fs::remove_file(receive_path).await?;
- }
-
- Err(Error::other(e))
- }
- }
- }
-
- /// Update a virtual file from a connection instance
- ///
- /// It's the only way to update virtual files!
- /// When the target machine executes `write_file`, use this function instead of `read_file`,
- /// and provide the member ID of the transmitting member.
- ///
- /// The system will automatically receive the file and
- /// update the virtual file.
- ///
- /// Note: The specified member must hold the edit right of the file,
- /// otherwise the file reception will not be allowed.
- ///
- /// Make sure to obtain the edit right of the file before calling this function.
- pub async fn update_virtual_file_from_connection(
- &self,
- instance: &mut ConnectionInstance,
- member: &MemberId,
- virtual_file_id: &VirtualFileId,
- new_version: &VirtualFileVersion,
- description: VirtualFileVersionDescription,
- ) -> Result<(), std::io::Error> {
- let new_version = snake_case!(new_version.clone());
- let mut meta = self.virtual_file_meta(virtual_file_id).await?;
-
- // Check if the member has edit right
- self.check_virtual_file_edit_right(member, virtual_file_id)
- .await?;
-
- // Check if the new version already exists
- if meta.version_description.contains_key(&new_version) {
- return Err(Error::new(
- ErrorKind::AlreadyExists,
- format!(
- "Version `{}` already exists for virtual file `{}`",
- new_version, virtual_file_id
- ),
- ));
- }
-
- // Verify success
- let receive_path = self.virtual_file_temp_path();
- let move_path = self.virtual_file_real_path(virtual_file_id, &new_version);
-
- match instance.read_file(receive_path.clone()).await {
- Ok(_) => {
- // Read success, move temp file to real path.
- fs::rename(receive_path, move_path).await?;
-
- // Update metadata
- meta.current_version = new_version.clone();
- meta.version_description
- .insert(new_version.clone(), description);
- meta.histories.push(new_version);
- VirtualFileMeta::write_to(&meta, self.virtual_file_meta_path(virtual_file_id))
- .await?;
-
- Ok(())
- }
- Err(e) => {
- // Read failed, remove temp file.
- if receive_path.exists() {
- fs::remove_file(receive_path).await?;
- }
-
- Err(Error::other(e))
- }
- }
- }
-
- /// Update virtual file from existing version
- ///
- /// This operation creates a new version based on the specified old version file instance.
- /// The new version will retain the same version name as the old version, but use a different version number.
- /// After the update, this version will be considered newer than the original version when comparing versions.
- pub async fn update_virtual_file_from_exist_version(
- &self,
- member: &MemberId,
- virtual_file_id: &VirtualFileId,
- old_version: &VirtualFileVersion,
- ) -> Result<(), std::io::Error> {
- let old_version = snake_case!(old_version.clone());
- let mut meta = self.virtual_file_meta(virtual_file_id).await?;
-
- // Check if the member has edit right
- self.check_virtual_file_edit_right(member, virtual_file_id)
- .await?;
-
- // Ensure virtual file exist
- let Ok(_) = self.virtual_file(virtual_file_id) else {
- return Err(Error::new(
- ErrorKind::NotFound,
- format!("Virtual file `{}` not found!", virtual_file_id),
- ));
- };
-
- // Ensure version exist
- if !meta.version_exists(&old_version) {
- return Err(Error::new(
- ErrorKind::NotFound,
- format!("Version `{}` not found!", old_version),
- ));
- }
-
- // Ok, Create new version
- meta.current_version = old_version.clone();
- meta.histories.push(old_version);
- VirtualFileMeta::write_to(&meta, self.virtual_file_meta_path(virtual_file_id)).await?;
-
- Ok(())
- }
-
- /// Grant a member the edit right for a virtual file
- /// This operation takes effect immediately upon success
- pub async fn grant_virtual_file_edit_right(
- &self,
- member_id: &MemberId,
- virtual_file_id: &VirtualFileId,
- ) -> Result<(), std::io::Error> {
- let mut meta = self.virtual_file_meta(virtual_file_id).await?;
- meta.hold_member = member_id.clone();
- self.write_virtual_file_meta(virtual_file_id, &meta).await
- }
-
- /// Check if a member has the edit right for a virtual file
- pub async fn has_virtual_file_edit_right(
- &self,
- member_id: &MemberId,
- virtual_file_id: &VirtualFileId,
- ) -> Result<bool, std::io::Error> {
- let meta = self.virtual_file_meta(virtual_file_id).await?;
- Ok(meta.hold_member.eq(member_id))
- }
-
- /// Check if a member has the edit right for a virtual file and return Result
- /// Returns Ok(()) if the member has edit right, otherwise returns PermissionDenied error
- pub async fn check_virtual_file_edit_right(
- &self,
- member_id: &MemberId,
- virtual_file_id: &VirtualFileId,
- ) -> Result<(), std::io::Error> {
- if !self
- .has_virtual_file_edit_right(member_id, virtual_file_id)
- .await?
- {
- return Err(Error::new(
- ErrorKind::PermissionDenied,
- format!(
- "Member `{}` not allowed to update virtual file `{}`",
- member_id, virtual_file_id
- ),
- ));
- }
- Ok(())
- }
-
- /// Revoke the edit right for a virtual file from the current holder
- /// This operation takes effect immediately upon success
- pub async fn revoke_virtual_file_edit_right(
- &self,
- virtual_file_id: &VirtualFileId,
- ) -> Result<(), std::io::Error> {
- let mut meta = self.virtual_file_meta(virtual_file_id).await?;
- meta.hold_member = String::default();
- self.write_virtual_file_meta(virtual_file_id, &meta).await
- }
-}
-
-impl<'a> VirtualFile<'a> {
- /// Get id of VirtualFile
- pub fn id(&self) -> VirtualFileId {
- self.id.clone()
- }
-
- /// Read metadata of VirtualFile
- pub async fn read_meta(&self) -> Result<VirtualFileMeta, std::io::Error> {
- self.current_vault.virtual_file_meta(&self.id).await
- }
-}
-
-impl VirtualFileMeta {
- /// Get all versions of the virtual file
- pub fn versions(&self) -> &Vec<VirtualFileVersion> {
- &self.histories
- }
-
- /// Get the total number of versions for this virtual file
- pub fn version_len(&self) -> i32 {
- self.histories.len() as i32
- }
-
- /// Check if a specific version exists
- /// Returns true if the version exists, false otherwise
- pub fn version_exists(&self, version: &VirtualFileVersion) -> bool {
- self.versions().iter().any(|v| v == version)
- }
-
- /// Get the version number (index) for a given version name
- /// Returns None if the version doesn't exist
- pub fn version_num(&self, version: &VirtualFileVersion) -> Option<i32> {
- self.histories
- .iter()
- .rev()
- .position(|v| v == version)
- .map(|pos| (self.histories.len() - 1 - pos) as i32)
- }
-
- /// Get the version name for a given version number (index)
- /// Returns None if the version number is out of range
- pub fn version_name(&self, version_num: i32) -> Option<VirtualFileVersion> {
- self.histories.get(version_num as usize).cloned()
- }
-}
diff --git a/crates/vcs/src/lib.rs b/crates/vcs/src/lib.rs
deleted file mode 100644
index 1b41391..0000000
--- a/crates/vcs/src/lib.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub mod constants;
-pub mod current;
-
-#[allow(dead_code)]
-pub mod data;
diff --git a/crates/vcs/todo.txt b/crates/vcs/todo.txt
deleted file mode 100644
index 65c94ef..0000000
--- a/crates/vcs/todo.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-本地文件操作
-设置上游服务器(仅设置,不会连接和修改染色标识)
-验证连接、权限,并为当前工作区染色(若已染色,则无法连接不同标识的服务器)
-进入表 (否则无法做任何操作)
-退出表 (文件将会从当前目录移出,等待下次进入时还原)
-去色 - 断开与上游服务器的关联
-跟踪本地文件的移动、重命名,立刻同步至表
-扫描本地文件结构,标记变化
-通过本地暂存的表索引搜索文件
-查询本地某个文件的状态
-查询当前目录的状态
-查询工作区状态
-将本地所有文件更新到最新状态
-提交所有产生变化的自身所属文件
-
-
-表操作(必须指定成员和表)
-表查看 - 指定表并查看结构
-从参照表拉入文件项目
-将文件项目(或多个)导出到指定表
-查看导入请求
-在某个本地地址同意并导入文件
-拒绝某个、某些或所有导入请求
-删除表中的映射,但要确保实际文件已被移除 (忽略文件)
-放弃表,所有者消失,下一个切换至表的人获得(放弃需要确保表中没有任何文件是所有者持有的)(替代目前的安全删除)
-
-
-虚拟文件操作
-跟踪本地某些文件,并将其创建为虚拟文件,然后添加到自己的表
-根据本地文件的目录查找虚拟文件,并为自己获得所有权(需要确保版本和上游同步才可)
-根据本地文件的目录查找虚拟文件,并放弃所有权(需要确保和上游同步才可)
-根据本地文件的目录查找虚拟文件,并定向到指定的存在的老版本
-
-
-?为什么虚拟文件不能删除:虚拟文件的唯一删除方式就是,没有人再用他
-?为什么没有删除表:同理,表权限可以转移,但是删除只能等待定期清除无主人的表
diff --git a/crates/vcs/vcs_test/Cargo.toml b/crates/vcs/vcs_test/Cargo.toml
deleted file mode 100644
index 1cc43ac..0000000
--- a/crates/vcs/vcs_test/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "vcs_test"
-edition = "2024"
-version.workspace = true
-
-[dependencies]
-tcp_connection = { path = "../../utils/tcp_connection" }
-tcp_connection_test = { path = "../../utils/tcp_connection/tcp_connection_test" }
-cfg_file = { path = "../../utils/cfg_file", features = ["default"] }
-vcs = { path = "../../vcs" }
-
-# Async & Networking
-tokio = { version = "1.46.1", features = ["full"] }
diff --git a/crates/vcs/vcs_test/lib.rs b/crates/vcs/vcs_test/lib.rs
deleted file mode 100644
index 5b65941..0000000
--- a/crates/vcs/vcs_test/lib.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use vcs_service::{action::Action, action_pool::ActionPool};
-
-use crate::actions::test::FindMemberInServer;
-
-pub mod constants;
-pub mod current;
-
-#[allow(dead_code)]
-pub mod data;
-
-pub mod actions;
diff --git a/crates/vcs/vcs_test/src/lib.rs b/crates/vcs/vcs_test/src/lib.rs
deleted file mode 100644
index 8ad03e1..0000000
--- a/crates/vcs/vcs_test/src/lib.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use std::{env::current_dir, path::PathBuf};
-
-use tokio::fs;
-
-#[cfg(test)]
-pub mod test_vault_setup_and_member_register;
-
-#[cfg(test)]
-pub mod test_virtual_file_creation_and_update;
-
-#[cfg(test)]
-pub mod test_local_workspace_setup_and_account_management;
-
-#[cfg(test)]
-pub mod test_sheet_creation_management_and_persistence;
-
-pub async fn get_test_dir(area: &str) -> Result<PathBuf, std::io::Error> {
- let dir = current_dir()?.join(".temp").join("test").join(area);
- if !dir.exists() {
- std::fs::create_dir_all(&dir)?;
- } else {
- // Regenerate existing directory
- fs::remove_dir_all(&dir).await?;
- fs::create_dir_all(&dir).await?;
- }
- Ok(dir)
-}
diff --git a/crates/vcs/vcs_test/src/test_local_workspace_setup_and_account_management.rs b/crates/vcs/vcs_test/src/test_local_workspace_setup_and_account_management.rs
deleted file mode 100644
index df766f7..0000000
--- a/crates/vcs/vcs_test/src/test_local_workspace_setup_and_account_management.rs
+++ /dev/null
@@ -1,248 +0,0 @@
-use std::io::Error;
-
-use cfg_file::config::ConfigFile;
-use vcs::{
- constants::{CLIENT_FILE_README, CLIENT_FILE_WORKSPACE, USER_FILE_KEY, USER_FILE_MEMBER},
- data::{
- local::{LocalWorkspace, config::LocalConfig},
- member::Member,
- user::UserDirectory,
- },
-};
-
-use crate::get_test_dir;
-
-#[tokio::test]
-async fn test_local_workspace_setup_and_account_management() -> Result<(), std::io::Error> {
- let dir = get_test_dir("local_workspace_account_management").await?;
-
- // Setup local workspace
- LocalWorkspace::setup_local_workspace(dir.clone()).await?;
-
- // Check if the following files are created in `dir`:
- // Files: CLIENT_FILE_WORKSPACE, CLIENT_FILE_README
- assert!(dir.join(CLIENT_FILE_WORKSPACE).exists());
- assert!(dir.join(CLIENT_FILE_README).exists());
-
- // Get local workspace
- let config = LocalConfig::read_from(dir.join(CLIENT_FILE_WORKSPACE)).await?;
- let Some(_local_workspace) = LocalWorkspace::init(config, &dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Local workspace not found!",
- ));
- };
-
- // Create user directory from workspace path
- let Some(user_directory) = UserDirectory::from_path(&dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "User directory not found!",
- ));
- };
-
- // Test account registration
- let member_id = "test_account";
- let member = Member::new(member_id);
-
- // Register account
- user_directory.register_account(member.clone()).await?;
-
- // Check if the account config file exists
- assert!(
- dir.join(USER_FILE_MEMBER.replace("{self_id}", member_id))
- .exists()
- );
-
- // Test account retrieval
- let retrieved_member = user_directory.account(&member_id.to_string()).await?;
- assert_eq!(retrieved_member.id(), member.id());
-
- // Test account IDs listing
- let account_ids = user_directory.account_ids()?;
- assert!(account_ids.contains(&member_id.to_string()));
-
- // Test accounts listing
- let accounts = user_directory.accounts().await?;
- assert_eq!(accounts.len(), 1);
- assert_eq!(accounts[0].id(), member.id());
-
- // Test account existence check
- assert!(user_directory.account_cfg(&member_id.to_string()).is_some());
-
- // Test private key check (should be false initially)
- assert!(!user_directory.has_private_key(&member_id.to_string()));
-
- // Test account update
- let mut updated_member = member.clone();
- updated_member.set_metadata("email", "test@example.com");
- user_directory
- .update_account(updated_member.clone())
- .await?;
-
- // Verify update
- let updated_retrieved = user_directory.account(&member_id.to_string()).await?;
- assert_eq!(
- updated_retrieved.metadata("email"),
- Some(&"test@example.com".to_string())
- );
-
- // Test account removal
- user_directory.remove_account(&member_id.to_string())?;
-
- // Check if the account config file no longer exists
- assert!(
- !dir.join(USER_FILE_MEMBER.replace("{self_id}", member_id))
- .exists()
- );
-
- // Check if account is no longer in the list
- let account_ids_after_removal = user_directory.account_ids()?;
- assert!(!account_ids_after_removal.contains(&member_id.to_string()));
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_account_private_key_management() -> Result<(), std::io::Error> {
- let dir = get_test_dir("account_private_key_management").await?;
-
- // Create user directory
- let Some(user_directory) = UserDirectory::from_path(&dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "User directory not found!",
- ));
- };
-
- // Register account
- let member_id = "test_account_with_key";
- let member = Member::new(member_id);
- user_directory.register_account(member).await?;
-
- // Create a dummy private key file for testing
- let private_key_path = dir.join(USER_FILE_KEY.replace("{self_id}", member_id));
- std::fs::create_dir_all(private_key_path.parent().unwrap())?;
- std::fs::write(&private_key_path, "dummy_private_key_content")?;
-
- // Test private key existence check
- assert!(user_directory.has_private_key(&member_id.to_string()));
-
- // Test private key path retrieval
- assert!(
- user_directory
- .account_private_key(&member_id.to_string())
- .is_some()
- );
-
- // Remove account (should also remove private key)
- user_directory.remove_account(&member_id.to_string())?;
-
- // Check if private key file is also removed
- assert!(!private_key_path.exists());
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_multiple_account_management() -> Result<(), std::io::Error> {
- let dir = get_test_dir("multiple_account_management").await?;
-
- // Create user directory
- let Some(user_directory) = UserDirectory::from_path(&dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "User directory not found!",
- ));
- };
-
- // Register multiple accounts
- let account_names = vec!["alice", "bob", "charlie"];
-
- for name in &account_names {
- user_directory.register_account(Member::new(*name)).await?;
- }
-
- // Test account IDs listing
- let account_ids = user_directory.account_ids()?;
- assert_eq!(account_ids.len(), 3);
-
- for name in &account_names {
- assert!(account_ids.contains(&name.to_string()));
- }
-
- // Test accounts listing
- let accounts = user_directory.accounts().await?;
- assert_eq!(accounts.len(), 3);
-
- // Remove one account
- user_directory.remove_account(&"bob".to_string())?;
-
- // Verify removal
- let account_ids_after_removal = user_directory.account_ids()?;
- assert_eq!(account_ids_after_removal.len(), 2);
- assert!(!account_ids_after_removal.contains(&"bob".to_string()));
- assert!(account_ids_after_removal.contains(&"alice".to_string()));
- assert!(account_ids_after_removal.contains(&"charlie".to_string()));
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_account_registration_duplicate_prevention() -> Result<(), std::io::Error> {
- let dir = get_test_dir("account_duplicate_prevention").await?;
-
- // Create user directory
- let Some(user_directory) = UserDirectory::from_path(&dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "User directory not found!",
- ));
- };
-
- // Register account
- let member_id = "duplicate_test";
- user_directory
- .register_account(Member::new(member_id))
- .await?;
-
- // Try to register same account again - should fail
- let result = user_directory
- .register_account(Member::new(member_id))
- .await;
- assert!(result.is_err());
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_nonexistent_account_operations() -> Result<(), std::io::Error> {
- let dir = get_test_dir("nonexistent_account_operations").await?;
-
- // Create user directory
- let Some(user_directory) = UserDirectory::from_path(&dir) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "User directory not found!",
- ));
- };
-
- // Try to read non-existent account - should fail
- let result = user_directory.account(&"nonexistent".to_string()).await;
- assert!(result.is_err());
-
- // Try to update non-existent account - should fail
- let result = user_directory
- .update_account(Member::new("nonexistent"))
- .await;
- assert!(result.is_err());
-
- // Try to remove non-existent account - should succeed (idempotent)
- let result = user_directory.remove_account(&"nonexistent".to_string());
- assert!(result.is_ok());
-
- // Check private key for non-existent account - should be false
- assert!(!user_directory.has_private_key(&"nonexistent".to_string()));
-
- Ok(())
-}
diff --git a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
deleted file mode 100644
index 3b038a0..0000000
--- a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
+++ /dev/null
@@ -1,307 +0,0 @@
-use std::io::Error;
-
-use cfg_file::config::ConfigFile;
-use vcs::{
- constants::{SERVER_FILE_SHEET, SERVER_FILE_VAULT},
- data::{
- member::{Member, MemberId},
- sheet::{InputRelativePathBuf, SheetName},
- vault::{Vault, config::VaultConfig, virtual_file::VirtualFileId},
- },
-};
-
-use crate::get_test_dir;
-
-#[tokio::test]
-async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io::Error> {
- let dir = get_test_dir("sheet_management").await?;
-
- // Setup vault
- Vault::setup_vault(dir.clone()).await?;
-
- // Get vault
- let config = VaultConfig::read_from(dir.join(SERVER_FILE_VAULT)).await?;
- let Some(vault) = Vault::init(config, &dir) else {
- return Err(Error::new(std::io::ErrorKind::NotFound, "Vault not found!"));
- };
-
- // Add a member to use as sheet holder
- let member_id: MemberId = "test_member".to_string();
- vault
- .register_member_to_vault(Member::new(&member_id))
- .await?;
-
- // Test 1: Create a new sheet
- let sheet_name: SheetName = "test_sheet".to_string();
- let sheet = vault.create_sheet(&sheet_name, &member_id).await?;
-
- // Verify sheet properties
- assert_eq!(sheet.holder(), &member_id);
- assert_eq!(sheet.holder(), &member_id);
- assert!(sheet.inputs().is_empty());
- assert!(sheet.mapping().is_empty());
-
- // Verify sheet file was created
- const SHEET_NAME_PARAM: &str = "{sheet-name}";
- let sheet_path = dir.join(SERVER_FILE_SHEET.replace(SHEET_NAME_PARAM, &sheet_name));
- assert!(sheet_path.exists());
-
- // Test 2: Add input packages to the sheet
- let input_name = "source_files".to_string();
-
- // First add mapping entries that will be used to generate the input package
- let mut sheet = vault.sheet(&sheet_name).await?;
-
- // Add mapping entries for the files
- let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs");
- let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs");
- let main_rs_id = VirtualFileId::new();
- let lib_rs_id = VirtualFileId::new();
-
- sheet
- .add_mapping(main_rs_path.clone(), main_rs_id.clone())
- .await?;
- sheet
- .add_mapping(lib_rs_path.clone(), lib_rs_id.clone())
- .await?;
-
- // Use output_mappings to generate the InputPackage
- let paths = vec![main_rs_path, lib_rs_path];
- let input_package = sheet.output_mappings(input_name.clone(), &paths)?;
- sheet.add_input(input_package)?;
-
- // Verify input was added
- assert_eq!(sheet.inputs().len(), 1);
- let added_input = &sheet.inputs()[0];
- assert_eq!(added_input.name, input_name);
- assert_eq!(added_input.files.len(), 2);
- assert_eq!(
- added_input.files[0].0,
- InputRelativePathBuf::from("source_files/main.rs")
- );
- assert_eq!(
- added_input.files[1].0,
- InputRelativePathBuf::from("source_files/lib.rs")
- );
-
- // Test 3: Add mapping entries
- let mapping_path = vcs::data::sheet::SheetPathBuf::from("output/build.exe");
- let virtual_file_id = VirtualFileId::new();
-
- sheet
- .add_mapping(mapping_path.clone(), virtual_file_id.clone())
- .await?;
-
- // Verify mapping was added
- assert_eq!(sheet.mapping().len(), 3);
- assert_eq!(sheet.mapping().get(&mapping_path), Some(&virtual_file_id));
-
- // Test 4: Persist sheet to disk
- sheet.persist().await?;
-
- // Verify persistence by reloading the sheet
- let reloaded_sheet = vault.sheet(&sheet_name).await?;
- assert_eq!(reloaded_sheet.holder(), &member_id);
- assert_eq!(reloaded_sheet.inputs().len(), 1);
- assert_eq!(reloaded_sheet.mapping().len(), 3);
-
- // Test 5: Remove input package
- let mut sheet_for_removal = vault.sheet(&sheet_name).await?;
- let removed_input = sheet_for_removal.deny_input(&input_name);
- assert!(removed_input.is_some());
- let removed_input = removed_input.unwrap();
- assert_eq!(removed_input.name, input_name);
- assert_eq!(removed_input.files.len(), 2);
- assert_eq!(sheet_for_removal.inputs().len(), 0);
-
- // Test 6: Remove mapping entry
- let _removed_virtual_file_id = sheet_for_removal.remove_mapping(&mapping_path).await;
- // Don't check the return value since it depends on virtual file existence
- assert_eq!(sheet_for_removal.mapping().len(), 2);
-
- // Test 7: List all sheets in vault
- let sheet_names = vault.sheet_names()?;
- assert_eq!(sheet_names.len(), 2);
- assert!(sheet_names.contains(&sheet_name));
- assert!(sheet_names.contains(&"ref".to_string()));
-
- let all_sheets = vault.sheets().await?;
- assert_eq!(all_sheets.len(), 2);
- // One sheet should be the test sheet, the other should be the ref sheet with host as holder
- let test_sheet_holder = all_sheets
- .iter()
- .find(|s| s.holder() == &member_id)
- .map(|s| s.holder())
- .unwrap();
- let ref_sheet_holder = all_sheets
- .iter()
- .find(|s| s.holder() == &"host".to_string())
- .map(|s| s.holder())
- .unwrap();
- assert_eq!(test_sheet_holder, &member_id);
- assert_eq!(ref_sheet_holder, &"host".to_string());
-
- // Test 8: Safe deletion (move to trash)
- vault.delete_sheet_safely(&sheet_name).await?;
-
- // Verify sheet is not in normal listing but can be restored
- let sheet_names_after_deletion = vault.sheet_names()?;
- assert_eq!(sheet_names_after_deletion.len(), 1);
- assert_eq!(sheet_names_after_deletion[0], "ref");
-
- // Test 9: Restore sheet from trash
- let restored_sheet = vault.sheet(&sheet_name).await?;
- assert_eq!(restored_sheet.holder(), &member_id);
- assert_eq!(restored_sheet.holder(), &member_id);
-
- // Verify sheet is back in normal listing
- let sheet_names_after_restore = vault.sheet_names()?;
- assert_eq!(sheet_names_after_restore.len(), 2);
- assert!(sheet_names_after_restore.contains(&sheet_name));
- assert!(sheet_names_after_restore.contains(&"ref".to_string()));
-
- // Test 10: Permanent deletion
- vault.delete_sheet(&sheet_name).await?;
-
- // Verify sheet is permanently gone
- let sheet_names_final = vault.sheet_names()?;
- assert_eq!(sheet_names_final.len(), 1);
- assert_eq!(sheet_names_final[0], "ref");
-
- // Attempt to access deleted sheet should fail
- let result = vault.sheet(&sheet_name).await;
- assert!(result.is_err());
-
- // Clean up: Remove member
- vault.remove_member_from_vault(&member_id)?;
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_sheet_error_conditions() -> Result<(), std::io::Error> {
- let dir = get_test_dir("sheet_error_conditions").await?;
-
- // Setup vault
- Vault::setup_vault(dir.clone()).await?;
-
- // Get vault
- let config = VaultConfig::read_from(dir.join(SERVER_FILE_VAULT)).await?;
- let Some(vault) = Vault::init(config, &dir) else {
- return Err(Error::new(std::io::ErrorKind::NotFound, "Vault not found!"));
- };
-
- // Test 1: Create sheet with non-existent member should fail
- let non_existent_member: MemberId = "non_existent_member".to_string();
- let sheet_name: SheetName = "test_sheet".to_string();
-
- let result = vault.create_sheet(&sheet_name, &non_existent_member).await;
- assert!(result.is_err());
-
- // Add a member first
- let member_id: MemberId = "test_member".to_string();
- vault
- .register_member_to_vault(Member::new(&member_id))
- .await?;
-
- // Test 2: Create duplicate sheet should fail
- vault.create_sheet(&sheet_name, &member_id).await?;
- let result = vault.create_sheet(&sheet_name, &member_id).await;
- assert!(result.is_err());
-
- // Test 3: Delete non-existent sheet should fail
- let non_existent_sheet: SheetName = "non_existent_sheet".to_string();
- let result = vault.delete_sheet(&non_existent_sheet).await;
- assert!(result.is_err());
-
- // Test 4: Safe delete non-existent sheet should fail
- let result = vault.delete_sheet_safely(&non_existent_sheet).await;
- assert!(result.is_err());
-
- // Test 5: Restore non-existent sheet from trash should fail
- let result = vault.restore_sheet(&non_existent_sheet).await;
- assert!(result.is_err());
-
- // Clean up
- vault.remove_member_from_vault(&member_id)?;
-
- Ok(())
-}
-
-#[tokio::test]
-async fn test_sheet_data_serialization() -> Result<(), std::io::Error> {
- let dir = get_test_dir("sheet_serialization").await?;
-
- // Test serialization by creating a sheet through the vault
- // Setup vault
- Vault::setup_vault(dir.clone()).await?;
-
- // Get vault
- let config = VaultConfig::read_from(dir.join(SERVER_FILE_VAULT)).await?;
- let Some(vault) = Vault::init(config, &dir) else {
- return Err(Error::new(std::io::ErrorKind::NotFound, "Vault not found!"));
- };
-
- // Add a member
- let member_id: MemberId = "test_member".to_string();
- vault
- .register_member_to_vault(Member::new(&member_id))
- .await?;
-
- // Create a sheet
- let sheet_name: SheetName = "test_serialization_sheet".to_string();
- let mut sheet = vault.create_sheet(&sheet_name, &member_id).await?;
-
- // Add some inputs
- let input_name = "source_files".to_string();
- let _files = vec![
- (
- InputRelativePathBuf::from("src/main.rs"),
- VirtualFileId::new(),
- ),
- (
- InputRelativePathBuf::from("src/lib.rs"),
- VirtualFileId::new(),
- ),
- ];
- // First add mapping entries
- let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs");
- let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs");
- let main_rs_id = VirtualFileId::new();
- let lib_rs_id = VirtualFileId::new();
-
- sheet
- .add_mapping(main_rs_path.clone(), main_rs_id.clone())
- .await?;
- sheet
- .add_mapping(lib_rs_path.clone(), lib_rs_id.clone())
- .await?;
-
- // Use output_mappings to generate the InputPackage
- let paths = vec![main_rs_path, lib_rs_path];
- let input_package = sheet.output_mappings(input_name.clone(), &paths)?;
- sheet.add_input(input_package)?;
-
- // Add some mappings
- let build_exe_id = VirtualFileId::new();
-
- sheet
- .add_mapping(
- vcs::data::sheet::SheetPathBuf::from("output/build.exe"),
- build_exe_id,
- )
- .await?;
-
- // Persist the sheet
- sheet.persist().await?;
-
- // Verify the sheet file was created
- const SHEET_NAME_PARAM: &str = "{sheet-name}";
- let sheet_path = dir.join(SERVER_FILE_SHEET.replace(SHEET_NAME_PARAM, &sheet_name));
- assert!(sheet_path.exists());
-
- // Clean up
- vault.remove_member_from_vault(&member_id)?;
-
- Ok(())
-}
diff --git a/crates/vcs/vcs_test/src/test_vault_setup_and_member_register.rs b/crates/vcs/vcs_test/src/test_vault_setup_and_member_register.rs
deleted file mode 100644
index 6a30cf7..0000000
--- a/crates/vcs/vcs_test/src/test_vault_setup_and_member_register.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use std::io::Error;
-
-use cfg_file::config::ConfigFile;
-use vcs::{
- constants::{
- SERVER_FILE_MEMBER_INFO, SERVER_FILE_README, SERVER_FILE_VAULT, SERVER_PATH_MEMBER_PUB,
- SERVER_PATH_MEMBERS, SERVER_PATH_SHEETS, SERVER_PATH_VF_ROOT,
- },
- data::{
- member::Member,
- vault::{Vault, config::VaultConfig},
- },
-};
-
-use crate::get_test_dir;
-
-#[tokio::test]
-async fn test_vault_setup_and_member_register() -> Result<(), std::io::Error> {
- let dir = get_test_dir("member_register").await?;
-
- // Setup vault
- Vault::setup_vault(dir.clone()).await?;
-
- // Check if the following files and directories are created in `dir`:
- // Files: SERVER_FILE_VAULT, SERVER_FILE_README
- // Directories: SERVER_PATH_SHEETS,
- // SERVER_PATH_MEMBERS,
- // SERVER_PATH_MEMBER_PUB,
- // SERVER_PATH_VIRTUAL_FILE_ROOT
- assert!(dir.join(SERVER_FILE_VAULT).exists());
- assert!(dir.join(SERVER_FILE_README).exists());
- assert!(dir.join(SERVER_PATH_SHEETS).exists());
- assert!(dir.join(SERVER_PATH_MEMBERS).exists());
- assert!(dir.join(SERVER_PATH_MEMBER_PUB).exists());
- assert!(dir.join(SERVER_PATH_VF_ROOT).exists());
-
- // Get vault
- let config = VaultConfig::read_from(dir.join(SERVER_FILE_VAULT)).await?;
- let Some(vault) = Vault::init(config, &dir) else {
- return Err(Error::new(std::io::ErrorKind::NotFound, "Vault not found!"));
- };
-
- // Add member
- let member_id = "test_member";
- vault
- .register_member_to_vault(Member::new(member_id))
- .await?;
-
- const ID_PARAM: &str = "{member_id}";
-
- // Check if the member info file exists
- assert!(
- dir.join(SERVER_FILE_MEMBER_INFO.replace(ID_PARAM, member_id))
- .exists()
- );
-
- // Remove member
- vault.remove_member_from_vault(&member_id.to_string())?;
-
- // Check if the member info file not exists
- assert!(
- !dir.join(SERVER_FILE_MEMBER_INFO.replace(ID_PARAM, member_id))
- .exists()
- );
-
- Ok(())
-}
diff --git a/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs b/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs
deleted file mode 100644
index d86c13a..0000000
--- a/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-use std::time::Duration;
-
-use cfg_file::config::ConfigFile;
-use tcp_connection_test::{
- handle::{ClientHandle, ServerHandle},
- target::TcpServerTarget,
- target_configure::ServerTargetConfig,
-};
-use tokio::{
- join,
- time::{sleep, timeout},
-};
-use vcs::{
- constants::SERVER_FILE_VAULT,
- data::{
- member::Member,
- vault::{Vault, config::VaultConfig, virtual_file::VirtualFileVersionDescription},
- },
-};
-
-use crate::get_test_dir;
-
-struct VirtualFileCreateClientHandle;
-struct VirtualFileCreateServerHandle;
-
-impl ClientHandle<VirtualFileCreateServerHandle> for VirtualFileCreateClientHandle {
- async fn process(mut instance: tcp_connection::instance::ConnectionInstance) {
- let dir = get_test_dir("virtual_file_creation_and_update_2")
- .await
- .unwrap();
- // Create first test file for virtual file creation
- let test_content_1 = b"Test file content for virtual file creation";
- let temp_file_path_1 = dir.join("test_virtual_file_1.txt");
-
- tokio::fs::write(&temp_file_path_1, test_content_1)
- .await
- .unwrap();
-
- // Send the first file to server for virtual file creation
- instance.write_file(&temp_file_path_1).await.unwrap();
-
- // Create second test file for virtual file update
- let test_content_2 = b"Updated test file content for virtual file";
- let temp_file_path_2 = dir.join("test_virtual_file_2.txt");
-
- tokio::fs::write(&temp_file_path_2, test_content_2)
- .await
- .unwrap();
-
- // Send the second file to server for virtual file update
- instance.write_file(&temp_file_path_2).await.unwrap();
- }
-}
-
-impl ServerHandle<VirtualFileCreateClientHandle> for VirtualFileCreateServerHandle {
- async fn process(mut instance: tcp_connection::instance::ConnectionInstance) {
- let dir = get_test_dir("virtual_file_creation_and_update")
- .await
- .unwrap();
-
- // Setup vault
- Vault::setup_vault(dir.clone()).await.unwrap();
-
- // Read vault
- let Some(vault) = Vault::init(
- VaultConfig::read_from(dir.join(SERVER_FILE_VAULT))
- .await
- .unwrap(),
- &dir,
- ) else {
- panic!("No vault found!");
- };
-
- // Register member
- let member_id = "test_member";
- vault
- .register_member_to_vault(Member::new(member_id))
- .await
- .unwrap();
-
- // Create visual file
- let virtual_file_id = vault
- .create_virtual_file_from_connection(&mut instance, &member_id.to_string())
- .await
- .unwrap();
-
- // Grant edit right to member
- vault
- .grant_virtual_file_edit_right(&member_id.to_string(), &virtual_file_id)
- .await
- .unwrap();
-
- // Update visual file
- vault
- .update_virtual_file_from_connection(
- &mut instance,
- &member_id.to_string(),
- &virtual_file_id,
- &"2".to_string(),
- VirtualFileVersionDescription {
- creator: member_id.to_string(),
- description: "Update".to_string(),
- },
- )
- .await
- .unwrap();
- }
-}
-
-#[tokio::test]
-async fn test_virtual_file_creation_and_update() -> Result<(), std::io::Error> {
- let host = "localhost:5009";
-
- // Server setup
- let Ok(server_target) = TcpServerTarget::<
- VirtualFileCreateClientHandle,
- VirtualFileCreateServerHandle,
- >::from_domain(host)
- .await
- else {
- panic!("Test target built failed from a domain named `{}`", host);
- };
-
- // Client setup
- let Ok(client_target) = TcpServerTarget::<
- VirtualFileCreateClientHandle,
- VirtualFileCreateServerHandle,
- >::from_domain(host)
- .await
- else {
- panic!("Test target built failed from a domain named `{}`", host);
- };
-
- let future_server = async move {
- // Only process once
- let configured_server = server_target.server_cfg(ServerTargetConfig::default().once());
-
- // Listen here
- let _ = configured_server.listen().await;
- };
-
- let future_client = async move {
- // Wait for server start
- let _ = sleep(Duration::from_secs_f32(1.5)).await;
-
- // Connect here
- let _ = client_target.connect().await;
- };
-
- let test_timeout = Duration::from_secs(15);
-
- timeout(test_timeout, async { join!(future_client, future_server) })
- .await
- .map_err(|_| {
- std::io::Error::new(
- std::io::ErrorKind::TimedOut,
- format!("Test timed out after {:?}", test_timeout),
- )
- })?;
-
- Ok(())
-}