mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 06:16: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:
@@ -118,6 +118,71 @@ func TestMediaUpsertRefreshesCloudExternalIDFromPathHint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMediaUpsertMigratesCloudLibraryIDOnRescan 复现"一键挂载子目录后媒体消失"的
|
||||
// 回归:同一 cloud:// 文件先被父目录库扫描入库,之后用户按二级分类重新挂载到更
|
||||
// 精确的分类库并扫描,library_id 必须迁移到新分类库,否则媒体被钉死在旧库、新库
|
||||
// 视图里看不到。本地媒体物理位置固定,不参与迁移。
|
||||
func TestMediaUpsertMigratesCloudLibraryIDOnRescan(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.AutoMigrate(db); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := New(db)
|
||||
parent := model.Library{Name: "OpenList", Path: "cloud://openlist", Type: "movie", Enabled: true}
|
||||
if err := repos.Library.Create(t.Context(), &parent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
category := model.Library{Name: "国漫", Path: "cloud://openlist/国漫", Type: "anime", Enabled: true}
|
||||
if err := repos.Library.Create(t.Context(), &category); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := "cloud://openlist/国漫/成何体统 (2024) {tmdb-256783}/Season 1/成何体统.S01E01.mkv"
|
||||
// 第一次:被父目录库扫描入库。
|
||||
first := model.Media{LibraryID: parent.ID, Title: "成何体统", Path: path, SeasonNum: 1, EpisodeNum: 1}
|
||||
if err := repos.Media.Upsert(t.Context(), &first); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 第二次:按二级分类重新挂载并扫描,归入更精确的「国漫」库。
|
||||
second := model.Media{LibraryID: category.ID, Title: "成何体统", Path: path, SeasonNum: 1, EpisodeNum: 1}
|
||||
if err := repos.Media.Upsert(t.Context(), &second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got model.Media
|
||||
if err := repos.DB.Where("path = ?", path).First(&got).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.LibraryID != category.ID {
|
||||
t.Fatalf("cloud media library_id should migrate to category library %q, got %q", category.ID, got.LibraryID)
|
||||
}
|
||||
|
||||
// 本地媒体不迁移:同一物理路径不应改库归属。
|
||||
localA := model.Library{Name: "Movies A", Path: "/media/a", Type: "movie", Enabled: true}
|
||||
localB := model.Library{Name: "Movies B", Path: "/media/b", Type: "movie", Enabled: true}
|
||||
if err := repos.Library.Create(t.Context(), &localA); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Library.Create(t.Context(), &localB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
localPath := "/media/a/Inception (2010)/Inception.mkv"
|
||||
if err := repos.Media.Upsert(t.Context(), &model.Media{LibraryID: localA.ID, Title: "Inception", Path: localPath}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Media.Upsert(t.Context(), &model.Media{LibraryID: localB.ID, Title: "Inception", Path: localPath}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var localGot model.Media
|
||||
if err := repos.DB.Where("path = ?", localPath).First(&localGot).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if localGot.LibraryID != localA.ID {
|
||||
t.Fatalf("local media library_id must not migrate, want %q got %q", localA.ID, localGot.LibraryID)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeMediaSearchBackend struct {
|
||||
ids []string
|
||||
err error
|
||||
|
||||
Reference in New Issue
Block a user