mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-02 04:16:38 +08:00
split duplicate grouping and hash helpers
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// Package service — duplicate grouping helpers.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
)
|
||||
|
||||
func (d *DuplicateService) markDuplicateGroup(ctx context.Context, rep *Report, key string, group []model.Media, markedIDs map[string]struct{}) {
|
||||
primary := pickPrimary(group)
|
||||
dupes := make([]model.Media, 0, len(group)-1)
|
||||
for _, m := range group {
|
||||
if m.ID == primary.ID || m.DuplicateOf == primary.ID {
|
||||
continue
|
||||
}
|
||||
if _, ok := markedIDs[m.ID]; ok {
|
||||
continue
|
||||
}
|
||||
dupes = append(dupes, m)
|
||||
if err := d.repo.DB.WithContext(ctx).
|
||||
Model(&model.Media{}).
|
||||
Where("id = ?", m.ID).
|
||||
Updates(map[string]any{
|
||||
"is_duplicate": true,
|
||||
"duplicate_of": primary.ID,
|
||||
}).Error; err != nil {
|
||||
d.log.Warn("dup mark failed", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
markedIDs[m.ID] = struct{}{}
|
||||
rep.ItemsMarked++
|
||||
}
|
||||
if len(dupes) == 0 {
|
||||
return
|
||||
}
|
||||
rep.Groups = append(rep.Groups, Group{
|
||||
Hash: key,
|
||||
Primary: primary,
|
||||
Duplicates: dupes,
|
||||
})
|
||||
}
|
||||
|
||||
func groupByExternalIdentity(rows []model.Media) map[string][]model.Media {
|
||||
groups := map[string][]model.Media{}
|
||||
for _, row := range rows {
|
||||
key := mediaExternalIdentityKey(row)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
groups[key] = append(groups[key], row)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func mediaExternalIdentityKey(row model.Media) string {
|
||||
var key string
|
||||
switch {
|
||||
case row.TMDbID > 0:
|
||||
key = fmt.Sprintf("tmdb:%d", row.TMDbID)
|
||||
case row.BangumiID > 0:
|
||||
key = fmt.Sprintf("bangumi:%d", row.BangumiID)
|
||||
case row.DoubanID != "":
|
||||
key = "douban:" + row.DoubanID
|
||||
case row.TheTVDBID != "":
|
||||
key = "thetvdb:" + row.TheTVDBID
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
if row.SeasonNum > 0 || row.EpisodeNum > 0 {
|
||||
key += fmt.Sprintf(":s%d:e%d", row.SeasonNum, row.EpisodeNum)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// pickPrimary picks the "best" media row to keep: prefer scraped > size > id.
|
||||
func pickPrimary(group []model.Media) model.Media {
|
||||
sort.SliceStable(group, func(i, j int) bool {
|
||||
ai, aj := group[i].ScrapeStatus == "matched", group[j].ScrapeStatus == "matched"
|
||||
if ai != aj {
|
||||
return ai
|
||||
}
|
||||
if group[i].SizeBytes != group[j].SizeBytes {
|
||||
return group[i].SizeBytes > group[j].SizeBytes
|
||||
}
|
||||
return group[i].ID < group[j].ID
|
||||
})
|
||||
return group[0]
|
||||
}
|
||||
Reference in New Issue
Block a user