MarketRepository
MarketRepository
Section titled “MarketRepository”MarketRepository 是市场数据层核心——负责和 GitHub 说话,从 Contents API 拉目录列表,从 raw URL 拉元数据/包/预览图。所有网络调用走 package:http,失败抛 MarketException。缓存委托给 MarketCache。
文件:lib/core/market/market_repository.dart
MarketPlugin 模型
Section titled “MarketPlugin 模型”class MarketPlugin { final String id; final PluginType type; final String name; final String description; final String version; final String author; final List<PlatformId> platforms; final String? icon; final String? fileSize; final int downloads; final double rating; final MarketDir dir;}.meta.json 解析后的类型化模型。字段和 manifest 公共字段对齐,加展示专用字段。
fromJson
Section titled “fromJson”factory MarketPlugin.fromJson(Map<String, Object?> json, {required MarketDir dir})严格解析——缺 id/type/name/description/version/author 任一抛 FormatException。platforms 必须是数组,每个值必须是合法平台 id(否则 PlatformId.fromString 抛 ArgumentError)。
final plugin = MarketPlugin.fromJson(json, dir: MarketDir.friend);MarketRepository
Section titled “MarketRepository”class MarketRepository { final MarketConfig config; final MarketCache cache;
MarketRepository(this.config, this.cache, {http.Client? client}); void dispose();}构造时可选注入 http.Client(测试用)。不注入则自建一个,dispose() 时关闭。
listDirectory
Section titled “listDirectory”Future<List<MarketPlugin>> listDirectory(MarketDir dir)列出某个目录的所有插件(含完整元数据)。流程:
- 查目录列表缓存(5 分钟 TTL)
- 未命中 → Contents API 拉文件列表 → 提取
.meta.json文件名 → 得到 id 列表 → 缓存 - 对每个 id 调
fetchMeta(1 小时 TTL 缓存) - 返回
List<MarketPlugin>
某个插件的 meta 拉失败会跳过(不中断整个列表)。
final repo = MarketRepository(config, cache);final friends = await repo.listDirectory(MarketDir.friend);for (final p in friends) { print('${p.name} v${p.version} by ${p.author}');}refreshDirectory
Section titled “refreshDirectory”Future<List<MarketPlugin>> refreshDirectory(MarketDir dir)强制刷新:先 invalidateDirectory,再 listDirectory。下拉刷新用。
final friends = await repo.refreshDirectory(MarketDir.friend);fetchMeta
Section titled “fetchMeta”Future<MarketPlugin> fetchMeta(MarketDir dir, String id)拉单个插件元数据。先查缓存(1 小时 TTL),未命中走 raw URL。失败抛 MarketException。
final plugin = await repo.fetchMeta(MarketDir.friend, 'moodpet.friend.default_smiley');print(plugin.description);downloadPackage
Section titled “downloadPackage”Future<File> downloadPackage( MarketDir dir, String id, { void Function(double progress)? onProgress,})下载插件包到磁盘缓存(永久),返回 File。流式下载——用 http.Client.send 拿 StreamedResponse,按 chunk 累积,据 Content-Length 调 onProgress(0.0–1.0)。
final file = await repo.downloadPackage( MarketDir.friend, 'moodpet.friend.default_smiley', onProgress: (p) => print('下载进度: ${(p * 100).toInt()}%'),);print('已下载到: ${file.path}');如果已缓存(之前下过),直接返回 File,不触发网络和 onProgress。
fetchPreview
Section titled “fetchPreview”Future<File> fetchPreview(MarketDir dir, String id)拉预览图到磁盘缓存(7 天 TTL),返回 File。不走流式(图片小),用一次性 http.get。
final file = await repo.fetchPreview(MarketDir.friend, 'moodpet.friend.default_smiley');// UI: Image.file(file, fit: BoxFit.cover)已缓存且 < 7 天直接返回。
HTTP 细节
Section titled “HTTP 细节”- API 调用(Contents API)用
config.apiHeaders(含 Authorization) - Raw 下载用
config.rawHeaders(无 Authorization) - 非 200 状态码抛
MarketException('HTTP $code: ...', code) - JSON 解析失败抛
MarketException('response is not valid JSON', code) - 超时:API 调用 20 秒,包下载 60 秒,图下载 60 秒
完整用法示例
Section titled “完整用法示例”final config = MarketConfig.defaultConfig;final cache = MarketCache(cacheDir);final repo = MarketRepository(config, cache);
try { // 列出所有 Friend 插件 final friends = await repo.listDirectory(MarketDir.friend);
for (final plugin in friends) { print('${plugin.name} - ${plugin.description}');
// 拉预览图 final image = await repo.fetchPreview(plugin.dir, plugin.id);
// 下载包 final package = await repo.downloadPackage( plugin.dir, plugin.id, onProgress: (p) => updateProgressBar(p), ); }} on MarketException catch (e) { print('市场请求失败: ${e.message}');}
repo.dispose();