模型库下载、导入与解压流程
本文档介绍 ArkPets 桌面端"模型管理"页中模型资源库的在线下载、本地 ZIP 导入与解压后处理的完整任务链实现,涵盖三步 GUI 任务编排(下载 → 解压 → 后处理)、多源下载策略、MirrorChyan CDK 校验,以及模型更新检查的双通道回退逻辑。
Purpose and Scope
本页覆盖以下内容:
- 下载/导入入口按钮的事件处理与任务编排(
ModelsModule) - 三步任务链:
DownloadModelsTask→UnzipModelsTask→PostUnzipModelTask - 多源下载策略
SourceStrategy(ModelDownload策略的注册与降级) - MirrorChyan CDK 校验与主源切换(
assertDownloadSource) - 本地 ZIP 导入流程(
FileChooser) - 模型更新检查的双通道回退(MirrorChyan / 数据集文件)
以下相关主题由兄弟页面承接,本页不做深入:
- 模型数据集的解析、模型列表加载与收藏等交互,参见模型资源管理相关页面
- 通用网络层与
SourceStrategy的完整实现细节,参见网络请求与多源策略相关页面 - GUI 任务框架(
GuiTask生命周期、进度弹窗)的通用机制,参见 GUI 任务框架相关页面
Overview
ArkPets 的桌宠模型(Spine 资源)不随安装包分发,而是由用户在"模型管理"页中通过两种方式获取:
- 在线下载:点击
modelFetch/modelReFetch按钮触发前台下载任务,从模型资源仓库拉取 ZIP 压缩包; - 本地导入:点击
modelImport按钮,通过FileChooser选择本地 ZIP 文件。
无论哪种方式,最终都会进入同一个两步收尾链:UnzipModelsTask(解压)→ PostUnzipModelTask(解压后处理),随后模型列表通过 modelReload(true) 重新加载。在线下载路径额外多一个前置下载步骤,并在收尾后删除临时 ZIP 文件。
下载源由 SourceStrategy 统一管理:RootModule 启动时注册 ModelDownload 策略并添加 GitHub 备份源;运行期 assertDownloadSource 会先尝试用用户填写的 MirrorChyan CDK 换取专属下载 URL,成功则将其设为主源,失败则回退到默认源。DownloadModelsTask 每次执行 getTargetURL() 时通过 getBestSource() 动态取源,下载失败或取消时调用 receiveError() 让该源"记过",从而在下次尝试时自动降级到其他源。
Architecture
架构说明:
- UI 控制层:
ModelsModule是模型管理页的控制器,负责按钮事件绑定与任务链编排。所有下载/导入任务以前台(GuiTaskStyle.COMMON)或严格(GuiTaskStyle.STRICT)样式弹出进度窗,阻塞用户交互直至完成。 - GUI 任务层:各任务继承自通用任务基类(如
FetchAsFileTask),通过匿名子类覆写回调(onDownloadedFile、onSucceeded)实现链式触发,避免阻塞 JavaFX UI 线程。 - 网络与源策略层:
SourceStrategy以策略名"ModelDownload"管理"主源 + 备源"集合,主源在运行期由 CDK 校验结果动态写入,备源(GitHub 模型仓库 zip 归档)在RootModule启动时注册。 - 数据层:下载的 ZIP 先落盘到
tempDirPath临时目录,解压与后处理完成后,临时文件被主动删除;导入路径则直接使用用户选择的本地 ZIP。
核心流程:在线下载三步任务链
在线下载入口 modelFetchEventHandler 将"下载 → 解压 → 后处理"串成一个嵌套回调链,注释中明确标注了 [Step 1/3]、[Step 2/3]、[Step 3/3]:
1EventHandler<ActionEvent> modelFetchEventHandler = e -> {
2 /* Foreground fetch models */
3 // Go to [Step 1/3]:
4 DownloadModelsTask task = new DownloadModelsTask(app.body, GuiTask.GuiTaskStyle.COMMON) {
5 @Override
6 protected void onDownloadedFile(File file) {
7 // Go to [Step 2/3]:
8 new UnzipModelsTask(parent, GuiTaskStyle.STRICT, file.getPath()) {
9 @Override
10 protected void onSucceeded(boolean result) {
11 // Go to [Step 3/3]:
12 new PostUnzipModelTask(parent, GuiTaskStyle.STRICT) {
13 @Override
14 protected void onSucceeded(boolean result) {
15 try {
16 IOUtils.FileUtil.delete(file.toPath(), false);
17 } catch (IOException ex) {
18 Logger.warn("Task", "The zip file cannot be deleted, because " + ex.getMessage());
19 }
20 app.modelsModule.modelReload(true);
21 }
22 }.start();
23 }
24 }.start();
25 }
26 };
27
28 assertDownloadSource(true, task::start);
29};Source: ModelsModule.java
逐步解析:
- Step 1/3 下载:
DownloadModelsTask以COMMON样式运行(普通进度弹窗)。任务完成后回调onDownloadedFile(File file),其中file是落盘在tempDirPath的 ZIP。 - Step 2/3 解压:在下载完成回调内以
STRICT样式启动UnzipModelsTask,参数为下载文件的路径。STRICT表示任务失败时不会静默继续,用户会看到明确的错误反馈。 - Step 3/3 后处理:解压成功后启动
PostUnzipModelTask,对解压产物做收尾处理(如目录结构整理/校验)。 - 清理与刷新:后处理成功后删除临时 ZIP(失败仅记录 warning 不中断流程),最后调用
modelReload(true)重新加载模型列表,让新模型立即出现在 UI 中。 - 前置校验:
assertDownloadSource(true, task::start)保证任务只有在下载源确认(CDK 校验)完成后才真正启动。
下载任务的取源与错误记账
1@Override
2protected URL getTargetURL() {
3 selectedSource = SourceStrategy.getStrategy("ModelDownload").getBestSource();
4 return selectedSource.toURL();
5}Source: DownloadModelsTask.java
getTargetURL() 在任务执行时动态调用 getBestSource() 取最优源,而不是在构造时固化 URL——这保证了 CDK 校验/主源切换发生在任务启动前后都能拿到最新源。
1@Override
2protected void onFailed(Throwable e) {
3 selectedSource.receiveError();
4 super.onFailed(e);
5}
6
7@Override
8protected void onCancelled() {
9 selectedSource.receiveError();
10 super.onCancelled();
11}Source: DownloadModelsTask.java
失败与取消两条路径都调用 receiveError() 给当前源"记过"。设计意图:用户主动取消下载也计入源质量评价,避免因该源网络质量差导致的反复取消而长期占据主源位置。下次执行 getBestSource() 时,带错误记录的源会被降权。
多源注册(启动期)
SourceStrategy.registerStrategy("AppDownload");
SourceStrategy.registerStrategy("ModelDownload")
.addBackupSource("GitHub", "https://github.com/isHarryh/Ark-Models/archive/refs/heads/main.zip");Source: RootModule.java
RootModule 启动时为 ModelDownload 策略注册 GitHub 备份源(Ark-Models 模型仓库的 main 分支 zip 归档)。注意源策略按用途分策略名隔离:AppDownload(应用自身更新)与 ModelDownload(模型下载)互不影响。
核心流程:本地 ZIP 导入(两步链)
导入流程跳过下载步骤,直接从用户选择的本地 ZIP 进入解压链,同样标注了 [Step 1/2]、[Step 2/2]:
1modelImport.setOnAction(e -> {
2 // Initialize the file chooser
3 Logger.info("ModelManager", "Opening file chooser to import zip file");
4 FileChooser fileChooser = new FileChooser();
5 FileChooser.ExtensionFilter extensionFilter1 = new FileChooser.ExtensionFilter("All Files", "*.*");
6 FileChooser.ExtensionFilter extensionFilter2 = new FileChooser.ExtensionFilter("Archives", "*.zip");
7 fileChooser.getExtensionFilters().addAll(extensionFilter1, extensionFilter2);
8 fileChooser.setSelectedExtensionFilter(extensionFilter2);
9 // Handle the chosen file
10 File zipFile = fileChooser.showOpenDialog(app.getWindow());
11 if (zipFile != null && zipFile.isFile()) {
12 Logger.info("ModelManager", "Importing zip file: " + zipFile);
13 // Go to [Step 1/2]:
14 new UnzipModelsTask(app.body, GuiTask.GuiTaskStyle.STRICT, zipFile.getPath()) {
15 @Override
16 protected void onSucceeded(boolean result) {
17 // Go to [Step 2/2]:
18 new PostUnzipModelTask(parent, GuiTaskStyle.STRICT) {
19 @OverrideSource: ModelsModule.java
设计要点:
- 过滤策略:扩展名过滤器同时提供
*.zip与*.*,默认选中Archives (*.zip)——既引导用户选择 ZIP,又不强制拦截非 zip 文件,保持导入的宽容度(解压任务自身会做真正的校验)。 - 空值防御:
if (zipFile != null && zipFile.isFile())双重判断,覆盖用户取消对话框与所选路径不是普通文件两种边界情况。 - 与下载共享收尾:导入与下载共用
UnzipModelsTask+PostUnzipModelTask收尾链,保证两条路径产出的模型资源目录状态一致,降低维护成本。导入路径不会删除用户本地 ZIP 文件(该文件不属于临时产物)。
三步任务链时序
核心流程:MirrorChyan CDK 校验与主源切换
1private void assertDownloadSource(boolean doDoubleCheck, Runnable onDone) {
2 SourceStrategy.getStrategy("ModelDownload").clearPrimarySource();
3 String cdk;
4 ...
5 value.raiseForCode();
6 SourceStrategy.getStrategy("ModelDownload").setPrimarySource("MirrorChyan", value.data.url);Source: ModelsModule.java
assertDownloadSource 是下载前的**源卫生(source hygiene)**步骤:
- 清空主源:
clearPrimarySource()抹掉上一次会话残留的主源,防止用过期 URL 下载。 - 读取 CDK:从本地获取用户配置的 MirrorChyan CDK。
- 换取专属 URL:若存在 CDK,则向 MirrorChyan 发起请求换取专属下载 URL;
value.raiseForCode()会按响应码抛错(如 CDK 无效)。 - 设置主源:成功则
setPrimarySource("MirrorChyan", value.data.url),主源优先于 GitHub 备源被getBestSource()选中。 - 异步回调启动:
onDone(即task::start)在校验流程结束后被调用,保证下载任务启动时主源状态已就绪。doDoubleCheck参数控制是否二次确认。
核心流程:模型更新检查(双通道回退)
1modelUpdate.setOnAction(e -> {
2 /* Foreground check models update */
3 if (!app.modelsModule.initModelsDataset(true))
4 return;
5 Logger.info("ModelManager", "Attempting checking model repo update by MirrorChyan");
6 checkModelUpdateByMc(() -> {
7 Logger.info("ModelManager", "Attempting checking model repo update by dataset file");
8 checkModelUpdateByDataset(() -> {
9 Logger.error("ModelManager", "All approaches to check model repo update failed");
10 GuiPrefabs.Dialogs.createCommonDialog(app.body,
11 GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_DANGER, GuiPrefabs.COLOR_DANGER),
12 "检查模型更新",
13 "尝试了两种渠道都未能检查更新",
14 "这可能是网络原因导致的,详情参见日志。",
15 null).show();
16 });
17 });
18});Source: ModelsModule.java
更新检查采用优先级回退:先尝试 MirrorChyan(checkModelUpdateByMc),失败则回退到数据集文件比对(checkModelUpdateByDataset),两者都失败才弹出错误对话框。数据集通道依赖 DownloadModelDatasetTask:
private void checkModelUpdateByDataset(Runnable onFail) {
new DownloadModelDatasetTask(app.body, GuiTask.GuiTaskStyle.COMMON) {
@OverrideSource: ModelsModule.java
DownloadModelDatasetTask 同样继承 FetchAsFileTask,说明数据集文件(模型元信息清单)与模型 ZIP 走同一套文件获取任务框架,仅目标源策略不同。前置条件 initModelsDataset(true) 确保本地已有可用数据集(首次安装无数据集时更新检查被跳过)。
UI 前置状态:版本兼容性提示
在用户进入下载流程之前,页面会根据数据集版本做兼容性预判并展示常驻警告条:
1datasetTooLowVerNotice = new NoticeBar(noticeBox) {
2 @Override
3 protected Color getColor() {
4 return GuiPrefabs.COLOR_WARNING;
5 }
6
7 @Override
8 protected String getText() {
9 return "模型库版本太旧,可能不被软件兼容,请您重新下载模型。";
10 }
11};
12datasetTooHighVerNotice = new NoticeBar(noticeBox) {
13 ...
14 @Override
15 protected String getText() {
16 return "软件版本太旧,可能不被模型库兼容,建议您更新软件。";
17 }
18
19 @Override
20 protected void onClick(MouseEvent event) {
21 app.popBrowser(urlOfficialDownloadPage);
22 }
23};Source: ModelsModule.java
两种警告语义不同:模型库太旧引导用户重新下载模型;软件太旧则引导用户跳转官方下载页更新 ArkPets 本身(点击通知条触发 popBrowser)。这把"版本不匹配"问题在下载入口前就拆分为可操作的指引,而不是等到解压/加载失败才报错。
API 参考
DownloadModelsTask(StackPane parent, GuiTaskStyle style)
继承自 FetchAsFileTask,负责从 ModelDownload 策略最优源下载模型 ZIP 到 tempDirPath。
参数:
parent(StackPane):进度弹窗的父容器(通常为app.body)。style(GuiTaskStyle):任务弹窗样式,下载场景使用COMMON。
可覆写回调:
getHeader():返回任务标题文案(实现返回"正在下载模型资源文件...")。getTargetURL():返回下载目标 URL,实现中动态调用getBestSource()并缓存到selectedSource。onDownloadedFile(File file):下载完成回调,file为临时目录中的 ZIP 文件。onFailed(Throwable e)/onCancelled():失败/取消时先对当前源调用receiveError()再走父类逻辑。
Source: DownloadModelsTask.java
UnzipModelsTask(parent, style, zipPath)
解压模型 ZIP 的 GUI 任务,下载链与导入链共用。构造时传入 ZIP 路径,成功后触发 onSucceeded(boolean result)。用于下载链时样式为 STRICT。
Source: ModelsModule.java
PostUnzipModelTask(parent, style)
解压后处理任务,负责把解压产物整理/校验为可用的模型资源目录状态。总在 UnzipModelsTask 成功后以 STRICT 样式启动。
Source: ModelsModule.java
assertDownloadSource(boolean doDoubleCheck, Runnable onDone)
下载源前置校验:清空主源 → 读取 CDK → 换取 MirrorChyan URL → 成功则设为主源,完成后回调 onDone。
参数:
doDoubleCheck(boolean):是否进行二次确认。onDone(Runnable):校验完成后执行的动作(通常是task::start)。
Source: ModelsModule.java
DownloadModelDatasetTask(StackPane parent, GuiTaskStyle style)
继承自 FetchAsFileTask,用于拉取模型数据集文件(模型元信息清单),是更新检查的数据集通道载体。
Source: DownloadModelDatasetTask.java
失败模式与边界情况
| 场景 | 触发代码 | 处理方式 |
|---|---|---|
| 下载源失效/网络错误 | DownloadModelsTask.onFailed | selectedSource.receiveError() 记过,源在下次 getBestSource() 时被降权 |
| 用户取消下载 | DownloadModelsTask.onCancelled | 同样 receiveError(),把主动取消视为对该源的一次负面信号 |
| CDK 无效/换取失败 | assertDownloadSource → value.raiseForCode() | 抛错后不设置主源,回退到默认源(GitHub 备源)继续 |
| 残留主源过期 | assertDownloadSource 开头 clearPrimarySource() | 每次校验前清空主源,杜绝用旧 URL 下载 |
| 临时 ZIP 删除失败 | IOUtils.FileUtil.delete(file.toPath(), false) 抛 IOException | 仅 Logger.warn 记录,不中断流程,模型已解压完成 |
| 解压/后处理失败 | UnzipModelsTask/PostUnzipModelTask 以 STRICT 样式 | 严格样式确保错误明确暴露给用户,不静默继续 |
| 用户取消文件选择 | zipFile == null | 直接跳过,不启动任务 |
| 所选路径非普通文件 | !zipFile.isFile() | 跳过导入,不启动任务 |
| 更新检查双通道全失败 | checkModelUpdateByMc → checkModelUpdateByDataset 都失败 | 弹出 SVG_DANGER 危险图标对话框,提示网络原因并引导查看日志 |
| 模型库/软件版本不匹配 | datasetTooLowVerNotice / datasetTooHighVerNotice | 常驻警告条:太旧则引导重新下载模型,太旧软件则跳转官方下载页 |
| 本地无数据集 | if (!app.modelsModule.initModelsDataset(true)) return; | 更新检查/校验按钮的前置守卫,无数据集时静默返回 |
并发与一致性设计
- UI 线程安全:所有任务通过 GUI 任务框架以异步任务形式执行,UI 侧仅在回调(
onDownloadedFile、onSucceeded)中编排下一步,避免在 JavaFX Application Thread 上执行网络 IO。 - 链式任务的隐式互斥:三步链内每一步在前一步成功回调中才启动下一步,天然串行,不存在同一 ZIP 被并发解压的问题。
- 源状态的会话隔离:
assertDownloadSource每次先clearPrimarySource(),配合任务内动态getBestSource(),保证"校验 → 启动"之间源状态不会被并发污染(每次下载会话拥有干净的主源快照)。 - 模型列表最终一致:无论下载链还是导入链,收尾统一调用
modelReload(true),保证 UI 与磁盘上的模型资源目录最终一致。
性能与运维要点
- 临时文件生命周期:下载 ZIP 固定落盘
tempDirPath,解压成功后主动删除(删除失败仅告警)。运维排查时若发现临时目录残留 ZIP,多为"删除失败被吞掉"或流程中途失败/取消所致。 - 多源容灾:MirrorChyan(主源,需 CDK)+ GitHub(备源,公开归档)两级容灾;
receiveError()记过机制让源在多次失败后自动失去优先级,无需人工切换。 - CDK 可选性:CDK 并非下载的前置硬性条件——校验失败时回退默认源,普通用户无 CDK 也能完成下载,CDK 只是把流量切到更优的镜像通道。
- 更新检查成本:双通道回退意味着最坏情况要连发两次网络请求(MirrorChyan 失败后再拉数据集),日志中分别记录两种渠道的尝试,便于定位网络问题。
- 日志可观测性:关键节点均有
Logger.info/Logger.error(如Opening file chooser to import zip file、Attempting checking model repo update by MirrorChyan、All approaches to check model repo update failed),日志是诊断下载/更新问题的首要入口。
扩展点
- 新增下载源:向
SourceStrategy.registerStrategy("ModelDownload")链式追加addBackupSource(name, url)即可加入备源池,无需改动任务代码——任务通过getBestSource()对源集合无感知。 - 自定义收尾处理:三步链的每一步都以匿名子类覆写回调实现,替换/插入新步骤(例如解压前做签名校验)只需在
onDownloadedFile或onSucceeded中插入新任务并串联回调。 - 其他资源类型:
FetchAsFileTask家族(DownloadModelsTask、DownloadModelDatasetTask)展示了"文件获取任务"的复用模式,新增资源类型(如壁纸资源)可按同样方式继承并指定独立SourceStrategy策略名。 - 版本兼容提示定制:
NoticeBar通过覆写getColor/getText/onClick定制展示与交互,datasetTooHighVerNotice的onClick演示了"通知条携带动作"的模式。
相关链接
- ModelsModule.java — 模型管理页控制器,下载/导入/更新检查的编排中心
- DownloadModelsTask.java — 模型 ZIP 下载任务
- DownloadModelDatasetTask.java — 模型数据集文件获取任务
- RootModule.java — 应用入口,
ModelDownload源策略注册处