mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
feat(translation): 增加离线任务状态查询
This commit is contained in:
@@ -12,14 +12,15 @@ use bat_infrastructure::{
|
||||
redact_proxy_url, resolve_curl_proxy, validate_output_root, validate_runtime_state_dir,
|
||||
write_file_atomic, CurlProxyConfig, CurlProxyMode, OfficialEndpointMarkerRole,
|
||||
OfficialFailedVersionRecord, OfficialResourceHashVerification, OfficialResourceVerification,
|
||||
OfficialServerInfoSource, OfficialTextUnitQuery, OfficialUpdateConfig, OfficialUpdateProgress,
|
||||
OfficialUpdateReport, OfficialUpdateService, OfficialUpdateSnapshot, OfficialUpdateStatus,
|
||||
OfficialVerificationSummary, OfficialVersionRecord, OfficialVersionState, PatchApplyKind,
|
||||
PatchApplyParams, PatchApplyReport, ReleaseFlowStatusCode, SqliteResourceRepository,
|
||||
UnityFsFieldPatchParams, UnityFsPatchReport, UnityFsStringFieldPatchParams,
|
||||
UnityFsTextAssetPatchParams, LOCALIZED_CURRENT_LINK, LOCALIZED_PATCH_MANIFEST_FILE,
|
||||
LOCALIZED_VERSIONS_DIR, LOCALIZED_VERSION_STATE_FILE, OFFICIAL_PARSE_CACHE_FILE,
|
||||
OFFICIAL_TEXTUNIT_INDEX_FILE, PRIVATE_FILE_MODE,
|
||||
OfficialServerInfoSource, OfficialTextUnitQuery, OfficialTextUnitTaskQuery,
|
||||
OfficialUpdateConfig, OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService,
|
||||
OfficialUpdateSnapshot, OfficialUpdateStatus, OfficialVerificationSummary,
|
||||
OfficialVersionRecord, OfficialVersionState, PatchApplyKind, PatchApplyParams,
|
||||
PatchApplyReport, ReleaseFlowStatusCode, SqliteResourceRepository, UnityFsFieldPatchParams,
|
||||
UnityFsPatchReport, UnityFsStringFieldPatchParams, UnityFsTextAssetPatchParams,
|
||||
LOCALIZED_CURRENT_LINK, LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_VERSIONS_DIR,
|
||||
LOCALIZED_VERSION_STATE_FILE, OFFICIAL_PARSE_CACHE_FILE, OFFICIAL_TEXTUNIT_INDEX_FILE,
|
||||
PRIVATE_FILE_MODE,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -173,6 +174,7 @@ fn run() -> anyhow::Result<i32> {
|
||||
CliCommand::ParseStatus
|
||||
| CliCommand::ParseTextUnits
|
||||
| CliCommand::ParseErrors
|
||||
| CliCommand::TranslationTasks
|
||||
| CliCommand::LocalizedStatus
|
||||
| CliCommand::ResourceIndex => {
|
||||
run_readonly_query_command(&options)?;
|
||||
@@ -231,6 +233,7 @@ struct CliOptions {
|
||||
tail_lines: usize,
|
||||
query_offset: usize,
|
||||
query_limit: usize,
|
||||
query_task_id: Option<String>,
|
||||
query_resource_type: Option<ResourceType>,
|
||||
query_hash: Option<String>,
|
||||
query_path_pattern: Option<String>,
|
||||
@@ -239,11 +242,13 @@ struct CliOptions {
|
||||
query_destination: Option<String>,
|
||||
query_bundle_path: Option<String>,
|
||||
query_archive_entry: Option<String>,
|
||||
query_task_status: Option<String>,
|
||||
query_parse_status: Option<String>,
|
||||
query_path_id: Option<i64>,
|
||||
query_class_id: Option<i32>,
|
||||
query_field_path: Option<String>,
|
||||
query_format: Option<String>,
|
||||
query_has_reason: Option<bool>,
|
||||
query_option_explicit: bool,
|
||||
patch_kind: Option<PatchApplyKind>,
|
||||
patch_source_path: Option<PathBuf>,
|
||||
@@ -287,6 +292,7 @@ impl Default for CliOptions {
|
||||
tail_lines: 200,
|
||||
query_offset: 0,
|
||||
query_limit: 100,
|
||||
query_task_id: None,
|
||||
query_resource_type: None,
|
||||
query_hash: None,
|
||||
query_path_pattern: None,
|
||||
@@ -295,11 +301,13 @@ impl Default for CliOptions {
|
||||
query_destination: None,
|
||||
query_bundle_path: None,
|
||||
query_archive_entry: None,
|
||||
query_task_status: None,
|
||||
query_parse_status: None,
|
||||
query_path_id: None,
|
||||
query_class_id: None,
|
||||
query_field_path: None,
|
||||
query_format: None,
|
||||
query_has_reason: None,
|
||||
query_option_explicit: false,
|
||||
patch_kind: None,
|
||||
patch_source_path: None,
|
||||
@@ -340,6 +348,7 @@ enum CliCommand {
|
||||
ParseStatus,
|
||||
ParseTextUnits,
|
||||
ParseErrors,
|
||||
TranslationTasks,
|
||||
LocalizedStatus,
|
||||
ResourceIndex,
|
||||
PatchApply,
|
||||
@@ -790,6 +799,7 @@ const RPC_METHOD_RESOURCE_LIST: &str = "resource.list";
|
||||
const RPC_METHOD_PARSE_STATUS: &str = "parse.status";
|
||||
const RPC_METHOD_PARSE_TEXT_UNITS: &str = "parse.text_units";
|
||||
const RPC_METHOD_PARSE_ERRORS: &str = "parse.errors";
|
||||
const RPC_METHOD_TRANSLATION_TASKS: &str = "translation.tasks";
|
||||
const RPC_METHOD_LOCALIZED_STATUS: &str = "localized.status";
|
||||
const RPC_METHOD_CATALOG_STATUS: &str = "catalog.status";
|
||||
const RPC_METHOD_CATALOG_VERSIONS: &str = "catalog.versions";
|
||||
@@ -2247,6 +2257,27 @@ fn dispatch_rpc_method(
|
||||
build_parse_errors_report(state_dir, query, offset, limit),
|
||||
)
|
||||
}
|
||||
RPC_METHOD_TRANSLATION_TASKS => {
|
||||
let (query, offset, limit) =
|
||||
match rpc_translation_task_query_params(request.params.as_ref()) {
|
||||
Ok(params) => params,
|
||||
Err(error) => {
|
||||
return rpc_envelope_error(
|
||||
request_id,
|
||||
ApiError::new(
|
||||
ErrorCode::RPC_INVALID_PARAMS,
|
||||
"translation.tasks",
|
||||
error.to_string(),
|
||||
),
|
||||
)
|
||||
}
|
||||
};
|
||||
rpc_envelope_from_result(
|
||||
request_id,
|
||||
"translation.tasks",
|
||||
build_translation_tasks_report(state_dir, query, offset, limit),
|
||||
)
|
||||
}
|
||||
RPC_METHOD_LOCALIZED_STATUS => rpc_envelope_from_result(
|
||||
request_id,
|
||||
"localized.status",
|
||||
@@ -3041,6 +3072,55 @@ fn build_parse_errors_report(
|
||||
}))
|
||||
}
|
||||
|
||||
/// `translation.tasks`:当前已发布版本的离线翻译任务状态查询。
|
||||
fn build_translation_tasks_report(
|
||||
state_dir: &Path,
|
||||
query: OfficialTextUnitTaskQuery,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
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 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,
|
||||
}));
|
||||
};
|
||||
let matches = bat_infrastructure::query_textunit_tasks(&queue, &query);
|
||||
let total_entries = matches.len();
|
||||
let entries = matches
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
Ok(serde_json::json!({
|
||||
"available": true,
|
||||
"current_version_id": record.id,
|
||||
"resource_root": record.resource_root,
|
||||
"textunit_task_queue_path": task_queue_path,
|
||||
"summary": queue.summary,
|
||||
"total_entries": total_entries,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"query": translation_task_query_json(&query),
|
||||
"entries": entries,
|
||||
}))
|
||||
}
|
||||
|
||||
fn textunit_query_json(query: &OfficialTextUnitQuery) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"destination": query.destination.clone(),
|
||||
@@ -3053,6 +3133,20 @@ fn textunit_query_json(query: &OfficialTextUnitQuery) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
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(),
|
||||
"parse_status": query.parse_status.clone(),
|
||||
"text_unit_format": query.text_unit_format.clone(),
|
||||
"has_reason": query.has_reason,
|
||||
})
|
||||
}
|
||||
|
||||
/// `localized.status`:当前官方版本对应的汉化 release 状态。
|
||||
fn build_localized_status_report(
|
||||
state_dir: &Path,
|
||||
@@ -3289,6 +3383,30 @@ fn rpc_textunit_query_params(
|
||||
Ok((query, offset, limit))
|
||||
}
|
||||
|
||||
fn rpc_translation_task_query_params(
|
||||
params: Option<&serde_json::Value>,
|
||||
) -> anyhow::Result<(OfficialTextUnitTaskQuery, usize, usize)> {
|
||||
let (offset, limit) = rpc_page_params(params)?;
|
||||
let query = OfficialTextUnitTaskQuery {
|
||||
task_id: rpc_string_param(params, "task_id").map(str::to_string),
|
||||
official_release_id: rpc_string_param(params, "official_release_id")
|
||||
.or_else(|| rpc_string_param(params, "release_id"))
|
||||
.map(str::to_string),
|
||||
destination: rpc_string_param(params, "destination").map(str::to_string),
|
||||
path_pattern: rpc_string_param(params, "path_pattern").map(str::to_string),
|
||||
archive_entry: rpc_string_param(params, "archive_entry").map(str::to_string),
|
||||
status: rpc_string_param(params, "status")
|
||||
.or_else(|| rpc_string_param(params, "task_status"))
|
||||
.map(str::to_string),
|
||||
parse_status: rpc_string_param(params, "parse_status").map(str::to_string),
|
||||
text_unit_format: rpc_string_param(params, "text_unit_format")
|
||||
.or_else(|| rpc_string_param(params, "format"))
|
||||
.map(str::to_string),
|
||||
has_reason: rpc_bool_param(params, "has_reason"),
|
||||
};
|
||||
Ok((query, offset, limit))
|
||||
}
|
||||
|
||||
fn rpc_string_param<'a>(params: Option<&'a serde_json::Value>, key: &str) -> Option<&'a str> {
|
||||
params
|
||||
.and_then(|params| params.get(key))
|
||||
@@ -4238,6 +4356,7 @@ fn readonly_query_rpc_method(command: CliCommand) -> Option<&'static str> {
|
||||
CliCommand::ParseStatus => Some(RPC_METHOD_PARSE_STATUS),
|
||||
CliCommand::ParseTextUnits => Some(RPC_METHOD_PARSE_TEXT_UNITS),
|
||||
CliCommand::ParseErrors => Some(RPC_METHOD_PARSE_ERRORS),
|
||||
CliCommand::TranslationTasks => Some(RPC_METHOD_TRANSLATION_TASKS),
|
||||
CliCommand::LocalizedStatus => Some(RPC_METHOD_LOCALIZED_STATUS),
|
||||
CliCommand::ResourceIndex => Some(RPC_METHOD_RESOURCE_INDEX),
|
||||
_ => None,
|
||||
@@ -4247,7 +4366,10 @@ fn readonly_query_rpc_method(command: CliCommand) -> Option<&'static str> {
|
||||
fn readonly_query_rpc_params(options: &CliOptions) -> Option<serde_json::Value> {
|
||||
let mut params = serde_json::Map::new();
|
||||
match options.command {
|
||||
CliCommand::ResourceIndex | CliCommand::ParseTextUnits | CliCommand::ParseErrors => {
|
||||
CliCommand::ResourceIndex
|
||||
| CliCommand::ParseTextUnits
|
||||
| CliCommand::ParseErrors
|
||||
| CliCommand::TranslationTasks => {
|
||||
params.insert(
|
||||
"offset".to_string(),
|
||||
serde_json::json!(options.query_offset),
|
||||
@@ -4324,6 +4446,41 @@ fn readonly_query_rpc_params(options: &CliOptions) -> Option<serde_json::Value>
|
||||
params.insert("format".to_string(), serde_json::json!(format));
|
||||
}
|
||||
}
|
||||
CliCommand::TranslationTasks => {
|
||||
if let Some(task_id) = options.query_task_id.as_ref() {
|
||||
params.insert("task_id".to_string(), serde_json::json!(task_id));
|
||||
}
|
||||
if let Some(release_id) = options.query_official_release_id.as_ref() {
|
||||
params.insert(
|
||||
"official_release_id".to_string(),
|
||||
serde_json::json!(release_id),
|
||||
);
|
||||
}
|
||||
if let Some(destination) = options.query_destination.as_ref() {
|
||||
params.insert("destination".to_string(), serde_json::json!(destination));
|
||||
}
|
||||
if let Some(path_pattern) = options.query_path_pattern.as_ref() {
|
||||
params.insert("path_pattern".to_string(), serde_json::json!(path_pattern));
|
||||
}
|
||||
if let Some(archive_entry) = options.query_archive_entry.as_ref() {
|
||||
params.insert(
|
||||
"archive_entry".to_string(),
|
||||
serde_json::json!(archive_entry),
|
||||
);
|
||||
}
|
||||
if let Some(status) = options.query_task_status.as_ref() {
|
||||
params.insert("status".to_string(), serde_json::json!(status));
|
||||
}
|
||||
if let Some(parse_status) = options.query_parse_status.as_ref() {
|
||||
params.insert("parse_status".to_string(), serde_json::json!(parse_status));
|
||||
}
|
||||
if let Some(format) = options.query_format.as_ref() {
|
||||
params.insert("text_unit_format".to_string(), serde_json::json!(format));
|
||||
}
|
||||
if let Some(has_reason) = options.query_has_reason {
|
||||
params.insert("has_reason".to_string(), serde_json::json!(has_reason));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(serde_json::Value::Object(params))
|
||||
@@ -4352,6 +4509,12 @@ fn build_readonly_query_report(
|
||||
options.query_offset,
|
||||
options.query_limit,
|
||||
),
|
||||
RPC_METHOD_TRANSLATION_TASKS => build_translation_tasks_report(
|
||||
&options.state_dir,
|
||||
translation_task_query_from_options(options),
|
||||
options.query_offset,
|
||||
options.query_limit,
|
||||
),
|
||||
RPC_METHOD_LOCALIZED_STATUS => {
|
||||
build_localized_status_report(&options.state_dir, &options.config)
|
||||
}
|
||||
@@ -4367,34 +4530,46 @@ fn build_readonly_query_report(
|
||||
}
|
||||
|
||||
fn validate_readonly_query_options(options: &CliOptions) -> anyhow::Result<()> {
|
||||
let has_resource_index_filter = options.query_resource_type.is_some()
|
||||
let has_resource_index_only_filter = options.query_resource_type.is_some()
|
||||
|| options.query_hash.is_some()
|
||||
|| options.query_official_release_id.is_some()
|
||||
|| options.query_platform.is_some()
|
||||
|| options.query_bundle_path.is_some()
|
||||
|| options.query_parse_status.is_some();
|
||||
|| options.query_bundle_path.is_some();
|
||||
let has_parse_object_filter = options.query_path_id.is_some()
|
||||
|| options.query_class_id.is_some()
|
||||
|| options.query_field_path.is_some();
|
||||
let has_translation_task_filter = options.query_task_id.is_some()
|
||||
|| options.query_task_status.is_some()
|
||||
|| options.query_has_reason.is_some();
|
||||
|
||||
match options.command {
|
||||
CliCommand::ResourceIndex => {
|
||||
if has_parse_object_filter {
|
||||
if has_parse_object_filter || has_translation_task_filter {
|
||||
return Err(anyhow::anyhow!(
|
||||
"--path-id/--class-id/--field-path 只适用于 parse-text-units 或 parse-errors"
|
||||
"--path-id/--class-id/--field-path 只适用于 parse-text-units 或 parse-errors;--task-id/--task-status/--has-reason 只适用于 translation-tasks"
|
||||
));
|
||||
}
|
||||
}
|
||||
CliCommand::ParseTextUnits | CliCommand::ParseErrors => {
|
||||
if has_resource_index_filter {
|
||||
if has_resource_index_only_filter
|
||||
|| has_translation_task_filter
|
||||
|| options.query_official_release_id.is_some()
|
||||
|| options.query_parse_status.is_some()
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"--resource-type/--hash/--release-id/--platform/--bundle-path/--parse-status 只适用于 resource-index"
|
||||
"--resource-type/--hash/--release-id/--platform/--bundle-path/--parse-status 只适用于 resource-index 或 translation-tasks;--task-id/--task-status/--has-reason 只适用于 translation-tasks"
|
||||
));
|
||||
}
|
||||
}
|
||||
CliCommand::TranslationTasks => {
|
||||
if has_resource_index_only_filter || has_parse_object_filter {
|
||||
return Err(anyhow::anyhow!(
|
||||
"--resource-type/--hash/--platform/--bundle-path 只适用于 resource-index;--path-id/--class-id/--field-path 只适用于 parse-text-units 或 parse-errors"
|
||||
));
|
||||
}
|
||||
}
|
||||
CliCommand::ParseStatus | CliCommand::LocalizedStatus if options.query_option_explicit => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"查询过滤参数只适用于 resource-index、parse-text-units 或 parse-errors"
|
||||
"查询过滤参数只适用于 resource-index、parse-text-units、parse-errors 或 translation-tasks"
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -4602,6 +4777,20 @@ fn textunit_query_from_options(options: &CliOptions) -> OfficialTextUnitQuery {
|
||||
}
|
||||
}
|
||||
|
||||
fn translation_task_query_from_options(options: &CliOptions) -> OfficialTextUnitTaskQuery {
|
||||
OfficialTextUnitTaskQuery {
|
||||
task_id: options.query_task_id.clone(),
|
||||
official_release_id: options.query_official_release_id.clone(),
|
||||
destination: options.query_destination.clone(),
|
||||
path_pattern: options.query_path_pattern.clone(),
|
||||
archive_entry: options.query_archive_entry.clone(),
|
||||
status: options.query_task_status.clone(),
|
||||
parse_status: options.query_parse_status.clone(),
|
||||
text_unit_format: options.query_format.clone(),
|
||||
has_reason: options.query_has_reason,
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_type_rpc_label(resource_type: ResourceType) -> &'static str {
|
||||
match resource_type {
|
||||
ResourceType::AssetBundle => "asset_bundle",
|
||||
@@ -7052,6 +7241,10 @@ fn parse_args_with_env(
|
||||
ensure_command_not_set(options.command, "parse-errors")?;
|
||||
options.command = CliCommand::ParseErrors;
|
||||
}
|
||||
"translation-tasks" => {
|
||||
ensure_command_not_set(options.command, "translation-tasks")?;
|
||||
options.command = CliCommand::TranslationTasks;
|
||||
}
|
||||
"localized-status" => {
|
||||
ensure_command_not_set(options.command, "localized-status")?;
|
||||
options.command = CliCommand::LocalizedStatus;
|
||||
@@ -7308,6 +7501,10 @@ fn parse_args_with_env(
|
||||
}
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--task-id" => {
|
||||
options.query_task_id = Some(next_option_value(&mut args, &flag)?);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--resource-type" => {
|
||||
options.query_resource_type = Some(parse_resource_type_param(&next_option_value(
|
||||
&mut args, &flag,
|
||||
@@ -7342,6 +7539,10 @@ fn parse_args_with_env(
|
||||
options.query_archive_entry = Some(next_option_value(&mut args, &flag)?);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--task-status" => {
|
||||
options.query_task_status = Some(next_option_value(&mut args, &flag)?);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--parse-status" => {
|
||||
options.query_parse_status = Some(next_option_value(&mut args, &flag)?);
|
||||
options.query_option_explicit = true;
|
||||
@@ -7378,6 +7579,14 @@ fn parse_args_with_env(
|
||||
options.query_format = Some(next_option_value(&mut args, &flag)?);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--has-reason" => {
|
||||
options.query_has_reason = Some(true);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--no-reason" => {
|
||||
options.query_has_reason = Some(false);
|
||||
options.query_option_explicit = true;
|
||||
}
|
||||
"--patch-kind" => {
|
||||
options.patch_kind = Some(parse_patch_apply_kind(&next_option_value(
|
||||
&mut args, &flag,
|
||||
@@ -7494,6 +7703,7 @@ fn parse_args_with_env(
|
||||
CliCommand::ParseStatus
|
||||
| CliCommand::ParseTextUnits
|
||||
| CliCommand::ParseErrors
|
||||
| CliCommand::TranslationTasks
|
||||
| CliCommand::LocalizedStatus
|
||||
| CliCommand::ResourceIndex => {
|
||||
validate_readonly_query_options(&options)?;
|
||||
@@ -7685,6 +7895,7 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" parse-status Show current official parse-cache status");
|
||||
eprintln!(" parse-text-units Query current official TextUnit detail index");
|
||||
eprintln!(" parse-errors Query current official parse/extraction diagnostics");
|
||||
eprintln!(" translation-tasks Query current offline TextUnit translation task status");
|
||||
eprintln!(" localized-status Show localized release status for current official release");
|
||||
eprintln!(" resource-index Query CAS + ResourceRepository index");
|
||||
eprintln!(" patch-apply Apply a Binary/JSON/Text patch file");
|
||||
@@ -7745,23 +7956,28 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" --repair | --no-repair Enable/disable automatic repair");
|
||||
eprintln!();
|
||||
eprintln!("Read-only queries:");
|
||||
eprintln!(" --offset <N> Query offset for resource-index/parse-text-units/parse-errors");
|
||||
eprintln!(" --limit <N> Query limit for resource-index/parse-text-units/parse-errors (1..=1000)");
|
||||
eprintln!(" --offset <N> Query offset for resource-index/parse-text-units/parse-errors/translation-tasks");
|
||||
eprintln!(" --limit <N> Query limit for resource-index/parse-text-units/parse-errors/translation-tasks (1..=1000)");
|
||||
eprintln!(" --task-id <ID> Filter translation-tasks by stable task ID");
|
||||
eprintln!(" --resource-type <TYPE> asset_bundle, manifest, table_bundle, text_asset, media, other");
|
||||
eprintln!(" --hash <HASH> Filter resource-index by full CAS hash");
|
||||
eprintln!(
|
||||
" --path-pattern <GLOB> Filter resource-index or parse detail by path pattern"
|
||||
);
|
||||
eprintln!(" --release-id <ID> Filter resource-index by official release ID");
|
||||
eprintln!(" --release-id <ID> Filter resource-index or translation-tasks by official release ID");
|
||||
eprintln!(" --platform <NAME> Filter resource-index by metadata platform");
|
||||
eprintln!(" --destination <PATH> Filter resource-index or parse detail by official destination");
|
||||
eprintln!(" --destination <PATH> Filter resource-index, parse detail, or translation-tasks by official destination");
|
||||
eprintln!(" --bundle-path <PATH> Filter resource-index by metadata bundle path");
|
||||
eprintln!(" --archive-entry <PATH> Filter resource-index or parse detail by ZIP/archive entry");
|
||||
eprintln!(" --parse-status <STATUS> Filter resource-index by parse status");
|
||||
eprintln!(" --archive-entry <PATH> Filter resource-index, parse detail, or translation-tasks by ZIP/archive entry");
|
||||
eprintln!(" --task-status <STATUS> Filter translation-tasks by task status");
|
||||
eprintln!(" --parse-status <STATUS> Filter resource-index or translation-tasks by parse status");
|
||||
eprintln!(" --path-id <ID> Filter parse detail by Unity object path ID");
|
||||
eprintln!(" --class-id <ID> Filter parse detail by Unity class ID");
|
||||
eprintln!(" --field-path <PATH> Filter parse detail, or TypeTree field path after UnityFS field patch commands");
|
||||
eprintln!(" --format <NAME> Filter resource-index or parse text units by payload format");
|
||||
eprintln!(" --format <NAME> Filter resource-index, parse text units, or translation-tasks by payload format");
|
||||
eprintln!(
|
||||
" --has-reason | --no-reason Filter translation-tasks by diagnostic reason presence"
|
||||
);
|
||||
eprintln!();
|
||||
eprintln!("Write patch:");
|
||||
eprintln!(" --patch-kind <binary|json|text> Patch type for patch-apply");
|
||||
@@ -8397,6 +8613,7 @@ mod tests {
|
||||
("parse-status", CliCommand::ParseStatus),
|
||||
("parse-text-units", CliCommand::ParseTextUnits),
|
||||
("parse-errors", CliCommand::ParseErrors),
|
||||
("translation-tasks", CliCommand::TranslationTasks),
|
||||
("localized-status", CliCommand::LocalizedStatus),
|
||||
("resource-index", CliCommand::ResourceIndex),
|
||||
] {
|
||||
@@ -8497,6 +8714,57 @@ mod tests {
|
||||
assert_eq!(text_units.query_offset, 2);
|
||||
assert_eq!(text_units.query_limit, 10);
|
||||
|
||||
let tasks = parse(&[
|
||||
"bat",
|
||||
"translation-tasks",
|
||||
"--task-id",
|
||||
"textunit/v-current/TextAssets/Scenario.json",
|
||||
"--release-id",
|
||||
"v-current",
|
||||
"--destination",
|
||||
"TextAssets/Scenario.json",
|
||||
"--archive-entry",
|
||||
"story/Scenario.json",
|
||||
"--task-status",
|
||||
"skipped_parse_failed",
|
||||
"--parse-status",
|
||||
"failed",
|
||||
"--format",
|
||||
"json",
|
||||
"--has-reason",
|
||||
"--offset",
|
||||
"1",
|
||||
"--limit",
|
||||
"20",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(tasks.command, CliCommand::TranslationTasks);
|
||||
assert_eq!(
|
||||
tasks.query_task_id.as_deref(),
|
||||
Some("textunit/v-current/TextAssets/Scenario.json")
|
||||
);
|
||||
assert_eq!(
|
||||
tasks.query_official_release_id.as_deref(),
|
||||
Some("v-current")
|
||||
);
|
||||
assert_eq!(
|
||||
tasks.query_destination.as_deref(),
|
||||
Some("TextAssets/Scenario.json")
|
||||
);
|
||||
assert_eq!(
|
||||
tasks.query_archive_entry.as_deref(),
|
||||
Some("story/Scenario.json")
|
||||
);
|
||||
assert_eq!(
|
||||
tasks.query_task_status.as_deref(),
|
||||
Some("skipped_parse_failed")
|
||||
);
|
||||
assert_eq!(tasks.query_parse_status.as_deref(), Some("failed"));
|
||||
assert_eq!(tasks.query_format.as_deref(), Some("json"));
|
||||
assert_eq!(tasks.query_has_reason, Some(true));
|
||||
assert_eq!(tasks.query_offset, 1);
|
||||
assert_eq!(tasks.query_limit, 20);
|
||||
|
||||
let error = parse(&["bat", "parse-status", "--limit", "10"]).unwrap_err();
|
||||
assert!(error.to_string().contains("查询过滤参数只适用于"));
|
||||
|
||||
@@ -8507,6 +8775,12 @@ mod tests {
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("--path-id/--class-id/--field-path"));
|
||||
|
||||
let error =
|
||||
parse(&["bat", "translation-tasks", "--resource-type", "text_asset"]).unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("--resource-type/--hash/--platform/--bundle-path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10040,6 +10314,70 @@ mod tests {
|
||||
assert_eq!(params["limit"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readonly_translation_tasks_command_uses_rpc_with_filters() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let state_dir = temp.path().join("state");
|
||||
fs::create_dir_all(&state_dir).unwrap();
|
||||
let seen = Arc::new(Mutex::new(Vec::<(String, Option<serde_json::Value>)>::new()));
|
||||
let seen_calls = Arc::clone(&seen);
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"translation-tasks",
|
||||
"--json",
|
||||
"--state-dir",
|
||||
state_dir.to_str().unwrap(),
|
||||
"--task-id",
|
||||
"textunit/v-current/Bundles/story.bundle",
|
||||
"--release-id",
|
||||
"v-current",
|
||||
"--destination",
|
||||
"Bundles/story.bundle",
|
||||
"--archive-entry",
|
||||
"story/TextAsset",
|
||||
"--task-status",
|
||||
"skipped_parse_failed",
|
||||
"--parse-status",
|
||||
"failed",
|
||||
"--format",
|
||||
"json",
|
||||
"--has-reason",
|
||||
"--offset",
|
||||
"4",
|
||||
"--limit",
|
||||
"5",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
run_readonly_query_command_with_rpc(
|
||||
&options,
|
||||
|_| true,
|
||||
move |_state_dir, method, params| {
|
||||
seen_calls
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((method.to_string(), params.clone()));
|
||||
Ok(serde_json::json!({ "available": true, "entries": [] }))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let seen = seen.lock().unwrap();
|
||||
assert_eq!(seen.len(), 1);
|
||||
assert_eq!(seen[0].0, RPC_METHOD_TRANSLATION_TASKS);
|
||||
let params = seen[0].1.as_ref().unwrap();
|
||||
assert_eq!(params["task_id"], "textunit/v-current/Bundles/story.bundle");
|
||||
assert_eq!(params["official_release_id"], "v-current");
|
||||
assert_eq!(params["destination"], "Bundles/story.bundle");
|
||||
assert_eq!(params["archive_entry"], "story/TextAsset");
|
||||
assert_eq!(params["status"], "skipped_parse_failed");
|
||||
assert_eq!(params["parse_status"], "failed");
|
||||
assert_eq!(params["text_unit_format"], "json");
|
||||
assert_eq!(params["has_reason"], true);
|
||||
assert_eq!(params["offset"], 4);
|
||||
assert_eq!(params["limit"], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_no_quiet_up_to_date_overrides_watch_default() {
|
||||
let options = parse(&[
|
||||
@@ -10843,6 +11181,67 @@ mod tests {
|
||||
assert_eq!(value["data"]["textunit_index_summary"]["unit_count"], 2);
|
||||
}
|
||||
|
||||
fn write_textunit_task_queue_fixture(current_dir: &Path) {
|
||||
let queue = bat_infrastructure::OfficialTextUnitTaskQueue {
|
||||
queue_version: bat_infrastructure::OFFICIAL_TEXTUNIT_TASK_QUEUE_VERSION,
|
||||
official_release_id: "v-current".to_string(),
|
||||
previous_release_id: Some("v-previous".to_string()),
|
||||
generated_unix_seconds: 43,
|
||||
current_resource_root: current_dir.to_path_buf(),
|
||||
summary: bat_infrastructure::OfficialTextUnitTaskSummary {
|
||||
resource_candidate_count: 2,
|
||||
parse_entry_count: 2,
|
||||
queued_task_count: 1,
|
||||
skipped_parse_failed_count: 1,
|
||||
text_unit_count: 5,
|
||||
..bat_infrastructure::OfficialTextUnitTaskSummary::default()
|
||||
},
|
||||
tasks: vec![
|
||||
bat_infrastructure::OfficialTextUnitTask {
|
||||
task_id: "textunit/v-current/Bundle/a.bundle".to_string(),
|
||||
official_release_id: "v-current".to_string(),
|
||||
destination: "Bundle/a.bundle".to_string(),
|
||||
change_kind: bat_infrastructure::OfficialResourceChangeKind::Added,
|
||||
url: "https://example.invalid/a.bundle".to_string(),
|
||||
bytes: 10,
|
||||
blake3: "hash-a".to_string(),
|
||||
parse_entry_key: Some("direct:a".to_string()),
|
||||
archive_entry: None,
|
||||
source_kind: Some(bat_infrastructure::OfficialParseSourceKind::DirectBundle),
|
||||
parse_status: Some(bat_infrastructure::OfficialParseStatus::Parsed),
|
||||
text_asset_count: 1,
|
||||
text_assets: vec!["Scenario".to_string()],
|
||||
text_unit_count: 5,
|
||||
text_unit_formats: vec!["plain".to_string()],
|
||||
text_unit_error_count: 0,
|
||||
status: bat_infrastructure::OfficialTextUnitTaskStatus::QueuedOffline,
|
||||
reason: None,
|
||||
},
|
||||
bat_infrastructure::OfficialTextUnitTask {
|
||||
task_id: "textunit/v-current/Bundle/b.bundle#assets/b.bundle".to_string(),
|
||||
official_release_id: "v-current".to_string(),
|
||||
destination: "Bundle/b.bundle".to_string(),
|
||||
change_kind: bat_infrastructure::OfficialResourceChangeKind::Modified,
|
||||
url: "https://example.invalid/b.bundle".to_string(),
|
||||
bytes: 20,
|
||||
blake3: "hash-b".to_string(),
|
||||
parse_entry_key: Some("zip:b".to_string()),
|
||||
archive_entry: Some("assets/b.bundle".to_string()),
|
||||
source_kind: Some(bat_infrastructure::OfficialParseSourceKind::ZipEntry),
|
||||
parse_status: Some(bat_infrastructure::OfficialParseStatus::Failed),
|
||||
text_asset_count: 0,
|
||||
text_assets: Vec::new(),
|
||||
text_unit_count: 0,
|
||||
text_unit_formats: Vec::new(),
|
||||
text_unit_error_count: 0,
|
||||
status: bat_infrastructure::OfficialTextUnitTaskStatus::SkippedParseFailed,
|
||||
reason: Some("parser failed at serialized object table".to_string()),
|
||||
},
|
||||
],
|
||||
};
|
||||
bat_infrastructure::write_textunit_task_queue_at(current_dir, &queue).unwrap();
|
||||
}
|
||||
|
||||
fn write_textunit_index_fixture(current_dir: &Path) {
|
||||
let index = bat_infrastructure::OfficialTextUnitIndex {
|
||||
version: bat_infrastructure::OFFICIAL_TEXTUNIT_INDEX_VERSION,
|
||||
@@ -10920,6 +11319,81 @@ mod tests {
|
||||
bat_infrastructure::write_textunit_index_at(current_dir, &index).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_translation_tasks_filters_current_queue() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let state_dir = temp.path().join("state");
|
||||
let output_root = temp.path().join("output");
|
||||
let current_dir = write_catalog_fixture(&state_dir, &output_root, "bundle-b2", None);
|
||||
write_textunit_task_queue_fixture(¤t_dir);
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request(
|
||||
"translation.tasks",
|
||||
Some(serde_json::json!({
|
||||
"release_id": "v-current",
|
||||
"path_pattern": "Bundle/*.bundle",
|
||||
"archive_entry": "assets/b.bundle",
|
||||
"status": "skipped_parse_failed",
|
||||
"parse_status": "failed",
|
||||
"has_reason": true,
|
||||
"offset": 0,
|
||||
"limit": 10
|
||||
})),
|
||||
),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&test_task_context(),
|
||||
"req-translation-1".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(value["data"]["available"], true);
|
||||
assert_eq!(value["data"]["current_version_id"], "v-current");
|
||||
assert_eq!(value["data"]["summary"]["queued_task_count"], 1);
|
||||
assert_eq!(value["data"]["summary"]["skipped_parse_failed_count"], 1);
|
||||
assert_eq!(value["data"]["total_entries"], 1);
|
||||
assert_eq!(value["data"]["query"]["official_release_id"], "v-current");
|
||||
assert_eq!(value["data"]["query"]["status"], "skipped_parse_failed");
|
||||
assert_eq!(value["data"]["query"]["parse_status"], "failed");
|
||||
assert_eq!(value["data"]["query"]["has_reason"], true);
|
||||
let entries = value["data"]["entries"].as_array().unwrap();
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert_eq!(entries[0]["destination"], "Bundle/b.bundle");
|
||||
assert_eq!(entries[0]["archive_entry"], "assets/b.bundle");
|
||||
assert_eq!(entries[0]["status"], "skipped_parse_failed");
|
||||
assert_eq!(
|
||||
entries[0]["reason"],
|
||||
"parser failed at serialized object table"
|
||||
);
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request(
|
||||
"translation.tasks",
|
||||
Some(serde_json::json!({
|
||||
"task_id": "textunit/v-current/Bundle/a.bundle",
|
||||
"status": "queued_offline",
|
||||
"format": "plain",
|
||||
"has_reason": false,
|
||||
"offset": 0,
|
||||
"limit": 10
|
||||
})),
|
||||
),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&test_task_context(),
|
||||
"req-translation-2".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(value["data"]["total_entries"], 1);
|
||||
assert_eq!(value["data"]["entries"][0]["status"], "queued_offline");
|
||||
assert_eq!(
|
||||
value["data"]["entries"][0]["reason"],
|
||||
serde_json::Value::Null
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_parse_text_units_filters_current_index() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
|
||||
@@ -94,12 +94,12 @@ pub use official_sync::{
|
||||
default_official_platforms, OfficialSyncDecision, OfficialSyncPlan,
|
||||
};
|
||||
pub use official_textunit_queue::{
|
||||
read_textunit_task_queue_at, write_crowdin_textunit_queue_at, write_official_textunit_queues,
|
||||
write_textunit_task_queue_at, CrowdinTextUnitQueue, CrowdinTextUnitQueueItem,
|
||||
OfficialTextUnitQueueReport, OfficialTextUnitTask, OfficialTextUnitTaskQueue,
|
||||
OfficialTextUnitTaskStatus, OfficialTextUnitTaskSummary, CROWDIN_TEXTUNIT_QUEUE_FILE,
|
||||
CROWDIN_TEXTUNIT_QUEUE_VERSION, OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE,
|
||||
OFFICIAL_TEXTUNIT_TASK_QUEUE_VERSION,
|
||||
query_textunit_tasks, read_textunit_task_queue_at, write_crowdin_textunit_queue_at,
|
||||
write_official_textunit_queues, write_textunit_task_queue_at, CrowdinTextUnitQueue,
|
||||
CrowdinTextUnitQueueItem, OfficialTextUnitQueueReport, OfficialTextUnitTask,
|
||||
OfficialTextUnitTaskQuery, OfficialTextUnitTaskQueue, OfficialTextUnitTaskStatus,
|
||||
OfficialTextUnitTaskSummary, CROWDIN_TEXTUNIT_QUEUE_FILE, CROWDIN_TEXTUNIT_QUEUE_VERSION,
|
||||
OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE, OFFICIAL_TEXTUNIT_TASK_QUEUE_VERSION,
|
||||
};
|
||||
pub use official_update::{
|
||||
cached_game_main_config_for_metadata, diff_extended_snapshot, gc_orphan_staging,
|
||||
|
||||
@@ -100,6 +100,29 @@ pub struct OfficialTextUnitTask {
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
/// Query filters for incremental TextUnit translation tasks.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct OfficialTextUnitTaskQuery {
|
||||
/// Filter by stable task ID.
|
||||
pub task_id: Option<String>,
|
||||
/// Filter by official release ID.
|
||||
pub official_release_id: Option<String>,
|
||||
/// Filter by resource destination.
|
||||
pub destination: Option<String>,
|
||||
/// Filter by destination glob pattern.
|
||||
pub path_pattern: Option<String>,
|
||||
/// Filter by ZIP/archive entry.
|
||||
pub archive_entry: Option<String>,
|
||||
/// Filter by task status, for example `queued_offline` or `skipped_parse_failed`.
|
||||
pub status: Option<String>,
|
||||
/// Filter by parse status, for example `parsed`, `failed`, or `skipped_unsupported`.
|
||||
pub parse_status: Option<String>,
|
||||
/// Filter by TextUnit format.
|
||||
pub text_unit_format: Option<String>,
|
||||
/// Filter tasks by whether a diagnostic reason is present.
|
||||
pub has_reason: Option<bool>,
|
||||
}
|
||||
|
||||
/// Aggregate counters for an incremental TextUnit task queue.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OfficialTextUnitTaskSummary {
|
||||
@@ -363,6 +386,18 @@ pub fn read_textunit_task_queue_at(
|
||||
Ok(Some(queue))
|
||||
}
|
||||
|
||||
/// Returns TextUnit translation tasks matching a query.
|
||||
pub fn query_textunit_tasks<'a>(
|
||||
queue: &'a OfficialTextUnitTaskQueue,
|
||||
query: &OfficialTextUnitTaskQuery,
|
||||
) -> Vec<&'a OfficialTextUnitTask> {
|
||||
queue
|
||||
.tasks
|
||||
.iter()
|
||||
.filter(|task| textunit_task_matches(task, query))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns whether the persisted TextUnit queue still matches current inputs.
|
||||
pub fn is_textunit_task_queue_current(
|
||||
resource_root: &Path,
|
||||
@@ -457,6 +492,97 @@ fn parse_entries_by_destination(
|
||||
by_destination
|
||||
}
|
||||
|
||||
fn textunit_task_matches(task: &OfficialTextUnitTask, query: &OfficialTextUnitTaskQuery) -> bool {
|
||||
if query
|
||||
.task_id
|
||||
.as_ref()
|
||||
.is_some_and(|task_id| &task.task_id != task_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.official_release_id
|
||||
.as_ref()
|
||||
.is_some_and(|release_id| &task.official_release_id != release_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.destination
|
||||
.as_ref()
|
||||
.is_some_and(|destination| &task.destination != destination)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.path_pattern
|
||||
.as_ref()
|
||||
.is_some_and(|pattern| !glob_matches(pattern, &task.destination))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.archive_entry
|
||||
.as_ref()
|
||||
.is_some_and(|archive_entry| task.archive_entry.as_ref() != Some(archive_entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.status
|
||||
.as_ref()
|
||||
.is_some_and(|status| task.status.as_str() != status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(status) = &query.parse_status {
|
||||
if task.parse_status.map(parse_status_label) != Some(status.as_str()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if query.text_unit_format.as_ref().is_some_and(|format| {
|
||||
!task
|
||||
.text_unit_formats
|
||||
.iter()
|
||||
.any(|task_format| task_format == format)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
if query
|
||||
.has_reason
|
||||
.is_some_and(|has_reason| task.reason.is_some() != has_reason)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_status_label(status: OfficialParseStatus) -> &'static str {
|
||||
match status {
|
||||
OfficialParseStatus::Parsed => "parsed",
|
||||
OfficialParseStatus::SkippedUnsupported => "skipped_unsupported",
|
||||
OfficialParseStatus::Failed => "failed",
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_matches(pattern: &str, value: &str) -> bool {
|
||||
glob_matches_bytes(pattern.as_bytes(), value.as_bytes())
|
||||
}
|
||||
|
||||
fn glob_matches_bytes(pattern: &[u8], value: &[u8]) -> bool {
|
||||
match pattern.split_first() {
|
||||
None => value.is_empty(),
|
||||
Some((&b'*', rest)) => {
|
||||
glob_matches_bytes(rest, value)
|
||||
|| (!value.is_empty() && glob_matches_bytes(pattern, &value[1..]))
|
||||
}
|
||||
Some((&b'?', rest)) => !value.is_empty() && glob_matches_bytes(rest, &value[1..]),
|
||||
Some((&literal, rest)) => value
|
||||
.split_first()
|
||||
.is_some_and(|(&head, tail)| head == literal && glob_matches_bytes(rest, tail)),
|
||||
}
|
||||
}
|
||||
|
||||
fn skipped_no_parse_entry_task(
|
||||
change_set: &OfficialResourceChangeSet,
|
||||
change: &OfficialResourceChange,
|
||||
@@ -772,6 +898,61 @@ mod tests {
|
||||
assert_eq!(crowdin.items[0].destination, "Bundles/a.bundle");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_textunit_tasks_filters_status_reason_and_format() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let queue = OfficialTextUnitTaskQueue::from_change_set_and_parse_cache(
|
||||
&change_set(temp.path()),
|
||||
&parse_cache(),
|
||||
);
|
||||
|
||||
let queued = query_textunit_tasks(
|
||||
&queue,
|
||||
&OfficialTextUnitTaskQuery {
|
||||
official_release_id: Some("release-new".to_string()),
|
||||
path_pattern: Some("Bundles/*.bundle".to_string()),
|
||||
status: Some("queued_offline".to_string()),
|
||||
parse_status: Some("parsed".to_string()),
|
||||
text_unit_format: Some("plain".to_string()),
|
||||
has_reason: Some(false),
|
||||
..OfficialTextUnitTaskQuery::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(queued.len(), 1);
|
||||
assert_eq!(queued[0].destination, "Bundles/a.bundle");
|
||||
assert_eq!(queued[0].reason, None);
|
||||
|
||||
let skipped_with_reason = query_textunit_tasks(
|
||||
&queue,
|
||||
&OfficialTextUnitTaskQuery {
|
||||
status: Some("skipped_unsupported".to_string()),
|
||||
has_reason: Some(true),
|
||||
..OfficialTextUnitTaskQuery::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(skipped_with_reason.len(), 1);
|
||||
assert_eq!(skipped_with_reason[0].destination, "Bundles/b.bundle");
|
||||
assert_eq!(
|
||||
skipped_with_reason[0].reason.as_deref(),
|
||||
Some("unsupported")
|
||||
);
|
||||
|
||||
let by_task_id = query_textunit_tasks(
|
||||
&queue,
|
||||
&OfficialTextUnitTaskQuery {
|
||||
task_id: Some("textunit/release-new/Bundles/c.bundle".to_string()),
|
||||
status: Some("skipped_no_parse_entry".to_string()),
|
||||
has_reason: Some(true),
|
||||
..OfficialTextUnitTaskQuery::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(by_task_id.len(), 1);
|
||||
assert_eq!(
|
||||
by_task_id[0].reason.as_deref(),
|
||||
Some("parse cache entry not found for changed resource")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_textunit_queues_persists_files() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user