use super::*; pub(super) trait HumanReport { fn print_human(&self) -> anyhow::Result<()>; } pub(super) fn print_report(format: OutputFormat, report: &T) -> anyhow::Result<()> where T: Serialize + HumanReport, { match format { OutputFormat::Json => println!("{}", serde_json::to_string_pretty(report)?), OutputFormat::Human => report.print_human()?, } Ok(()) } pub(super) fn print_json_value( format: OutputFormat, value: &serde_json::Value, ) -> anyhow::Result<()> { match format { OutputFormat::Json => println!("{}", serde_json::to_string_pretty(value)?), OutputFormat::Human => print_human_json_value(value)?, } Ok(()) } impl HumanReport for serde_json::Value { fn print_human(&self) -> anyhow::Result<()> { print_human_json_value(self) } } impl HumanReport for RepackReport { fn print_human(&self) -> anyhow::Result<()> { print_title("UnityFS 重打包"); print_field("命令", self.command); print_field("状态", self.status); print_path_field("源 bundle", &self.source_bundle); print_path_field("目标 bundle", &self.target_bundle); print_field("操作数", self.operation_count); print_field("源字节", self.source_bytes); print_field("目标字节", self.target_bytes); print_field("源 BLAKE3", &self.source_blake3); print_field("目标 BLAKE3", &self.target_blake3); Ok(()) } } impl HumanReport for LocalizedPatchReport { fn print_human(&self) -> anyhow::Result<()> { print_title("汉化 release 发布"); print_path_field("版本目录", &self.version_path); print_path_field("current", &self.current_path); print_path_field("状态文件", &self.state_path); print_path_field("patch manifest", &self.patch_manifest_path); print_field("变更文件数", self.files.len()); print_field("TextAsset 操作数", self.manifest.text_asset_operation_count); print_field("校验文件数", self.integrity.verified_changed_file_count); Ok(()) } } impl HumanReport for LocalizedRollbackReport { fn print_human(&self) -> anyhow::Result<()> { print_title("汉化 release 回滚"); print_field("命令", self.command); print_field("状态", self.status); print_field("回滚 release", &self.rolled_back_release_id); print_optional_field("恢复 release", self.restored_release_id.as_deref()); print_path_field("汉化输出目录", &self.localized_output_root); print_path_field("current", &self.current_path); print_path_field("状态文件", &self.state_path); print_path_field("删除版本目录", &self.removed_version_path); print_optional_path_field("恢复 current 目标", self.restored_current_target.as_ref()); print_field("新状态", &self.state.status); print_optional_field("当前 release", self.state.current_release_id.as_deref()); Ok(()) } } impl HumanReport for bat_infrastructure::LocalizedTranslationWorkflowReport { fn print_human(&self) -> anyhow::Result<()> { print_title("汉化工作流状态"); print_field("命令", self.command); print_field("状态", self.status); print_field("官方 release", &self.official_release_id); print_optional_field("汉化 release", self.current_release_id.as_deref()); print_field("汉化发布状态", &self.localized_release_status); print_field("工作流状态", &self.translation_workflow_status); print_field("工作流状态码", self.translation_workflow_status_code); print_field("工作流标签", self.translation_workflow_label); print_field("允许发布", format_bool(self.publish_allowed)); print_path_field("汉化输出目录", &self.localized_output_root); print_path_field("状态文件", &self.state_path); Ok(()) } } impl HumanReport for bat_infrastructure::TranslationWorkerReport { fn print_human(&self) -> anyhow::Result<()> { print_title("翻译 provider worker"); print_field("命令", self.command); print_field("状态", self.status); print_field("官方 release", &self.official_release_id); print_field("provider", &self.provider); print_field("回收 lease", self.recovered_lease_count); print_field("领取任务", self.claimed_count); print_field("完成任务", self.completed_count); print_field("失败任务", self.failed_count); print_field("已安排重试", self.retry_scheduled_count); print_field("剩余任务", self.remaining_count); print_path_field("Translation Memory", &self.translation_memory_path); print_field("TM 可用", format_bool(self.translation_memory_available)); print_field("TM 命中 TextUnit", self.translation_memory_hit_count); print_field("Provider TextUnit", self.provider_unit_count); print_path_field("Glossary", &self.glossary_path); print_field("Glossary 可用", format_bool(self.glossary_available)); print_field("Glossary blocking TextUnit", self.glossary_blocked_count); for failure in &self.translation_memory_failures { println!(" - TM: {failure}"); } for failure in &self.glossary_failures { println!(" - Glossary: {failure}"); } for failure in &self.failures { println!( " - {} [{}] retryable={} {}", failure.task_id, failure.failure_class, format_bool(failure.retryable), failure.failure_reason ); } Ok(()) } } fn print_human_json_value(value: &serde_json::Value) -> anyhow::Result<()> { if value.get("running").is_some() && value.get("state_dir").is_some() { print_title("后台状态"); print_json_field(value, "status", "状态"); print_json_field(value, "message", "消息"); print_json_field(value, "running", "运行中"); print_json_field(value, "pid", "PID"); print_json_field(value, "daemon_state", "后台状态"); print_json_field(value, "rpc_available", "RPC 可用"); print_json_field(value, "stale_pid_file", "失效 PID"); print_json_field(value, "stale_socket", "失效 socket"); print_json_field(value, "last_update_status", "上次同步"); print_json_field(value, "last_success_unix_seconds", "最后成功时间"); print_json_field(value, "last_error", "上次错误"); print_json_field(value, "next_retry_seconds", "下次重试秒数"); print_json_field(value, "next_check_unix_seconds", "下次检查时间"); print_json_field(value, "current_stage", "当前阶段"); print_json_field(value, "current_message", "当前消息"); print_daemon_download_progress_json_summary(value.get("download_progress")); print_json_field(value, "version_state_path", "版本状态路径"); print_daemon_version_state_json_summary(value.get("version_state")); print_json_field(value, "resource_output_root", "资源目录"); print_json_field(value, "state_dir", "状态目录"); print_json_field(value, "socket_path", "socket"); print_json_field(value, "log_path", "日志"); print_json_field(value, "structured_log_path", "结构化日志"); print_json_field(value, "rotated_structured_log_paths", "轮转日志"); return Ok(()); } if value.get("command").and_then(serde_json::Value::as_str) == Some("logs") { print_title("后台日志"); print_json_field(value, "status", "状态"); print_json_field(value, "message", "消息"); print_json_field(value, "log_path", "日志"); print_json_field(value, "bytes", "字节"); print_json_field(value, "total_lines", "总行数"); print_json_field(value, "returned_lines", "返回行数"); if let Some(content) = value.get("content").and_then(serde_json::Value::as_str) { if !content.is_empty() { println!(); println!("{content}"); } } return Ok(()); } if value.get("command").is_some() && value.get("status").is_some() { print_title("后台命令"); print_json_field(value, "command", "命令"); print_json_field(value, "status", "状态"); print_json_field(value, "message", "消息"); print_json_field(value, "force", "force"); print_json_field(value, "state_dir", "状态目录"); print_json_field(value, "socket_path", "socket"); return Ok(()); } println!("{}", serde_json::to_string_pretty(value)?); Ok(()) } fn print_daemon_download_progress_json_summary(value: Option<&serde_json::Value>) { let Some(value) = value else { return; }; if value.is_null() { return; } print_field( "下载进度", format_daemon_download_progress_json(value) .unwrap_or_else(|error| format!("无法解析:{error}")), ); } pub(super) fn format_daemon_download_progress_json( value: &serde_json::Value, ) -> Result { let progress = serde_json::from_value::(value.clone()) .map_err(|error| error.to_string())?; Ok(format_daemon_download_progress(&progress)) } fn print_daemon_version_state_json_summary(value: Option<&serde_json::Value>) { let Some(value) = value else { return; }; if value.is_null() { return; } match serde_json::from_value::(value.clone()) { Ok(version_state) => print_daemon_version_state_summary(&version_state), Err(error) => print_field("版本状态", format!("无法解析:{error}")), } } fn print_daemon_version_state_summary(version_state: &OfficialVersionState) { print_optional_field( "当前完成版本", version_state .current_completed_version .as_ref() .map(|version| version.id.as_str()), ); print_optional_field( "正在拉取版本", version_state .in_progress_version .as_ref() .map(|version| version.id.as_str()), ); print_optional_field( "上一个可用版本", version_state .previous_available_version .as_ref() .map(|version| version.id.as_str()), ); let historical_failures = visible_historical_failed_versions(version_state); print_field("历史失败版本数", historical_failures.len()); if let Some(failed) = historical_failures.last() { print_field("最近历史失败版本", &failed.version.id); print_field("最近历史失败时间", failed.failed_unix_seconds); print_field("最近历史失败原因", &failed.error); } } pub(super) fn visible_historical_failed_versions( version_state: &OfficialVersionState, ) -> Vec<&OfficialFailedVersionRecord> { let in_progress = version_state.in_progress_version.as_ref(); version_state .failed_versions .iter() .filter(|failed| { !in_progress.is_some_and(|version| version_matches_for_status(&failed.version, version)) }) .collect() } fn version_matches_for_status(left: &OfficialVersionRecord, right: &OfficialVersionRecord) -> bool { left.app_version == right.app_version && left.bundle_version == right.bundle_version && left.addressables_root == right.addressables_root } fn print_json_field(value: &serde_json::Value, key: &str, label: &str) { let Some(value) = value.get(key) else { return; }; if value.is_null() { return; } if let Some(value) = value.as_str() { print_field(label, value); } else { print_field(label, value); } } fn print_title(title: &str) { println!("{title}"); } fn print_field(label: &str, value: impl std::fmt::Display) { println!(" {label:<18} {value}"); } fn print_optional_field(label: &str, value: Option) where T: std::fmt::Display, { if let Some(value) = value { print_field(label, value); } } fn print_path_field(label: &str, value: &Path) { print_field(label, value.display()); } fn print_optional_path_field(label: &str, value: Option<&PathBuf>) { if let Some(value) = value { print_path_field(label, value); } } fn print_list(label: &str, values: &[String], limit: usize) { if values.is_empty() { return; } println!(" {label}:"); for value in values.iter().take(limit) { println!(" - {value}"); } if values.len() > limit { println!(" ... 还有 {} 项", values.len() - limit); } } fn format_daemon_download_progress(progress: &DaemonDownloadProgress) -> String { let status = progress.status.as_deref().unwrap_or("running"); if let Some(hash) = progress.official_hash.as_ref() { return format!( "{}/{} official_hash algorithm={} expected={} actual={} data={} hash={}", progress.index, progress.total, hash.algorithm.as_str(), hash.expected, hash.actual, hash.data_url, hash.hash_url ); } if status == "failed" { return format!( "{}/{} failed kind={} http={} retryable={} attempts={} quarantined={} {}", progress.index, progress.total, progress.failure_kind.as_deref().unwrap_or("unknown"), progress .failure_http_status .map(|status| status.to_string()) .unwrap_or_else(|| "none".to_string()), progress .failure_retryable .map(|retryable| retryable.to_string()) .unwrap_or_else(|| "unknown".to_string()), progress .failure_attempts .map(|attempts| attempts.to_string()) .unwrap_or_else(|| "0".to_string()), progress .quarantined .map(|quarantined| quarantined.to_string()) .unwrap_or_else(|| "false".to_string()), progress.url ); } if let Some(verification) = progress.verification.as_ref() { return format!( "{}/{} {} bytes={} blake3={} zip_checked={} zip_verified={} {}", progress.index, progress.total, status, verification.actual_bytes, verification.actual_blake3, verification.zip_checked, verification.zip_structure_verified, progress.url ); } format!( "{}/{} {} {}", progress.index, progress.total, status, progress.url ) } fn print_verification_summary(summary: &OfficialVerificationSummary) { println!(" 校验摘要:"); for line in verification_summary_lines(summary) { println!(" - {line}"); } } pub(super) fn verification_summary_lines(summary: &OfficialVerificationSummary) -> Vec { vec![ format!( "官方 .hash 强校验: {} 对 ({})", summary.official_hash_verified_count, summary.official_hash_scope ), format!( "本地 BLAKE3 复用校验: {} 项通过, {} 项需修复 ({})", summary.local_manifest_blake3_verified_count, summary.local_manifest_repair_needed_count, summary.local_manifest_blake3_scope ), format!( "ZIP 结构校验: {} 个 ZIP 通过 ({})", summary.zip_structure_verified_count, summary.zip_structure_scope ), ] } fn format_bool(value: bool) -> &'static str { if value { "yes" } else { "no" } } fn format_bytes(value: u64) -> String { const KIB: f64 = 1024.0; const MIB: f64 = 1024.0 * 1024.0; const GIB: f64 = 1024.0 * 1024.0 * 1024.0; let value_f = value as f64; if value_f >= GIB { format!("{value_f:.2} GiB", value_f = value_f / GIB) } else if value_f >= MIB { format!("{value_f:.2} MiB", value_f = value_f / MIB) } else if value_f >= KIB { format!("{value_f:.2} KiB", value_f = value_f / KIB) } else { format!("{value} B") } } fn platform_label(platform: PatchPlatform) -> &'static str { match platform { PatchPlatform::Windows => "Windows", PatchPlatform::Android => "Android", } } fn endpoint_kind_label_for_human(kind: YostarJpResourceEndpointKind) -> &'static str { match kind { YostarJpResourceEndpointKind::TableCatalog => "table_catalog", YostarJpResourceEndpointKind::TableCatalogHash => "table_catalog_hash", YostarJpResourceEndpointKind::AddressablesCatalog => "addressables_catalog", YostarJpResourceEndpointKind::AddressablesCatalogHash => "addressables_catalog_hash", YostarJpResourceEndpointKind::BundlePackingInfo => "bundle_packing_info", YostarJpResourceEndpointKind::BundlePackingInfoHash => "bundle_packing_info_hash", YostarJpResourceEndpointKind::MediaCatalog => "media_catalog", YostarJpResourceEndpointKind::MediaCatalogHash => "media_catalog_hash", } } impl HumanReport for OfficialUpdateReport { fn print_human(&self) -> anyhow::Result<()> { print_title("官方资源同步"); print_field("状态", self.update_status.as_str()); print_field("状态码", self.status_code.as_str()); print_field("应用版本", &self.app_version); print_optional_field("Bundle 版本", self.bundle_version.as_deref()); print_field("连接组", &self.connection_group); print_field( "平台", self.platforms .iter() .map(|platform| platform_label(*platform)) .collect::>() .join(", "), ); print_field("需要下载", format_bool(self.should_download)); print_field( "等待官方资源", format_bool(self.waiting_for_official_resources), ); print_field("首次同步", format_bool(self.is_initial)); print_field("强制刷新", format_bool(self.force)); print_field("本地审计", format_bool(self.audit_local)); print_field("自动修复", format_bool(self.repair)); print_field("dry-run", format_bool(self.dry_run)); print_path_field("官方资源目录", &self.output_root); print_path_field("汉化输出目录", &self.localized_output_root); print_field("汉化发布状态", self.localized_release_status.as_str()); print_path_field("汉化 current", &self.localized_current_path); print_optional_path_field( "汉化 published", self.localized_published_version_path.as_ref(), ); print_path_field("active release", &self.active_resource_root); print_path_field("current", &self.current_path); print_path_field("version state", &self.version_state_path); print_optional_path_field("staging", self.staging_path.as_ref()); print_optional_path_field("published", self.published_version_path.as_ref()); print_path_field("snapshot", &self.snapshot_path); print_path_field("manifest", &self.download_manifest); print_optional_path_field("资源变更集", self.resource_change_set_path.as_ref()); print_optional_path_field("Crowdin handoff", self.crowdin_handoff_path.as_ref()); print_optional_path_field("解析缓存", self.parse_cache_path.as_ref()); print_optional_path_field("TextUnit 任务队列", self.textunit_task_queue_path.as_ref()); print_optional_path_field( "Crowdin TextUnit 队列", self.crowdin_textunit_queue_path.as_ref(), ); print_optional_path_field("写入 snapshot", self.snapshot_written.as_ref()); print_optional_path_field( "启动器引导产物", self.launcher_bootstrap_artifact_path.as_ref(), ); print_optional_path_field("bootstrap cache", self.bootstrap_cache_path.as_ref()); print_optional_field("bootstrap 命中", self.bootstrap_cache_hit.map(format_bool)); print_optional_field("计划 URL 数", self.download_url_count); print_optional_field("资源数", self.resource_count); print_field("已下载", self.downloaded_count); print_field("已续传", self.resumed_count); print_field("当前 manifest 复用", self.skipped_count); print_field("历史 release 复用", self.release_reused_count); print_field("CAS 复用", self.cas_reused_count); print_field("复用量", format_bytes(self.reused_bytes)); print_field("传输量", format_bytes(self.transferred_bytes)); print_field("复用诊断", self.reuse_warnings.len()); for warning in &self.reuse_warnings { println!( " - 复用回退 [{}] {} {}", warning.source, warning.url, warning.message ); } print_field("最终大小", format_bytes(self.final_bytes)); print_field("本地校验通过", self.local_manifest_verified_count); print_field("需修复", self.local_manifest_repair_needed_count); print_field("官方 hash 校验", self.official_seed_hash_verified_count); print_verification_summary(&self.verification_summary); if let Some(summary) = self.resource_change_summary.as_ref() { print_field("新增资源", summary.added_count); print_field("变更资源", summary.modified_count); print_field("删除资源", summary.removed_count); print_field("解析候选", summary.parse_candidate_count); print_field("Crowdin 候选", summary.translation_candidate_count); } if let Some(summary) = self.parse_summary.as_ref() { print_field("解析缓存条目", summary.cache_entry_count); print_field("解析成功 bundle", summary.parsed_bundle_count); print_field("解析复用", summary.skipped_unchanged_count); print_field("解析不支持", summary.unsupported_count); print_field("解析失败", summary.failed_count); print_field("TextAsset", summary.text_asset_count); print_field("TextUnit", summary.text_unit_count); print_field("二进制 TextAsset", summary.skipped_binary_text_asset_count); print_field("TextUnit 诊断", summary.text_unit_error_count); } if let Some(summary) = self.textunit_task_summary.as_ref() { print_field("TextUnit 资源候选", summary.resource_candidate_count); print_field("TextUnit 解析条目", summary.parse_entry_count); print_field("TextUnit 任务", summary.queued_task_count); print_field("增量 TextUnit", summary.text_unit_count); print_field("TextUnit 无解析", summary.skipped_no_parse_entry_count); print_field("TextUnit 无文本", summary.skipped_no_text_unit_count); print_field("TextUnit 解析失败", summary.skipped_parse_failed_count); print_field("TextUnit 不支持", summary.skipped_unsupported_count); } print_field("catalog marker", self.addressables_marker_checked_count); if !self.unavailable_endpoints.is_empty() { let unavailable = self .unavailable_endpoints .iter() .map(|endpoint| { format!( "{}{} kind={} http={} {}", endpoint_kind_label_for_human(endpoint.kind), endpoint .platform .map(|platform| format!(" ({})", platform_label(platform))) .unwrap_or_default(), endpoint.error_kind, endpoint .http_status .map(|status| status.to_string()) .unwrap_or_else(|| "none".to_string()), endpoint.url ) }) .collect::>(); print_list("不可用官方 endpoint", &unavailable, 8); } print_list("变更 endpoint", &self.changed_endpoint_urls, 8); print_list("计划 URL", &self.download_urls, 8); Ok(()) } } impl HumanReport for CommandReport where T: Serialize + HumanReport, { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); self.data.print_human() } } impl HumanReport for PatchApplyReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_field("Patch 类型", self.kind.as_str()); print_path_field("源文件", &self.source_path); print_path_field("Patch 文件", &self.patch_path); print_path_field("目标文件", &self.target_path); print_field("源字节", self.source_size); print_field("Patch 字节", self.patch_size); print_field("目标字节", self.target_size); print_field("源 BLAKE3", &self.source_blake3); print_field("Patch BLAKE3", &self.patch_blake3); print_field("目标 BLAKE3", &self.target_blake3); Ok(()) } } impl HumanReport for UnityFsPatchReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_path_field("源 bundle", &self.bundle_path); print_field("Serialized 文件", &self.serialized_file_path); print_field("Path ID", self.path_id); print_optional_field("字段路径", self.field_path.as_deref()); print_path_field("目标 bundle", &self.target_path); print_field("源字节", self.source_size); print_field("替换字节", self.replacement_size); print_field("目标字节", self.target_size); print_field("源 BLAKE3", &self.source_blake3); print_field("替换 BLAKE3", &self.replacement_blake3); print_field("目标 BLAKE3", &self.target_blake3); Ok(()) } } impl HumanReport for DaemonStartReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("状态", self.status); print_field("PID", self.pid); print_path_field("资源目录", &self.resource_output_root); print_path_field("汉化目录", &self.localized_output_root); print_path_field("状态目录", &self.state_dir); print_path_field("socket", &self.socket_path); print_path_field("日志", &self.log_path); print_path_field("结构化日志", &self.structured_log_path); Ok(()) } } impl HumanReport for DaemonStatusReport { fn print_human(&self) -> anyhow::Result<()> { print_title("后台状态"); print_field("状态", self.status); print_field("消息", self.message); print_field("运行中", format_bool(self.running)); print_optional_field("PID", self.pid); print_optional_field("后台状态", self.daemon_state.as_deref()); print_field("RPC 可用", format_bool(self.rpc_available)); print_field("失效 PID", format_bool(self.stale_pid_file)); print_field("失效 socket", format_bool(self.stale_socket)); print_optional_field("上次同步", self.last_update_status.as_deref()); print_optional_field("最后成功时间", self.last_success_unix_seconds); print_optional_field("上次错误", self.last_error.as_deref()); print_optional_field("下次重试秒数", self.next_retry_seconds); print_optional_field("下次检查时间", self.next_check_unix_seconds); print_optional_field("当前阶段", self.current_stage.as_deref()); print_optional_field("当前消息", self.current_message.as_deref()); if let Some(progress) = self.download_progress.as_ref() { print_field("下载进度", format_daemon_download_progress(progress)); } if let Some(version_state) = self.version_state.as_ref() { print_daemon_version_state_summary(version_state); } print_optional_path_field("资源目录", self.resource_output_root.as_ref()); print_optional_path_field("汉化目录", self.localized_output_root.as_ref()); print_optional_path_field("版本状态", self.version_state_path.as_ref()); print_path_field("状态目录", &self.state_dir); print_path_field("socket", &self.socket_path); print_optional_path_field("日志", self.log_path.as_ref()); print_optional_path_field("结构化日志", self.structured_log_path.as_ref()); let rotated = self .rotated_structured_log_paths .iter() .map(|path| path.display().to_string()) .collect::>(); print_list("轮转日志", &rotated, 5); Ok(()) } } impl HumanReport for DaemonStopReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("状态", self.status); print_field("已停止", format_bool(self.stopped)); print_optional_field("PID", self.pid); print_path_field("状态目录", &self.state_dir); print_path_field("socket", &self.socket_path); Ok(()) } } impl HumanReport for DaemonControlReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_field("策略", self.strategy); print_optional_field("旧 PID", self.previous_pid); print_field("PID", self.pid); print_path_field("资源目录", &self.resource_output_root); print_path_field("状态目录", &self.state_dir); print_path_field("socket", &self.socket_path); print_path_field("日志", &self.log_path); Ok(()) } } impl HumanReport for VerifyCommandReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_field("健康", format_bool(self.healthy)); print_field("远端状态", &self.remote_update_status); print_path_field("校验资源目录", &self.verified_resource_root); print_optional_field("计划 URL 数", self.planned_url_count); print_field("计划异常数", self.expected_plan_failure_count); print_field("本地 manifest 项", self.local_manifest_entry_count); print_field("本地校验通过", self.local_manifest_verified_count); print_field("本地失败数", self.local_manifest_failure_count); print_field("官方 hash 对", self.official_hash_pair_count); print_field("官方 hash 通过", self.official_hash_verified_count); print_verification_summary(&self.verification_summary); if !self.official_hash_errors.is_empty() { print_list("官方 hash 错误", &self.official_hash_errors, 8); } if !self.failures.is_empty() { println!(" 失败项:"); for item in self.failures.iter().take(12) { println!(" - {} -> {}", item.status, item.destination.display()); } if self.failures.len() > 12 { println!(" ... 还有 {} 项", self.failures.len() - 12); } } Ok(()) } } impl HumanReport for LogsReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("状态", self.status); print_path_field("日志", &self.log_path); print_field("存在", format_bool(self.exists)); print_field("为空", format_bool(self.empty)); print_field("字节", self.bytes); print_field("总行数", self.total_lines); print_field("返回行数", self.returned_lines); if !self.content.is_empty() { println!(); println!("{}", self.content); } Ok(()) } } impl HumanReport for DoctorReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_field("健康", format_bool(self.healthy)); println!(" 检查:"); for check in &self.checks { println!( " [{}] {} - {}", if check.ok { "OK" } else { "FAIL" }, check.name, check.message ); } Ok(()) } } impl HumanReport for DoctorCasReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_field("健康", format_bool(self.healthy)); print_path_field("CAS 目录", &self.cas_root); print_path_field("对象目录", &self.objects_dir); print_path_field("元数据库", &self.metadata_db_path); print_field("对象数", self.object_count); print_field("总字节", self.total_size); print_field("无效对象文件", self.invalid_object_count); println!(" 检查:"); for check in &self.checks { println!( " [{}] {} - {}", if check.ok { "OK" } else { "FAIL" }, check.name, check.message ); } Ok(()) } } impl HumanReport for CleanStableReport { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_path_field("资源目录", &self.output_root); print_path_field("状态目录", &self.state_dir); if !self.removed_paths.is_empty() { println!(" 已清理:"); for path in &self.removed_paths { println!(" - {}", path.display()); } } if !self.skipped_paths.is_empty() { println!(" 已跳过:"); for path in &self.skipped_paths { println!(" - {}", path.display()); } } Ok(()) } } impl HumanReport for DaemonRpcAck { fn print_human(&self) -> anyhow::Result<()> { print_title(self.message); print_field("命令", self.command); print_field("状态", self.status); print_optional_field("force", self.force.map(format_bool)); print_path_field("状态目录", &self.state_dir); print_path_field("socket", &self.socket_path); Ok(()) } }