use super::*; pub(super) fn build_translation_tasks_report( state_dir: &Path, query: OfficialTextUnitTaskQuery, offset: usize, limit: usize, ) -> anyhow::Result { let (_, version_state) = read_daemon_resource_state(state_dir)?; let current = version_state .as_ref() .and_then(|state| state.current_completed_version.as_ref()); let Some(record) = current else { return Ok(serde_json::json!({ "available": false })); }; let task_queue_path = record .resource_root .join(bat_infrastructure::OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE); let task_repository_path = SqliteTranslationTaskRepository::repository_path(&record.resource_root); let Some(queue) = bat_infrastructure::read_textunit_task_queue_at(&record.resource_root) .map_err(anyhow::Error::msg)? else { return Ok(serde_json::json!({ "available": false, "current_version_id": record.id, "resource_root": record.resource_root, "textunit_task_queue_path": task_queue_path, "task_repository_path": task_repository_path, "task_repository_available": false, })); }; let task_repository_available = sqlite_file_exists_no_symlink(&task_repository_path, "翻译任务状态数据库")?; let (total_entries, entries) = if task_repository_available { query_translation_task_repository(&task_repository_path, &query, offset, limit)? } else { let mut queue_query = query.clone(); queue_query.task_status = None; queue_query.has_failure_reason = None; let matches = bat_infrastructure::query_textunit_tasks(&queue, &queue_query); let persisted = matches .into_iter() .cloned() .map(|task| { bat_infrastructure::PersistedTranslationTask::from_queued_task( task, queue.generated_unix_seconds, ) }) .filter(|task| { query .task_status .as_ref() .is_none_or(|status| task.task_status.as_str() == status) }) .filter(|task| { query .has_failure_reason .is_none_or(|has_reason| task.failure_reason.is_some() == has_reason) }) .collect::>(); let total_entries = persisted.len(); let entries = persisted .into_iter() .skip(offset) .take(limit) .collect::>(); (total_entries as u64, entries) }; Ok(serde_json::json!({ "available": true, "current_version_id": record.id, "resource_root": record.resource_root, "textunit_task_queue_path": task_queue_path, "task_repository_path": task_repository_path, "task_repository_available": task_repository_available, "task_repository_schema_version": bat_infrastructure::TRANSLATION_TASK_SCHEMA_VERSION, "summary": queue.summary, "total_entries": total_entries, "offset": offset, "limit": limit, "query": translation_task_query_json(&query), "entries": entries, })) } pub(super) fn build_translation_handoff_report( state_dir: &Path, ) -> anyhow::Result { let (_, version_state) = read_daemon_resource_state(state_dir)?; let current = version_state .as_ref() .and_then(|state| state.current_completed_version.as_ref()); let Some(record) = current else { return Ok(serde_json::json!({ "available": false })); }; let resource_root = &record.resource_root; let task_queue_path = resource_root.join(bat_infrastructure::OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE); let handoff_path = resource_root.join(bat_infrastructure::TRANSLATION_HANDOFF_FILE); let repository_path = SqliteTranslationTaskRepository::repository_path(resource_root); let Some(queue) = bat_infrastructure::read_textunit_task_queue_at(resource_root) .map_err(anyhow::Error::msg)? else { return Ok(serde_json::json!({ "available": false, "current_version_id": record.id, "resource_root": resource_root, "textunit_task_queue_path": task_queue_path, "translation_handoff_path": handoff_path, "task_repository_path": repository_path, })); }; let task_repository_available = sqlite_file_exists_no_symlink(&repository_path, "翻译任务状态数据库")?; let tasks = if task_repository_available { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; runtime.block_on(async { let repository = SqliteTranslationTaskRepository::open(&repository_path) .await .map_err(|error| anyhow::anyhow!("{error}"))?; repository .list(&OfficialTextUnitTaskQuery::default()) .await .map_err(|error| anyhow::anyhow!("{error}")) })? } else { queue .tasks .iter() .cloned() .map(|task| { bat_infrastructure::PersistedTranslationTask::from_queued_task( task, queue.generated_unix_seconds, ) }) .collect::>() }; let handoff = bat_infrastructure::build_translation_handoff(&queue, &tasks); let handoff_file_available = sqlite_file_exists_no_symlink(&handoff_path, "翻译 handoff")?; Ok(serde_json::json!({ "available": true, "current_version_id": record.id, "resource_root": resource_root, "textunit_task_queue_path": task_queue_path, "translation_handoff_path": handoff_path, "translation_handoff_file_available": handoff_file_available, "task_repository_path": repository_path, "task_repository_available": task_repository_available, "task_repository_schema_version": bat_infrastructure::TRANSLATION_TASK_SCHEMA_VERSION, "handoff_schema_version": bat_infrastructure::TRANSLATION_HANDOFF_SCHEMA_VERSION, "handoff": handoff, })) } pub(super) fn update_translation_task_status_report( state_dir: &Path, params: Option<&serde_json::Value>, ) -> anyhow::Result { let task_id = rpc_string_param(params, "task_id") .ok_or_else(|| anyhow::anyhow!("translation.task.update 缺少 task_id"))?; let status_label = rpc_string_param(params, "status") .ok_or_else(|| anyhow::anyhow!("translation.task.update 缺少 status"))?; let status = TranslationTaskStatus::parse(status_label) .ok_or_else(|| anyhow::anyhow!("不支持的翻译任务 worker 状态:{status_label}"))?; let failure_reason = rpc_string_param(params, "failure_reason") .or_else(|| rpc_string_param(params, "reason")) .map(str::to_string); let provider_run_id = rpc_string_param(params, "provider_run_id").map(str::to_string); let (_, version_state) = read_daemon_resource_state(state_dir)?; let current = version_state .as_ref() .and_then(|state| state.current_completed_version.as_ref()) .ok_or_else(|| anyhow::anyhow!("没有可更新翻译任务的当前官方 release"))?; let repository_path = SqliteTranslationTaskRepository::repository_path(¤t.resource_root); if !sqlite_file_exists_no_symlink(&repository_path, "翻译任务状态数据库")? { return Err(anyhow::anyhow!( "翻译任务状态数据库不存在:{}", repository_path.display() )); } let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; let task = runtime.block_on(async { let repository = SqliteTranslationTaskRepository::open(&repository_path) .await .map_err(|error| anyhow::anyhow!("{error}"))?; repository .update_status(task_id, status, failure_reason, provider_run_id) .await .map_err(|error| anyhow::anyhow!("{error}")) })?; Ok(serde_json::json!({ "available": true, "current_version_id": current.id, "task_repository_path": repository_path, "entry": task, })) } pub(super) fn textunit_query_json(query: &OfficialTextUnitQuery) -> serde_json::Value { serde_json::json!({ "destination": query.destination.clone(), "path_pattern": query.path_pattern.clone(), "archive_entry": query.archive_entry.clone(), "path_id": query.path_id, "class_id": query.class_id, "field_path": query.field_path.clone(), "format": query.format.clone(), }) } pub(super) fn translation_task_query_json(query: &OfficialTextUnitTaskQuery) -> serde_json::Value { serde_json::json!({ "task_id": query.task_id.clone(), "official_release_id": query.official_release_id.clone(), "destination": query.destination.clone(), "path_pattern": query.path_pattern.clone(), "archive_entry": query.archive_entry.clone(), "status": query.status.clone(), "task_status": query.task_status.clone(), "parse_status": query.parse_status.clone(), "text_unit_format": query.text_unit_format.clone(), "has_reason": query.has_reason, "has_failure_reason": query.has_failure_reason, }) }