Files
BlueArchiveToolkit/infrastructure/src/bin/bat/workflow_commands.rs
T

294 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::*;
pub(super) fn run_parse_once(options: &CliOptions) -> anyhow::Result<()> {
let (resource_root, release_id) = current_official_release(options)?;
let parse_config =
OfficialParseConfig::new(&resource_root, options.config.unzip_command.clone())
.with_force(options.config.force);
let parse_report = OfficialParseCacheService::new()
.run(&parse_config)
.map_err(anyhow::Error::msg)?;
let queue_report =
write_official_textunit_queues(&resource_root).map_err(anyhow::Error::msg)?;
let data = serde_json::json!({
"official_release_id": release_id,
"resource_root": resource_root,
"forced": options.config.force,
"parse": parse_report,
"translation_queue": queue_report,
});
print_report(
options.output_format,
&CommandReport {
command: "parse",
status: "completed",
message: "官方资源解析已执行",
data,
},
)
}
pub(super) fn run_parse_clear_cache(options: &CliOptions) -> anyhow::Result<()> {
let (resource_root, release_id) = current_official_release(options)?;
let artifact_names = [
OFFICIAL_PARSE_CACHE_FILE,
OFFICIAL_TEXTUNIT_INDEX_FILE,
OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE,
CROWDIN_TEXTUNIT_QUEUE_FILE,
];
let mut removed = Vec::new();
for name in artifact_names {
let path = resource_root.join(name);
if remove_regenerable_file(&path)? {
removed.push(path);
}
}
let data = serde_json::json!({
"official_release_id": release_id,
"resource_root": resource_root,
"removed": removed,
"translation_task_repository_preserved": true,
});
print_report(
options.output_format,
&CommandReport {
command: "parse-clear-cache",
status: "cleared",
message: "当前官方 release 的可再生解析缓存和翻译队列已清理",
data,
},
)
}
pub(super) fn run_translate_once(options: &CliOptions) -> anyhow::Result<()> {
let (resource_root, release_id) = current_official_release(options)?;
let queue = write_official_textunit_queues(&resource_root).map_err(anyhow::Error::msg)?;
let exported = options
.translation_file
.as_ref()
.map(|path| {
export_translation_workbench(&resource_root, release_id.clone(), path).map(
|workbench| {
serde_json::json!({
"path": path,
"entry_count": workbench.entries.len(),
})
},
)
})
.transpose()?;
let data = serde_json::json!({
"official_release_id": release_id,
"resource_root": resource_root,
"queue": queue,
"workbench": exported,
"provider": "offline",
"note": "当前 translate 只生成/刷新离线队列和可编辑工作台,不调用外部翻译 provider",
});
print_report(
options.output_format,
&CommandReport {
command: "translate",
status: "queued",
message: "翻译离线队列已刷新",
data,
},
)
}
pub(super) fn run_translation_validate(options: &CliOptions) -> anyhow::Result<()> {
let path = options
.translation_file
.as_ref()
.ok_or_else(|| anyhow::anyhow!("i18n validate 必须指定 --translation-file"))?;
let (resource_root, release_id) = current_official_release(options)?;
let workbench = read_translation_workbench(path)?;
let validation = validate_translation_workbench(&resource_root, &release_id, &workbench)?;
let data = serde_json::json!({
"official_release_id": release_id,
"resource_root": resource_root,
"translation_file": path,
"validation": validation,
});
print_json_value(options.output_format, &data)
}
pub(super) fn run_translation_set(options: &CliOptions) -> anyhow::Result<()> {
let path = options
.translation_file
.as_ref()
.ok_or_else(|| anyhow::anyhow!("translation-set 必须指定 --translation-file"))?;
let text = match (&options.translation_text, &options.translation_text_file) {
(Some(_), Some(_)) => {
return Err(anyhow::anyhow!(
"--translated-text 与 --translated-file 只能指定一个"
))
}
(Some(text), None) => text.clone(),
(None, Some(path)) => String::from_utf8(
read_file_no_symlink(path, "翻译文本文件")
.map_err(anyhow::Error::msg)?
.ok_or_else(|| anyhow::anyhow!("翻译文本文件不存在:{}", path.display()))?,
)?,
(None, None) => {
return Err(anyhow::anyhow!(
"translation-set 必须指定 --translated-text 或 --translated-file"
))
}
};
let entry_id = options
.translation_id
.as_deref()
.ok_or_else(|| anyhow::anyhow!("translation-set 必须指定 --translation-id"))?;
let entry = set_translation(path, entry_id, text)?;
let data = serde_json::json!({
"translation_file": path,
"entry": entry,
});
print_report(
options.output_format,
&CommandReport {
command: "translation-set",
status: "updated",
message: "翻译工作台条目已更新",
data,
},
)
}
pub(super) fn run_translation_task_update(options: &CliOptions) -> anyhow::Result<()> {
let task_id = options
.query_task_id
.as_deref()
.ok_or_else(|| anyhow::anyhow!("i18n task update 必须指定 --task-id"))?;
let status = options
.query_task_status
.as_deref()
.ok_or_else(|| anyhow::anyhow!("i18n task update 必须指定 --task-status"))?;
let mut params = serde_json::Map::from_iter([
("task_id".to_string(), serde_json::json!(task_id)),
("status".to_string(), serde_json::json!(status)),
]);
if let Some(reason) = options.translation_failure_reason.as_deref() {
params.insert("failure_reason".to_string(), serde_json::json!(reason));
}
if let Some(provider_run_id) = options.translation_provider_run_id.as_deref() {
params.insert(
"provider_run_id".to_string(),
serde_json::json!(provider_run_id),
);
}
let report = update_translation_task_status_report(
&options.state_dir,
Some(&serde_json::Value::Object(params)),
)?;
print_json_value(options.output_format, &report)
}
pub(super) fn run_repack(options: &CliOptions) -> anyhow::Result<()> {
let spec = options
.repack_spec
.as_ref()
.ok_or_else(|| anyhow::anyhow!("repack 必须指定 --repack-spec"))?;
let report = repack_bundle(spec)?;
print_report(options.output_format, &report)
}
pub(super) fn run_publish_localized(options: &CliOptions) -> anyhow::Result<()> {
let translation_file = options
.translation_file
.as_ref()
.ok_or_else(|| anyhow::anyhow!("publish-localized 必须指定 --translation-file"))?;
let (resource_root, official_release_id) = current_official_release(options)?;
let workbench = read_translation_workbench(translation_file)?;
if workbench.official_release_id != official_release_id {
return Err(anyhow::anyhow!(
"翻译工作台 release={} 与当前官方 release={} 不一致;请重新导出",
workbench.official_release_id,
official_release_id
));
}
let expected_root = lexical_absolute(&resource_root).map_err(anyhow::Error::msg)?;
if workbench.official_resource_root != expected_root {
return Err(anyhow::anyhow!(
"翻译工作台资源根目录与当前 release 不一致;请重新导出"
));
}
let patches = localized_text_asset_patches(&resource_root, &workbench)?;
let localized_release_id = options.localized_release_id.clone().or_else(|| {
options
.config
.force
.then(|| format!("{}-manual-{}", official_release_id, unix_seconds_now()))
});
let mut config = LocalizedPatchConfig::new(
resource_root,
options.config.localized_output_root.clone(),
official_release_id,
patches,
)
.with_force(options.config.force);
if let Some(release_id) = localized_release_id {
config = config.with_localized_release_id(release_id);
}
let report = LocalizedPatchService::new().publish(&config)?;
print_report(options.output_format, &report)
}
fn current_official_release(options: &CliOptions) -> anyhow::Result<(PathBuf, String)> {
let state = read_version_state(&options.config.version_state_path())?;
let resource_root = if let Some(resource_root) = options.resource_root.clone() {
lexical_absolute(&resource_root).map_err(anyhow::Error::msg)?
} else {
state
.as_ref()
.and_then(|state| state.current_completed_version.as_ref())
.map(|record| record.resource_root.clone())
.unwrap_or(active_official_resource_root(&options.config.output_root)?)
};
let release_id = state
.as_ref()
.and_then(|state| state.current_completed_version.as_ref())
.filter(|_| options.resource_root.is_none())
.map(|record| record.id.clone())
.or_else(|| {
resource_root
.file_name()
.and_then(|name| name.to_str())
.map(str::to_string)
})
.ok_or_else(|| anyhow::anyhow!("无法从当前官方资源根目录确定 release id"))?;
if read_download_manifest_at(&resource_root)
.map_err(anyhow::Error::msg)?
.is_none()
{
return Err(anyhow::anyhow!(
"当前官方 release 缺少官方下载 manifest{}",
resource_root.display()
));
}
Ok((resource_root, release_id))
}
fn remove_regenerable_file(path: &Path) -> anyhow::Result<bool> {
let metadata = match fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error.into()),
};
if metadata.file_type().is_symlink() {
return Err(anyhow::anyhow!(
"拒绝删除符号链接形式的可再生文件:{}",
path.display()
));
}
if !metadata.is_file() {
return Err(anyhow::anyhow!(
"可再生缓存路径不是普通文件:{}",
path.display()
));
}
fs::remove_file(path)?;
Ok(true)
}