Files
silo-server/internal/watchstate/identity.go
T

142 lines
4.1 KiB
Go

package watchstate
import (
"context"
"errors"
"strings"
"github.com/Silo-Server/silo-server/internal/catalog"
"github.com/Silo-Server/silo-server/internal/models"
"github.com/Silo-Server/silo-server/internal/userstore"
)
type itemLookup interface {
GetByID(ctx context.Context, contentID string) (*models.MediaItem, error)
}
type episodeLookup interface {
GetByID(ctx context.Context, contentID string) (*models.Episode, error)
GetBySeriesAndNumber(ctx context.Context, seriesID string, season, episode int) (*models.Episode, error)
}
type providerIDLookup interface {
GetByContentID(ctx context.Context, contentID string) ([]*models.MediaItemProviderID, error)
FindContentIDByProviderIDs(ctx context.Context, providerIDs map[string]string, itemType, excludeContentID string) (string, error)
}
// StableIdentityResolver translates volatile local content IDs to provider-ID
// based identities that survive rescans or catalog rebinding.
type StableIdentityResolver struct {
items itemLookup
episodes episodeLookup
providerIDs providerIDLookup
}
func NewStableIdentityResolver(items itemLookup, episodes episodeLookup, providerIDs providerIDLookup) *StableIdentityResolver {
return &StableIdentityResolver{
items: items,
episodes: episodes,
providerIDs: providerIDs,
}
}
func (r *StableIdentityResolver) ResolveHistoryIdentity(ctx context.Context, mediaItemID string) userstore.WatchIdentity {
if r == nil || strings.TrimSpace(mediaItemID) == "" || r.providerIDs == nil {
return userstore.WatchIdentity{}
}
if r.episodes != nil {
episode, err := r.episodes.GetByID(ctx, mediaItemID)
if err == nil && episode != nil {
seriesIDs := providerIDMap(r.loadProviderIDs(ctx, episode.SeriesID))
if len(seriesIDs) == 0 {
return userstore.WatchIdentity{}
}
seasonNumber := episode.SeasonNumber
episodeNumber := episode.EpisodeNumber
return userstore.WatchIdentity{
StableType: "episode",
SeriesProviderIDs: seriesIDs,
Season: &seasonNumber,
Episode: &episodeNumber,
}
}
}
if r.items == nil {
return userstore.WatchIdentity{}
}
item, err := r.items.GetByID(ctx, mediaItemID)
if err != nil || item == nil || item.Type != "movie" {
return userstore.WatchIdentity{}
}
itemIDs := providerIDMap(r.loadProviderIDs(ctx, mediaItemID))
if len(itemIDs) == 0 {
return userstore.WatchIdentity{}
}
return userstore.WatchIdentity{
StableType: "movie",
ProviderIDs: itemIDs,
}
}
func (r *StableIdentityResolver) ResolveMovieContentID(ctx context.Context, providerIDs map[string]string) (string, error) {
if r == nil || r.providerIDs == nil {
return "", nil
}
return r.providerIDs.FindContentIDByProviderIDs(ctx, providerIDs, "movie", "")
}
func (r *StableIdentityResolver) ResolveEpisodeContentID(
ctx context.Context,
seriesProviderIDs map[string]string,
seasonNumber, episodeNumber int,
) (string, error) {
if r == nil || r.providerIDs == nil || r.episodes == nil || seasonNumber < 0 || episodeNumber <= 0 {
return "", nil
}
seriesID, err := r.providerIDs.FindContentIDByProviderIDs(ctx, seriesProviderIDs, "series", "")
if err != nil || strings.TrimSpace(seriesID) == "" {
return "", err
}
episode, err := r.episodes.GetBySeriesAndNumber(ctx, seriesID, seasonNumber, episodeNumber)
if err != nil {
if errors.Is(err, catalog.ErrEpisodeNotFound) {
return "", nil
}
return "", err
}
if episode == nil {
return "", nil
}
return episode.ContentID, nil
}
func (r *StableIdentityResolver) loadProviderIDs(ctx context.Context, contentID string) []*models.MediaItemProviderID {
ids, err := r.providerIDs.GetByContentID(ctx, contentID)
if err != nil {
return nil
}
return ids
}
func providerIDMap(rows []*models.MediaItemProviderID) map[string]string {
if len(rows) == 0 {
return map[string]string{}
}
result := make(map[string]string, len(rows))
for _, row := range rows {
if row == nil {
continue
}
provider := strings.TrimSpace(row.Provider)
providerID := strings.TrimSpace(row.ProviderID)
if provider == "" || providerID == "" {
continue
}
result[provider] = providerID
}
return result
}