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_LOCAL_SHEET, CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE}, current::{current_local_path, find_local_path}, data::{ local::{ config::LocalConfig, local_sheet::{LocalSheet, LocalSheetData}, }, member::MemberId, sheet::SheetName, }, }; pub mod cached_sheet; pub mod config; 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>, local_path: PathBuf, } impl LocalWorkspace { /// Get the path of the local workspace. pub fn local_path(&self) -> &PathBuf { &self.local_path } /// Initialize local workspace. pub fn init(config: LocalConfig, local_path: impl Into) -> Option { let local_path = find_local_path(local_path)?; Some(Self { config: Arc::new(Mutex::new(config)), local_path, }) } /// Initialize local workspace in the current directory. pub fn init_current_dir(config: LocalConfig) -> Option { let local_path = current_local_path()?; Some(Self { config: Arc::new(Mutex::new(config)), local_path, }) } /// Setup local workspace pub async fn setup_local_workspace( local_path: impl Into, ) -> Result<(), std::io::Error> { let local_path: PathBuf = local_path.into(); // Ensure directory is empty if local_path.exists() && local_path.read_dir()?.next().is_some() { return Err(std::io::Error::new( std::io::ErrorKind::DirectoryNotEmpty, "DirectoryNotEmpty", )); } // 1. Setup config let config = LocalConfig::default(); LocalConfig::write_to(&config, local_path.join(CLIENT_FILE_WORKSPACE)).await?; // 2. Setup README.md let readme_content = READMES_LOCAL_WORKSPACE_TODOLIST.trim().to_string(); fs::write(local_path.join(CLIENT_FILE_TODOLIST), readme_content).await?; // On Windows, set the .jv directory as hidden #[cfg(target_os = "windows")] { use std::os::windows::fs::MetadataExt; use winapi_util::file::set_hidden; let jv_dir = local_path.join(".jv"); if jv_dir.exists() { if let Err(e) = set_hidden(&jv_dir, true) { eprintln!("Warning: Failed to set .jv directory as hidden: {}", e); } } } Ok(()) } /// Get a reference to the local configuration. pub fn config(&self) -> Arc> { self.config.clone() } /// Setup local workspace in current directory pub async fn setup_local_workspace_current_dir() -> Result<(), std::io::Error> { 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, 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) } }