From c811c5818d21a67280ef9dd35ad40f6f5411daa5 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sat, 21 Mar 2026 16:37:51 +0800 Subject: Good Bye! Legacy JVCS --- legacy_data/src/data/vault/lock_status.rs | 40 --- legacy_data/src/data/vault/mapping_share.rs | 420 ----------------------- legacy_data/src/data/vault/member_manage.rs | 144 -------- legacy_data/src/data/vault/sheet_manage.rs | 274 --------------- legacy_data/src/data/vault/vault_config.rs | 233 ------------- legacy_data/src/data/vault/virtual_file.rs | 500 ---------------------------- 6 files changed, 1611 deletions(-) delete mode 100644 legacy_data/src/data/vault/lock_status.rs delete mode 100644 legacy_data/src/data/vault/mapping_share.rs delete mode 100644 legacy_data/src/data/vault/member_manage.rs delete mode 100644 legacy_data/src/data/vault/sheet_manage.rs delete mode 100644 legacy_data/src/data/vault/vault_config.rs delete mode 100644 legacy_data/src/data/vault/virtual_file.rs (limited to 'legacy_data/src/data/vault') 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, - - /// 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, -} - -#[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, - - /// Duplicate files exist - pub duplicate_file: Vec, -} - -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 { - 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, 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 { - 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(©_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, - sharer: &MemberId, - description: String, - ) -> Result { - 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 { - 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, 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, 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 { - 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 { - 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>, 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, 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, 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, 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 for bool { - fn from(val: ServiceEnabled) -> Self { - match val { - ServiceEnabled::Enable => true, - ServiceEnabled::Disable => false, - } - } -} - -impl From 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, - - /// 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, - - /// Logger Level - #[serde(rename = "logger_level")] - logger_level: Option, - - /// 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, // 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, // 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) { - 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 { - &self.vault_host_list - } - - /// Set vault admin list - pub fn set_vault_host_list(&mut self, vault_host_list: Vec) { - 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, - - /// Histories - #[serde(rename = "histories")] - histories: Vec, -} - -#[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 { - 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 { - // 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, 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 { - 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 { - 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::::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 { - 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 { - self.current_vault.virtual_file_meta(&self.id).await - } -} - -impl VirtualFileMeta { - /// Get all versions of the virtual file - pub fn versions(&self) -> &Vec { - &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 { - 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 { - 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 { - &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) - } -} -- cgit