diff options
| -rw-r--r-- | resources/locales/jvn/cmds/status/en.yml | 59 | ||||
| -rw-r--r-- | resources/locales/jvn/cmds/status/zh-CN.yml | 59 | ||||
| -rw-r--r-- | src/cmds/status.rs | 98 | ||||
| -rw-r--r-- | src/inputs.rs | 1 | ||||
| -rw-r--r-- | src/inputs/status.rs | 43 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/outputs/status.rs | 46 | ||||
| -rw-r--r-- | src/renderers/status.rs | 252 | ||||
| -rw-r--r-- | src/systems/cmd/renderer.rs | 4 | ||||
| -rw-r--r-- | src/utils/display.rs | 24 | ||||
| -rw-r--r-- | src/utils/workspace_reader.rs | 121 |
11 files changed, 658 insertions, 51 deletions
diff --git a/resources/locales/jvn/cmds/status/en.yml b/resources/locales/jvn/cmds/status/en.yml new file mode 100644 index 0000000..df0de19 --- /dev/null +++ b/resources/locales/jvn/cmds/status/en.yml @@ -0,0 +1,59 @@ +status: + header: | + You are viewing the status of `%{account}/%{sheet}` + + current_mode: + structural: | + Use `jv align <item> <plan>` to align or confirm moved, erased, or lost items + %{info} + content: | + Use `jv track <mapping>` to track your changes + %{info} + clean: | + No changes, clean workspace + + info_display: + moved: + header: | + [[yellow]]> MOVED:[[/]] + left: | + [[yellow]]> %{left}[[/]] + right: | + [[yellow]]-> %{right}[[/]] + created: + header: | + [[green]]+ CREATED:[[/]] + item: | + [[green]]+ %{item}[[/]] + lost: + header: | + [[red]]? LOST:[[/]] + item: | + [[red]]? %{item}[[/]] + erased: + header: | + [[magenta]]& ERASED:[[/]] + item: | + [[magenta]]& %{item}[[/]] + modified: + header: | + [[cyan]]* MODIFIED:[[/]] + item: | + [[cyan]]* %{item}[[/]] + item_wrong: | + [[red]]X %{item}[[/]] + [[red]] \____ %{reason}[[/]] + reason: + base_version_mismatch: Edited based on version `%{base_version}`, but the latest version is `%{latest_version}` + modified_but_not_held: Edited and held by @%{holder} + no_holder: Not held by anyone + + hints: + outdate: | + [[yellow]]Note: Workspace information is from %{h}:%{m}:%{s} ago. Please use `jv update` to refresh the status.[[/]] + + readonly: | + [[yellow]]Note: You are accessing the reference sheet as a guest. You cannot perform any version advancement or structural edits![[/]] + + host: | + [[yellow]]Note: You are accessing this sheet in HOST mode. You can now directly operate on mappings and content![[/]] diff --git a/resources/locales/jvn/cmds/status/zh-CN.yml b/resources/locales/jvn/cmds/status/zh-CN.yml new file mode 100644 index 0000000..9136ab3 --- /dev/null +++ b/resources/locales/jvn/cmds/status/zh-CN.yml @@ -0,0 +1,59 @@ +status: + header: | + 正在查看表:`%{account}/%{sheet}` + + current_mode: + structural: | + 使用 `jv align <项> <方案>` 以对齐或确认移动、擦除或丢失项 + %{info} + content: | + 使用 `jv track <映射>` 来追踪您的变更 + %{info} + clean: | + 当前无任何变化,干净的工作区 + + info_display: + moved: + header: | + [[yellow]]> 移动:[[/]] + left: | + [[yellow]]> %{left}[[/]] + right: | + [[yellow]]-> %{right}[[/]] + created: + header: | + [[green]]+ 新增:[[/]] + item: | + [[green]]+ %{item}[[/]] + lost: + header: | + [[red]]? 丢失:[[/]] + item: | + [[red]]? %{item}[[/]] + erased: + header: | + [[magenta]]& 擦除:[[/]] + item: | + [[magenta]]& %{item}[[/]] + modified: + header: | + [[cyan]]* 修改:[[/]] + item: | + [[cyan]]* %{item}[[/]] + item_wrong: | + [[red]]X %{item}[[/]] + [[red]] \____ %{reason}[[/]] + reason: + base_version_mismatch: 基于 `%{base_version}` 版本编辑,但最新版本为 `%{latest_version}` + modified_but_not_held: 由 @%{holder} 持有并编辑 + no_holder: 无人持有 + + hints: + outdate: | + [[yellow]]注意:工作区信息基于 %{h}:%{m}:%{s} 前,请使用 `jv update` 刷新状态[[/]] + + readonly: | + [[yellow]]注意:您正在以访客身份访问参考表,无法进行任何版本推进和结构编辑![[/]] + + host: | + [[yellow]]注意:您正在使用 HOST 模式访问该表,现可直接操作映射和内容![[/]] diff --git a/src/cmds/status.rs b/src/cmds/status.rs index 8279e50..54e033e 100644 --- a/src/cmds/status.rs +++ b/src/cmds/status.rs @@ -1,8 +1,11 @@ use std::time::SystemTime; +use just_enough_vcs::vcs::constants::VAULT_HOST_NAME; + use crate::{ arguments::status::JVStatusArgument, - outputs::status::JVStatusResult, + inputs::status::JVStatusInput, + outputs::status::{JVStatusOutput, JVStatusWrongModifyReason}, renderers::status::JVStatusRenderer, systems::cmd::{ cmd_system::{JVCommand, JVCommandContext}, @@ -13,16 +16,16 @@ use crate::{ pub struct JVStatusCommand; -impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRenderer> +impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusOutput, JVStatusRenderer> for JVStatusCommand { async fn prepare( _args: JVStatusArgument, _ctx: JVCommandContext, - ) -> Result<JVStatusResult, CmdPrepareError> { + ) -> Result<JVStatusInput, CmdPrepareError> { // Initialize a reader for the local workspace and a default result structure let mut reader = LocalWorkspaceReader::default(); - let mut input = JVStatusResult::default(); + let mut input = JVStatusInput::default(); // Analyze the current status of the local workspace // (detects changes like created, modified, moved, etc.) @@ -34,6 +37,16 @@ impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRendere // Retrieve the name of the current sheet let sheet_name = reader.sheet_name().await?; + // Is Host Mode + let is_host_mode = reader.is_host_mode().await?; + + let cached_sheet = reader.cached_sheet(&sheet_name).await?; + let sheet_holder = cached_sheet.holder().cloned().unwrap_or_default(); + let is_ref_sheet = sheet_holder == VAULT_HOST_NAME; + + // Get Latest file data + let latest_file_data = reader.pop_latest_file_data(&account).await?; + // Get the timestamp of the last update, defaulting to the current time if not available let update_time = reader .latest_info() @@ -47,19 +60,82 @@ impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRendere // Populate the result structure with the gathered data input.current_account = account; input.current_sheet = sheet_name; - input.moved = analyzed.moved; - input.created = analyzed.created; - input.lost = analyzed.lost; - input.erased = analyzed.erased; - input.modified = analyzed.modified; + input.is_host_mode = is_host_mode; + input.in_ref_sheet = is_ref_sheet; + input.analyzed_result = analyzed; input.update_time = update_time; input.now_time = now_time; + input.latest_file_data = latest_file_data; Ok(input) } - async fn exec(input: JVStatusResult) -> Result<JVStatusResult, CmdExecuteError> { - Ok(input) // Analyze command, no needs execute + async fn exec(mut input: JVStatusInput) -> Result<JVStatusOutput, CmdExecuteError> { + let latest_file_data = &input.latest_file_data; + + // Calculate whether modifications are correc + let modified = &input.analyzed_result.modified; + for item in modified { + // Get mapping + let Ok(mapping) = input.local_sheet_data.mapping_data(&item) else { + continue; + }; + + // Check base version + { + let base_version = mapping.version_when_updated().clone(); + let Some(latest_version) = latest_file_data + .file_version(mapping.mapping_vfid()) + .cloned() + else { + continue; + }; + + // Base version dismatch + if base_version != latest_version { + input.wrong_modified_items.insert( + item.clone(), + JVStatusWrongModifyReason::BaseVersionMismatch { + base_version, + latest_version, + }, + ); + continue; + } + } + + // Check edit right (only check when current is not HOST) + if input.current_account != VAULT_HOST_NAME { + let holder = latest_file_data.file_holder(mapping.mapping_vfid()); + if holder.is_none() { + input + .wrong_modified_items + .insert(item.clone(), JVStatusWrongModifyReason::NoHolder); + continue; + } + + let holder = holder.cloned().unwrap(); + if &input.current_account != &holder { + input.wrong_modified_items.insert( + item.clone(), + JVStatusWrongModifyReason::ModifiedButNotHeld { holder: holder }, + ); + } + } + } + + let output = JVStatusOutput { + current_account: input.current_account, + current_sheet: input.current_sheet, + is_host_mode: input.is_host_mode, + in_ref_sheet: input.in_ref_sheet, + analyzed_result: input.analyzed_result, + wrong_modified_items: input.wrong_modified_items, + update_time: input.update_time, + now_time: input.now_time, + }; + + Ok(output) } fn get_help_str() -> String { diff --git a/src/inputs.rs b/src/inputs.rs index e69de29..822c729 100644 --- a/src/inputs.rs +++ b/src/inputs.rs @@ -0,0 +1 @@ +pub mod status; diff --git a/src/inputs/status.rs b/src/inputs/status.rs new file mode 100644 index 0000000..eb3b504 --- /dev/null +++ b/src/inputs/status.rs @@ -0,0 +1,43 @@ +use std::{collections::HashMap, time::SystemTime}; + +use just_enough_vcs::vcs::data::{ + local::{ + latest_file_data::LatestFileData, + local_sheet::LocalSheetData, + workspace_analyzer::{AnalyzeResultPure, ModifiedRelativePathBuf}, + }, + member::MemberId, + sheet::SheetName, +}; + +use crate::outputs::status::JVStatusWrongModifyReason; + +pub struct JVStatusInput { + pub current_account: MemberId, + pub current_sheet: SheetName, + pub is_host_mode: bool, + pub in_ref_sheet: bool, + pub analyzed_result: AnalyzeResultPure, + pub latest_file_data: LatestFileData, + pub local_sheet_data: LocalSheetData, + pub wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason>, + pub update_time: SystemTime, + pub now_time: SystemTime, +} + +impl Default for JVStatusInput { + fn default() -> Self { + Self { + current_account: MemberId::default(), + current_sheet: SheetName::default(), + is_host_mode: false, + in_ref_sheet: false, + analyzed_result: AnalyzeResultPure::default(), + latest_file_data: LatestFileData::default(), + local_sheet_data: LocalSheetData::default(), + wrong_modified_items: HashMap::new(), + update_time: SystemTime::now(), + now_time: SystemTime::now(), + } + } +} @@ -1,3 +1,5 @@ +rust_i18n::i18n!("resources/locales/jvn", fallback = "en"); + // --- LIBS --- /// Utils diff --git a/src/outputs/status.rs b/src/outputs/status.rs index f9ce875..2b8d9c6 100644 --- a/src/outputs/status.rs +++ b/src/outputs/status.rs @@ -1,43 +1,45 @@ -use std::{ - collections::{HashMap, HashSet}, - path::PathBuf, - time::SystemTime, -}; +use std::{collections::HashMap, time::SystemTime}; use just_enough_vcs::vcs::data::{ - local::workspace_analyzer::{ - CreatedRelativePathBuf, FromRelativePathBuf, LostRelativePathBuf, ModifiedRelativePathBuf, - ToRelativePathBuf, - }, + local::workspace_analyzer::{AnalyzeResultPure, ModifiedRelativePathBuf}, member::MemberId, sheet::SheetName, - vault::virtual_file::VirtualFileId, }; use serde::Serialize; #[derive(Serialize)] -pub struct JVStatusResult { +pub struct JVStatusOutput { pub current_account: MemberId, pub current_sheet: SheetName, - pub moved: HashMap<VirtualFileId, (FromRelativePathBuf, ToRelativePathBuf)>, - pub created: HashSet<CreatedRelativePathBuf>, - pub lost: HashSet<LostRelativePathBuf>, - pub erased: HashSet<PathBuf>, - pub modified: HashSet<ModifiedRelativePathBuf>, + pub is_host_mode: bool, + pub in_ref_sheet: bool, + pub analyzed_result: AnalyzeResultPure, + pub wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason>, pub update_time: SystemTime, pub now_time: SystemTime, } -impl Default for JVStatusResult { +#[derive(Serialize)] +pub enum JVStatusWrongModifyReason { + BaseVersionMismatch { + base_version: String, + latest_version: String, + }, + ModifiedButNotHeld { + holder: String, + }, + NoHolder, +} + +impl Default for JVStatusOutput { fn default() -> Self { Self { current_account: MemberId::default(), current_sheet: SheetName::default(), - moved: HashMap::default(), - created: HashSet::default(), - lost: HashSet::default(), - erased: HashSet::default(), - modified: HashSet::default(), + is_host_mode: false, + in_ref_sheet: false, + analyzed_result: AnalyzeResultPure::default(), + wrong_modified_items: HashMap::new(), update_time: SystemTime::now(), now_time: SystemTime::now(), } diff --git a/src/renderers/status.rs b/src/renderers/status.rs index 0bf1c5c..d51252e 100644 --- a/src/renderers/status.rs +++ b/src/renderers/status.rs @@ -1,18 +1,262 @@ +use rust_i18n::t; + use crate::{ - outputs::status::JVStatusResult, + outputs::status::{JVStatusOutput, JVStatusWrongModifyReason}, r_println, systems::cmd::{ errors::CmdRenderError, renderer::{JVRenderResult, JVResultRenderer}, }, + utils::{ + display::{SimpleTable, md}, + env::auto_update_outdate, + }, }; pub struct JVStatusRenderer; -impl JVResultRenderer<JVStatusResult> for JVStatusRenderer { - async fn render(_data: &JVStatusResult) -> Result<JVRenderResult, CmdRenderError> { +enum Mode { + StructuralChangesMode, + ContentChangesMode, + Clean, +} + +impl JVResultRenderer<JVStatusOutput> for JVStatusRenderer { + async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> { let mut r = JVRenderResult::default(); - r_println!(r, "Nothing"); + + // Render Header + render_header(&mut r, data); + + // Render Info and Mode + render_info_and_mode(&mut r, data); + + // Render Hint + render_hint(&mut r, data); + Ok(r) } } + +fn render_header(r: &mut JVRenderResult, data: &JVStatusOutput) { + let account = &data.current_account; + let sheet = &data.current_sheet; + r_println!( + r, + "{}", + md(t!("status.header", account = account, sheet = sheet)) + ); +} + +fn render_info_and_mode(r: &mut JVRenderResult, data: &JVStatusOutput) { + let mut info_erased = String::default(); + let mut info_moved = String::default(); + let mut info_lost = String::default(); + let mut info_created = String::default(); + let mut info_modified = String::default(); + + // Collect erased items + if !data.analyzed_result.erased.is_empty() { + info_erased.push_str(format!("{}\n", md(t!("status.info_display.erased.header"))).as_str()); + for erased in data.analyzed_result.erased.iter() { + info_erased.push_str( + format!( + "{}\n", + md(t!( + "status.info_display.erased.item", + item = erased.display() + )) + ) + .as_str(), + ); + } + } + + // Collect moved items + if !data.analyzed_result.moved.is_empty() { + let mut table = SimpleTable::new(vec![ + format!("{}", md(t!("status.info_display.moved.header"))), + "".to_string(), + ]); + for (_, (from, to)) in data.analyzed_result.moved.iter() { + table.push_item(vec![ + format!( + "{}", + md(t!("status.info_display.moved.left", left = from.display())) + ), + format!( + "{}", + md(t!("status.info_display.moved.right", right = to.display())) + ), + ]); + } + info_moved.push_str(table.to_string().as_str()); + } + + // Collect lost items + if !data.analyzed_result.lost.is_empty() { + info_lost.push_str(format!("{}\n", md(t!("status.info_display.lost.header"))).as_str()); + for lost in data.analyzed_result.lost.iter() { + info_lost.push_str( + format!( + "{}\n", + md(t!("status.info_display.lost.item", item = lost.display())) + ) + .as_str(), + ); + } + } + + // Collect created items + if !data.analyzed_result.created.is_empty() { + info_created + .push_str(format!("{}\n", md(t!("status.info_display.created.header"))).as_str()); + for created in data.analyzed_result.created.iter() { + info_created.push_str( + format!( + "{}\n", + md(t!( + "status.info_display.created.item", + item = created.display() + )) + ) + .as_str(), + ); + } + } + + // Collect modified items + if !data.analyzed_result.modified.is_empty() { + info_modified + .push_str(format!("{}\n", md(t!("status.info_display.modified.header"))).as_str()); + for modified in data.analyzed_result.modified.iter() { + if let Some(reason) = data.wrong_modified_items.get(modified) { + let reason_str = match reason { + JVStatusWrongModifyReason::BaseVersionMismatch { + base_version, + latest_version, + } => md(t!( + "status.info_display.modified.reason.base_version_mismatch", + base_version = base_version, + latest_version = latest_version + )), + JVStatusWrongModifyReason::ModifiedButNotHeld { holder } => md(t!( + "status.info_display.modified.reason.modified_but_not_held", + holder = holder + )), + JVStatusWrongModifyReason::NoHolder => { + md(t!("status.info_display.modified.reason.no_holder")) + } + }; + info_modified.push_str( + format!( + "{}\n", + md(t!( + "status.info_display.modified.item_wrong", + item = modified.display(), + reason = reason_str + )) + ) + .as_str(), + ); + continue; + } + info_modified.push_str( + format!( + "{}\n", + md(t!( + "status.info_display.modified.item", + item = modified.display() + )) + ) + .as_str(), + ); + } + } + + let structural_info = vec![info_erased, info_moved, info_lost].join("\n"); + let content_info = vec![info_created, info_modified].join("\n"); + + let mode = get_mode(data); + match mode { + Mode::StructuralChangesMode => { + r_println!( + r, + "{}", + md(t!("status.current_mode.structural", info = structural_info)) + ); + } + Mode::ContentChangesMode => { + r_println!( + r, + "{}", + md(t!("status.current_mode.content", info = content_info)) + ); + } + Mode::Clean => r_println!(r, "{}", md(t!("status.current_mode.clean"))), + } +} + +fn render_hint(r: &mut JVRenderResult, data: &JVStatusOutput) { + // Outdate Hint + let update_time = &data.update_time; + let now_time = &data.now_time; + let duration_minutes: i64 = (now_time + .duration_since(*update_time) + .unwrap_or_default() + .as_secs() + / 60) as i64; + let outdate_minutes = auto_update_outdate(); + + // Outdated + if duration_minutes > outdate_minutes { + let hours = duration_minutes / 60; + let minutes = duration_minutes % 60; + let seconds = (now_time + .duration_since(*update_time) + .unwrap_or_default() + .as_secs() + % 60) as i64; + + r_println!( + r, + "{}", + md(t!( + "status.hints.outdate", + h = hours, + m = minutes, + s = seconds + )) + ); + } + + let in_ref_sheet = &data.in_ref_sheet; + let is_host_mode = &data.is_host_mode; + + // Readonly + if *in_ref_sheet && !is_host_mode { + r_println!(r, "{}", md(t!("status.hints.readonly"))); + } + + // Host + if *is_host_mode { + r_println!(r, "{}", md(t!("status.hints.host"))); + } +} + +fn get_mode(data: &JVStatusOutput) -> Mode { + let analyzed = &data.analyzed_result; + + // If there are any lost, moved, or erased items, use structural changes mode + if !analyzed.moved.is_empty() || !analyzed.lost.is_empty() || !analyzed.erased.is_empty() { + Mode::StructuralChangesMode + } + // Otherwise, if there are any created or modified items, use content changes mode + else if !analyzed.created.is_empty() || !analyzed.modified.is_empty() { + Mode::ContentChangesMode + } + // Otherwise, it's clean + else { + Mode::Clean + } +} diff --git a/src/systems/cmd/renderer.rs b/src/systems/cmd/renderer.rs index bdd702d..1849ee9 100644 --- a/src/systems/cmd/renderer.rs +++ b/src/systems/cmd/renderer.rs @@ -42,13 +42,13 @@ impl JVRenderResult { #[macro_export] macro_rules! r_print { ($result:expr, $($arg:tt)*) => { - $result.print(&format!($($arg)*)); + $result.print(&format!($($arg)*)) }; } #[macro_export] macro_rules! r_println { ($result:expr, $($arg:tt)*) => { - $result.println(&format!($($arg)*)); + $result.println(&format!($($arg)*)) }; } diff --git a/src/utils/display.rs b/src/utils/display.rs index f0532f3..835313b 100644 --- a/src/utils/display.rs +++ b/src/utils/display.rs @@ -268,17 +268,17 @@ pub fn md(text: impl AsRef<str>) -> String { } } - // Check for inline code `text` - if chars[i] == '`' { + // Check for angle-bracketed content <text> + if chars[i] == '<' { let mut j = i + 1; - while j < chars.len() && chars[j] != '`' { + while j < chars.len() && chars[j] != '>' { j += 1; } if j < chars.len() { - // Include the backticks in the output - let code_text: String = chars[i..=j].iter().collect(); - let mut formatted_text = code_text.green().to_string(); + // Include the angle brackets in the output + let angle_text: String = chars[i..=j].iter().collect(); + let mut formatted_text = angle_text.cyan().to_string(); // Apply current color stack for color in color_stack.iter().rev() { @@ -291,17 +291,17 @@ pub fn md(text: impl AsRef<str>) -> String { } } - // Check for angle-bracketed content <text> - if chars[i] == '<' { + // Check for inline code `text` + if chars[i] == '`' { let mut j = i + 1; - while j < chars.len() && chars[j] != '>' { + while j < chars.len() && chars[j] != '`' { j += 1; } if j < chars.len() { - // Include the angle brackets in the output - let angle_text: String = chars[i..=j].iter().collect(); - let mut formatted_text = angle_text.cyan().to_string(); + // Include the backticks in the output + let code_text: String = chars[i..=j].iter().collect(); + let mut formatted_text = code_text.green().to_string(); // Apply current color stack for color in color_stack.iter().rev() { diff --git a/src/utils/workspace_reader.rs b/src/utils/workspace_reader.rs index a3bc754..c001330 100644 --- a/src/utils/workspace_reader.rs +++ b/src/utils/workspace_reader.rs @@ -253,4 +253,125 @@ impl LocalWorkspaceReader { }; Ok(analyzed.into()) } + + // Pop the local configuration (take ownership if cached) + pub async fn pop_local_config(&mut self) -> Result<LocalConfig, CmdPrepareError> { + if let Some(local_config) = self.local_config.take() { + Ok(local_config) + } else { + let workspace_dir = self.workspace_dir()?; + let local_config = entry_dir!(workspace_dir, { + LocalConfig::read() + .await + .map_err(|_| CmdPrepareError::LocalConfigNotFound)? + }); + Ok(local_config) + } + } + + // Pop the local workspace (take ownership if cached) + pub async fn pop_local_workspace(&mut self) -> Result<LocalWorkspace, CmdPrepareError> { + if let Some(local_workspace) = self.local_workspace.take() { + Ok(local_workspace) + } else { + let workspace_dir = self.workspace_dir()?.clone(); + let local_config = self.local_config().await?.clone(); + let Some(local_workspace) = entry_dir!(&workspace_dir, { + LocalWorkspace::init_current_dir(local_config) + }) else { + return Err(CmdPrepareError::LocalWorkspaceNotFound); + }; + Ok(local_workspace) + } + } + + // Pop the latest information (take ownership if cached) + pub async fn pop_latest_info(&mut self) -> Result<LatestInfo, CmdPrepareError> { + if let Some(latest_info) = self.latest_info.take() { + Ok(latest_info) + } else { + let local_dir = self.workspace_dir()?.clone(); + let local_config = self.local_config().await?.clone(); + let latest_info = entry_dir!(&local_dir, { + match LatestInfo::read_from(LatestInfo::latest_info_path( + &local_dir, + &local_config.current_account(), + )) + .await + { + Ok(info) => info, + Err(_) => { + return Err(CmdPrepareError::LatestInfoNotFound); + } + } + }); + Ok(latest_info) + } + } + + // Pop the latest file data for a specific account (take ownership if cached) + pub async fn pop_latest_file_data( + &mut self, + account: &MemberId, + ) -> Result<LatestFileData, CmdPrepareError> { + if let Some(latest_file_data) = self.latest_file_data.remove(account) { + Ok(latest_file_data) + } else { + let local_dir = self.workspace_dir()?; + let latest_file_data_path = + match entry_dir!(&local_dir, { LatestFileData::data_path(account) }) { + Ok(p) => p, + Err(_) => return Err(CmdPrepareError::LatestFileDataNotExist(account.clone())), + }; + let latest_file_data = LatestFileData::read_from(&latest_file_data_path).await?; + Ok(latest_file_data) + } + } + + // Pop the cached sheet for a specific sheet name (take ownership if cached) + pub async fn pop_cached_sheet( + &mut self, + sheet_name: &SheetName, + ) -> Result<SheetData, CmdPrepareError> { + if let Some(cached_sheet) = self.cached_sheet.remove(sheet_name) { + Ok(cached_sheet) + } else { + let workspace_dir = self.workspace_dir()?; + let cached_sheet = entry_dir!(&workspace_dir, { + match just_enough_vcs::vcs::data::local::cached_sheet::CachedSheet::cached_sheet_data(sheet_name).await { + Ok(data) => data, + Err(_) => return Err(CmdPrepareError::CachedSheetNotFound(sheet_name.clone())), + } + }); + Ok(cached_sheet) + } + } + + // Pop the local sheet data for a specific account and sheet name (take ownership if cached) + pub async fn pop_local_sheet_data( + &mut self, + account: &MemberId, + sheet_name: &SheetName, + ) -> Result<LocalSheetData, CmdPrepareError> { + let key = (account.clone(), sheet_name.clone()); + if let Some(local_sheet_data) = self.local_sheet_data.remove(&key) { + Ok(local_sheet_data) + } else { + let workspace_dir = self.workspace_dir()?.clone(); + let local_workspace = self.local_workspace().await?; + let path = entry_dir!(&workspace_dir, { + local_workspace.local_sheet_path(account, sheet_name) + }); + let local_sheet_data = match LocalSheetData::read_from(path).await { + Ok(data) => data, + Err(_) => { + return Err(CmdPrepareError::LocalSheetNotFound( + account.clone(), + sheet_name.clone(), + )); + } + }; + Ok(local_sheet_data) + } + } } |
