summaryrefslogtreecommitdiff
path: root/crates/env/src/workspace
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-09-25 13:22:50 +0800
committer魏曹先生 <1992414357@qq.com>2025-09-25 13:22:50 +0800
commit07b1ce2c3398c69b021ea2418a057eb7b6cacc40 (patch)
tree73d6c8f35df425b15c905819fb0d37aec02e6563 /crates/env/src/workspace
parent28828776b7aaa6f2bf723837eab1cae859582be5 (diff)
Rename `env` to `vcs`
Diffstat (limited to 'crates/env/src/workspace')
-rw-r--r--crates/env/src/workspace/local.rs107
-rw-r--r--crates/env/src/workspace/local/accounts.rs1
-rw-r--r--crates/env/src/workspace/local/config.rs53
-rw-r--r--crates/env/src/workspace/member.rs67
-rw-r--r--crates/env/src/workspace/vault.rs137
-rw-r--r--crates/env/src/workspace/vault/config.rs46
-rw-r--r--crates/env/src/workspace/vault/member.rs105
-rw-r--r--crates/env/src/workspace/vault/virtual_file.rs422
8 files changed, 0 insertions, 938 deletions
diff --git a/crates/env/src/workspace/local.rs b/crates/env/src/workspace/local.rs
deleted file mode 100644
index 829fc51..0000000
--- a/crates/env/src/workspace/local.rs
+++ /dev/null
@@ -1,107 +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},
- workspace::local::config::LocalConfig,
-};
-
-pub mod accounts;
-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 Some(local_path) = find_local_path(local_path) else {
- return None;
- };
- Some(Self { config, local_path })
- }
-
- /// Initialize local workspace in the current directory.
- pub fn init_current_dir(config: LocalConfig) -> Option<Self> {
- let Some(local_path) = current_local_path() else {
- return None;
- };
- 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 = format!(
- "\
-# 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!*
-"
- )
- .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/env/src/workspace/local/accounts.rs b/crates/env/src/workspace/local/accounts.rs
deleted file mode 100644
index 8b13789..0000000
--- a/crates/env/src/workspace/local/accounts.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/crates/env/src/workspace/local/config.rs b/crates/env/src/workspace/local/config.rs
deleted file mode 100644
index d641880..0000000
--- a/crates/env/src/workspace/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::workspace::vault::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/env/src/workspace/member.rs b/crates/env/src/workspace/member.rs
deleted file mode 100644
index 208c78c..0000000
--- a/crates/env/src/workspace/member.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use std::collections::HashMap;
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-use string_proc::snake_case;
-
-#[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/env/src/workspace/vault.rs b/crates/env/src/workspace/vault.rs
deleted file mode 100644
index caac662..0000000
--- a/crates/env/src/workspace/vault.rs
+++ /dev/null
@@ -1,137 +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_VIRTUAL_FILE_ROOT,
- },
- current::{current_vault_path, find_vault_path},
- workspace::vault::config::VaultConfig,
-};
-
-pub mod config;
-pub mod member;
-pub mod virtual_file;
-
-pub type MemberId = String;
-
-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 Some(vault_path) = find_vault_path(vault_path) else {
- return None;
- };
- Some(Self { config, vault_path })
- }
-
- /// Initialize vault
- pub fn init_current_dir(config: VaultConfig) -> Option<Self> {
- let Some(vault_path) = current_vault_path() else {
- return None;
- };
- 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_VIRTUAL_FILE_ROOT))?;
-
- // 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_VIRTUAL_FILE_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/env/src/workspace/vault/config.rs b/crates/env/src/workspace/vault/config.rs
deleted file mode 100644
index 5414e4d..0000000
--- a/crates/env/src/workspace/vault/config.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-
-use crate::constants::SERVER_FILE_VAULT;
-use crate::workspace::member::Member;
-use crate::workspace::vault::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>,
-}
-
-impl Default for VaultConfig {
- fn default() -> Self {
- Self {
- vault_name: "JustEnoughVault".to_string(),
- vault_admin_list: Vec::new(),
- }
- }
-}
-
-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/env/src/workspace/vault/member.rs b/crates/env/src/workspace/vault/member.rs
deleted file mode 100644
index 793ba2a..0000000
--- a/crates/env/src/workspace/vault/member.rs
+++ /dev/null
@@ -1,105 +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},
- workspace::{
- member::Member,
- vault::{MemberId, 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!"))
- }
-
- /// Update member info
- pub async fn update_member(&self, member: Member) -> Result<(), std::io::Error> {
- // Ensure member exist
- if let Some(_) = self.member_cfg(&member.id()) {
- 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 let Some(_) = self.member_cfg(&member.id()) {
- 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 {
- let path = self
- .vault_path
- .join(SERVER_FILE_MEMBER_INFO.replace(ID_PARAM, id.to_string().as_str()));
- path
- }
-
- /// 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 {
- let path = self
- .vault_path
- .join(SERVER_FILE_MEMBER_PUB.replace(ID_PARAM, id.to_string().as_str()));
- path
- }
-}
diff --git a/crates/env/src/workspace/vault/virtual_file.rs b/crates/env/src/workspace/vault/virtual_file.rs
deleted file mode 100644
index 321f0e1..0000000
--- a/crates/env/src/workspace/vault/virtual_file.rs
+++ /dev/null
@@ -1,422 +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_VIRTUAL_FILE_META, SERVER_FILE_VIRTUAL_FILE_VERSION_INSTANCE,
- SERVER_PATH_VIRTUAL_FILE_ROOT, SERVER_PATH_VIRTUAL_FILE_STORAGE,
- SERVER_PATH_VIRTUAL_FILE_TEMP,
- },
- workspace::vault::{MemberId, Vault},
-};
-
-pub type VirtualFileId = String;
-pub type VirtualFileVersion = String;
-
-const ID_PARAM: &str = "{vf_id}";
-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,
- }
- }
-}
-
-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_VIRTUAL_FILE_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_VIRTUAL_FILE_ROOT)
- }
-
- /// Get the directory where a specific virtual file is stored
- pub fn virtual_file_dir(&self, id: VirtualFileId) -> PathBuf {
- self.vault_path()
- .join(SERVER_PATH_VIRTUAL_FILE_STORAGE.replace(ID_PARAM, &id.to_string()))
- }
-
- /// 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_VIRTUAL_FILE_VERSION_INSTANCE
- .replace(ID_PARAM, &id.to_string())
- .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_VIRTUAL_FILE_META.replace(ID_PARAM, &id.to_string()))
- }
-
- /// Get the virtual file with the given ID
- pub fn virtual_file(&self, id: &VirtualFileId) -> Option<VirtualFile<'_>> {
- let dir = self.virtual_file_dir(id.clone());
- if dir.exists() {
- Some(VirtualFile {
- id: id.clone(),
- current_vault: self,
- })
- } else {
- None
- }
- }
-
- /// 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_{}", 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: String::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() {
- if !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::new(ErrorKind::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?;
-
- return Ok(());
- }
- Err(e) => {
- // Read failed, remove temp file.
- if receive_path.exists() {
- fs::remove_file(receive_path).await?;
- }
-
- return Err(Error::new(ErrorKind::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 Some(_) = 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()
- }
-}