Hydra 搭配 Real-Debrid 为什么总把同一个游戏下载两遍?3处改动完整指南
【免费下载链接】hydraHydra Launcher is an open-source gaming platform created to be the single tool that you need项目地址: https://gitcode.com/GitHub_Trending/hy/hydra
Hydra Launcher 是一款开源游戏发行与下载平台,一个客户端就覆盖了找资源、下文件、装游戏的全流程。不少人在 Hydra 里挂 Real-Debrid 走磁力下载时,都碰到过这种糟心事:明明上次就下完过的游戏,今天再点一次又从 0% 开始;或者下载目录里躺着一份下完的压缩包,旁边又多出一份"双胞胎"。这篇文章带你从原理层面看懂 Hydra Real-Debrid 重复下载是怎么发生的,并用 3 处不大不小的改动把它按住。
先还原一个真实场景:带宽又"烧"了一遍
假设你昨晚用磁力链接下好了某款游戏,今天打开 Hydra 想顺手把更新包也拉下来,结果发现主文件又在重新下载了。更隐蔽的情况是:你根本没主动操作,只是重启了客户端,队列里的任务状态一刷新,原本"已完成"的任务又变回了"等待中",然后老老实实从头传。
这类问题集中出现在磁力链接场景,直接后果有三个:白白消耗 Real-Debrid 的流量额度、下载目录里堆着文件名高度相似的两份文件、以及队列被卡住后其他游戏跟着等。好消息是,它并不是 Hydra 的下载队列本身记不住任务,而是"磁力链接 → 直链"这段路上有几个环节会重复走、重复判断。
动手前,先避开 3 个常见误区
误区一:同一游戏点了两次下载,所以出问题了。其实 Hydra 的下载队列是按"商店 + 游戏 ID"做唯一键存的(见 src/main/level/sublevels/downloads.ts),你点对同一个游戏两次,本地只会有一条记录,不会真的在队列里生成两个任务。真正反复跑的是另一层——每次任务启动时,都会重新向 Real-Debrid 要一次直链。
误区二:磁力链接相同,服务器端就一定只有一份。方向对了,但结论下早了。Real-Debrid 内部确实以种子的哈希值(infoHash)去重,问题在于"去重成功"和"状态可用"是两回事——哈希能对上,但那份种子在服务端可能还卡在转换、压缩环节,客户端此刻去取直链只会拿到空手。
误区三:把本地文件删掉重来就好。这解决不了任何根因,只是把问题藏起来,下次还会原样复现。
看懂一条"传送带":磁力链接到直链之间发生了什么
可以把 Real-Debrid 想成一条快递传送带。你丢上去一个 magnet 链接(相当于下单),服务器要先做几道工序:解析磁力、等待文件列表、(必要时)全部选中文件、然后下载进它的缓存,状态才会走到downloaded,这时才有一张"提货单"(直链)可以给你。
Hydra 这边负责对接传送带的代码在 src/main/services/download/real-debrid.ts,核心就两个方法:
getTorrentId:拿到 magnet 后,先从你账号的种子里按 infoHash 找有没有"老订单",找到了就复用它的 ID,找不到就调addMagnet下一张新订单。getDownloadUrl:拿着种子 ID 查状态,只有状态恰好是downloaded时才返回直链,其他任何状态一律返回 null。
对照 src/types/download.types.ts 里的RealDebridTorrentInfo类型能看到,状态远不止downloaded一种:还有magnet_conversion(磁力转换中)、waiting_files_selection(等选文件)、compressing(打包压缩中)等等。重复下载正是从这条传送带的缝隙里漏出来的,主要是两个点:
- 状态竞争:种子明明已经在传送带上了(甚至快传完了),但客户端查到的那一刻还没到
downloaded,于是本次任务失败、抛NotCachedOnRealDebrid,下次再启动任务时整个流程又走一遍。 - 没有"提货记录":Hydra 本地只存了"这个游戏用哪个下载器、下到百分之几",并没有存"这个游戏对应的是哪条种子、直链长什么样"。于是每次启动都重新翻全量种子列表、重新走一遍状态判断。
改动一:下新订单之前,先翻一遍旧订单
这是收益最直接的一处。思路很简单:既然 Real-Debrid 按 infoHash 去重,那客户端在调addMagnet之前,应该先把自己账号里的种子按 infoHash 排一遍队——
- 找到
downloaded状态的:直接复用它的 ID,一条新订单都不用下; - 找到还在
downloading/waiting_files_selection的:同样复用,别再去addMagnet,避免服务端出现同哈希的重复条目(它们会互相干扰,还各占一份资源); - 完全没找到:才老老实实创建新种子。
核心改动集中在getTorrentId,示意如下:
static async getTorrentId(magnetUri: string) { const { infoHash } = await parseTorrent(magnetUri); const userTorrents = await RealDebridClient.getAllTorrentsFromUser(); const reusable = userTorrents.find((t) => t.hash === infoHash && t.status !== "dead" ); if (reusable) return reusable.id; // 老订单直接续用 return (await RealDebridClient.addMagnet(magnetUri)).id; }改完之后,"同一个游戏反复下"的大头就砍掉了:只要服务端那份种子还在,客户端永远不会为它再下一张新订单。
改动二:别只瞄一眼就下结论,轮询把状态等出来
改动一解决"不再重复下单",改动二解决"不再误判失败"。原逻辑是查一次状态,不是downloaded就返回 null,上层 src/main/services/download/download-manager.ts 收到 null 会立刻抛错。对于一条刚丢上去、正在转换磁力的种子,这一眼瞄下去几乎注定扑空。
更稳的做法是:当状态落在"还在路上的状态"(magnet_conversion、waiting_files_selection、compressing)时,隔几秒再查,最多重试若干轮,再下判断:
let info = await this.getTorrentInfo(torrentId); if (info.status === "waiting_files_selection") { await this.selectAllFiles(torrentId); info = await this.getTorrentInfo(torrentId); } for (let i = 0; i < 3 && info.status !== "downloaded"; i++) { await sleep(2000); info = await this.getTorrentInfo(torrentId); } if (info.status === "downloaded" && info.links.length > 0) { const { download } = await this.unrestrictLink(info.links[0]); return decodeURIComponent(download); } return null;注意保留selectAllFiles这一步:磁力种子到达时通常处于waiting_files_selection,不替用户把文件全勾上,它一辈子都到不了downloaded。这段逻辑原代码里已经有了,重构时别顺手删掉。
改动三:在本地记账本上留一笔"提货记录"
前两条改动都发生在"每次启动任务"的路径上。再往前进一步,可以像收银台贴小票一样,把解析结果记在本地:
Hydra 用 LevelDB 做本地存储,下载任务存在downloads子库里。按同样的模式,可以新增一个以 infoHash 为键的轻量缓存,内容就是"这条种子对应的 Real-Debrid 内部 ID + 解析出的直链 + 过期时间"。Real-Debrid 的直链有时效,所以记账时必须写过期时间,读的时候先验期:
// 写入:rd:<infoHash> -> { torrentId, url, expiresAt } await downloadsSublevel.put(`rd:${infoHash}`, { torrentId, url: directUrl, expiresAt: Date.now() + 24 * 3600 * 1000, }); // 读取:过期即弃 const hit = await downloadsSublevel.get(`rd:${infoHash}`).catch(() => null); if (hit && hit.expiresAt > Date.now()) return hit.url;有两点值得强调:
- 直链过期后,优先回退到"按 infoHash 找种子再重新取链"(即改动一的路径),而不是重新
addMagnet。因为文件很可能早在 Real-Debrid 缓存里躺好了,重新取链是秒级操作,重新下单才是分钟级。 - 这份缓存只是"加速与兜底",不是数据源。真正确认文件是否可用的,永远是服务端的种子状态。
改完之后怎么确认:一份 5 分钟自查清单
- 重下同一个游戏:选一个已完成的磁力游戏,重新发起下载。观察日志——应该走"复用已有种子"的分支,而不是出现新的
addMagnet调用。 - 盯着状态跑一遍:对一个刚丢上去的磁力链接,确认任务在
magnet_conversion→downloaded之间是平滑过渡的,而不是失败后由人工再点一次。 - 看本地存储:找到 Hydra 的数据目录(Linux 下通常在
~/.config/hydra),确认出现了以rd:为前缀的缓存键,且再次下载同游戏时能命中。 - 看下载目录:确认没有再生成文件名仅差一个序号的重复压缩包。
- 重启客户端:重启后队列恢复、任务不再"死灰复燃",是本地记账生效的最直观信号。
改动前后对比
| 观察点 | 改动前 | 改动后 |
|---|---|---|
| 同游戏二次下载 | 可能重新下单、重新取链 | 直接复用已有种子 |
| 状态未就绪时 | 一次查询失败即报错 | 有限次轮询,减少误判 |
| 每次启动任务 | 拉全量种子列表比对 | 本地缓存命中则跳过 |
| 带宽消耗 | 重复拉取已缓存文件 | 基本只走一次 |
进阶提示:让这套流程长期不掉链子
- 并发别贪多:同时挂太多磁力任务时,Real-Debrid 侧的转换排队会更久,状态轮询的窗口也要相应放宽。2~3 个并发是比较稳的节奏。
- 网络抖动有兜底:Hydra 的下载编排器(src/main/services/download-orchestrator.ts)已经处理了断网宽限与重连恢复,改上面三处逻辑时别绕过它,否则断网场景会退化。
- 定期给账号"减负重":Real-Debrid 里死掉、过期的种子建议手动清一清。本地按 infoHash 记账虽然不怕重复,但服务端条目太多会让全量列表拉取变慢。
- 想读完整实现:可以拉源码对着看,
git clone https://gitcode.com/GitHub_Trending/hy/hydra,重点目录是 src/main/services/download/(各家下载服务的适配层)和 src/main/level/sublevels/(本地状态存储)。
一句话收个尾:重复下载从来不是"下载器忘了自己下过",而是"每次都在重新问一遍,而答案来得太慢"。把"先查旧账、等状态、记一笔"这三件事补上,Real-Debrid 这条传送带就不会再让你重复付运费了。
【免费下载链接】hydraHydra Launcher is an open-source gaming platform created to be the single tool that you need项目地址: https://gitcode.com/GitHub_Trending/hy/hydra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考