summaryrefslogtreecommitdiff
path: root/legacy_data/src/data/local.rs
diff options
context:
space:
mode:
Diffstat (limited to 'legacy_data/src/data/local.rs')
-rw-r--r--legacy_data/src/data/local.rs266
1 files changed, 266 insertions, 0 deletions
diff --git a/legacy_data/src/data/local.rs b/legacy_data/src/data/local.rs
new file mode 100644
index 0000000..d4115c6
--- /dev/null
+++ b/legacy_data/src/data/local.rs
@@ -0,0 +1,266 @@
+use std::{
+ collections::HashMap,
+ env::current_dir,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+
+use cfg_file::config::ConfigFile;
+use string_proc::format_path::format_path;
+use tokio::{fs, sync::Mutex};
+use vcs_docs::docs::READMES_LOCAL_WORKSPACE_TODOLIST;
+
+use crate::{
+ constants::{
+ CLIENT_CONTENT_GITIGNORE, CLIENT_FILE_GITIGNORE, CLIENT_FILE_LOCAL_SHEET,
+ CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE, CLIENT_FOLDER_WORKSPACE_ROOT_NAME,
+ CLIENT_PATH_LOCAL_SHEET, CLIENT_SUFFIX_LOCAL_SHEET_FILE, KEY_ACCOUNT, KEY_SHEET_NAME,
+ },
+ data::{
+ local::{
+ local_sheet::{LocalSheet, LocalSheetData, LocalSheetPathBuf},
+ workspace_config::LocalConfig,
+ },
+ member::MemberId,
+ sheet::SheetName,
+ },
+ env::{current_local_path, find_local_path},
+};
+
+pub mod align_tasks;
+pub mod cached_sheet;
+pub mod latest_file_data;
+pub mod latest_info;
+pub mod local_files;
+pub mod local_sheet;
+pub mod modified_status;
+pub mod workspace_analyzer;
+pub mod workspace_config;
+
+pub struct LocalWorkspace {
+ config: Arc<Mutex<LocalConfig>>,
+ 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<PathBuf>) -> Option<Self> {
+ 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<Self> {
+ 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<PathBuf>,
+ ) -> 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 SETUP.md
+ let readme_content = READMES_LOCAL_WORKSPACE_TODOLIST.trim().to_string();
+ fs::write(local_path.join(CLIENT_FILE_TODOLIST), readme_content).await?;
+
+ // 3. Setup .gitignore
+ fs::write(
+ local_path.join(CLIENT_FILE_GITIGNORE),
+ CLIENT_CONTENT_GITIGNORE,
+ )
+ .await?;
+
+ // On Windows, set the .jv directory as hidden
+ let jv_dir = local_path.join(CLIENT_FOLDER_WORKSPACE_ROOT_NAME);
+ let _ = hide_folder::hide_folder(&jv_dir);
+
+ Ok(())
+ }
+
+ /// Get a reference to the local configuration.
+ pub fn config(&self) -> Arc<Mutex<LocalConfig>> {
+ 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 {
+ self.local_path.join(
+ CLIENT_FILE_LOCAL_SHEET
+ .replace(KEY_ACCOUNT, member)
+ .replace(KEY_SHEET_NAME, sheet),
+ )
+ }
+
+ /// 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(),
+ vfs: 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)
+ }
+
+ /// Collect all theet names
+ pub async fn local_sheet_paths(&self) -> Result<Vec<LocalSheetPathBuf>, std::io::Error> {
+ let local_sheet_path = self.local_path.join(CLIENT_PATH_LOCAL_SHEET);
+ let mut sheet_paths = Vec::new();
+
+ async fn collect_sheet_paths(
+ dir: &Path,
+ suffix: &str,
+ paths: &mut Vec<LocalSheetPathBuf>,
+ ) -> Result<(), std::io::Error> {
+ if dir.is_dir() {
+ let mut entries = fs::read_dir(dir).await?;
+ while let Some(entry) = entries.next_entry().await? {
+ let path = entry.path();
+
+ if path.is_dir() {
+ Box::pin(collect_sheet_paths(&path, suffix, paths)).await?;
+ } else if path.is_file()
+ && let Some(extension) = path.extension()
+ && extension == suffix.trim_start_matches('.')
+ {
+ let formatted_path = format_path(path)?;
+ paths.push(formatted_path);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ collect_sheet_paths(
+ &local_sheet_path,
+ CLIENT_SUFFIX_LOCAL_SHEET_FILE,
+ &mut sheet_paths,
+ )
+ .await?;
+ Ok(sheet_paths)
+ }
+}
+
+mod hide_folder {
+ use std::io;
+ use std::path::Path;
+
+ #[cfg(windows)]
+ use std::os::windows::ffi::OsStrExt;
+ #[cfg(windows)]
+ use winapi::um::fileapi::{GetFileAttributesW, INVALID_FILE_ATTRIBUTES, SetFileAttributesW};
+
+ pub fn hide_folder(path: &Path) -> io::Result<()> {
+ if !path.is_dir() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Path must be a directory",
+ ));
+ }
+
+ if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
+ if !file_name.starts_with('.') {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Directory name must start with '.'",
+ ));
+ }
+ } else {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid directory name",
+ ));
+ }
+
+ hide_folder_impl(path)
+ }
+
+ #[cfg(windows)]
+ fn hide_folder_impl(path: &Path) -> io::Result<()> {
+ // Convert to Windows wide string format
+ let path_str: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
+
+ // Get current attributes
+ let attrs = unsafe { GetFileAttributesW(path_str.as_ptr()) };
+ if attrs == INVALID_FILE_ATTRIBUTES {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Add hidden attribute flag
+ let new_attrs = attrs | winapi::um::winnt::FILE_ATTRIBUTE_HIDDEN;
+
+ // Set new attributes
+ let success = unsafe { SetFileAttributesW(path_str.as_ptr(), new_attrs) };
+ if success == 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(())
+ }
+
+ #[cfg(unix)]
+ fn hide_folder_impl(_path: &Path) -> io::Result<()> {
+ Ok(())
+ }
+
+ #[cfg(not(any(windows, unix)))]
+ fn hide_folder_impl(_path: &Path) -> io::Result<()> {
+ Err(io::Error::new(
+ io::ErrorKind::Unsupported,
+ "Unsupported operating system",
+ ))
+ }
+}