diff options
| author | 魏曹先生 <1992414357@qq.com> | 2025-11-26 13:33:27 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2025-11-26 13:33:27 +0800 |
| commit | 4015ac6d594f971f83e9ff70578eb08fea390c80 (patch) | |
| tree | 7524639a860f72b57bffa609c0ab447c5474787a /src/bin | |
| parent | d1e3c62072f3ab816a8f6fc71424a022f320f373 (diff) | |
Add hold and throw commands for file edit rights
- Implement `jv hold` and `jv throw` commands with file selection - Add
pre-check validation for file existence, mapping, and edit rights -
Support --details and --skip-failed flags for error handling - Add
localization strings for both English and Chinese
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/jv.rs | 441 |
1 files changed, 429 insertions, 12 deletions
diff --git a/src/bin/jv.rs b/src/bin/jv.rs index 9d7220a..ac6c5b1 100644 --- a/src/bin/jv.rs +++ b/src/bin/jv.rs @@ -22,6 +22,10 @@ use just_enough_vcs::{ TrackFileActionResult, UpdateDescription, UpdateTaskResult, VerifyFailReason, proc_track_file_action, }, + user_actions::{ + ChangeVirtualFileEditRightResult, EditRightChangeBehaviour, + proc_change_virtual_file_edit_right_action, + }, }, constants::{ CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE, CLIENT_FOLDER_WORKSPACE_ROOT_NAME, @@ -30,9 +34,14 @@ use just_enough_vcs::{ current::{current_doc_dir, current_local_path}, data::{ local::{ - LocalWorkspace, align::AlignTasks, cached_sheet::CachedSheet, config::LocalConfig, - file_status::AnalyzeResult, latest_file_data::LatestFileData, - latest_info::LatestInfo, local_files::get_relative_paths, + LocalWorkspace, + align::AlignTasks, + cached_sheet::CachedSheet, + config::LocalConfig, + file_status::AnalyzeResult, + latest_file_data::LatestFileData, + latest_info::LatestInfo, + local_files::{RelativeFiles, get_relative_paths}, vault_modified::check_vault_modified, }, member::{Member, MemberId}, @@ -511,6 +520,17 @@ struct HoldFileArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Hold files + hold_files: Option<Vec<PathBuf>>, + + /// Show fail details + #[arg(short = 'd', long = "details")] + show_fail_details: bool, + + /// Skip failed items + #[arg(short = 'S', long)] + skip_failed: bool, } #[derive(Parser, Debug)] @@ -518,6 +538,17 @@ struct ThrowFileArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Throw files + throw_files: Option<Vec<PathBuf>>, + + /// Show fail details + #[arg(short = 'd', long = "details")] + show_fail_details: bool, + + /// Skip failed items + #[arg(short = 'S', long)] + skip_failed: bool, } #[derive(Parser, Debug)] @@ -1425,11 +1456,6 @@ async fn jv_status(_args: StatusArgs) { return; }; - let Ok(cached_sheet) = CachedSheet::cached_sheet_data(&sheet_name).await else { - eprintln!("{}", md(t!("jv.fail.read_cfg"))); - return; - }; - let Ok(analyzed) = AnalyzeResult::analyze_local_status(&local_workspace).await else { eprintln!("{}", md(t!("jv.fail.status.analyze")).trim()); return; @@ -2419,12 +2445,403 @@ async fn start_update_editor( update_info } -async fn jv_hold(_args: HoldFileArgs) { - todo!() +async fn jv_hold(args: HoldFileArgs) { + let hold_files = if let Some(files) = args.hold_files.clone() { + files + .iter() + .map(|f| current_dir().unwrap().join(f)) + .collect::<Vec<_>>() + } else { + println!("{}", md(t!("jv.hold"))); + return; + }; + + jv_change_edit_right( + hold_files, + EditRightChangeBehaviour::Hold, + args.show_fail_details, + args.skip_failed, + ) + .await; } -async fn jv_throw(_args: ThrowFileArgs) { - todo!() +async fn jv_throw(args: ThrowFileArgs) { + let throw_files = if let Some(files) = args.throw_files.clone() { + files + .iter() + .map(|f| current_dir().unwrap().join(f)) + .collect::<Vec<_>>() + } else { + println!("{}", md(t!("jv.throw"))); + return; + }; + + jv_change_edit_right( + throw_files, + EditRightChangeBehaviour::Throw, + args.show_fail_details, + args.skip_failed, + ) + .await; +} + +async fn jv_change_edit_right( + files: Vec<PathBuf>, + behaviour: EditRightChangeBehaviour, + show_fail_details: bool, + mut skip_failed: bool, +) { + // If both `--details` and `--skip-failed` are set, only enable `--details` + if show_fail_details && skip_failed { + skip_failed = false; + } + + let Some(local_dir) = current_local_path() else { + eprintln!("{}", t!("jv.fail.workspace_not_found").trim()); + return; + }; + + let Ok(local_cfg) = LocalConfig::read_from(local_dir.join(CLIENT_FILE_WORKSPACE)).await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + let Some(local_workspace) = LocalWorkspace::init_current_dir(local_cfg.clone()) else { + eprintln!("{}", md(t!("jv.fail.workspace_not_found")).trim()); + return; + }; + + // Get files + let Ok(analyzed) = AnalyzeResult::analyze_local_status(&local_workspace).await else { + eprintln!("{}", md(t!("jv.fail.status.analyze")).trim()); + return; + }; + + let account = local_cfg.current_account(); + + let Ok(latest_file_data_path) = LatestFileData::data_path(&account) else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + let Ok(latest_file_data) = LatestFileData::read_from(&latest_file_data_path).await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + let Some(sheet_name) = local_cfg.sheet_in_use().clone() else { + eprintln!("{}", md(t!("jv.fail.status.no_sheet_in_use")).trim()); + return; + }; + + let Ok(local_sheet) = local_workspace.local_sheet(&account, &sheet_name).await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + let Ok(cached_sheet) = CachedSheet::cached_sheet_data(&sheet_name).await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + // Precheck and filter + let Some(filtered_files) = get_relative_paths(&local_dir, &files).await else { + eprintln!( + "{}", + md(t!("jv.fail.track.parse_fail", param = "track_files")) + ); + return; + }; + let num = filtered_files.iter().len(); + if num < 1 { + eprintln!("{}", md(t!("jv.fail.change_edit_right.no_selection"))); + return; + } + + let mut passed_files = Vec::new(); + let mut details = Vec::new(); + let mut failed = 0; + + for file in filtered_files { + let full_path = local_dir.join(&file); + + // File exists + if !full_path.exists() { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.not_found_in_local") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + + // Mapping exists + if !cached_sheet.mapping().contains_key(&file) { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.not_found_in_sheet") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + + // Not tracked + let Ok(local_mapping) = local_sheet.mapping_data(&file) else { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.not_a_tracked_file") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + }; + + let vfid = local_mapping.mapping_vfid(); + let local_version = local_mapping.version_when_updated(); + + // Base version unmatch + if local_version + != latest_file_data + .file_version(vfid) + .unwrap_or(&String::default()) + { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.base_version_unmatch") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + + // Hold + let holder = latest_file_data.file_holder(vfid); + match behaviour { + EditRightChangeBehaviour::Hold => { + if holder.is_some_and(|h| h != &account) { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = t!( + "jv.fail.change_edit_right.check_fail_reason.has_holder", + holder = holder.unwrap() + ) + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + + if holder.is_some_and(|h| h == &account) { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.already_held") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + } + EditRightChangeBehaviour::Throw => { + if holder.is_some_and(|h| h != &account) { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = + t!("jv.fail.change_edit_right.check_fail_reason.not_holder") + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + if analyzed.modified.contains(&file) { + if show_fail_details { + details.push( + t!( + "jv.fail.change_edit_right.check_fail_item", + path = file.display(), + reason = t!( + "jv.fail.change_edit_right.check_fail_reason.already_modified" + ) + ) + .trim() + .to_string(), + ); + failed += 1; + continue; + } else { + eprintln!( + "{}", + md(t!("jv.fail.change_edit_right.check_failed", num = num)) + ); + return; + } + } + } + } + passed_files.push(file); + } + if failed > 0 && show_fail_details { + eprintln!( + "{}", + md(t!( + "jv.fail.change_edit_right.check_failed_details", + num = num, + failed = failed, + items = details.join("\n").trim() + )) + ); + return; + } + + if !(failed > 0 && skip_failed) && failed != 0 { + return; + } + + let (pool, ctx) = match build_pool_and_ctx(&local_cfg).await { + Some(result) => result, + None => return, + }; + + let passed = passed_files + .iter() + .map(|f| (f.clone(), behaviour.clone())) + .collect(); + + match proc_change_virtual_file_edit_right_action(&pool, ctx, (passed, true)).await { + Ok(r) => match r { + ChangeVirtualFileEditRightResult::Success { + success_hold, + success_throw, + } => { + if success_hold.len() > 0 && success_throw.len() == 0 { + println!( + "{}", + md(t!( + "jv.result.change_edit_right.success.hold", + num = success_hold.len() + )) + ) + } else if success_hold.len() == 0 && success_throw.len() > 0 { + println!( + "{}", + md(t!( + "jv.result.change_edit_right.success.throw", + num = success_throw.len() + )) + ) + } else if success_hold.len() > 0 && success_throw.len() > 0 { + println!( + "{}", + md(t!( + "jv.result.change_edit_right.success.mixed", + num = success_hold.len() + success_throw.len(), + num_hold = success_hold.len(), + num_throw = success_throw.len() + )) + ) + } else { + eprintln!("{}", md(t!("jv.result.change_edit_right.failed.none"))) + } + } + ChangeVirtualFileEditRightResult::AuthorizeFailed(e) => { + eprintln!("{}", md(t!("jv.result.common.authroize_failed", err = e))) + } + ChangeVirtualFileEditRightResult::DoNothing => { + eprintln!("{}", md(t!("jv.result.change_edit_right.failed.none"))) + } + }, + Err(e) => handle_err(e), + } } async fn jv_move(_args: MoveFileArgs) { |
