mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-07 13:56:37 +08:00
fix: 订阅恢复补缺集/单媒体手动整理入口/媒体库排序/网盘媒体分类迁移
- 订阅(问题1): selectPreparedSubscriptionCandidates 在本地已有部分集时不再 无条件丢弃整季/全集包,单集候选不足时用全集包兜底;Restore 重置 total_episodes 让下次 run 从权威元数据重算,避免被 feed 低估值锁死误判'无缺集' - 单媒体整理(问题2): 后端已具备,新增 OrganizeMediaDialog 组件并在媒体详情页 接入'整理入库'按钮(可选类型/二级分类/目标路径/转移方式/预览) - 媒体库排序(问题3): ListByLibrariesFiltered 改多级排序 year/updated_at/ created_at/id,id 作稳定 tie-breaker 消除云盘批量扫描同批时间戳的随机观感 - 网盘媒体消失(问题4): 云盘媒体 upsert 时允许 library_id 迁移到重新挂载的 更精确分类库(本地媒体物理位置固定不迁移),修复一键挂载子目录后媒体消失
This commit is contained in:
@@ -143,6 +143,11 @@ func (s *SubscriptionService) Restore(ctx context.Context, id string) (*model.Su
|
||||
Updates(map[string]any{
|
||||
"enabled": true,
|
||||
"archive_reason": "",
|
||||
// 重置为 0:此前可能被 feed 低估并锁死(updateSubscriptionTotalEpisodes
|
||||
// 只增不减,resolveSubscriptionTotalEpisodes 见 >0 即不再回查元数据)。
|
||||
// 归零后下次 run 会从 TMDb/豆瓣等权威源重算真实总集数,避免恢复后
|
||||
// 因"误判已无缺集"而不再搜索资源。
|
||||
"total_episodes": 0,
|
||||
}).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -157,8 +157,16 @@ func selectPreparedSubscriptionCandidates(candidates []siteSearchCandidate, sub
|
||||
}
|
||||
missingSet := missingEpisodeSet(local)
|
||||
onlyMissing := make([]siteSearchCandidate, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
var packFallback *siteSearchCandidate
|
||||
for i := range candidates {
|
||||
candidate := candidates[i]
|
||||
if candidate.Episode <= 0 {
|
||||
// 整季/全集包(无单集号)。剧集完结后站点常只挂全集包,
|
||||
// 这里记下来作兜底:当单集候选不足以补齐缺失集时启用,
|
||||
// 否则"补全缺失集"在站点只有全集包时永远匹配为空。
|
||||
if candidate.Pack && packFallback == nil {
|
||||
packFallback = &candidates[i]
|
||||
}
|
||||
continue
|
||||
}
|
||||
season := candidate.Season
|
||||
@@ -175,7 +183,13 @@ func selectPreparedSubscriptionCandidates(candidates []siteSearchCandidate, sub
|
||||
}
|
||||
onlyMissing = append(onlyMissing, candidate)
|
||||
}
|
||||
return sortedEpisodeCandidates(onlyMissing)
|
||||
selected := sortedEpisodeCandidates(onlyMissing)
|
||||
if len(selected) == 0 && packFallback != nil {
|
||||
// 没有可用的单集候选,但站点有整季/全集包 → 用包兜底补缺集。
|
||||
// 代价是会重下已有集,但用户主动触发补全时这是可接受的。
|
||||
return []siteSearchCandidate{*packFallback}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
|
||||
@@ -283,7 +283,9 @@ func TestSelectSiteSearchCandidatesSingleExistingEpisodeIsSkipped(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectSiteSearchCandidatesSinglePackIsSkippedWhenLibraryPartiallyExists(t *testing.T) {
|
||||
func TestSelectSiteSearchCandidatesFullPackUsedAsFallbackWhenLibraryPartiallyExists(t *testing.T) {
|
||||
// 本地缺第 3 集,站点只有整季全集包(无单集种)。剧集完结后站点常只挂全集包,
|
||||
// 此时必须用全集包兜底补缺集,否则"补全缺失集"永远匹配为空(用户报告的 bug)。
|
||||
sub := &model.Subscription{Name: "间谍过家家 自动订阅", Filter: "间谍过家家", MediaType: "tv", TotalEpisodes: 3}
|
||||
results := []SearchResult{
|
||||
{Title: "间谍过家家 S01 Complete 1080p", DownloadURL: "https://pt/download/pack", Seeders: 100},
|
||||
@@ -296,8 +298,11 @@ func TestSelectSiteSearchCandidatesSinglePackIsSkippedWhenLibraryPartiallyExists
|
||||
}
|
||||
|
||||
got := selectSiteSearchCandidates(results, sub, map[string]struct{}{}, availability)
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("selected %#v, want none because a full pack would redownload existing episodes", got)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("selected %#v, want the full pack as fallback to cover missing episode 3", got)
|
||||
}
|
||||
if got[0].Download != "https://pt/download/pack" {
|
||||
t.Fatalf("selected %#v, want the Complete pack", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +850,9 @@ func TestRestoreArchivedSubscriptionReturnsToActiveAndClearsSeenState(t *testing
|
||||
if restored.ArchivedAt != nil || restored.ArchiveReason != "" || !restored.Enabled {
|
||||
t.Fatalf("restored subscription not active: archived=%v reason=%q enabled=%v", restored.ArchivedAt, restored.ArchiveReason, restored.Enabled)
|
||||
}
|
||||
if restored.TotalEpisodes != 0 {
|
||||
t.Fatalf("restored total_episodes = %d, want 0 so it gets recomputed from authoritative metadata", restored.TotalEpisodes)
|
||||
}
|
||||
active, err := repos.Subscription.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user