mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
294 lines
11 KiB
Rust
294 lines
11 KiB
Rust
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)
|
||
}
|