core:新增 addon::online —— 按 Modrinth v2 真实形状建模(search facets / project /
project versions / version_file by hash),网络出口只有 Transport 一个 trait,生产实现
EngineTransport 走既有 DownloadEngine::download_bytes(零新依赖);错误分大类,网络类
失败映射成 NotConnected(= 未联网),不再用空列表冒充「没有结果」。
core:online::download::download_verified —— sha1 交给引擎流式校验、sha512 落盘后复核
(download::HashAlgo 没有 Sha512,改它超出 scope,已登记 finding),不符/失败删文件重试,
最多 3 次尝试且不留半截文件。
core:远端条目补全 —— PackFetcher::modrinth_file_by_sha512(默认 Unsupported = 补不出),
import 管线与新增的 scan_pack_online 都会在「有 path 但缺 hashes.sha1」时按 sha512 反查;
补出的 sha1 走 M160 同一套 judge 与合并判定,补不出仍进 unknown_remote 计数(口径未改)。
DownloadEngineFetcher 改用 download_verified,FetchRequest 增 sha512。
presenter:Modrinth 检索/详情改由 core 的 online 客户端提供(不再自己拼 URL、不再读错
date_modified);ContentFailure{kind} 取代裸字符串,无网 = Offline;新增
ContentDetailRequest/ContentDetail + LoadContentDetail 命令与 CoreEvent::ContentDetail 通道;
预检改走 scan_pack_online;内容库安装也走 download_verified(sha1+sha512)。
app-iced:Status::Offline 独立一态(未联网 ≠ 空列表 ≠ 普通失败),详情面板消费新 DTO
显示可用版本;选中项目即拉详情。i18n 新增 6 个键并同步金表。
测试:online 模块 24 个离线用例(录制 JSON + 假字节:搜索/详情/版本/按摘要反查/校验失败
即删/重试/离线分类/补全),import 侧 6 个(离线计数不变、补全后判冲突、补错摘要 ⇒ Failed),
presenter/app-iced 各补失败分类与详情状态机用例。
66 lines
3.4 KiB
Rust
66 lines
3.4 KiB
Rust
//! 在线内容源(M162):**只做匿名可用的那半**——Modrinth v2。
|
||
//!
|
||
//! # 为什么形状是 Modrinth 的,而不是「内容源通用抽象」
|
||
//!
|
||
//! CurseForge 的检索/文件端点要 `x-api-key`(D10 §5 的客户端还没写),而它的 JSON 方言与
|
||
//! Modrinth 几乎没有一处同名(`project_id` vs `id`、`description` vs `summary`、
|
||
//! `downloads` vs `downloadCount`、版本列表还要再套一层 `latestFilesIndexes`)。为了让两者
|
||
//! 共用而发明一套谁都不像的抽象,代价是两边都得翻译两次、错一处两边都错。
|
||
//!
|
||
//! 所以本模块**按 Modrinth 的真实形状建模**(`/v2/search` 的 facets、`/v2/project/{id}`、
|
||
//! `/v2/project/{id}/version`、`/v2/version_file/{hash}`),把「平台差异」留到将来真要加第二个
|
||
//! 源时再抽。留的缝只有两处:
|
||
//!
|
||
//! | 缝 | 在哪 | 换源时要动什么 |
|
||
//! |----|------|----------------|
|
||
//! | [`Transport`] | 唯一的 HTTP 出口(trait) | 换/加一个实现;测试注入录制 JSON |
|
||
//! | [`Modrinth::new`] | `Modrinth<'a, T: Transport + ?Sized>` | 传 `&dyn Transport` 就能换站点客户端,一行业务代码不改 |
|
||
//!
|
||
//! # 网络层(零新依赖)
|
||
//!
|
||
//! `Cargo.lock` 里已有的 `reqwest` 0.12 + `tokio` + `serde_json` 就是全部。生产实现
|
||
//! [`EngineTransport`] 走 `DownloadEngine::download_bytes`,于是**信号量限流、源健康度、
|
||
//! 指数退避、错误分类**全由 `core::download` 提供,本模块不写第二个重试循环,也不持有第二个
|
||
//! `reqwest::Client`(与 `addon::import::fetch` 同一条纪律)。
|
||
//!
|
||
//! 没有 POST:Modrinth 的批量接口(`POST /v2/version_files`)需要 POST 请求体,而引擎的
|
||
//! `DownloadRequest` 没有请求体字段。补全路径因此按**单摘要 GET** 逐条查(见 [`complete`]),
|
||
//! mrpack 里缺 `sha1` 的条目本来就是少数,不值得为它引入第二条 HTTP 路径。
|
||
//!
|
||
//! # 离线不是「空结果」
|
||
//!
|
||
//! 无网时上层必须能说清「未联网」而不是「没有匹配的内容」,所以错误是枚举不是字符串:
|
||
//! 网络类失败一律映射成 [`OnlineErrorKind::NotConnected`](见 [`OnlineError`]),
|
||
//! presenter 再把它翻成 DTO 的 `ContentFailureKind::Offline`。
|
||
//!
|
||
//! # 模块
|
||
//!
|
||
//! | 文件 | 职责 |
|
||
//! |------|------|
|
||
//! | [`model`] | Modrinth 的响应形状 + 检索请求(facets / 排序 / 分页) |
|
||
//! | [`transport`] | HTTP 出口 trait + 生产实现(走下载引擎)+ 错误分类 |
|
||
//! | [`modrinth`] | 端点拼装与响应解析(纯逻辑,测试直接喂录制 JSON) |
|
||
//! | [`download`] | 取下载地址之后那一步:引擎下载 + **sha1/sha512 复核** + 重试 + 不留半截文件 |
|
||
//! | [`complete`] | 远端条目补全:缺 `sha1` 的 mrpack 条目按 `sha512` 反查 |
|
||
|
||
pub mod complete;
|
||
pub mod download;
|
||
pub mod model;
|
||
pub mod modrinth;
|
||
pub mod transport;
|
||
|
||
pub use complete::{complete_files, CompletionSummary};
|
||
pub use download::{
|
||
digest_file, download_verified, ArtifactFetcher, Digest, Downloaded, EngineArtifactFetcher,
|
||
FetchArgs, RETRY_ATTEMPTS,
|
||
};
|
||
pub use model::{
|
||
ContentType, FileHashes, ProjectDetail, ProjectHit, SearchPage, SearchQuery, SortIndex,
|
||
Version, VersionFile, VersionQuery, VersionType,
|
||
};
|
||
pub use modrinth::{HashAlgorithm, Modrinth, MODRINTH_API};
|
||
pub use transport::{EngineTransport, OnlineError, OnlineErrorKind, OnlineFuture, Transport};
|
||
|
||
#[cfg(test)]
|
||
mod tests;
|