summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-11-07 13:24:43 +0800
committer魏曹先生 <1992414357@qq.com>2025-11-07 13:24:43 +0800
commit9e5a374164aca71ec99aa8b46e7932a1b74f68cc (patch)
tree04d72a86b09b5d434e8317f2e2cfafe5e6522d36
parent898969ffe4a7ac007e53fe1fc1cb121971d6d8ed (diff)
Add path formatting utility and local sheet management
- Implement format_path_str function to clean and normalize file paths - Add LocalSheet struct for tracking local file metadata - Support CRUD operations on local sheet mappings - Integrate path formatting into local sheet operations
-rw-r--r--crates/utils/string_proc/src/format_path.rs64
-rw-r--r--crates/utils/string_proc/src/lib.rs1
-rw-r--r--crates/vcs_data/src/data/local.rs58
-rw-r--r--crates/vcs_data/src/data/local/latest_info.rs2
-rw-r--r--crates/vcs_data/src/data/local/local_sheet.rs107
-rw-r--r--crates/vcs_data/src/data/local/member_held.rs2
6 files changed, 219 insertions, 15 deletions
diff --git a/crates/utils/string_proc/src/format_path.rs b/crates/utils/string_proc/src/format_path.rs
new file mode 100644
index 0000000..2d8927b
--- /dev/null
+++ b/crates/utils/string_proc/src/format_path.rs
@@ -0,0 +1,64 @@
+use std::path::PathBuf;
+
+/// Format path str
+pub fn format_path_str(path: impl Into<String>) -> Result<String, std::io::Error> {
+ let path_str = path.into();
+
+ // ANSI Strip
+ let cleaned = strip_ansi_escapes::strip(&path_str);
+ let path_without_ansi = String::from_utf8(cleaned)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+
+ let path_with_forward_slash = path_without_ansi.replace('\\', "/");
+ let mut result = String::new();
+ let mut prev_char = '\0';
+
+ for c in path_with_forward_slash.chars() {
+ if c == '/' && prev_char == '/' {
+ continue;
+ }
+ result.push(c);
+ prev_char = c;
+ }
+
+ let unfriendly_chars = ['*', '?', '"', '<', '>', '|'];
+ result = result
+ .chars()
+ .filter(|c| !unfriendly_chars.contains(c))
+ .collect();
+
+ if result.ends_with('/') {
+ Ok(result)
+ } else {
+ Ok(result)
+ }
+}
+
+pub fn format_path(path: impl Into<PathBuf>) -> Result<PathBuf, std::io::Error> {
+ let path_str = format_path_str(path.into().display().to_string())?;
+ Ok(PathBuf::from(path_str))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_format_path() -> Result<(), std::io::Error> {
+ assert_eq!(format_path_str("C:\\Users\\\\test")?, "C:/Users/test");
+
+ assert_eq!(
+ format_path_str("/path/with/*unfriendly?chars")?,
+ "/path/with/unfriendlychars"
+ );
+
+ assert_eq!(format_path_str("\x1b[31m/path\x1b[0m")?, "/path");
+ assert_eq!(format_path_str("/home/user/dir/")?, "/home/user/dir/");
+ assert_eq!(
+ format_path_str("/home/user/file.txt")?,
+ "/home/user/file.txt"
+ );
+
+ Ok(())
+ }
+}
diff --git a/crates/utils/string_proc/src/lib.rs b/crates/utils/string_proc/src/lib.rs
index e5559b9..76588c1 100644
--- a/crates/utils/string_proc/src/lib.rs
+++ b/crates/utils/string_proc/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod format_path;
pub mod format_processer;
pub mod macros;
pub mod simple_processer;
diff --git a/crates/vcs_data/src/data/local.rs b/crates/vcs_data/src/data/local.rs
index 96080b2..76711a7 100644
--- a/crates/vcs_data/src/data/local.rs
+++ b/crates/vcs_data/src/data/local.rs
@@ -1,13 +1,20 @@
-use std::{env::current_dir, path::PathBuf, sync::Arc};
+use std::{collections::HashMap, env::current_dir, path::PathBuf, sync::Arc};
use cfg_file::config::ConfigFile;
use tokio::{fs, sync::Mutex};
use vcs_docs::docs::READMES_LOCAL_WORKSPACE_TODOLIST;
use crate::{
- constants::{CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE},
+ constants::{CLIENT_FILE_LOCAL_SHEET, CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE},
current::{current_local_path, find_local_path},
- data::local::config::LocalConfig,
+ data::{
+ local::{
+ config::LocalConfig,
+ local_sheet::{LocalSheet, LocalSheetData},
+ },
+ member::MemberId,
+ sheet::SheetName,
+ },
};
pub mod cached_sheet;
@@ -16,6 +23,9 @@ pub mod latest_info;
pub mod local_sheet;
pub mod member_held;
+const SHEET_NAME: &str = "{sheet_name}";
+const ACCOUNT_NAME: &str = "{account}";
+
pub struct LocalWorkspace {
config: Arc<Mutex<LocalConfig>>,
local_path: PathBuf,
@@ -94,4 +104,46 @@ impl LocalWorkspace {
Self::setup_local_workspace(current_dir()?).await?;
Ok(())
}
+
+ /// Get the path to a local sheet.
+ pub fn local_sheet_path(&self, member: &MemberId, sheet: &SheetName) -> PathBuf {
+ let result = self.local_path.join(
+ CLIENT_FILE_LOCAL_SHEET
+ .replace(ACCOUNT_NAME, member)
+ .replace(SHEET_NAME, sheet),
+ );
+ result
+ }
+
+ /// Read or initialize a local sheet.
+ pub async fn local_sheet(
+ &self,
+ member: &MemberId,
+ sheet: &SheetName,
+ ) -> Result<LocalSheet<'_>, std::io::Error> {
+ let local_sheet_path = self.local_sheet_path(member, sheet);
+
+ if !local_sheet_path.exists() {
+ let sheet_data = LocalSheetData {
+ mapping: HashMap::new(),
+ };
+ LocalSheetData::write_to(&sheet_data, local_sheet_path).await?;
+ return Ok(LocalSheet {
+ local_workspace: self,
+ member: member.clone(),
+ sheet_name: sheet.clone(),
+ data: sheet_data,
+ });
+ }
+
+ let data = LocalSheetData::read_from(&local_sheet_path).await?;
+ let local_sheet = LocalSheet {
+ local_workspace: self,
+ member: member.clone(),
+ sheet_name: sheet.clone(),
+ data,
+ };
+
+ Ok(local_sheet)
+ }
}
diff --git a/crates/vcs_data/src/data/local/latest_info.rs b/crates/vcs_data/src/data/local/latest_info.rs
index 28434b0..e8fa641 100644
--- a/crates/vcs_data/src/data/local/latest_info.rs
+++ b/crates/vcs_data/src/data/local/latest_info.rs
@@ -28,8 +28,6 @@ pub struct LatestInfo {
pub vault_members: Vec<Member>,
}
-impl LatestInfo {}
-
#[derive(Default, Serialize, Deserialize)]
pub struct SheetInfo {
pub sheet_name: SheetName,
diff --git a/crates/vcs_data/src/data/local/local_sheet.rs b/crates/vcs_data/src/data/local/local_sheet.rs
index 63b73ac..bfe8d01 100644
--- a/crates/vcs_data/src/data/local/local_sheet.rs
+++ b/crates/vcs_data/src/data/local/local_sheet.rs
@@ -1,45 +1,61 @@
-use std::{collections::HashMap, path::PathBuf};
+use std::{collections::HashMap, io::Error, path::PathBuf};
use ::serde::{Deserialize, Serialize};
-use cfg_file::ConfigFile;
+use cfg_file::{ConfigFile, config::ConfigFile};
use chrono::NaiveDate;
+use string_proc::format_path::format_path;
use crate::{
constants::CLIENT_FILE_LOCAL_SHEET_NOSET,
- data::vault::virtual_file::{VirtualFileId, VirtualFileVersionDescription},
+ data::{
+ local::LocalWorkspace,
+ member::MemberId,
+ vault::virtual_file::{VirtualFileId, VirtualFileVersionDescription},
+ },
};
pub type LocalFilePathBuf = PathBuf;
+/// # Local Sheet
+/// Local sheet information, used to record metadata of actual local files,
+/// to compare with upstream information for more optimized file submission,
+/// and to determine whether files need to be updated or submitted.
+pub struct LocalSheet<'a> {
+ pub(crate) local_workspace: &'a LocalWorkspace,
+ pub(crate) member: MemberId,
+ pub(crate) sheet_name: String,
+ pub(crate) data: LocalSheetData,
+}
+
#[derive(Debug, Default, Serialize, Deserialize, ConfigFile)]
#[cfg_file(path = CLIENT_FILE_LOCAL_SHEET_NOSET)] // Do not use LocalSheet::write or LocalSheet::read
-pub struct LocalSheet {
+pub struct LocalSheetData {
/// Local file path to virtual file ID mapping.
#[serde(rename = "mapping")]
- mapping: HashMap<LocalFilePathBuf, MappingMetaData>, // Path to VFID
+ pub(crate) mapping: HashMap<LocalFilePathBuf, MappingMetaData>, // Path to VFID
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MappingMetaData {
/// Hash value generated immediately after the file is downloaded to the local workspace
#[serde(rename = "hash")]
- hash_when_updated: String,
+ pub(crate) hash_when_updated: String,
/// Time when the file was downloaded to the local workspace
#[serde(rename = "date", with = "naive_date_serde")]
- date_when_updated: NaiveDate,
+ pub(crate) date_when_updated: NaiveDate,
/// Size of the file when downloaded to the local workspace
#[serde(rename = "size")]
- size_when_updated: u64,
+ pub(crate) size_when_updated: u64,
/// Version description when the file was downloaded to the local workspace
#[serde(rename = "version")]
- version_desc_when_updated: VirtualFileVersionDescription,
+ pub(crate) version_desc_when_updated: VirtualFileVersionDescription,
/// Virtual file ID corresponding to the local path
#[serde(rename = "id")]
- mapping_vfid: VirtualFileId,
+ pub(crate) mapping_vfid: VirtualFileId,
}
mod naive_date_serde {
@@ -61,3 +77,74 @@ mod naive_date_serde {
NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(serde::de::Error::custom)
}
}
+
+impl<'a> LocalSheet<'a> {
+ /// Add mapping to local sheet data
+ pub fn add_mapping(
+ &mut self,
+ path: LocalFilePathBuf,
+ mapping: MappingMetaData,
+ ) -> Result<(), std::io::Error> {
+ let path = format_path(path)?;
+ if self.data.mapping.contains_key(&path) {
+ return Err(Error::new(
+ std::io::ErrorKind::AlreadyExists,
+ "Mapping already exists",
+ ));
+ }
+
+ self.data.mapping.insert(path, mapping);
+ Ok(())
+ }
+
+ /// Move mapping to other path
+ pub fn move_mapping(
+ &mut self,
+ from: LocalFilePathBuf,
+ to: LocalFilePathBuf,
+ ) -> Result<(), std::io::Error> {
+ let from = format_path(from)?;
+ let to = format_path(to)?;
+ if self.data.mapping.contains_key(&to) {
+ return Err(Error::new(
+ std::io::ErrorKind::AlreadyExists,
+ "To path already exists.",
+ ));
+ }
+
+ let Some(old_value) = self.data.mapping.remove(&from) else {
+ return Err(Error::new(
+ std::io::ErrorKind::NotFound,
+ "From path is not found.",
+ ));
+ };
+
+ self.data.mapping.insert(to, old_value);
+
+ Ok(())
+ }
+
+ /// Get muttable mapping data
+ pub fn mapping_data_mut(
+ &mut self,
+ path: LocalFilePathBuf,
+ ) -> Result<&mut MappingMetaData, std::io::Error> {
+ let path = format_path(path)?;
+ let Some(data) = self.data.mapping.get_mut(&path) else {
+ return Err(Error::new(
+ std::io::ErrorKind::NotFound,
+ "Path is not found.",
+ ));
+ };
+ Ok(data)
+ }
+
+ /// Persist the sheet to disk
+ pub async fn persist(&mut self) -> Result<(), std::io::Error> {
+ let _path = self
+ .local_workspace
+ .local_sheet_path(&self.member, &self.sheet_name);
+ LocalSheetData::write_to(&self.data, &self.sheet_name).await?;
+ Ok(())
+ }
+}
diff --git a/crates/vcs_data/src/data/local/member_held.rs b/crates/vcs_data/src/data/local/member_held.rs
index e9e384d..37bc18e 100644
--- a/crates/vcs_data/src/data/local/member_held.rs
+++ b/crates/vcs_data/src/data/local/member_held.rs
@@ -8,6 +8,8 @@ use crate::{
data::{member::MemberId, vault::virtual_file::VirtualFileId},
};
+/// # Member Held Information
+/// Records the files held by the member, used for permission validation
#[derive(Debug, Default, Clone, Serialize, Deserialize, ConfigFile)]
#[cfg_file(path = CLIENT_FILE_MEMBER_HELD_NOSET)]
pub struct MemberHeld {