# 下载、校验与取消

## 先检查下载计划

```aardio
import web.rest.polyHaven;
var ph=web.rest.polyHaven("My3DTool/1.0");
var plan,err=ph.planDownload("ceramic_vase_03",{
    kind="blend";resolution="1k";format="blend"
});
if(!plan) { ph.close();return null,err; }
print(plan.mainRelativePath,plan.totalSize,plan.files);

var report,err,partial=ph.download(plan,"/assets/ceramic_vase_03/1k",{
    maxBytes=16*1024*1024;
    onProgress=function(event){
        print(event.relativePath,event.fileBytes,event.fileSize);
        // return false; // 只有全等于 false 才取消
    }
});
ph.close();
if(!report) return null,err,partial;
return report.mainFile;
```

`planDownload` 只请求清单，不下载资产。需要离线重建时：

```aardio
import web.rest.polyHaven;
import JSON;
var files=JSON.load("/cached-files.json");
var plan,err=web.rest.polyHaven.buildPlan("ceramic_vase_03",files,{resolution="1k"});
return plan,err;
```

`files` 应为该资产完整 `/files/{id}` 响应，而不是下载报告。

## 选择规则

- 默认 `resolution="1k"`。`kind` 未指定时，有 `hdri` 则选择 HDRI，否则选择 `blend`。
- 格式默认 HDRI 用 `exr`，其他类型与 `kind` 相同。
- 单张 PBR 可指定 `{kind="Diffuse";resolution="2k";format="jpg"}`；法线可用 `nor_gl`。类型名和格式大小写须与实际清单一致。
- glTF 可指定 `{kind="gltf";resolution="1k";format="gltf"}`；模型的 FBX/USD 同理，前提是清单提供。只选择真实存在的变体，不拼接下载 URL。
- 辅助的 `tonemapped/colorchart/backplates` 未统一为三层结构，本版高层选择器不处理；用原生 `getFiles` 取得叶节点并按原生 `receiveFile` 下载。
- 计划包含主文件和该叶节点的全部 `include`，不额外下载其他分辨率/格式；不修改清单或源文件。

## 下载选项

| 选项 | 默认 | 行为 |
|---|---|---|
| `maxBytes` | 536870912（512 MiB） | 整个计划的字节上限，含缓存文件，0 会拒绝非空计划；可显式提高 |
| `overwrite` | false | 现有文件不匹配时拒绝；true 允许用校验通过的新文件替换 |
| `onProgress` | 无 | `function(event)`；返回 false 取消，返回 null/0 不取消 |

每个进度事件包含 `phase`（`begin` 或 `download`）、`relativePath`、`path`、`fileBytes`（当前文件已接收累计值）、`fileSize`、`completedBytes`（此前已完成文件字节数）、`totalSize`、`index`、`count`。缓存文件只有 `begin` 事件；最终结果以返回 report 为准，没有伪造的网络进度或独立 done 事件。

`begin` 在网络前触发。下载开始通知可能还有一次 `fileBytes=0`。分块回调返回 false 会中断读取，库自行记录取消，防止底层仍返回 true 时误报下载完成。取消是协作式的，不能取消正在阻塞的 DNS/连接；不要在回调里重入同一个客户端。

## 完整性与文件布局

- 下载前校验完整计划：官方 HTTPS 文件域名、MD5、大小、相对路径、Windows 保留名、大小写冲突、文件/目录冲突、大小预算。
- 相对路径采用保守字符集：ASCII 字母/数字、下划线、空格、点、括号、连字符及 `/` 分隔。未知新命名返回错误，不擅自重命名导致 Blender 引用失效。目标根目录可包含中文。
- 临时文件位于目标文件同目录，名为 `文件名.ph-UUID.part`。流式落盘后分块计算 MD5，同时核对实际文件大小，再改名提交。
- 在选定根目录和其内部拒绝现存 reparse point。这不是防御其他进程并发改动的沙箱；根目录由调用方信任选择，同一目录请串行操作。
- 保留已有有效文件，正常失败/取消删除本次未完成的 part 文件。进程崩溃、断电或删除权限不足仍可能留下 part 文件。
- 文件级提交，不是整个目录的事务。后续依赖失败时返回 partialReport，并保留此前完成文件；再次执行会校验复用。
- 每项 MD5 是传输完整性检查，不是数字签名。API 清单和官方 HTTPS 来源是信任依据。

成功报告有 `complete=true`、`mainFile`（绝对路径）、`directory`、`files`、`downloaded`、`cached`、`totalSize`、`source`、`assetUrl`。仅在返回成功后把 `mainFile` 交给 Blender。

失败返回 `null,err,detail`；网络/文件处理中 detail 通常为 `complete=false` 的 partialReport，预算失败则为 `{totalSize,maxBytes}`，输入错误可能无 detail。不把失败 detail 当成完整资产报告。

库不主动写清单文件，避免覆盖用户文件；需要留档可以显式 `JSON.save` 保存 plan/report。跨机器搬移后 report 中的绝对路径失效，应按新根目录和 `relativePath` 重建；复制整个资产目录以保留贴图相对布局。
