summaryrefslogtreecommitdiff
path: root/legacy_data/src/data/local
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-21 16:37:51 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-21 16:37:51 +0800
commitc811c5818d21a67280ef9dd35ad40f6f5411daa5 (patch)
tree46062311242da24771466ad99be26b530e83d497 /legacy_data/src/data/local
parent9a60751a901f568bdeb154c4115235d4f3a0f8b9 (diff)
Good Bye! Legacy JVCS
Diffstat (limited to 'legacy_data/src/data/local')
-rw-r--r--legacy_data/src/data/local/align_tasks.rs110
-rw-r--r--legacy_data/src/data/local/cached_sheet.rs100
-rw-r--r--legacy_data/src/data/local/latest_file_data.rs103
-rw-r--r--legacy_data/src/data/local/latest_info.rs81
-rw-r--r--legacy_data/src/data/local/local_files.rs148
-rw-r--r--legacy_data/src/data/local/local_sheet.rs450
-rw-r--r--legacy_data/src/data/local/modified_status.rs30
-rw-r--r--legacy_data/src/data/local/workspace_analyzer.rs358
-rw-r--r--legacy_data/src/data/local/workspace_config.rs374
9 files changed, 0 insertions, 1754 deletions
diff --git a/legacy_data/src/data/local/align_tasks.rs b/legacy_data/src/data/local/align_tasks.rs
deleted file mode 100644
index b72804c..0000000
--- a/legacy_data/src/data/local/align_tasks.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use std::{
- collections::{HashMap, HashSet},
- path::PathBuf,
-};
-
-use data_struct::data_sort::quick_sort_with_cmp;
-
-use crate::data::local::workspace_analyzer::AnalyzeResult;
-
-pub type AlignTaskName = String;
-pub type AlignPathBuf = PathBuf;
-pub type AlignLostPathBuf = PathBuf;
-pub type AlignCreatedPathBuf = PathBuf;
-
-pub struct AlignTasks {
- pub created: Vec<(AlignTaskName, AlignPathBuf)>,
- pub lost: Vec<(AlignTaskName, AlignPathBuf)>,
- pub moved: Vec<(AlignTaskName, (AlignLostPathBuf, AlignCreatedPathBuf))>,
- pub erased: Vec<(AlignTaskName, AlignPathBuf)>,
-}
-
-impl AlignTasks {
- pub fn clone_from_analyze_result(result: &AnalyzeResult) -> Self {
- AlignTasks {
- created: path_hash_set_sort_helper(result.created.clone(), "created"),
- lost: path_hash_set_sort_helper(result.lost.clone(), "lost"),
- moved: path_hash_map_sort_helper(result.moved.clone(), "moved"),
- erased: path_hash_set_sort_helper(result.erased.clone(), "erased"),
- }
- }
-
- pub fn from_analyze_result(result: AnalyzeResult) -> Self {
- AlignTasks {
- created: path_hash_set_sort_helper(result.created, "created"),
- lost: path_hash_set_sort_helper(result.lost, "lost"),
- moved: path_hash_map_sort_helper(result.moved, "moved"),
- erased: path_hash_set_sort_helper(result.erased, "erased"),
- }
- }
-}
-
-fn path_hash_set_sort_helper(
- hash_set: HashSet<PathBuf>,
- prefix: impl Into<String>,
-) -> Vec<(String, PathBuf)> {
- let prefix_str = prefix.into();
- let mut vec: Vec<(String, PathBuf)> = hash_set
- .into_iter()
- .map(|path| {
- let hash = sha1_hash::calc_sha1_string(path.to_string_lossy());
- let hash_prefix: String = hash.chars().take(8).collect();
- let name = format!("{}:{}", prefix_str, hash_prefix);
- (name, path)
- })
- .collect();
-
- quick_sort_with_cmp(&mut vec, false, |a, b| {
- // Compare by path depth first
- let a_depth = a.1.components().count();
- let b_depth = b.1.components().count();
-
- if a_depth != b_depth {
- return if a_depth < b_depth { -1 } else { 1 };
- }
-
- // If same depth, compare lexicographically
- match a.1.cmp(&b.1) {
- std::cmp::Ordering::Less => -1,
- std::cmp::Ordering::Equal => 0,
- std::cmp::Ordering::Greater => 1,
- }
- });
-
- vec
-}
-
-fn path_hash_map_sort_helper(
- hash_map: HashMap<String, (PathBuf, PathBuf)>,
- prefix: impl Into<String>,
-) -> Vec<(String, (PathBuf, PathBuf))> {
- let prefix_str = prefix.into();
- let mut vec: Vec<(String, (PathBuf, PathBuf))> = hash_map
- .into_values()
- .map(|(path1, path2)| {
- let hash = sha1_hash::calc_sha1_string(path1.to_string_lossy());
- let hash_prefix: String = hash.chars().take(8).collect();
- let name = format!("{}:{}", prefix_str, hash_prefix);
- (name, (path1, path2))
- })
- .collect();
-
- quick_sort_with_cmp(&mut vec, false, |a, b| {
- // Compare by first PathBuf's path depth first
- let a_depth = a.1.0.components().count();
- let b_depth = b.1.0.components().count();
-
- if a_depth != b_depth {
- return if a_depth < b_depth { -1 } else { 1 };
- }
-
- // If same depth, compare lexicographically by first PathBuf
- match a.1.0.cmp(&b.1.0) {
- std::cmp::Ordering::Less => -1,
- std::cmp::Ordering::Equal => 0,
- std::cmp::Ordering::Greater => 1,
- }
- });
-
- vec
-}
diff --git a/legacy_data/src/data/local/cached_sheet.rs b/legacy_data/src/data/local/cached_sheet.rs
deleted file mode 100644
index e67861b..0000000
--- a/legacy_data/src/data/local/cached_sheet.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use std::{
- io::{Error, ErrorKind},
- path::PathBuf,
-};
-
-use cfg_file::config::ConfigFile;
-use just_fmt::{fmt_path::fmt_path, snake_case};
-use tokio::fs;
-
-use crate::{
- constants::{
- CLIENT_FILE_CACHED_SHEET, CLIENT_PATH_CACHED_SHEET, CLIENT_SUFFIX_CACHED_SHEET_FILE,
- KEY_SHEET_NAME,
- },
- data::sheet::{SheetData, SheetName},
- env::current_local_path,
-};
-
-pub type CachedSheetPathBuf = PathBuf;
-
-/// # Cached Sheet
-/// The cached sheet is a read-only version cloned from the upstream repository to the local environment,
-/// automatically generated during update operations,
-/// which records the latest Sheet information stored locally to accelerate data access and reduce network requests.
-pub struct CachedSheet;
-
-impl CachedSheet {
- /// Read the cached sheet data.
- pub async fn cached_sheet_data(sheet_name: &SheetName) -> Result<SheetData, std::io::Error> {
- let sheet_name = snake_case!(sheet_name.clone());
-
- let Some(path) = Self::cached_sheet_path(sheet_name) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Local workspace not found!",
- ));
- };
- let data = SheetData::read_from(path).await?;
- Ok(data)
- }
-
- /// Get the path to the cached sheet file.
- pub fn cached_sheet_path(sheet_name: SheetName) -> Option<PathBuf> {
- let current_workspace = current_local_path()?;
- Some(
- current_workspace
- .join(CLIENT_FILE_CACHED_SHEET.replace(KEY_SHEET_NAME, &sheet_name.to_string())),
- )
- }
-
- /// Get all cached sheet names
- pub async fn cached_sheet_names() -> Result<Vec<SheetName>, std::io::Error> {
- let mut dir = fs::read_dir(CLIENT_PATH_CACHED_SHEET).await?;
- let mut sheet_names = Vec::new();
-
- while let Some(entry) = dir.next_entry().await? {
- let path = entry.path();
-
- if path.is_file()
- && let Some(file_name) = path.file_name().and_then(|n| n.to_str())
- && file_name.ends_with(CLIENT_SUFFIX_CACHED_SHEET_FILE)
- {
- let name_without_ext = file_name
- .trim_end_matches(CLIENT_SUFFIX_CACHED_SHEET_FILE)
- .to_string();
- sheet_names.push(name_without_ext);
- }
- }
-
- Ok(sheet_names)
- }
-
- /// Get all cached sheet paths
- pub async fn cached_sheet_paths() -> Result<Vec<CachedSheetPathBuf>, std::io::Error> {
- let mut dir = fs::read_dir(CLIENT_PATH_CACHED_SHEET).await?;
- let mut sheet_paths = Vec::new();
- let Some(workspace_path) = current_local_path() else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Local workspace not found!",
- ));
- };
-
- while let Some(entry) = dir.next_entry().await? {
- let path = entry.path();
-
- if path.is_file()
- && let Some(file_name) = path.file_name().and_then(|n| n.to_str())
- && file_name.ends_with(CLIENT_SUFFIX_CACHED_SHEET_FILE)
- {
- sheet_paths
- .push(fmt_path(workspace_path.join(path)).map_err(|e| {
- std::io::Error::new(ErrorKind::InvalidInput, e.to_string())
- })?);
- }
- }
-
- Ok(sheet_paths)
- }
-}
diff --git a/legacy_data/src/data/local/latest_file_data.rs b/legacy_data/src/data/local/latest_file_data.rs
deleted file mode 100644
index f9b3aeb..0000000
--- a/legacy_data/src/data/local/latest_file_data.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::{collections::HashMap, io::Error, path::PathBuf};
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-
-use crate::{
- constants::{CLIENT_FILE_LATEST_DATA, CLIENT_FILE_MEMBER_HELD_NOSET, KEY_ACCOUNT},
- data::{
- member::MemberId,
- vault::virtual_file::{VirtualFileId, VirtualFileVersion, VirtualFileVersionDescription},
- },
- env::current_local_path,
-};
-
-/// # Latest file data
-/// Records the file holder and the latest version for permission and update checks
-#[derive(Debug, Default, Clone, Serialize, Deserialize, ConfigFile)]
-#[cfg_file(path = CLIENT_FILE_MEMBER_HELD_NOSET)]
-pub struct LatestFileData {
- /// File holding status
- #[serde(rename = "held")]
- held_status: HashMap<VirtualFileId, HeldStatus>,
-
- /// File version
- #[serde(rename = "ver")]
- versions: HashMap<VirtualFileId, VirtualFileVersion>,
-
- /// File histories and descriptions
- #[serde(rename = "his")]
- histories: HashMap<VirtualFileId, Vec<(VirtualFileVersion, VirtualFileVersionDescription)>>,
-}
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-pub enum HeldStatus {
- #[serde(rename = "Hold")]
- HeldWith(MemberId), // Held, status changes are sync to the client
-
- #[serde(rename = "None")]
- NotHeld, // Not held, status changes are sync to the client
-
- #[default]
- #[serde(rename = "Unknown")]
- WantedToKnow, // Holding status is unknown, notify server must inform client
-}
-
-impl LatestFileData {
- /// Get the path to the file holding the held status information for the given member.
- pub fn data_path(account: &MemberId) -> Result<PathBuf, std::io::Error> {
- let Some(local_path) = current_local_path() else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Workspace not found.",
- ));
- };
- Ok(local_path.join(CLIENT_FILE_LATEST_DATA.replace(KEY_ACCOUNT, account)))
- }
-
- /// Get the member who holds the file with the given ID.
- pub fn file_holder(&self, vfid: &VirtualFileId) -> Option<&MemberId> {
- self.held_status.get(vfid).and_then(|status| match status {
- HeldStatus::HeldWith(id) => Some(id),
- _ => None,
- })
- }
-
- /// Get the version of the file with the given ID.
- pub fn file_version(&self, vfid: &VirtualFileId) -> Option<&VirtualFileVersion> {
- self.versions.get(vfid)
- }
-
- /// Get the version of the file with the given ID.
- pub fn file_histories(
- &self,
- vfid: &VirtualFileId,
- ) -> Option<&Vec<(VirtualFileVersion, VirtualFileVersionDescription)>> {
- self.histories.get(vfid)
- }
-
- /// Update the held status of the files.
- pub fn update_info(
- &mut self,
- map: HashMap<
- VirtualFileId,
- (
- Option<MemberId>,
- VirtualFileVersion,
- Vec<(VirtualFileVersion, VirtualFileVersionDescription)>,
- ),
- >,
- ) {
- for (vfid, (member_id, version, desc)) in map {
- self.held_status.insert(
- vfid.clone(),
- match member_id {
- Some(member_id) => HeldStatus::HeldWith(member_id),
- None => HeldStatus::NotHeld,
- },
- );
- self.versions.insert(vfid.clone(), version);
- self.histories.insert(vfid, desc);
- }
- }
-}
diff --git a/legacy_data/src/data/local/latest_info.rs b/legacy_data/src/data/local/latest_info.rs
deleted file mode 100644
index 5748793..0000000
--- a/legacy_data/src/data/local/latest_info.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use std::{
- collections::{HashMap, HashSet},
- path::{Path, PathBuf},
- time::SystemTime,
-};
-
-use cfg_file::ConfigFile;
-use serde::{Deserialize, Serialize};
-
-use crate::{
- constants::{CLIENT_FILE_LATEST_INFO, CLIENT_FILE_LATEST_INFO_NOSET, KEY_ACCOUNT},
- data::{
- member::{Member, MemberId},
- sheet::{SheetData, SheetName, SheetPathBuf},
- vault::{
- mapping_share::{Share, SheetShareId},
- virtual_file::VirtualFileId,
- },
- },
-};
-
-/// # Latest Info
-/// Locally cached latest information,
-/// used to cache personal information from upstream for querying and quickly retrieving member information.
-#[derive(Default, Serialize, Deserialize, ConfigFile)]
-#[cfg_file(path = CLIENT_FILE_LATEST_INFO_NOSET)]
-pub struct LatestInfo {
- // Sheets
- /// Visible sheets,
- /// indicating which sheets I can edit
- #[serde(rename = "my")]
- pub visible_sheets: Vec<SheetName>,
-
- /// Invisible sheets,
- /// indicating which sheets I can export files to (these sheets are not readable to me)
- #[serde(rename = "others")]
- pub invisible_sheets: Vec<SheetInfo>,
-
- /// Reference sheets,
- /// indicating sheets owned by the host, visible to everyone,
- /// but only the host can modify or add mappings within them
- #[serde(rename = "refsheets")]
- pub reference_sheets: HashSet<SheetName>,
-
- /// Reference sheet data, indicating what files I can get from the reference sheet
- #[serde(rename = "ref")]
- pub ref_sheet_content: SheetData,
-
- /// Reverse mapping from virtual file IDs to actual paths in reference sheets
- #[serde(rename = "ref_vfs")]
- pub ref_sheet_vfs_mapping: HashMap<VirtualFileId, SheetPathBuf>,
-
- /// Shares in my sheets, indicating which external merge requests have entries that I can view
- #[serde(rename = "shares")]
- pub shares_in_my_sheets: HashMap<SheetName, HashMap<SheetShareId, Share>>,
-
- /// Update instant
- #[serde(rename = "update")]
- pub update_instant: Option<SystemTime>,
-
- // Members
- /// All member information of the vault, allowing me to contact them more conveniently
- #[serde(rename = "members")]
- pub vault_members: Vec<Member>,
-}
-
-impl LatestInfo {
- /// Get the path to the latest info file for a given workspace and member ID
- pub fn latest_info_path(local_workspace_path: &Path, member_id: &MemberId) -> PathBuf {
- local_workspace_path.join(CLIENT_FILE_LATEST_INFO.replace(KEY_ACCOUNT, member_id))
- }
-}
-
-#[derive(Default, Serialize, Deserialize)]
-pub struct SheetInfo {
- #[serde(rename = "name")]
- pub sheet_name: SheetName,
-
- #[serde(rename = "holder")]
- pub holder_name: Option<MemberId>,
-}
diff --git a/legacy_data/src/data/local/local_files.rs b/legacy_data/src/data/local/local_files.rs
deleted file mode 100644
index 2f02b32..0000000
--- a/legacy_data/src/data/local/local_files.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-use std::path::{Path, PathBuf};
-
-use just_fmt::fmt_path::fmt_path;
-use tokio::fs;
-
-use crate::constants::CLIENT_FOLDER_WORKSPACE_ROOT_NAME;
-
-pub struct RelativeFiles {
- pub(crate) files: Vec<PathBuf>,
-}
-
-impl IntoIterator for RelativeFiles {
- type Item = PathBuf;
- type IntoIter = std::vec::IntoIter<Self::Item>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.files.into_iter()
- }
-}
-
-impl RelativeFiles {
- pub fn iter(&self) -> std::slice::Iter<'_, PathBuf> {
- self.files.iter()
- }
-}
-
-/// Read the relative paths within the project from the input file list
-pub async fn get_relative_paths(local_path: &PathBuf, paths: &[PathBuf]) -> Option<RelativeFiles> {
- // Get Relative Paths
- let Ok(paths) = format_input_paths_and_ignore_outside_paths(local_path, paths).await else {
- return None;
- };
- let files: Vec<PathBuf> = abs_paths_to_abs_files(paths).await;
- let Ok(files) = parse_to_relative(local_path, files) else {
- return None;
- };
- Some(RelativeFiles { files })
-}
-
-/// Normalize the input paths
-async fn format_input_paths(
- local_path: &Path,
- track_files: &[PathBuf],
-) -> Result<Vec<PathBuf>, std::io::Error> {
- let current_dir = local_path;
-
- let mut real_paths = Vec::new();
- for file in track_files {
- let path = current_dir.join(file);
-
- // Skip paths that contain .jv directories
- if path.components().any(|component| {
- if let std::path::Component::Normal(name) = component {
- name.to_str() == Some(CLIENT_FOLDER_WORKSPACE_ROOT_NAME)
- } else {
- false
- }
- }) {
- continue;
- }
-
- match fmt_path(path) {
- Ok(path) => real_paths.push(path),
- Err(e) => {
- return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidData,
- format!("Failed to format path: {}", e),
- ));
- }
- }
- }
-
- Ok(real_paths)
-}
-
-/// Ignore files outside the workspace
-async fn format_input_paths_and_ignore_outside_paths(
- local_path: &PathBuf,
- files: &[PathBuf],
-) -> Result<Vec<PathBuf>, std::io::Error> {
- let result = format_input_paths(local_path, files).await?;
- let result: Vec<PathBuf> = result
- .into_iter()
- .filter(|path| path.starts_with(local_path))
- .collect();
- Ok(result)
-}
-
-/// Normalize the input paths to relative paths
-fn parse_to_relative(
- local_dir: &PathBuf,
- files: Vec<PathBuf>,
-) -> Result<Vec<PathBuf>, std::io::Error> {
- let result: Result<Vec<PathBuf>, _> = files
- .iter()
- .map(|p| {
- p.strip_prefix(local_dir)
- .map(|relative| relative.to_path_buf())
- .map_err(|_| {
- std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Path prefix stripping failed",
- )
- })
- })
- .collect();
-
- result
-}
-
-/// Convert absolute paths to absolute file paths, expanding directories to their contained files
-async fn abs_paths_to_abs_files(paths: Vec<PathBuf>) -> Vec<PathBuf> {
- let mut files = Vec::new();
-
- for path in paths {
- if !path.exists() {
- continue;
- }
-
- let metadata = match fs::metadata(&path).await {
- Ok(meta) => meta,
- Err(_) => continue,
- };
-
- if metadata.is_file() {
- files.push(path);
- } else if metadata.is_dir() {
- let walker = walkdir::WalkDir::new(&path);
- for entry in walker.into_iter().filter_map(|e| e.ok()) {
- if entry.path().components().any(|component| {
- if let std::path::Component::Normal(name) = component {
- name == CLIENT_FOLDER_WORKSPACE_ROOT_NAME
- } else {
- false
- }
- }) {
- continue;
- }
-
- if entry.file_type().is_file() {
- files.push(entry.path().to_path_buf());
- }
- }
- }
- }
-
- files
-}
diff --git a/legacy_data/src/data/local/local_sheet.rs b/legacy_data/src/data/local/local_sheet.rs
deleted file mode 100644
index eee0866..0000000
--- a/legacy_data/src/data/local/local_sheet.rs
+++ /dev/null
@@ -1,450 +0,0 @@
-use std::{
- collections::HashMap,
- io::{Error, ErrorKind},
- path::PathBuf,
- time::SystemTime,
-};
-
-use ::serde::{Deserialize, Serialize};
-use cfg_file::{ConfigFile, config::ConfigFile};
-use just_fmt::fmt_path::fmt_path;
-
-use crate::{
- constants::CLIENT_FILE_LOCAL_SHEET_NOSET,
- data::{
- local::LocalWorkspace,
- member::MemberId,
- sheet::SheetName,
- vault::virtual_file::{VirtualFileId, VirtualFileVersion, VirtualFileVersionDescription},
- },
-};
-
-pub type LocalFilePathBuf = PathBuf;
-pub type LocalSheetPathBuf = 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,
-}
-
-impl<'a> LocalSheet<'a> {
- /// Create a new LocalSheet instance
- pub fn new(
- local_workspace: &'a LocalWorkspace,
- member: MemberId,
- sheet_name: String,
- data: LocalSheetData,
- ) -> Self {
- Self {
- local_workspace,
- member,
- sheet_name,
- data,
- }
- }
-}
-
-#[derive(Debug, Default, Serialize, Deserialize, ConfigFile, Clone)]
-#[cfg_file(path = CLIENT_FILE_LOCAL_SHEET_NOSET)] // Do not use LocalSheet::write or LocalSheet::read
-pub struct LocalSheetData {
- /// Local file path to metadata mapping.
- #[serde(rename = "map")]
- pub(crate) mapping: HashMap<LocalFilePathBuf, LocalMappingMetadata>,
-
- #[serde(rename = "vfs")]
- pub(crate) vfs: HashMap<VirtualFileId, LocalFilePathBuf>,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct LocalMappingMetadata {
- /// Hash value generated immediately after the file is downloaded to the local workspace
- #[serde(rename = "base_hash")]
- pub(crate) hash_when_updated: String,
-
- /// Time when the file was downloaded to the local workspace
- #[serde(rename = "time")]
- pub(crate) time_when_updated: SystemTime,
-
- /// Size of the file when downloaded to the local workspace
- #[serde(rename = "size")]
- pub(crate) size_when_updated: u64,
-
- /// Version description when the file was downloaded to the local workspace
- #[serde(rename = "desc")]
- pub(crate) version_desc_when_updated: VirtualFileVersionDescription,
-
- /// Version when the file was downloaded to the local workspace
- #[serde(rename = "ver")]
- pub(crate) version_when_updated: VirtualFileVersion,
-
- /// Virtual file ID corresponding to the local path
- #[serde(rename = "id")]
- pub(crate) mapping_vfid: VirtualFileId,
-
- /// Latest modifiy check time
- #[serde(rename = "check_time")]
- pub(crate) last_modify_check_time: SystemTime,
-
- /// Latest modifiy check result
- #[serde(rename = "modified")]
- pub(crate) last_modify_check_result: bool,
-
- /// Latest modifiy check hash result
- #[serde(rename = "current_hash")]
- pub(crate) last_modify_check_hash: Option<String>,
-}
-
-impl LocalSheetData {
- /// Wrap LocalSheetData into LocalSheet with workspace, member, and sheet name
- pub fn wrap_to_local_sheet<'a>(
- self,
- workspace: &'a LocalWorkspace,
- member: MemberId,
- sheet_name: SheetName,
- ) -> LocalSheet<'a> {
- LocalSheet {
- local_workspace: workspace,
- member,
- sheet_name,
- data: self,
- }
- }
-}
-
-impl LocalMappingMetadata {
- /// Create a new MappingMetaData instance
- #[allow(clippy::too_many_arguments)]
- pub fn new(
- hash_when_updated: String,
- time_when_updated: SystemTime,
- size_when_updated: u64,
- version_desc_when_updated: VirtualFileVersionDescription,
- version_when_updated: VirtualFileVersion,
- mapping_vfid: VirtualFileId,
- last_modifiy_check_time: SystemTime,
- last_modifiy_check_result: bool,
- ) -> Self {
- Self {
- hash_when_updated,
- time_when_updated,
- size_when_updated,
- version_desc_when_updated,
- version_when_updated,
- mapping_vfid,
- last_modify_check_time: last_modifiy_check_time,
- last_modify_check_result: last_modifiy_check_result,
- last_modify_check_hash: None,
- }
- }
-
- /// Getter for hash_when_updated
- pub fn hash_when_updated(&self) -> &String {
- &self.hash_when_updated
- }
-
- /// Setter for hash_when_updated
- pub fn set_hash_when_updated(&mut self, hash: String) {
- self.hash_when_updated = hash;
- }
-
- /// Getter for date_when_updated
- pub fn time_when_updated(&self) -> &SystemTime {
- &self.time_when_updated
- }
-
- /// Setter for time_when_updated
- pub fn set_time_when_updated(&mut self, time: SystemTime) {
- self.time_when_updated = time;
- }
-
- /// Getter for size_when_updated
- pub fn size_when_updated(&self) -> u64 {
- self.size_when_updated
- }
-
- /// Setter for size_when_updated
- pub fn set_size_when_updated(&mut self, size: u64) {
- self.size_when_updated = size;
- }
-
- /// Getter for version_desc_when_updated
- pub fn version_desc_when_updated(&self) -> &VirtualFileVersionDescription {
- &self.version_desc_when_updated
- }
-
- /// Setter for version_desc_when_updated
- pub fn set_version_desc_when_updated(&mut self, version_desc: VirtualFileVersionDescription) {
- self.version_desc_when_updated = version_desc;
- }
-
- /// Getter for version_when_updated
- pub fn version_when_updated(&self) -> &VirtualFileVersion {
- &self.version_when_updated
- }
-
- /// Setter for version_when_updated
- pub fn set_version_when_updated(&mut self, version: VirtualFileVersion) {
- self.version_when_updated = version;
- }
-
- /// Getter for mapping_vfid
- pub fn mapping_vfid(&self) -> &VirtualFileId {
- &self.mapping_vfid
- }
-
- /// Setter for mapping_vfid
- pub fn set_mapping_vfid(&mut self, vfid: VirtualFileId) {
- self.mapping_vfid = vfid;
- }
-
- /// Getter for last_modifiy_check_time
- pub fn last_modifiy_check_time(&self) -> &SystemTime {
- &self.last_modify_check_time
- }
-
- /// Setter for last_modifiy_check_time
- pub fn set_last_modifiy_check_time(&mut self, time: SystemTime) {
- self.last_modify_check_time = time;
- }
-
- /// Getter for last_modifiy_check_result
- pub fn last_modifiy_check_result(&self) -> bool {
- self.last_modify_check_result
- }
-
- /// Setter for last_modifiy_check_result
- pub fn set_last_modifiy_check_result(&mut self, result: bool) {
- self.last_modify_check_result = result;
- }
-
- /// Getter for last_modifiy_check_hash
- pub fn last_modifiy_check_hash(&self) -> &Option<String> {
- &self.last_modify_check_hash
- }
-
- /// Setter for last_modifiy_check_hash
- pub fn set_last_modifiy_check_hash(&mut self, hash: Option<String>) {
- self.last_modify_check_hash = hash;
- }
-}
-
-impl Default for LocalMappingMetadata {
- fn default() -> Self {
- Self {
- hash_when_updated: Default::default(),
- time_when_updated: SystemTime::now(),
- size_when_updated: Default::default(),
- version_desc_when_updated: Default::default(),
- version_when_updated: Default::default(),
- mapping_vfid: Default::default(),
- last_modify_check_time: SystemTime::now(),
- last_modify_check_result: false,
- last_modify_check_hash: None,
- }
- }
-}
-
-mod instant_serde {
- use serde::{self, Deserialize, Deserializer, Serializer};
- use tokio::time::Instant;
-
- pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- serializer.serialize_u64(instant.elapsed().as_secs())
- }
-
- pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
- where
- D: Deserializer<'de>,
- {
- let secs = u64::deserialize(deserializer)?;
- Ok(Instant::now() - std::time::Duration::from_secs(secs))
- }
-}
-
-impl<'a> From<&'a LocalSheet<'a>> for &'a LocalSheetData {
- fn from(sheet: &'a LocalSheet<'a>) -> Self {
- &sheet.data
- }
-}
-
-impl LocalSheetData {
- /// Add mapping to local sheet data
- pub fn add_mapping(
- &mut self,
- path: &LocalFilePathBuf,
- mapping: LocalMappingMetadata,
- ) -> Result<(), std::io::Error> {
- let path = fmt_path(path)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- if self.mapping.contains_key(&path) || self.vfs.contains_key(&mapping.mapping_vfid) {
- return Err(Error::new(
- std::io::ErrorKind::AlreadyExists,
- "Mapping already exists",
- ));
- }
-
- self.mapping.insert(path.clone(), mapping.clone());
- self.vfs.insert(mapping.mapping_vfid.clone(), path);
- Ok(())
- }
-
- /// Move mapping to other path
- pub fn move_mapping(
- &mut self,
- from: &LocalFilePathBuf,
- to: &LocalFilePathBuf,
- ) -> Result<(), std::io::Error> {
- let from = fmt_path(from)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- let to = fmt_path(to)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- if self.mapping.contains_key(&to) {
- return Err(Error::new(
- std::io::ErrorKind::AlreadyExists,
- "To path already exists.",
- ));
- }
-
- let Some(old_value) = self.mapping.remove(&from) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "From path is not found.",
- ));
- };
-
- // Update vfs mapping
- self.vfs.insert(old_value.mapping_vfid.clone(), to.clone());
- self.mapping.insert(to, old_value);
-
- Ok(())
- }
-
- /// Remove mapping from local sheet
- pub fn remove_mapping(
- &mut self,
- path: &LocalFilePathBuf,
- ) -> Result<LocalMappingMetadata, std::io::Error> {
- let path = fmt_path(path)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- match self.mapping.remove(&path) {
- Some(mapping) => {
- self.vfs.remove(&mapping.mapping_vfid);
- Ok(mapping)
- }
- None => Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Path is not found.",
- )),
- }
- }
-
- /// Get immutable mapping data
- pub fn mapping_data(
- &self,
- path: &LocalFilePathBuf,
- ) -> Result<&LocalMappingMetadata, std::io::Error> {
- let path = fmt_path(path)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- let Some(data) = self.mapping.get(&path) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Path is not found.",
- ));
- };
- Ok(data)
- }
-
- /// Get mutable mapping data
- pub fn mapping_data_mut(
- &mut self,
- path: &LocalFilePathBuf,
- ) -> Result<&mut LocalMappingMetadata, std::io::Error> {
- let path = fmt_path(path)
- .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))?;
- let Some(data) = self.mapping.get_mut(&path) else {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Path is not found.",
- ));
- };
- Ok(data)
- }
-
- /// Get path by VirtualFileId
- pub fn path_by_id(&self, vfid: &VirtualFileId) -> Option<&PathBuf> {
- self.vfs.get(vfid)
- }
-}
-
-impl<'a> LocalSheet<'a> {
- /// Add mapping to local sheet data
- pub fn add_mapping(
- &mut self,
- path: &LocalFilePathBuf,
- mapping: LocalMappingMetadata,
- ) -> Result<(), std::io::Error> {
- self.data.add_mapping(path, mapping)
- }
-
- /// Move mapping to other path
- pub fn move_mapping(
- &mut self,
- from: &LocalFilePathBuf,
- to: &LocalFilePathBuf,
- ) -> Result<(), std::io::Error> {
- self.data.move_mapping(from, to)
- }
-
- /// Remove mapping from local sheet
- pub fn remove_mapping(
- &mut self,
- path: &LocalFilePathBuf,
- ) -> Result<LocalMappingMetadata, std::io::Error> {
- self.data.remove_mapping(path)
- }
-
- /// Get immutable mapping data
- pub fn mapping_data(
- &self,
- path: &LocalFilePathBuf,
- ) -> Result<&LocalMappingMetadata, std::io::Error> {
- self.data.mapping_data(path)
- }
-
- /// Get mutable mapping data
- pub fn mapping_data_mut(
- &mut self,
- path: &LocalFilePathBuf,
- ) -> Result<&mut LocalMappingMetadata, std::io::Error> {
- self.data.mapping_data_mut(path)
- }
-
- /// Write the sheet to disk
- pub async fn write(&mut self) -> Result<(), std::io::Error> {
- let path = self
- .local_workspace
- .local_sheet_path(&self.member, &self.sheet_name);
- self.write_to_path(path).await
- }
-
- /// Write the sheet to custom path
- pub async fn write_to_path(&mut self, path: impl Into<PathBuf>) -> Result<(), std::io::Error> {
- let path = path.into();
- LocalSheetData::write_to(&self.data, path).await?;
- Ok(())
- }
-
- /// Get path by VirtualFileId
- pub fn path_by_id(&self, vfid: &VirtualFileId) -> Option<&PathBuf> {
- self.data.path_by_id(vfid)
- }
-}
diff --git a/legacy_data/src/data/local/modified_status.rs b/legacy_data/src/data/local/modified_status.rs
deleted file mode 100644
index e0e6dd5..0000000
--- a/legacy_data/src/data/local/modified_status.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use crate::{constants::CLIENT_FILE_VAULT_MODIFIED, env::current_local_path};
-
-pub async fn check_vault_modified() -> bool {
- let Some(current_dir) = current_local_path() else {
- return false;
- };
-
- let record_file = current_dir.join(CLIENT_FILE_VAULT_MODIFIED);
- if !record_file.exists() {
- return false;
- }
-
- let Ok(contents) = tokio::fs::read_to_string(&record_file).await else {
- return false;
- };
-
- matches!(contents.trim().to_lowercase().as_str(), "true")
-}
-
-pub async fn sign_vault_modified(modified: bool) {
- let Some(current_dir) = current_local_path() else {
- return;
- };
-
- let record_file = current_dir.join(CLIENT_FILE_VAULT_MODIFIED);
-
- let contents = if modified { "true" } else { "false" };
-
- let _ = tokio::fs::write(&record_file, contents).await;
-}
diff --git a/legacy_data/src/data/local/workspace_analyzer.rs b/legacy_data/src/data/local/workspace_analyzer.rs
deleted file mode 100644
index 6373525..0000000
--- a/legacy_data/src/data/local/workspace_analyzer.rs
+++ /dev/null
@@ -1,358 +0,0 @@
-use std::{
- collections::{HashMap, HashSet},
- io::Error,
- path::PathBuf,
-};
-
-use just_fmt::fmt_path::fmt_path;
-use serde::Serialize;
-use sha1_hash::calc_sha1_multi;
-use walkdir::WalkDir;
-
-use crate::data::{
- local::{LocalWorkspace, cached_sheet::CachedSheet, local_sheet::LocalSheet},
- member::MemberId,
- sheet::{SheetData, SheetName},
- vault::virtual_file::VirtualFileId,
-};
-
-pub type FromRelativePathBuf = PathBuf;
-pub type ToRelativePathBuf = PathBuf;
-pub type CreatedRelativePathBuf = PathBuf;
-pub type LostRelativePathBuf = PathBuf;
-pub type ModifiedRelativePathBuf = PathBuf;
-
-pub struct AnalyzeResult<'a> {
- local_workspace: &'a LocalWorkspace,
-
- /// Moved local files
- pub moved: HashMap<VirtualFileId, (FromRelativePathBuf, ToRelativePathBuf)>,
-
- /// Newly created local files
- pub created: HashSet<CreatedRelativePathBuf>,
-
- /// Lost local files
- pub lost: HashSet<LostRelativePathBuf>,
-
- /// Erased local files
- pub erased: HashSet<LostRelativePathBuf>,
-
- /// Modified local files (excluding moved files)
- /// For files that were both moved and modified, changes can only be detected after LocalSheet mapping is aligned with actual files
- pub modified: HashSet<ModifiedRelativePathBuf>,
-}
-
-#[derive(Serialize, Default)]
-pub struct AnalyzeResultPure {
- /// Moved local files
- pub moved: HashMap<VirtualFileId, (FromRelativePathBuf, ToRelativePathBuf)>,
-
- /// Newly created local files
- pub created: HashSet<CreatedRelativePathBuf>,
-
- /// Lost local files
- pub lost: HashSet<LostRelativePathBuf>,
-
- /// Erased local files
- pub erased: HashSet<LostRelativePathBuf>,
-
- /// Modified local files (excluding moved files)
- /// For files that were both moved and modified, changes can only be detected after LocalSheet mapping is aligned with actual files
- pub modified: HashSet<ModifiedRelativePathBuf>,
-}
-
-impl<'a> From<AnalyzeResult<'a>> for AnalyzeResultPure {
- fn from(result: AnalyzeResult<'a>) -> Self {
- AnalyzeResultPure {
- moved: result.moved,
- created: result.created,
- lost: result.lost,
- erased: result.erased,
- modified: result.modified,
- }
- }
-}
-
-struct AnalyzeContext<'a> {
- member: MemberId,
- sheet_name: SheetName,
- local_sheet: Option<LocalSheet<'a>>,
- cached_sheet_data: Option<SheetData>,
-}
-
-impl<'a> AnalyzeResult<'a> {
- /// Analyze all files, calculate the file information provided
- pub async fn analyze_local_status(
- local_workspace: &'a LocalWorkspace,
- ) -> Result<AnalyzeResult<'a>, std::io::Error> {
- // Workspace
- let workspace = local_workspace;
-
- // Current member, sheet
- let (member, sheet_name) = {
- let mut_workspace = workspace.config.lock().await;
- let member = mut_workspace.current_account();
- let Some(sheet) = mut_workspace.sheet_in_use().clone() else {
- return Err(Error::new(std::io::ErrorKind::NotFound, "Sheet not found"));
- };
- (member, sheet)
- };
-
- // Local files (RelativePaths)
- let local_path = workspace.local_path();
- let file_relative_paths = {
- let mut paths = HashSet::new();
- for entry in WalkDir::new(local_path) {
- let entry = match entry {
- Ok(entry) => entry,
- Err(_) => continue,
- };
-
- // Skip entries that contain ".jv" in their path
- if entry.path().to_string_lossy().contains(".jv") {
- continue;
- }
-
- if entry.file_type().is_file()
- && let Ok(relative_path) = entry.path().strip_prefix(local_path)
- {
- let format = fmt_path(relative_path.to_path_buf());
- let Ok(format) = format else {
- continue;
- };
- paths.insert(format);
- }
- }
-
- paths
- };
-
- // Read local sheet
- let local_sheet = (workspace.local_sheet(&member, &sheet_name).await).ok();
-
- // Read cached sheet
- let cached_sheet_data = match CachedSheet::cached_sheet_data(&sheet_name).await {
- Ok(v) => Some(v),
- Err(_) => {
- return Err(Error::new(
- std::io::ErrorKind::NotFound,
- "Cached sheet not found",
- ));
- }
- };
-
- // Create new result
- let mut result = Self::none_result(workspace);
-
- // Analyze entry
- let mut analyze_ctx = AnalyzeContext {
- member,
- sheet_name,
- local_sheet,
- cached_sheet_data,
- };
- Self::analyze_moved(&mut result, &file_relative_paths, &analyze_ctx, workspace).await?;
- Self::analyze_modified(
- &mut result,
- &file_relative_paths,
- &mut analyze_ctx,
- workspace,
- )
- .await?;
-
- Ok(result)
- }
-
- /// Track file moves by comparing recorded SHA1 hashes with actual file SHA1 hashes
- /// For files that cannot be directly matched, continue searching using fuzzy matching algorithms
- async fn analyze_moved(
- result: &mut AnalyzeResult<'_>,
- file_relative_paths: &HashSet<PathBuf>,
- analyze_ctx: &AnalyzeContext<'a>,
- workspace: &LocalWorkspace,
- ) -> Result<(), std::io::Error> {
- let local_sheet_paths: HashSet<&PathBuf> = match &analyze_ctx.local_sheet {
- Some(local_sheet) => local_sheet.data.mapping.keys().collect(),
- None => HashSet::new(),
- };
- let file_relative_paths_ref: HashSet<&PathBuf> = file_relative_paths.iter().collect();
-
- // Files that exist locally but not in remote
- let mut erased_files: HashSet<PathBuf> = HashSet::new();
-
- if let Some(cached_data) = &analyze_ctx.cached_sheet_data
- && let Some(local_sheet) = &analyze_ctx.local_sheet {
- let cached_sheet_mapping = cached_data.mapping();
- let local_sheet_mapping = &local_sheet.data.mapping;
-
- // Find paths that exist in local sheet but not in cached sheet
- for local_path in local_sheet_mapping.keys() {
- if !cached_sheet_mapping.contains_key(local_path) {
- erased_files.insert(local_path.clone());
- }
- }
- }
-
- // Files that exist in the local sheet but not in reality are considered lost
- let mut lost_files: HashSet<&PathBuf> = local_sheet_paths
- .difference(&file_relative_paths_ref)
- .filter(|&&path| !erased_files.contains(path))
- .cloned()
- .collect();
-
- // Files that exist in reality but not in the local sheet are recorded as newly created
- let mut new_files: HashSet<&PathBuf> = file_relative_paths_ref
- .difference(&local_sheet_paths)
- .cloned()
- .collect();
-
- // Calculate hashes for new files
- let new_files_for_hash: Vec<PathBuf> = new_files
- .iter()
- .map(|p| workspace.local_path.join(p))
- .collect();
- let file_hashes: HashSet<(PathBuf, String)> =
- match calc_sha1_multi::<PathBuf, Vec<PathBuf>>(new_files_for_hash, 8192).await {
- Ok(hash) => hash,
- Err(e) => return Err(Error::other(e)),
- }
- .iter()
- .map(|r| (r.file_path.clone(), r.hash.to_string()))
- .collect();
-
- // Build hash mapping table for lost files
- let mut lost_files_hash_mapping: HashMap<String, FromRelativePathBuf> =
- match &analyze_ctx.local_sheet {
- Some(local_sheet) => lost_files
- .iter()
- .filter_map(|f| {
- local_sheet.mapping_data(f).ok().map(|mapping_data| {
- (
- // Using the most recently recorded Hash can more accurately identify moved items,
- // but if it doesn't exist, fall back to the initially recorded Hash
- mapping_data
- .last_modify_check_hash
- .as_ref()
- .cloned()
- .unwrap_or(mapping_data.hash_when_updated.clone()),
- (*f).clone(),
- )
- })
- })
- .collect(),
- None => HashMap::new(),
- };
-
- // If these hashes correspond to the hashes of missing files, then this pair of new and lost items will be merged into moved items
- let mut moved_files: HashSet<(FromRelativePathBuf, ToRelativePathBuf)> = HashSet::new();
- for (new_path, new_hash) in file_hashes {
- let new_path = new_path
- .strip_prefix(&workspace.local_path)
- .map(|p| p.to_path_buf())
- .unwrap_or(new_path);
-
- // If the new hash value hits the mapping, add a moved item
- if let Some(lost_path) = lost_files_hash_mapping.remove(&new_hash) {
- // Remove this new item and lost item
- lost_files.remove(&lost_path);
- new_files.remove(&new_path);
-
- // Create moved item
- moved_files.insert((lost_path.clone(), new_path));
- }
- }
-
- // Enter fuzzy matching to match other potentially moved items that haven't been matched
- // If the total number of new and lost files is divisible by 2, it indicates there might still be files that have been moved, consider trying fuzzy matching
- if new_files.len() + lost_files.len() % 2 == 0 {
- // Try fuzzy matching
- // ...
- }
-
- // Collect results and set the result
- result.created = new_files.iter().map(|p| (*p).clone()).collect();
- result.lost = lost_files.iter().map(|p| (*p).clone()).collect();
- result.moved = moved_files
- .iter()
- .filter_map(|(from, to)| {
- let vfid = analyze_ctx
- .local_sheet
- .as_ref()
- .and_then(|local_sheet| local_sheet.mapping_data(from).ok())
- .map(|mapping_data| mapping_data.mapping_vfid.clone());
- vfid.map(|vfid| (vfid, (from.clone(), to.clone())))
- })
- .collect();
- result.erased = erased_files;
-
- Ok(())
- }
-
- /// Compare using file modification time and SHA1 hash values.
- /// Note: For files that have been both moved and modified, they can only be recognized as modified after their location is matched.
- async fn analyze_modified(
- result: &mut AnalyzeResult<'_>,
- file_relative_paths: &HashSet<PathBuf>,
- analyze_ctx: &mut AnalyzeContext<'a>,
- workspace: &LocalWorkspace,
- ) -> Result<(), std::io::Error> {
- let local_sheet = &mut analyze_ctx.local_sheet.as_mut().unwrap();
- let local_path = local_sheet.local_workspace.local_path().clone();
-
- for path in file_relative_paths {
- // Get mapping data
- let Ok(mapping_data) = local_sheet.mapping_data_mut(path) else {
- continue;
- };
-
- // If modified time not changed, skip
- let modified_time = std::fs::metadata(local_path.join(path))?.modified()?;
- if &modified_time == mapping_data.last_modifiy_check_time() {
- if mapping_data.last_modifiy_check_result() {
- result.modified.insert(path.clone());
- }
- continue;
- }
-
- // Calculate hash
- let hash_calc = match sha1_hash::calc_sha1(workspace.local_path.join(path), 2048).await
- {
- Ok(hash) => hash,
- Err(e) => return Err(Error::other(e)),
- };
-
- // If hash not match, mark as modified
- if &hash_calc.hash != mapping_data.hash_when_updated() {
- result.modified.insert(path.clone());
-
- // Update last modified check time to modified time
- mapping_data.last_modify_check_time = modified_time;
- mapping_data.last_modify_check_result = true;
- } else {
- // Update last modified check time to modified time
- mapping_data.last_modify_check_time = modified_time;
- mapping_data.last_modify_check_result = false;
- }
-
- // Record latest hash
- mapping_data.last_modify_check_hash = Some(hash_calc.hash)
- }
-
- // Persist the local sheet data
- LocalSheet::write(local_sheet).await?;
-
- Ok(())
- }
-
- /// Generate a empty AnalyzeResult
- fn none_result(local_workspace: &'a LocalWorkspace) -> AnalyzeResult<'a> {
- AnalyzeResult {
- local_workspace,
- moved: HashMap::new(),
- created: HashSet::new(),
- lost: HashSet::new(),
- modified: HashSet::new(),
- erased: HashSet::new(),
- }
- }
-}
diff --git a/legacy_data/src/data/local/workspace_config.rs b/legacy_data/src/data/local/workspace_config.rs
deleted file mode 100644
index fc63e9c..0000000
--- a/legacy_data/src/data/local/workspace_config.rs
+++ /dev/null
@@ -1,374 +0,0 @@
-use cfg_file::ConfigFile;
-use cfg_file::config::ConfigFile;
-use just_fmt::snake_case;
-use serde::{Deserialize, Serialize};
-use std::io::Error;
-use std::net::SocketAddr;
-use std::path::Path;
-use std::path::PathBuf;
-
-use crate::constants::CLIENT_FILE_WORKSPACE;
-use crate::constants::CLIENT_FOLDER_WORKSPACE_ROOT_NAME;
-use crate::constants::CLIENT_PATH_LOCAL_DRAFT;
-use crate::constants::CLIENT_PATH_WORKSPACE_ROOT;
-use crate::constants::KEY_ACCOUNT;
-use crate::constants::KEY_SHEET_NAME;
-use crate::constants::PORT;
-use crate::data::local::latest_info::LatestInfo;
-use crate::data::member::MemberId;
-use crate::data::sheet::SheetName;
-use crate::data::vault::vault_config::VaultUuid;
-use crate::env::current_local_path;
-
-#[derive(Serialize, Deserialize, ConfigFile, Clone)]
-#[cfg_file(path = CLIENT_FILE_WORKSPACE)]
-pub struct LocalConfig {
- /// The upstream address, representing the upstream address of the local workspace,
- /// to facilitate timely retrieval of new updates from the upstream source.
- #[serde(rename = "addr")]
- upstream_addr: SocketAddr,
-
- /// The member ID used by the current local workspace.
- /// This ID will be used to verify access permissions when connecting to the upstream server.
- #[serde(rename = "as")]
- using_account: MemberId,
-
- /// Whether the current member is interacting as a host.
- /// In host mode, full Vault operation permissions are available except for adding new content.
- #[serde(rename = "host")]
- using_host_mode: bool,
-
- /// Whether the local workspace is stained.
- ///
- /// If stained, it can only set an upstream server with the same identifier.
- ///
- /// If the value is None, it means not stained;
- /// otherwise, it contains the stain identifier (i.e., the upstream vault's unique ID)
- #[serde(rename = "up_uid")]
- stained_uuid: Option<VaultUuid>,
-
- /// The name of the sheet currently in use.
- #[serde(rename = "use")]
- sheet_in_use: Option<SheetName>,
-}
-
-impl Default for LocalConfig {
- fn default() -> Self {
- Self {
- upstream_addr: SocketAddr::V4(std::net::SocketAddrV4::new(
- std::net::Ipv4Addr::new(127, 0, 0, 1),
- PORT,
- )),
- using_account: "unknown".to_string(),
- using_host_mode: false,
- stained_uuid: None,
- sheet_in_use: None,
- }
- }
-}
-
-impl LocalConfig {
- /// Set the vault address.
- pub fn set_vault_addr(&mut self, addr: SocketAddr) {
- self.upstream_addr = addr;
- }
-
- /// Get the vault address.
- pub fn vault_addr(&self) -> SocketAddr {
- self.upstream_addr
- }
-
- /// Set the currently used account
- pub fn set_current_account(&mut self, account: MemberId) -> Result<(), std::io::Error> {
- if self.sheet_in_use().is_some() {
- return Err(Error::new(
- std::io::ErrorKind::DirectoryNotEmpty,
- "Please exit the current sheet before switching accounts",
- ));
- }
- self.using_account = account;
- Ok(())
- }
-
- /// Set the host mode
- pub fn set_host_mode(&mut self, host_mode: bool) {
- self.using_host_mode = host_mode;
- }
-
- /// Set the currently used sheet
- pub async fn use_sheet(&mut self, sheet: SheetName) -> Result<(), std::io::Error> {
- let sheet = snake_case!(sheet);
-
- // Check if the sheet is already in use
- if self.sheet_in_use().is_some() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::AlreadyExists,
- "Sheet already in use",
- ));
- };
-
- // Check if the local path exists
- let local_path = self.get_local_path().await?;
-
- // Get latest info
- let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
- &local_path,
- &self.current_account(),
- ))
- .await
- else {
- return Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "No latest info found",
- ));
- };
-
- // Check if the sheet exists
- if !latest_info.visible_sheets.contains(&sheet) {
- return Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "Sheet not found",
- ));
- }
-
- // Check if there are any files or folders other than .jv
- self.check_local_path_empty(&local_path).await?;
-
- // Get the draft folder path
- let draft_folder = self.draft_folder(&self.using_account, &sheet, &local_path);
-
- if draft_folder.exists() {
- // Exists
- // Move the contents of the draft folder to the local path with rollback support
- self.move_draft_to_local(&draft_folder, &local_path).await?;
- }
-
- self.sheet_in_use = Some(sheet);
- LocalConfig::write(self).await?;
-
- Ok(())
- }
-
- /// Exit the currently used sheet
- pub async fn exit_sheet(&mut self) -> Result<(), std::io::Error> {
- // Check if the sheet is already in use
- if self.sheet_in_use().is_none() {
- return Ok(());
- }
-
- // Check if the local path exists
- let local_path = self.get_local_path().await?;
-
- // Get the current sheet name
- let sheet_name = self.sheet_in_use().as_ref().unwrap().clone();
-
- // Get the draft folder path
- let draft_folder = self.draft_folder(&self.using_account, &sheet_name, &local_path);
-
- // Create the draft folder if it doesn't exist
- if !draft_folder.exists() {
- std::fs::create_dir_all(&draft_folder).map_err(std::io::Error::other)?;
- }
-
- // Move all files and folders (except .jv folder) to the draft folder with rollback support
- self.move_local_to_draft(&local_path, &draft_folder).await?;
-
- // Clear the sheet in use
- self.sheet_in_use = None;
- LocalConfig::write(self).await?;
-
- Ok(())
- }
-
- /// Get local path or return error
- async fn get_local_path(&self) -> Result<PathBuf, std::io::Error> {
- current_local_path().ok_or_else(|| {
- std::io::Error::new(std::io::ErrorKind::NotFound, "Fail to get local path")
- })
- }
-
- /// Check if local path is empty (except for .jv folder)
- async fn check_local_path_empty(&self, local_path: &Path) -> Result<(), std::io::Error> {
- let jv_folder = local_path.join(CLIENT_PATH_WORKSPACE_ROOT);
- let mut entries = std::fs::read_dir(local_path).map_err(std::io::Error::other)?;
-
- if entries.any(|entry| {
- if let Ok(entry) = entry {
- let path = entry.path();
- path != jv_folder
- && path.file_name().and_then(|s| s.to_str())
- != Some(CLIENT_FOLDER_WORKSPACE_ROOT_NAME)
- } else {
- false
- }
- }) {
- return Err(std::io::Error::new(
- std::io::ErrorKind::DirectoryNotEmpty,
- "Local path is not empty!",
- ));
- }
-
- Ok(())
- }
-
- /// Move contents from draft folder to local path with rollback support
- async fn move_draft_to_local(
- &self,
- draft_folder: &Path,
- local_path: &Path,
- ) -> Result<(), std::io::Error> {
- let draft_entries: Vec<_> = std::fs::read_dir(draft_folder)
- .map_err(std::io::Error::other)?
- .collect::<Result<Vec<_>, _>>()
- .map_err(std::io::Error::other)?;
-
- let mut moved_items: Vec<MovedItem> = Vec::new();
-
- for entry in &draft_entries {
- let entry_path = entry.path();
- let target_path = local_path.join(entry_path.file_name().unwrap());
-
- // Move each file/directory from draft folder to local path
- std::fs::rename(&entry_path, &target_path).map_err(|e| {
- // Rollback all previously moved items
- for moved_item in &moved_items {
- let _ = std::fs::rename(&moved_item.target, &moved_item.source);
- }
- std::io::Error::other(e)
- })?;
-
- moved_items.push(MovedItem {
- source: entry_path.clone(),
- target: target_path.clone(),
- });
- }
-
- // Remove the now-empty draft folder
- std::fs::remove_dir(draft_folder).map_err(|e| {
- // Rollback all moved items if folder removal fails
- for moved_item in &moved_items {
- let _ = std::fs::rename(&moved_item.target, &moved_item.source);
- }
- std::io::Error::other(e)
- })?;
-
- Ok(())
- }
-
- /// Move contents from local path to draft folder with rollback support (except .jv folder)
- async fn move_local_to_draft(
- &self,
- local_path: &Path,
- draft_folder: &Path,
- ) -> Result<(), std::io::Error> {
- let jv_folder = local_path.join(CLIENT_PATH_WORKSPACE_ROOT);
- let entries: Vec<_> = std::fs::read_dir(local_path)
- .map_err(std::io::Error::other)?
- .collect::<Result<Vec<_>, _>>()
- .map_err(std::io::Error::other)?;
-
- let mut moved_items: Vec<MovedItem> = Vec::new();
-
- for entry in &entries {
- let entry_path = entry.path();
-
- // Skip the .jv folder
- if entry_path == jv_folder
- || entry_path.file_name().and_then(|s| s.to_str())
- == Some(CLIENT_FOLDER_WORKSPACE_ROOT_NAME)
- {
- continue;
- }
-
- let target_path = draft_folder.join(entry_path.file_name().unwrap());
-
- // Move each file/directory from local path to draft folder
- std::fs::rename(&entry_path, &target_path).map_err(|e| {
- // Rollback all previously moved items
- for moved_item in &moved_items {
- let _ = std::fs::rename(&moved_item.target, &moved_item.source);
- }
- std::io::Error::other(e)
- })?;
-
- moved_items.push(MovedItem {
- source: entry_path.clone(),
- target: target_path.clone(),
- });
- }
-
- Ok(())
- }
-
- /// Get the currently used account
- pub fn current_account(&self) -> MemberId {
- self.using_account.clone()
- }
-
- /// Check if the current member is interacting as a host.
- pub fn is_host_mode(&self) -> bool {
- self.using_host_mode
- }
-
- /// Check if the local workspace is stained.
- pub fn stained(&self) -> bool {
- self.stained_uuid.is_some()
- }
-
- /// Get the UUID of the vault that the local workspace is stained with.
- pub fn stained_uuid(&self) -> Option<VaultUuid> {
- self.stained_uuid
- }
-
- /// Stain the local workspace with the given UUID.
- pub fn stain(&mut self, uuid: VaultUuid) {
- self.stained_uuid = Some(uuid);
- }
-
- /// Unstain the local workspace.
- pub fn unstain(&mut self) {
- self.stained_uuid = None;
- }
-
- /// Get the upstream address.
- pub fn upstream_addr(&self) -> SocketAddr {
- self.upstream_addr
- }
-
- /// Get the currently used sheet
- pub fn sheet_in_use(&self) -> &Option<SheetName> {
- &self.sheet_in_use
- }
-
- /// Get draft folder
- pub fn draft_folder(
- &self,
- account: &MemberId,
- sheet_name: &SheetName,
- local_workspace_path: impl Into<PathBuf>,
- ) -> PathBuf {
- let account_str = snake_case!(account.as_str());
- let sheet_name_str = snake_case!(sheet_name.as_str());
- let draft_path = CLIENT_PATH_LOCAL_DRAFT
- .replace(KEY_ACCOUNT, &account_str)
- .replace(KEY_SHEET_NAME, &sheet_name_str);
- local_workspace_path.into().join(draft_path)
- }
-
- /// Get current draft folder
- pub fn current_draft_folder(&self) -> Option<PathBuf> {
- let Some(sheet_name) = self.sheet_in_use() else {
- return None;
- };
-
- let current_dir = current_local_path()?;
-
- Some(self.draft_folder(&self.using_account, sheet_name, current_dir))
- }
-}
-
-#[derive(Clone)]
-struct MovedItem {
- source: PathBuf,
- target: PathBuf,
-}