From 67014cfb169ecb88667b150aa8c25a0a34a9c898 Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Fri, 13 Dec 2024 11:35:32 +0800 Subject: [PATCH] feat: save media alternative titles --- db/db.go | 1 + ent/media.go | 15 ++++++- ent/media/media.go | 3 ++ ent/media/where.go | 10 +++++ ent/media_create.go | 10 +++++ ent/media_update.go | 59 +++++++++++++++++++++++++ ent/migrate/schema.go | 1 + ent/mutation.go | 92 ++++++++++++++++++++++++++++++++++++++- ent/schema/media.go | 7 +++ server/core/importlist.go | 50 +++++++++++++++++++++ 10 files changed, 246 insertions(+), 2 deletions(-) diff --git a/db/db.go b/db/db.go index f0b72ad7..7c85bc91 100644 --- a/db/db.go +++ b/db/db.go @@ -157,6 +157,7 @@ func (c *Client) AddMediaWatchlist(m *ent.Media, episodes []int) (*ent.Media, er SetDownloadHistoryEpisodes(m.DownloadHistoryEpisodes). SetLimiter(m.Limiter). SetExtras(m.Extras). + SetAlternativeTitles(m.AlternativeTitles). AddEpisodeIDs(episodes...). Save(context.TODO()) return r, err diff --git a/ent/media.go b/ent/media.go index e2ecb7a8..a0abb944 100644 --- a/ent/media.go +++ b/ent/media.go @@ -49,6 +49,8 @@ type Media struct { Limiter schema.MediaLimiter `json:"limiter,omitempty"` // Extras holds the value of the "extras" field. Extras schema.MediaExtras `json:"extras,omitempty"` + // AlternativeTitles holds the value of the "alternative_titles" field. + AlternativeTitles []schema.AlternativeTilte `json:"alternative_titles,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the MediaQuery when eager-loading is set. Edges MediaEdges `json:"edges"` @@ -78,7 +80,7 @@ func (*Media) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case media.FieldLimiter, media.FieldExtras: + case media.FieldLimiter, media.FieldExtras, media.FieldAlternativeTitles: values[i] = new([]byte) case media.FieldDownloadHistoryEpisodes: values[i] = new(sql.NullBool) @@ -203,6 +205,14 @@ func (m *Media) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field extras: %w", err) } } + case media.FieldAlternativeTitles: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field alternative_titles", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &m.AlternativeTitles); err != nil { + return fmt.Errorf("unmarshal field alternative_titles: %w", err) + } + } default: m.selectValues.Set(columns[i], values[i]) } @@ -288,6 +298,9 @@ func (m *Media) String() string { builder.WriteString(", ") builder.WriteString("extras=") builder.WriteString(fmt.Sprintf("%v", m.Extras)) + builder.WriteString(", ") + builder.WriteString("alternative_titles=") + builder.WriteString(fmt.Sprintf("%v", m.AlternativeTitles)) builder.WriteByte(')') return builder.String() } diff --git a/ent/media/media.go b/ent/media/media.go index d602086c..8d5e7c9c 100644 --- a/ent/media/media.go +++ b/ent/media/media.go @@ -45,6 +45,8 @@ const ( FieldLimiter = "limiter" // FieldExtras holds the string denoting the extras field in the database. FieldExtras = "extras" + // FieldAlternativeTitles holds the string denoting the alternative_titles field in the database. + FieldAlternativeTitles = "alternative_titles" // EdgeEpisodes holds the string denoting the episodes edge name in mutations. EdgeEpisodes = "episodes" // Table holds the table name of the media in the database. @@ -76,6 +78,7 @@ var Columns = []string{ FieldDownloadHistoryEpisodes, FieldLimiter, FieldExtras, + FieldAlternativeTitles, } // ValidColumn reports if the column name is valid (part of the table columns). diff --git a/ent/media/where.go b/ent/media/where.go index 7652745c..61eb4485 100644 --- a/ent/media/where.go +++ b/ent/media/where.go @@ -795,6 +795,16 @@ func ExtrasNotNil() predicate.Media { return predicate.Media(sql.FieldNotNull(FieldExtras)) } +// AlternativeTitlesIsNil applies the IsNil predicate on the "alternative_titles" field. +func AlternativeTitlesIsNil() predicate.Media { + return predicate.Media(sql.FieldIsNull(FieldAlternativeTitles)) +} + +// AlternativeTitlesNotNil applies the NotNil predicate on the "alternative_titles" field. +func AlternativeTitlesNotNil() predicate.Media { + return predicate.Media(sql.FieldNotNull(FieldAlternativeTitles)) +} + // HasEpisodes applies the HasEdge predicate on the "episodes" edge. func HasEpisodes() predicate.Media { return predicate.Media(func(s *sql.Selector) { diff --git a/ent/media_create.go b/ent/media_create.go index cacf0e8a..1deefefc 100644 --- a/ent/media_create.go +++ b/ent/media_create.go @@ -184,6 +184,12 @@ func (mc *MediaCreate) SetNillableExtras(se *schema.MediaExtras) *MediaCreate { return mc } +// SetAlternativeTitles sets the "alternative_titles" field. +func (mc *MediaCreate) SetAlternativeTitles(st []schema.AlternativeTilte) *MediaCreate { + mc.mutation.SetAlternativeTitles(st) + return mc +} + // AddEpisodeIDs adds the "episodes" edge to the Episode entity by IDs. func (mc *MediaCreate) AddEpisodeIDs(ids ...int) *MediaCreate { mc.mutation.AddEpisodeIDs(ids...) @@ -377,6 +383,10 @@ func (mc *MediaCreate) createSpec() (*Media, *sqlgraph.CreateSpec) { _spec.SetField(media.FieldExtras, field.TypeJSON, value) _node.Extras = value } + if value, ok := mc.mutation.AlternativeTitles(); ok { + _spec.SetField(media.FieldAlternativeTitles, field.TypeJSON, value) + _node.AlternativeTitles = value + } if nodes := mc.mutation.EpisodesIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/ent/media_update.go b/ent/media_update.go index a7c03481..616234fa 100644 --- a/ent/media_update.go +++ b/ent/media_update.go @@ -14,6 +14,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" ) @@ -290,6 +291,24 @@ func (mu *MediaUpdate) ClearExtras() *MediaUpdate { return mu } +// SetAlternativeTitles sets the "alternative_titles" field. +func (mu *MediaUpdate) SetAlternativeTitles(st []schema.AlternativeTilte) *MediaUpdate { + mu.mutation.SetAlternativeTitles(st) + return mu +} + +// AppendAlternativeTitles appends st to the "alternative_titles" field. +func (mu *MediaUpdate) AppendAlternativeTitles(st []schema.AlternativeTilte) *MediaUpdate { + mu.mutation.AppendAlternativeTitles(st) + return mu +} + +// ClearAlternativeTitles clears the value of the "alternative_titles" field. +func (mu *MediaUpdate) ClearAlternativeTitles() *MediaUpdate { + mu.mutation.ClearAlternativeTitles() + return mu +} + // AddEpisodeIDs adds the "episodes" edge to the Episode entity by IDs. func (mu *MediaUpdate) AddEpisodeIDs(ids ...int) *MediaUpdate { mu.mutation.AddEpisodeIDs(ids...) @@ -454,6 +473,17 @@ func (mu *MediaUpdate) sqlSave(ctx context.Context) (n int, err error) { if mu.mutation.ExtrasCleared() { _spec.ClearField(media.FieldExtras, field.TypeJSON) } + if value, ok := mu.mutation.AlternativeTitles(); ok { + _spec.SetField(media.FieldAlternativeTitles, field.TypeJSON, value) + } + if value, ok := mu.mutation.AppendedAlternativeTitles(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, media.FieldAlternativeTitles, value) + }) + } + if mu.mutation.AlternativeTitlesCleared() { + _spec.ClearField(media.FieldAlternativeTitles, field.TypeJSON) + } if mu.mutation.EpisodesCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -779,6 +809,24 @@ func (muo *MediaUpdateOne) ClearExtras() *MediaUpdateOne { return muo } +// SetAlternativeTitles sets the "alternative_titles" field. +func (muo *MediaUpdateOne) SetAlternativeTitles(st []schema.AlternativeTilte) *MediaUpdateOne { + muo.mutation.SetAlternativeTitles(st) + return muo +} + +// AppendAlternativeTitles appends st to the "alternative_titles" field. +func (muo *MediaUpdateOne) AppendAlternativeTitles(st []schema.AlternativeTilte) *MediaUpdateOne { + muo.mutation.AppendAlternativeTitles(st) + return muo +} + +// ClearAlternativeTitles clears the value of the "alternative_titles" field. +func (muo *MediaUpdateOne) ClearAlternativeTitles() *MediaUpdateOne { + muo.mutation.ClearAlternativeTitles() + return muo +} + // AddEpisodeIDs adds the "episodes" edge to the Episode entity by IDs. func (muo *MediaUpdateOne) AddEpisodeIDs(ids ...int) *MediaUpdateOne { muo.mutation.AddEpisodeIDs(ids...) @@ -973,6 +1021,17 @@ func (muo *MediaUpdateOne) sqlSave(ctx context.Context) (_node *Media, err error if muo.mutation.ExtrasCleared() { _spec.ClearField(media.FieldExtras, field.TypeJSON) } + if value, ok := muo.mutation.AlternativeTitles(); ok { + _spec.SetField(media.FieldAlternativeTitles, field.TypeJSON, value) + } + if value, ok := muo.mutation.AppendedAlternativeTitles(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, media.FieldAlternativeTitles, value) + }) + } + if muo.mutation.AlternativeTitlesCleared() { + _spec.ClearField(media.FieldAlternativeTitles, field.TypeJSON) + } if muo.mutation.EpisodesCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index 7ac0668d..14bd096c 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -143,6 +143,7 @@ var ( {Name: "download_history_episodes", Type: field.TypeBool, Nullable: true, Default: false}, {Name: "limiter", Type: field.TypeJSON, Nullable: true}, {Name: "extras", Type: field.TypeJSON, Nullable: true}, + {Name: "alternative_titles", Type: field.TypeJSON, Nullable: true}, } // MediaTable holds the schema information for the "media" table. MediaTable = &schema.Table{ diff --git a/ent/mutation.go b/ent/mutation.go index 4227016e..65cff10f 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -5115,6 +5115,8 @@ type MediaMutation struct { download_history_episodes *bool limiter *schema.MediaLimiter extras *schema.MediaExtras + alternative_titles *[]schema.AlternativeTilte + appendalternative_titles []schema.AlternativeTilte clearedFields map[string]struct{} episodes map[int]struct{} removedepisodes map[int]struct{} @@ -5881,6 +5883,71 @@ func (m *MediaMutation) ResetExtras() { delete(m.clearedFields, media.FieldExtras) } +// SetAlternativeTitles sets the "alternative_titles" field. +func (m *MediaMutation) SetAlternativeTitles(st []schema.AlternativeTilte) { + m.alternative_titles = &st + m.appendalternative_titles = nil +} + +// AlternativeTitles returns the value of the "alternative_titles" field in the mutation. +func (m *MediaMutation) AlternativeTitles() (r []schema.AlternativeTilte, exists bool) { + v := m.alternative_titles + if v == nil { + return + } + return *v, true +} + +// OldAlternativeTitles returns the old "alternative_titles" field's value of the Media entity. +// If the Media object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *MediaMutation) OldAlternativeTitles(ctx context.Context) (v []schema.AlternativeTilte, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAlternativeTitles is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAlternativeTitles requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAlternativeTitles: %w", err) + } + return oldValue.AlternativeTitles, nil +} + +// AppendAlternativeTitles adds st to the "alternative_titles" field. +func (m *MediaMutation) AppendAlternativeTitles(st []schema.AlternativeTilte) { + m.appendalternative_titles = append(m.appendalternative_titles, st...) +} + +// AppendedAlternativeTitles returns the list of values that were appended to the "alternative_titles" field in this mutation. +func (m *MediaMutation) AppendedAlternativeTitles() ([]schema.AlternativeTilte, bool) { + if len(m.appendalternative_titles) == 0 { + return nil, false + } + return m.appendalternative_titles, true +} + +// ClearAlternativeTitles clears the value of the "alternative_titles" field. +func (m *MediaMutation) ClearAlternativeTitles() { + m.alternative_titles = nil + m.appendalternative_titles = nil + m.clearedFields[media.FieldAlternativeTitles] = struct{}{} +} + +// AlternativeTitlesCleared returns if the "alternative_titles" field was cleared in this mutation. +func (m *MediaMutation) AlternativeTitlesCleared() bool { + _, ok := m.clearedFields[media.FieldAlternativeTitles] + return ok +} + +// ResetAlternativeTitles resets all changes to the "alternative_titles" field. +func (m *MediaMutation) ResetAlternativeTitles() { + m.alternative_titles = nil + m.appendalternative_titles = nil + delete(m.clearedFields, media.FieldAlternativeTitles) +} + // AddEpisodeIDs adds the "episodes" edge to the Episode entity by ids. func (m *MediaMutation) AddEpisodeIDs(ids ...int) { if m.episodes == nil { @@ -5969,7 +6036,7 @@ func (m *MediaMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *MediaMutation) Fields() []string { - fields := make([]string, 0, 15) + fields := make([]string, 0, 16) if m.tmdb_id != nil { fields = append(fields, media.FieldTmdbID) } @@ -6015,6 +6082,9 @@ func (m *MediaMutation) Fields() []string { if m.extras != nil { fields = append(fields, media.FieldExtras) } + if m.alternative_titles != nil { + fields = append(fields, media.FieldAlternativeTitles) + } return fields } @@ -6053,6 +6123,8 @@ func (m *MediaMutation) Field(name string) (ent.Value, bool) { return m.Limiter() case media.FieldExtras: return m.Extras() + case media.FieldAlternativeTitles: + return m.AlternativeTitles() } return nil, false } @@ -6092,6 +6164,8 @@ func (m *MediaMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldLimiter(ctx) case media.FieldExtras: return m.OldExtras(ctx) + case media.FieldAlternativeTitles: + return m.OldAlternativeTitles(ctx) } return nil, fmt.Errorf("unknown Media field %s", name) } @@ -6206,6 +6280,13 @@ func (m *MediaMutation) SetField(name string, value ent.Value) error { } m.SetExtras(v) return nil + case media.FieldAlternativeTitles: + v, ok := value.([]schema.AlternativeTilte) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAlternativeTitles(v) + return nil } return fmt.Errorf("unknown Media field %s", name) } @@ -6281,6 +6362,9 @@ func (m *MediaMutation) ClearedFields() []string { if m.FieldCleared(media.FieldExtras) { fields = append(fields, media.FieldExtras) } + if m.FieldCleared(media.FieldAlternativeTitles) { + fields = append(fields, media.FieldAlternativeTitles) + } return fields } @@ -6313,6 +6397,9 @@ func (m *MediaMutation) ClearField(name string) error { case media.FieldExtras: m.ClearExtras() return nil + case media.FieldAlternativeTitles: + m.ClearAlternativeTitles() + return nil } return fmt.Errorf("unknown Media nullable field %s", name) } @@ -6366,6 +6453,9 @@ func (m *MediaMutation) ResetField(name string) error { case media.FieldExtras: m.ResetExtras() return nil + case media.FieldAlternativeTitles: + m.ResetAlternativeTitles() + return nil } return fmt.Errorf("unknown Media field %s", name) } diff --git a/ent/schema/media.go b/ent/schema/media.go index 1119c151..a9e09b13 100644 --- a/ent/schema/media.go +++ b/ent/schema/media.go @@ -31,6 +31,7 @@ func (Media) Fields() []ent.Field { field.Bool("download_history_episodes").Optional().Default(false).Comment("tv series only"), field.JSON("limiter", MediaLimiter{}).Optional(), field.JSON("extras", MediaExtras{}).Optional(), + field.JSON("alternative_titles", []AlternativeTilte{}).Optional(), } } @@ -41,6 +42,12 @@ func (Media) Edges() []ent.Edge { } } +type AlternativeTilte struct { + Iso3166_1 string `json:"iso_3166_1"` + Title string `json:"title"` + Type string `json:"type"` +} + type MediaLimiter struct { SizeMin int64 `json:"size_min"` //in B SizeMax int64 `json:"size_max"` //in B diff --git a/server/core/importlist.go b/server/core/importlist.go index 2c0bbe18..e807eebd 100644 --- a/server/core/importlist.go +++ b/server/core/importlist.go @@ -149,6 +149,11 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) { log.Debugf("latest season is %v", lastSeason) + alterTitles, err := c.getAlterTitles(in.TmdbID, media.MediaTypeTv) + if err != nil { + return nil, errors.Wrap(err, "get alter titles") + } + var epIds []int for _, season := range detail.Seasons { seasonId := season.SeasonNumber @@ -195,6 +200,7 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) { epIds = append(epIds, epid) } } + m := &ent.Media{ TmdbID: int(detail.ID), ImdbID: detail.IMDbID, @@ -213,6 +219,7 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) { OriginalLanguage: detail.OriginalLanguage, Genres: detail.Genres, }, + AlternativeTitles: alterTitles, } r, err := c.db.AddMediaWatchlist(m, epIds) @@ -240,6 +247,42 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) { return nil, nil } +func (c *Client) getAlterTitles(tmdbId int, mediaType media.MediaType) ([]schema.AlternativeTilte, error){ + var titles []schema.AlternativeTilte + + if mediaType == media.MediaTypeTv { + alterTitles, err := c.MustTMDB().GetTVAlternativeTitles(tmdbId, c.language) + if err != nil { + return nil, errors.Wrap(err, "tmdb") + } + + for _, t := range alterTitles.Results { + titles = append(titles, schema.AlternativeTilte{ + Iso3166_1: t.Iso3166_1, + Title: t.Title, + Type: t.Type, + }) + } + + } else if mediaType == media.MediaTypeMovie { + alterTitles, err := c.MustTMDB().GetMovieAlternativeTitles(tmdbId, c.language) + if err != nil { + return nil, errors.Wrap(err, "tmdb") + } + + for _, t := range alterTitles.Titles { + titles = append(titles, schema.AlternativeTilte{ + Iso3166_1: t.Iso3166_1, + Title: t.Title, + Type: t.Type, + }) + } + } + log.Debugf("get alternative titles: %+v", titles) + + return titles, nil +} + func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) { log.Infof("add movie watchlist input: %+v", in) detailCn, err := c.MustTMDB().GetMovieDetails(in.TmdbID, db.LanguageCN) @@ -258,6 +301,12 @@ func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) { } log.Infof("find detail for movie id %d: %v", in.TmdbID, detail) + alterTitles, err := c.getAlterTitles(in.TmdbID, media.MediaTypeMovie) + if err != nil { + return nil, errors.Wrap(err, "get alter titles") + } + + epid, err := c.db.SaveEposideDetail(&ent.Episode{ SeasonNumber: 1, EpisodeNumber: 1, @@ -284,6 +333,7 @@ func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) { StorageID: in.StorageID, TargetDir: in.Folder, Limiter: schema.MediaLimiter{SizeMin: in.SizeMin, SizeMax: in.SizeMax}, + AlternativeTitles: alterTitles, } extras := schema.MediaExtras{