summaryrefslogtreecommitdiff
path: root/legacy_data/src/data/vault
diff options
context:
space:
mode:
Diffstat (limited to 'legacy_data/src/data/vault')
-rw-r--r--legacy_data/src/data/vault/lock_status.rs40
-rw-r--r--legacy_data/src/data/vault/mapping_share.rs420
-rw-r--r--legacy_data/src/data/vault/member_manage.rs144
-rw-r--r--legacy_data/src/data/vault/sheet_manage.rs274
-rw-r--r--legacy_data/src/data/vault/vault_config.rs233
-rw-r--r--legacy_data/src/data/vault/virtual_file.rs500
6 files changed, 0 insertions, 1611 deletions
diff --git a/legacy_data/src/data/vault/lock_status.rs b/legacy_data/src/data/vault/lock_status.rs
deleted file mode 100644
index 3f59c30..0000000
--- a/legacy_data/src/data/vault/lock_status.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use std::path::PathBuf;
-
-use crate::{constants::SERVER_FILE_LOCKFILE, data::vault::Vault};
-
-impl Vault {
- /// Get the path of the lock file for the current Vault
- pub fn lock_file_path(&self) -> PathBuf {
- self.vault_path().join(SERVER_FILE_LOCKFILE)
- }
-
- /// Check if the current Vault is locked
- pub fn is_locked(&self) -> bool {
- self.lock_file_path().exists()
- }
-
- /// Lock the current Vault
- pub fn lock(&self) -> Result<(), std::io::Error> {
- if self.is_locked() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::AlreadyExists,
- format!(
- "Vault is locked! This indicates a service is already running here.\nPlease stop other services or delete the lock file at the vault root directory: {}",
- self.lock_file_path().display()
- ),
- ));
- }
- std::fs::File::create(self.lock_file_path())?;
- Ok(())
- }
-
- /// Unlock the current Vault
- pub fn unlock(&self) -> Result<(), std::io::Error> {
- if let Err(e) = std::fs::remove_file(self.lock_file_path())
- && e.kind() != std::io::ErrorKind::NotFound
- {
- return Err(e);
- }
- Ok(())
- }
-}
diff --git a/legacy_data/src/data/vault/mapping_share.rs b/legacy_data/src/data/vault/mapping_share.rs
deleted file mode 100644
index 2635b48..0000000
--- a/legacy_data/src/data/vault/mapping_share.rs
+++ /dev/null
@@ -1,420 +0,0 @@
-use std::{collections::HashMap, io::Error, path::PathBuf};
-
-use cfg_file::{ConfigFile, config::ConfigFile};
-use just_fmt::{fmt_path::fmt_path_str, snake_case};
-use rand::{Rng, rng};
-use serde::{Deserialize, Serialize};
-use tokio::fs;
-
-use crate::{
- constants::{
- KEY_SHARE_ID, KEY_SHEET_NAME, SERVER_FILE_SHEET_SHARE, SERVER_PATH_SHARES,
- SERVER_SUFFIX_SHEET_SHARE_FILE_NO_DOT,
- },
- data::{
- member::MemberId,
- sheet::{Sheet, SheetMappingMetadata, SheetName, SheetPathBuf},
- vault::Vault,
- },
-};
-
-pub type SheetShareId = String;
-
-#[derive(Default, Serialize, Deserialize, ConfigFile, Clone, Debug)]
-pub struct Share {
- /// Sharer: the member who created this share item
- #[serde(rename = "sharer")]
- pub sharer: MemberId,
-
- /// Description of the share item
- #[serde(rename = "desc")]
- pub description: String,
-
- /// Metadata path
- #[serde(skip)]
- pub path: Option<PathBuf>,
-
- /// From: which sheet the member exported the file from
- #[serde(rename = "from")]
- pub from_sheet: SheetName,
-
- /// Mappings: the sheet mappings contained in the share item
- #[serde(rename = "map")]
- pub mappings: HashMap<SheetPathBuf, SheetMappingMetadata>,
-}
-
-#[derive(Default, Serialize, Deserialize, ConfigFile, Clone, PartialEq, Eq)]
-pub enum ShareMergeMode {
- /// If a path or file already exists during merge, prioritize the incoming share
- /// Path conflict: replace the mapping content at the local path with the incoming content
- /// File conflict: delete the original file mapping and create a new one
- Overwrite,
-
- /// If a path or file already exists during merge, skip overwriting this entry
- Skip,
-
- /// Pre-check for conflicts, prohibit merging if any conflicts are found
- #[default]
- Safe,
-
- /// Reject all shares
- RejectAll,
-}
-
-#[derive(Default, Serialize, Deserialize, ConfigFile, Clone)]
-pub struct ShareMergeConflict {
- /// Duplicate mappings exist
- pub duplicate_mapping: Vec<PathBuf>,
-
- /// Duplicate files exist
- pub duplicate_file: Vec<PathBuf>,
-}
-
-impl ShareMergeConflict {
- /// Check if there are no conflicts
- pub fn ok(&self) -> bool {
- self.duplicate_mapping.is_empty() && self.duplicate_file.is_empty()
- }
-}
-
-impl Vault {
- /// Get the path of a share item in a sheet
- pub fn share_file_path(&self, sheet_name: &SheetName, share_id: &SheetShareId) -> PathBuf {
- let sheet_name = snake_case!(sheet_name.clone());
- let share_id = share_id.clone();
-
- // Format the path to remove "./" prefix and normalize it
- let path_str = SERVER_FILE_SHEET_SHARE
- .replace(KEY_SHEET_NAME, &sheet_name)
- .replace(KEY_SHARE_ID, &share_id);
-
- // Use format_path to normalize the path
- match fmt_path_str(&path_str) {
- Ok(normalized_path) => self.vault_path().join(normalized_path),
- Err(_) => {
- // Fallback to original behavior if formatting fails
- self.vault_path().join(path_str)
- }
- }
- }
-
- /// Get the actual paths of all share items in a sheet
- pub async fn share_file_paths(&self, sheet_name: &SheetName) -> Vec<PathBuf> {
- let sheet_name = snake_case!(sheet_name.clone());
- let shares_dir = self
- .vault_path()
- .join(SERVER_PATH_SHARES.replace(KEY_SHEET_NAME, &sheet_name));
-
- let mut result = Vec::new();
- if let Ok(mut entries) = fs::read_dir(shares_dir).await {
- while let Ok(Some(entry)) = entries.next_entry().await {
- let path = entry.path();
- if path.is_file()
- && path.extension().and_then(|s| s.to_str())
- == Some(SERVER_SUFFIX_SHEET_SHARE_FILE_NO_DOT)
- {
- result.push(path);
- }
- }
- }
- result
- }
-}
-
-impl<'a> Sheet<'a> {
- /// Get the shares of a sheet
- pub async fn get_shares(&self) -> Result<Vec<Share>, std::io::Error> {
- let paths = self.vault_reference.share_file_paths(&self.name).await;
- let mut shares = Vec::new();
-
- for path in paths {
- match Share::read_from(&path).await {
- Ok(mut share) => {
- share.path = Some(path);
- shares.push(share);
- }
- Err(e) => return Err(e),
- }
- }
-
- Ok(shares)
- }
-
- /// Get a share of a sheet
- pub async fn get_share(&self, share_id: &SheetShareId) -> Result<Share, std::io::Error> {
- let path = self.vault_reference.share_file_path(&self.name, share_id);
- let mut share = Share::read_from(&path).await?;
- share.path = Some(path);
- Ok(share)
- }
-
- /// Import a share of a sheet by its ID
- pub async fn merge_share_by_id(
- self,
- share_id: &SheetShareId,
- share_merge_mode: ShareMergeMode,
- ) -> Result<(), std::io::Error> {
- let share = self.get_share(share_id).await?;
- self.merge_share(share, share_merge_mode).await
- }
-
- /// Import a share of a sheet
- pub async fn merge_share(
- mut self,
- share: Share,
- share_merge_mode: ShareMergeMode,
- ) -> Result<(), std::io::Error> {
- // Backup original data and edit based on this backup
- let mut copy_share = share.clone();
- let mut copy_sheet = self.clone_data();
-
- // Pre-check
- let conflicts = self.precheck(&copy_share);
- let mut reject_mode = false;
-
- match share_merge_mode {
- // Safe mode: conflicts are not allowed
- ShareMergeMode::Safe => {
- // Conflicts found
- if !conflicts.ok() {
- // Do nothing, return Error
- return Err(Error::new(
- std::io::ErrorKind::AlreadyExists,
- "Mappings or files already exist!",
- ));
- }
- }
- // Overwrite mode: when conflicts occur, prioritize the share item
- ShareMergeMode::Overwrite => {
- // Handle duplicate mappings
- for path in conflicts.duplicate_mapping {
- // Get the share data
- let Some(share_value) = copy_share.mappings.remove(&path) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Share value `{}` not found!", &path.display()),
- ));
- };
- // Overwrite
- copy_sheet.mapping_mut().insert(path, share_value);
- }
-
- // Handle duplicate IDs
- for path in conflicts.duplicate_file {
- // Get the share data
- let Some(share_value) = copy_share.mappings.remove(&path) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Share value `{}` not found!", &path.display()),
- ));
- };
-
- // Extract the file ID
- let conflict_vfid = &share_value.id;
-
- // Through the sheet's ID mapping
- let Some(id_mapping) = copy_sheet.id_mapping_mut() else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Id mapping not found!",
- ));
- };
-
- // Get the original path from the ID mapping
- let Some(raw_path) = id_mapping.remove(conflict_vfid) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("The path of virtual file `{}' not found!", conflict_vfid),
- ));
- };
-
- // Remove the original path mapping
- if copy_sheet.mapping_mut().remove(&raw_path).is_none() {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Remove mapping `{}` failed!", &raw_path.display()),
- ));
- }
- // Insert the new item
- copy_sheet.mapping_mut().insert(path, share_value);
- }
- }
- // Skip mode: when conflicts occur, prioritize the local sheet
- ShareMergeMode::Skip => {
- // Directly remove conflicting items
- for path in conflicts.duplicate_mapping {
- copy_share.mappings.remove(&path);
- }
- for path in conflicts.duplicate_file {
- copy_share.mappings.remove(&path);
- }
- }
- // Reject all mode: reject all shares
- ShareMergeMode::RejectAll => {
- reject_mode = true; // Only mark as rejected
- }
- }
-
- if !reject_mode {
- // Subsequent merging
- copy_sheet
- .mapping_mut()
- .extend(copy_share.mappings.into_iter());
-
- // Merge completed
- self.data = copy_sheet; // Write the result
-
- // Merge completed, consume the sheet
- self.persist().await.map_err(|err| {
- Error::new(
- std::io::ErrorKind::NotFound,
- format!("Write sheet failed: {}", err),
- )
- })?;
- }
-
- // Persistence succeeded, continue to consume the share item
- share.remove().await.map_err(|err| {
- Error::new(
- std::io::ErrorKind::NotFound,
- format!("Remove share failed: {}", err.1),
- )
- })
- }
-
- // Pre-check whether the share can be imported into the current sheet without conflicts
- fn precheck(&self, share: &Share) -> ShareMergeConflict {
- let mut conflicts = ShareMergeConflict::default();
-
- for (mapping, metadata) in &share.mappings {
- // Check for duplicate mappings
- if self.mapping().contains_key(mapping.as_path()) {
- conflicts.duplicate_mapping.push(mapping.clone());
- continue;
- }
-
- // Check for duplicate IDs
- if let Some(id_mapping) = self.id_mapping()
- && id_mapping.contains_key(&metadata.id) {
- conflicts.duplicate_file.push(mapping.clone());
- continue;
- }
- }
-
- conflicts
- }
-
- /// Share mappings with another sheet
- pub async fn share_mappings(
- &self,
- other_sheet: &SheetName,
- mappings: Vec<PathBuf>,
- sharer: &MemberId,
- description: String,
- ) -> Result<Share, std::io::Error> {
- let other_sheet = snake_case!(other_sheet.clone());
- let sharer = snake_case!(sharer.clone());
-
- // Check if the sheet exists
- let sheet_names = self.vault_reference.sheet_names()?;
- if !sheet_names.contains(&other_sheet) {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Sheet `{}` not found!", &other_sheet),
- ));
- }
-
- // Check if the target file exists, regenerate ID if path already exists, up to 20 attempts
- let target_path = {
- let mut id;
- let mut share_path;
- let mut attempts = 0;
-
- loop {
- id = Share::gen_share_id(&sharer);
- share_path = self.vault_reference.share_file_path(&other_sheet, &id);
-
- if !share_path.exists() {
- break share_path;
- }
-
- attempts += 1;
- if attempts >= 20 {
- return Err(Error::new(
- std::io::ErrorKind::AlreadyExists,
- "Failed to generate unique share ID after 20 attempts!",
- ));
- }
- }
- };
-
- // Validate that the share is valid
- let mut share_mappings = HashMap::new();
- for mapping_path in &mappings {
- if let Some(metadata) = self.mapping().get(mapping_path) {
- share_mappings.insert(mapping_path.clone(), metadata.clone());
- } else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- format!("Mapping `{}` not found in sheet!", mapping_path.display()),
- ));
- }
- }
-
- // Build share data
- let share_data = Share {
- sharer,
- description,
- path: None, // This is only needed during merging (reading), no need to serialize now
- from_sheet: self.name.clone(),
- mappings: share_mappings,
- };
-
- // Write data
- Share::write_to(&share_data, target_path).await?;
-
- Ok(share_data)
- }
-}
-
-impl Share {
- /// Generate a share ID for a given sharer
- pub fn gen_share_id(sharer: &MemberId) -> String {
- let sharer_snake = snake_case!(sharer.clone());
- let random_part: String = rng()
- .sample_iter(&rand::distr::Alphanumeric)
- .take(8)
- .map(char::from)
- .collect();
- format!("{}@{}", sharer_snake, random_part)
- }
-
- /// Delete a share (reject or remove the share item)
- /// If deletion succeeds, returns `Ok(())`;
- /// If deletion fails, returns `Err((self, std::io::Error))`, containing the original share object and the error information.
- pub async fn remove(self) -> Result<(), (Self, std::io::Error)> {
- let Some(path) = &self.path else {
- return Err((
- self,
- Error::new(std::io::ErrorKind::NotFound, "No share path recorded!"),
- ));
- };
-
- if !path.exists() {
- return Err((
- self,
- Error::new(std::io::ErrorKind::NotFound, "No share file exists!"),
- ));
- }
-
- match fs::remove_file(path).await {
- Err(err) => Err((
- self,
- Error::other(
- format!("Failed to delete share file: {}", err),
- ),
- )),
- Ok(_) => Ok(()),
- }
- }
-}
diff --git a/legacy_data/src/data/vault/member_manage.rs b/legacy_data/src/data/vault/member_manage.rs
deleted file mode 100644
index 9d22d09..0000000
--- a/legacy_data/src/data/vault/member_manage.rs
+++ /dev/null
@@ -1,144 +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,
- SERVER_SUFFIX_MEMBER_INFO_NO_DOT,
- },
- 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(SERVER_SUFFIX_MEMBER_INFO_NO_DOT)
- {
- 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/legacy_data/src/data/vault/sheet_manage.rs b/legacy_data/src/data/vault/sheet_manage.rs
deleted file mode 100644
index 70fef88..0000000
--- a/legacy_data/src/data/vault/sheet_manage.rs
+++ /dev/null
@@ -1,274 +0,0 @@
-use std::{collections::HashMap, io::Error};
-
-use cfg_file::config::ConfigFile;
-use just_fmt::snake_case;
-use tokio::fs;
-
-use crate::{
- constants::{SERVER_PATH_SHEETS, SERVER_SUFFIX_SHEET_FILE_NO_DOT},
- 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 == SERVER_SUFFIX_SHEET_FILE_NO_DOT)
- && 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: Some(holder.clone()),
- mapping: HashMap::new(),
- id_mapping: None,
- write_count: 0,
- };
- 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!(
- "{}_{}.{}",
- sheet_name, timestamp, SERVER_SUFFIX_SHEET_FILE_NO_DOT
- );
- 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/legacy_data/src/data/vault/vault_config.rs b/legacy_data/src/data/vault/vault_config.rs
deleted file mode 100644
index 156083b..0000000
--- a/legacy_data/src/data/vault/vault_config.rs
+++ /dev/null
@@ -1,233 +0,0 @@
-use std::net::{IpAddr, Ipv4Addr};
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
-
-use crate::constants::{PORT, SERVER_FILE_VAULT};
-use crate::data::member::{Member, MemberId};
-
-pub type VaultName = String;
-pub type VaultUuid = Uuid;
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum AuthMode {
- /// Use asymmetric keys: both client and server need to register keys, after which they can connect
- Key,
-
- /// Use password: the password stays on the server, and the client needs to set the password locally for connection
- #[default]
- Password,
-
- /// No authentication: generally used in a strongly secure environment, skipping verification directly
- NoAuth,
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum LoggerLevel {
- Debug,
- Trace,
-
- #[default]
- Info,
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum ServiceEnabled {
- Enable,
-
- #[default]
- Disable,
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum BehaviourEnabled {
- Yes,
-
- #[default]
- No,
-}
-
-impl From<ServiceEnabled> for bool {
- fn from(val: ServiceEnabled) -> Self {
- match val {
- ServiceEnabled::Enable => true,
- ServiceEnabled::Disable => false,
- }
- }
-}
-
-impl From<BehaviourEnabled> for bool {
- fn from(val: BehaviourEnabled) -> Self {
- match val {
- BehaviourEnabled::Yes => true,
- BehaviourEnabled::No => false,
- }
- }
-}
-
-#[derive(Serialize, Deserialize, ConfigFile)]
-#[cfg_file(path = SERVER_FILE_VAULT)]
-pub struct VaultConfig {
- /// Vault uuid, unique identifier for the vault
- #[serde(rename = "uuid")]
- vault_uuid: VaultUuid,
-
- /// Vault name, which can be used as the project name and generally serves as a hint
- #[serde(rename = "name")]
- vault_name: VaultName,
-
- /// Vault host ids, a list of member id representing administrator identities
- #[serde(rename = "hosts")]
- vault_host_list: Vec<MemberId>,
-
- /// Vault server configuration, which will be loaded when connecting to the server
- #[serde(rename = "profile")]
- server_config: VaultServerConfig,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct VaultServerConfig {
- /// Local IP address to bind to when the server starts
- #[serde(rename = "bind")]
- local_bind: IpAddr,
-
- /// TCP port to bind to when the server starts
- #[serde(rename = "port")]
- port: u16,
-
- /// Enable logging
- #[serde(rename = "logger")]
- logger: Option<BehaviourEnabled>,
-
- /// Logger Level
- #[serde(rename = "logger_level")]
- logger_level: Option<LoggerLevel>,
-
- /// Whether to enable LAN discovery, allowing members on the same LAN to more easily find the upstream server
- #[serde(rename = "lan_discovery")]
- lan_discovery: Option<ServiceEnabled>, // TODO
-
- /// Authentication mode for the vault server
- /// key: Use asymmetric keys for authentication
- /// password: Use a password for authentication
- /// noauth: No authentication required, requires a strongly secure environment
- #[serde(rename = "auth_mode")]
- auth_mode: Option<AuthMode>, // TODO
-}
-
-impl Default for VaultConfig {
- fn default() -> Self {
- Self {
- vault_uuid: Uuid::new_v4(),
- vault_name: "JustEnoughVault".to_string(),
- vault_host_list: Vec::new(),
- server_config: VaultServerConfig {
- local_bind: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
- port: PORT,
- logger: Some(BehaviourEnabled::default()),
- logger_level: Some(LoggerLevel::default()),
- lan_discovery: Some(ServiceEnabled::default()),
- auth_mode: Some(AuthMode::Key),
- },
- }
- }
-}
-
-/// 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_host_list.contains(&uuid) {
- self.vault_host_list.push(uuid);
- }
- }
-
- /// Remove admin
- pub fn remove_admin(&mut self, member: &Member) {
- let id = member.id();
- self.vault_host_list.retain(|x| x != &id);
- }
-
- /// Get vault UUID
- pub fn vault_uuid(&self) -> &VaultUuid {
- &self.vault_uuid
- }
-
- /// Set vault UUID
- pub fn set_vault_uuid(&mut self, vault_uuid: VaultUuid) {
- self.vault_uuid = vault_uuid;
- }
-
- /// Get vault name
- pub fn vault_name(&self) -> &VaultName {
- &self.vault_name
- }
-
- /// Set vault name
- pub fn set_vault_name(&mut self, vault_name: VaultName) {
- self.vault_name = vault_name;
- }
-
- /// Get vault admin list
- pub fn vault_host_list(&self) -> &Vec<MemberId> {
- &self.vault_host_list
- }
-
- /// Set vault admin list
- pub fn set_vault_host_list(&mut self, vault_host_list: Vec<MemberId>) {
- self.vault_host_list = vault_host_list;
- }
-
- /// Get server config
- pub fn server_config(&self) -> &VaultServerConfig {
- &self.server_config
- }
-
- /// Set server config
- pub fn set_server_config(&mut self, server_config: VaultServerConfig) {
- self.server_config = server_config;
- }
-}
-
-impl VaultServerConfig {
- /// Get local bind IP address
- pub fn local_bind(&self) -> &IpAddr {
- &self.local_bind
- }
-
- /// Get port
- pub fn port(&self) -> u16 {
- self.port
- }
-
- /// Check if LAN discovery is enabled
- pub fn is_lan_discovery_enabled(&self) -> bool {
- self.lan_discovery.clone().unwrap_or_default().into()
- }
-
- /// Get logger enabled status
- pub fn is_logger_enabled(&self) -> bool {
- self.logger.clone().unwrap_or_default().into()
- }
-
- /// Get logger level
- pub fn logger_level(&self) -> LoggerLevel {
- self.logger_level.clone().unwrap_or_default()
- }
-
- /// Get authentication mode
- pub fn auth_mode(&self) -> AuthMode {
- self.auth_mode.clone().unwrap_or_default()
- }
-}
diff --git a/legacy_data/src/data/vault/virtual_file.rs b/legacy_data/src/data/vault/virtual_file.rs
deleted file mode 100644
index 06ec3f4..0000000
--- a/legacy_data/src/data/vault/virtual_file.rs
+++ /dev/null
@@ -1,500 +0,0 @@
-use std::{
- collections::HashMap,
- io::{Error, ErrorKind},
- path::PathBuf,
-};
-
-use cfg_file::{ConfigFile, config::ConfigFile};
-use just_fmt::{dot_case, snake_case};
-use serde::{Deserialize, Serialize};
-use tcp_connection::instance::ConnectionInstance;
-use tokio::fs;
-use uuid::Uuid;
-
-use crate::{
- constants::{
- DEFAULT_VF_DESCRIPTION, DEFAULT_VF_VERSION, KEY_TEMP_NAME, KEY_VF_ID, KEY_VF_INDEX,
- KEY_VF_VERSION, SERVER_FILE_VF_META, SERVER_FILE_VF_VERSION_INSTANCE, SERVER_PATH_VF_ROOT,
- SERVER_PATH_VF_STORAGE, SERVER_PATH_VF_TEMP, VF_PREFIX,
- },
- data::{member::MemberId, vault::Vault},
-};
-
-pub type VirtualFileId = String;
-pub type VirtualFileVersion = String;
-
-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
- #[serde(rename = "ver")]
- current_version: VirtualFileVersion,
-
- /// The member who holds the edit right of the file
- #[serde(rename = "holder")]
- hold_member: MemberId,
-
- /// Description of each version
- #[serde(rename = "descs")]
- version_description: HashMap<VirtualFileVersion, VirtualFileVersionDescription>,
-
- /// Histories
- #[serde(rename = "histories")]
- histories: Vec<VirtualFileVersion>,
-}
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-pub struct VirtualFileVersionDescription {
- /// The member who created this version
- #[serde(rename = "creator")]
- pub creator: MemberId,
-
- /// The description of this version
- #[serde(rename = "desc")]
- 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(KEY_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(KEY_VF_ID, &id.to_string())
- .replace(KEY_VF_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 at least 4 characters
- if first_part.len() < 4 {
- return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Invalid virtual file ID format: first part must have at least 4 characters",
- ))?;
- }
-
- // Take only the first 4 characters and split into two 2-character chunks
- let first_four = &first_part[0..4];
- let mut path = String::new();
- for i in (0..first_four.len()).step_by(2) {
- if i > 0 {
- path.push('/');
- }
- path.push_str(&first_four[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(KEY_VF_ID, &id.to_string())
- .replace(KEY_VF_INDEX, &Self::vf_index(id).unwrap_or_default())
- .replace(KEY_VF_VERSION, &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(KEY_VF_ID, &id.to_string())
- .replace(KEY_VF_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> {
- 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, &DEFAULT_VF_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(
- DEFAULT_VF_VERSION.to_string(),
- VirtualFileVersionDescription {
- creator: member_id.clone(),
- description: DEFAULT_VF_DESCRIPTION.to_string(),
- },
- );
- // Create metadata
- let mut meta = VirtualFileMeta {
- current_version: DEFAULT_VF_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(DEFAULT_VF_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 = dot_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 latest version of the virtual file
- pub fn version_latest(&self) -> VirtualFileVersion {
- // After creating a virtual file in `update_virtual_file_from_connection`,
- // the Vec will never be empty, so unwrap is allowed here
- self.histories.last().unwrap().clone()
- }
-
- /// 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()
- }
-
- /// Get the member who holds the edit right of the file
- pub fn hold_member(&self) -> &MemberId {
- &self.hold_member
- }
-
- /// Get the version descriptions for all versions
- pub fn version_descriptions(
- &self,
- ) -> &HashMap<VirtualFileVersion, VirtualFileVersionDescription> {
- &self.version_description
- }
-
- /// Get the version description for a given version
- pub fn version_description(
- &self,
- version: VirtualFileVersion,
- ) -> Option<&VirtualFileVersionDescription> {
- let desc = self.version_descriptions();
- desc.get(&version)
- }
-}