Repository Wiki
isHarryh/Ark-Pets

模型库下载、导入与解压流程

本文档介绍 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 资源)不随安装包分发,而是由用户在"模型管理"页中通过两种方式获取:

  1. 在线下载:点击 modelFetch / modelReFetch 按钮触发前台下载任务,从模型资源仓库拉取 ZIP 压缩包;
  2. 本地导入:点击 modelImport 按钮,通过 FileChooser 选择本地 ZIP 文件。

无论哪种方式,最终都会进入同一个两步收尾链:UnzipModelsTask(解压)→ PostUnzipModelTask(解压后处理),随后模型列表通过 modelReload(true) 重新加载。在线下载路径额外多一个前置下载步骤,并在收尾后删除临时 ZIP 文件。

下载源由 SourceStrategy 统一管理:RootModule 启动时注册 ModelDownload 策略并添加 GitHub 备份源;运行期 assertDownloadSource 会先尝试用用户填写的 MirrorChyan CDK 换取专属下载 URL,成功则将其设为主源,失败则回退到默认源。DownloadModelsTask 每次执行 getTargetURL() 时通过 getBestSource() 动态取源,下载失败或取消时调用 receiveError() 让该源"记过",从而在下次尝试时自动降级到其他源。

Architecture

Loading diagram...

架构说明:

  • 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]:

java
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

逐步解析:

  1. Step 1/3 下载:DownloadModelsTask 以 COMMON 样式运行(普通进度弹窗)。任务完成后回调 onDownloadedFile(File file),其中 file 是落盘在 tempDirPath 的 ZIP。
  2. Step 2/3 解压:在下载完成回调内以 STRICT 样式启动 UnzipModelsTask,参数为下载文件的路径。STRICT 表示任务失败时不会静默继续,用户会看到明确的错误反馈。
  3. Step 3/3 后处理:解压成功后启动 PostUnzipModelTask,对解压产物做收尾处理(如目录结构整理/校验)。
  4. 清理与刷新:后处理成功后删除临时 ZIP(失败仅记录 warning 不中断流程),最后调用 modelReload(true) 重新加载模型列表,让新模型立即出现在 UI 中。
  5. 前置校验:assertDownloadSource(true, task::start) 保证任务只有在下载源确认(CDK 校验)完成后才真正启动。

下载任务的取源与错误记账

java
1@Override 2protected URL getTargetURL() { 3 selectedSource = SourceStrategy.getStrategy("ModelDownload").getBestSource(); 4 return selectedSource.toURL(); 5}

Source: DownloadModelsTask.java

getTargetURL() 在任务执行时动态调用 getBestSource() 取最优源,而不是在构造时固化 URL——这保证了 CDK 校验/主源切换发生在任务启动前后都能拿到最新源。

java
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() 时,带错误记录的源会被降权。

多源注册(启动期)

java
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]:

java
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 @Override

Source: ModelsModule.java

设计要点:

  • 过滤策略:扩展名过滤器同时提供 *.zip 与 *.*,默认选中 Archives (*.zip)——既引导用户选择 ZIP,又不强制拦截非 zip 文件,保持导入的宽容度(解压任务自身会做真正的校验)。
  • 空值防御:if (zipFile != null && zipFile.isFile()) 双重判断,覆盖用户取消对话框与所选路径不是普通文件两种边界情况。
  • 与下载共享收尾:导入与下载共用 UnzipModelsTask + PostUnzipModelTask 收尾链,保证两条路径产出的模型资源目录状态一致,降低维护成本。导入路径不会删除用户本地 ZIP 文件(该文件不属于临时产物)。

三步任务链时序

Loading diagram...

核心流程:MirrorChyan CDK 校验与主源切换

java
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)**步骤:

  1. 清空主源:clearPrimarySource() 抹掉上一次会话残留的主源,防止用过期 URL 下载。
  2. 读取 CDK:从本地获取用户配置的 MirrorChyan CDK。
  3. 换取专属 URL:若存在 CDK,则向 MirrorChyan 发起请求换取专属下载 URL;value.raiseForCode() 会按响应码抛错(如 CDK 无效)。
  4. 设置主源:成功则 setPrimarySource("MirrorChyan", value.data.url),主源优先于 GitHub 备源被 getBestSource() 选中。
  5. 异步回调启动:onDone(即 task::start)在校验流程结束后被调用,保证下载任务启动时主源状态已就绪。doDoubleCheck 参数控制是否二次确认。

核心流程:模型更新检查(双通道回退)

java
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:

java
private void checkModelUpdateByDataset(Runnable onFail) { new DownloadModelDatasetTask(app.body, GuiTask.GuiTaskStyle.COMMON) { @Override

Source: ModelsModule.java

DownloadModelDatasetTask 同样继承 FetchAsFileTask,说明数据集文件(模型元信息清单)与模型 ZIP 走同一套文件获取任务框架,仅目标源策略不同。前置条件 initModelsDataset(true) 确保本地已有可用数据集(首次安装无数据集时更新检查被跳过)。

UI 前置状态:版本兼容性提示

在用户进入下载流程之前,页面会根据数据集版本做兼容性预判并展示常驻警告条:

java
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.onFailedselectedSource.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 演示了"通知条携带动作"的模式。

相关链接

Sources

(2 files)
desktop/src/cn/harryh/arkpets/controllers
desktop/src/cn/harryh/arkpets/guitasks/requests