diff --git a/ent/history.go b/ent/history.go index 146c141..d8f9462 100644 --- a/ent/history.go +++ b/ent/history.go @@ -3,6 +3,7 @@ package ent import ( + "encoding/json" "fmt" "polaris/ent/history" "strings" @@ -19,8 +20,12 @@ type History struct { ID int `json:"id,omitempty"` // MediaID holds the value of the "media_id" field. MediaID int `json:"media_id,omitempty"` - // EpisodeID holds the value of the "episode_id" field. + // deprecated EpisodeID int `json:"episode_id,omitempty"` + // EpisodeNums holds the value of the "episode_nums" field. + EpisodeNums []int `json:"episode_nums,omitempty"` + // SeasonNum holds the value of the "season_num" field. + SeasonNum int `json:"season_num,omitempty"` // SourceTitle holds the value of the "source_title" field. SourceTitle string `json:"source_title,omitempty"` // Date holds the value of the "date" field. @@ -47,7 +52,9 @@ func (*History) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case history.FieldID, history.FieldMediaID, history.FieldEpisodeID, history.FieldSize, history.FieldDownloadClientID, history.FieldIndexerID: + case history.FieldEpisodeNums: + values[i] = new([]byte) + case history.FieldID, history.FieldMediaID, history.FieldEpisodeID, history.FieldSeasonNum, history.FieldSize, history.FieldDownloadClientID, history.FieldIndexerID: values[i] = new(sql.NullInt64) case history.FieldSourceTitle, history.FieldTargetDir, history.FieldLink, history.FieldStatus, history.FieldSaved: values[i] = new(sql.NullString) @@ -86,6 +93,20 @@ func (h *History) assignValues(columns []string, values []any) error { } else if value.Valid { h.EpisodeID = int(value.Int64) } + case history.FieldEpisodeNums: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field episode_nums", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &h.EpisodeNums); err != nil { + return fmt.Errorf("unmarshal field episode_nums: %w", err) + } + } + case history.FieldSeasonNum: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field season_num", values[i]) + } else if value.Valid { + h.SeasonNum = int(value.Int64) + } case history.FieldSourceTitle: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field source_title", values[i]) @@ -182,6 +203,12 @@ func (h *History) String() string { builder.WriteString("episode_id=") builder.WriteString(fmt.Sprintf("%v", h.EpisodeID)) builder.WriteString(", ") + builder.WriteString("episode_nums=") + builder.WriteString(fmt.Sprintf("%v", h.EpisodeNums)) + builder.WriteString(", ") + builder.WriteString("season_num=") + builder.WriteString(fmt.Sprintf("%v", h.SeasonNum)) + builder.WriteString(", ") builder.WriteString("source_title=") builder.WriteString(h.SourceTitle) builder.WriteString(", ") diff --git a/ent/history/history.go b/ent/history/history.go index e815b97..1dcd79d 100644 --- a/ent/history/history.go +++ b/ent/history/history.go @@ -17,6 +17,10 @@ const ( FieldMediaID = "media_id" // FieldEpisodeID holds the string denoting the episode_id field in the database. FieldEpisodeID = "episode_id" + // FieldEpisodeNums holds the string denoting the episode_nums field in the database. + FieldEpisodeNums = "episode_nums" + // FieldSeasonNum holds the string denoting the season_num field in the database. + FieldSeasonNum = "season_num" // FieldSourceTitle holds the string denoting the source_title field in the database. FieldSourceTitle = "source_title" // FieldDate holds the string denoting the date field in the database. @@ -44,6 +48,8 @@ var Columns = []string{ FieldID, FieldMediaID, FieldEpisodeID, + FieldEpisodeNums, + FieldSeasonNum, FieldSourceTitle, FieldDate, FieldTargetDir, @@ -114,6 +120,11 @@ func ByEpisodeID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEpisodeID, opts...).ToFunc() } +// BySeasonNum orders the results by the season_num field. +func BySeasonNum(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSeasonNum, opts...).ToFunc() +} + // BySourceTitle orders the results by the source_title field. func BySourceTitle(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSourceTitle, opts...).ToFunc() diff --git a/ent/history/where.go b/ent/history/where.go index 361cc6c..347f607 100644 --- a/ent/history/where.go +++ b/ent/history/where.go @@ -64,6 +64,11 @@ func EpisodeID(v int) predicate.History { return predicate.History(sql.FieldEQ(FieldEpisodeID, v)) } +// SeasonNum applies equality check predicate on the "season_num" field. It's identical to SeasonNumEQ. +func SeasonNum(v int) predicate.History { + return predicate.History(sql.FieldEQ(FieldSeasonNum, v)) +} + // SourceTitle applies equality check predicate on the "source_title" field. It's identical to SourceTitleEQ. func SourceTitle(v string) predicate.History { return predicate.History(sql.FieldEQ(FieldSourceTitle, v)) @@ -194,6 +199,66 @@ func EpisodeIDNotNil() predicate.History { return predicate.History(sql.FieldNotNull(FieldEpisodeID)) } +// EpisodeNumsIsNil applies the IsNil predicate on the "episode_nums" field. +func EpisodeNumsIsNil() predicate.History { + return predicate.History(sql.FieldIsNull(FieldEpisodeNums)) +} + +// EpisodeNumsNotNil applies the NotNil predicate on the "episode_nums" field. +func EpisodeNumsNotNil() predicate.History { + return predicate.History(sql.FieldNotNull(FieldEpisodeNums)) +} + +// SeasonNumEQ applies the EQ predicate on the "season_num" field. +func SeasonNumEQ(v int) predicate.History { + return predicate.History(sql.FieldEQ(FieldSeasonNum, v)) +} + +// SeasonNumNEQ applies the NEQ predicate on the "season_num" field. +func SeasonNumNEQ(v int) predicate.History { + return predicate.History(sql.FieldNEQ(FieldSeasonNum, v)) +} + +// SeasonNumIn applies the In predicate on the "season_num" field. +func SeasonNumIn(vs ...int) predicate.History { + return predicate.History(sql.FieldIn(FieldSeasonNum, vs...)) +} + +// SeasonNumNotIn applies the NotIn predicate on the "season_num" field. +func SeasonNumNotIn(vs ...int) predicate.History { + return predicate.History(sql.FieldNotIn(FieldSeasonNum, vs...)) +} + +// SeasonNumGT applies the GT predicate on the "season_num" field. +func SeasonNumGT(v int) predicate.History { + return predicate.History(sql.FieldGT(FieldSeasonNum, v)) +} + +// SeasonNumGTE applies the GTE predicate on the "season_num" field. +func SeasonNumGTE(v int) predicate.History { + return predicate.History(sql.FieldGTE(FieldSeasonNum, v)) +} + +// SeasonNumLT applies the LT predicate on the "season_num" field. +func SeasonNumLT(v int) predicate.History { + return predicate.History(sql.FieldLT(FieldSeasonNum, v)) +} + +// SeasonNumLTE applies the LTE predicate on the "season_num" field. +func SeasonNumLTE(v int) predicate.History { + return predicate.History(sql.FieldLTE(FieldSeasonNum, v)) +} + +// SeasonNumIsNil applies the IsNil predicate on the "season_num" field. +func SeasonNumIsNil() predicate.History { + return predicate.History(sql.FieldIsNull(FieldSeasonNum)) +} + +// SeasonNumNotNil applies the NotNil predicate on the "season_num" field. +func SeasonNumNotNil() predicate.History { + return predicate.History(sql.FieldNotNull(FieldSeasonNum)) +} + // SourceTitleEQ applies the EQ predicate on the "source_title" field. func SourceTitleEQ(v string) predicate.History { return predicate.History(sql.FieldEQ(FieldSourceTitle, v)) diff --git a/ent/history_create.go b/ent/history_create.go index a2aace3..ae7d4c7 100644 --- a/ent/history_create.go +++ b/ent/history_create.go @@ -40,6 +40,26 @@ func (hc *HistoryCreate) SetNillableEpisodeID(i *int) *HistoryCreate { return hc } +// SetEpisodeNums sets the "episode_nums" field. +func (hc *HistoryCreate) SetEpisodeNums(i []int) *HistoryCreate { + hc.mutation.SetEpisodeNums(i) + return hc +} + +// SetSeasonNum sets the "season_num" field. +func (hc *HistoryCreate) SetSeasonNum(i int) *HistoryCreate { + hc.mutation.SetSeasonNum(i) + return hc +} + +// SetNillableSeasonNum sets the "season_num" field if the given value is not nil. +func (hc *HistoryCreate) SetNillableSeasonNum(i *int) *HistoryCreate { + if i != nil { + hc.SetSeasonNum(*i) + } + return hc +} + // SetSourceTitle sets the "source_title" field. func (hc *HistoryCreate) SetSourceTitle(s string) *HistoryCreate { hc.mutation.SetSourceTitle(s) @@ -234,6 +254,14 @@ func (hc *HistoryCreate) createSpec() (*History, *sqlgraph.CreateSpec) { _spec.SetField(history.FieldEpisodeID, field.TypeInt, value) _node.EpisodeID = value } + if value, ok := hc.mutation.EpisodeNums(); ok { + _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) + _node.EpisodeNums = value + } + if value, ok := hc.mutation.SeasonNum(); ok { + _spec.SetField(history.FieldSeasonNum, field.TypeInt, value) + _node.SeasonNum = value + } if value, ok := hc.mutation.SourceTitle(); ok { _spec.SetField(history.FieldSourceTitle, field.TypeString, value) _node.SourceTitle = value diff --git a/ent/history_update.go b/ent/history_update.go index 6a05416..ebe9cf9 100644 --- a/ent/history_update.go +++ b/ent/history_update.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" ) @@ -76,6 +77,51 @@ func (hu *HistoryUpdate) ClearEpisodeID() *HistoryUpdate { return hu } +// SetEpisodeNums sets the "episode_nums" field. +func (hu *HistoryUpdate) SetEpisodeNums(i []int) *HistoryUpdate { + hu.mutation.SetEpisodeNums(i) + return hu +} + +// AppendEpisodeNums appends i to the "episode_nums" field. +func (hu *HistoryUpdate) AppendEpisodeNums(i []int) *HistoryUpdate { + hu.mutation.AppendEpisodeNums(i) + return hu +} + +// ClearEpisodeNums clears the value of the "episode_nums" field. +func (hu *HistoryUpdate) ClearEpisodeNums() *HistoryUpdate { + hu.mutation.ClearEpisodeNums() + return hu +} + +// SetSeasonNum sets the "season_num" field. +func (hu *HistoryUpdate) SetSeasonNum(i int) *HistoryUpdate { + hu.mutation.ResetSeasonNum() + hu.mutation.SetSeasonNum(i) + return hu +} + +// SetNillableSeasonNum sets the "season_num" field if the given value is not nil. +func (hu *HistoryUpdate) SetNillableSeasonNum(i *int) *HistoryUpdate { + if i != nil { + hu.SetSeasonNum(*i) + } + return hu +} + +// AddSeasonNum adds i to the "season_num" field. +func (hu *HistoryUpdate) AddSeasonNum(i int) *HistoryUpdate { + hu.mutation.AddSeasonNum(i) + return hu +} + +// ClearSeasonNum clears the value of the "season_num" field. +func (hu *HistoryUpdate) ClearSeasonNum() *HistoryUpdate { + hu.mutation.ClearSeasonNum() + return hu +} + // SetSourceTitle sets the "source_title" field. func (hu *HistoryUpdate) SetSourceTitle(s string) *HistoryUpdate { hu.mutation.SetSourceTitle(s) @@ -316,6 +362,26 @@ func (hu *HistoryUpdate) sqlSave(ctx context.Context) (n int, err error) { if hu.mutation.EpisodeIDCleared() { _spec.ClearField(history.FieldEpisodeID, field.TypeInt) } + if value, ok := hu.mutation.EpisodeNums(); ok { + _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) + } + if value, ok := hu.mutation.AppendedEpisodeNums(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, history.FieldEpisodeNums, value) + }) + } + if hu.mutation.EpisodeNumsCleared() { + _spec.ClearField(history.FieldEpisodeNums, field.TypeJSON) + } + if value, ok := hu.mutation.SeasonNum(); ok { + _spec.SetField(history.FieldSeasonNum, field.TypeInt, value) + } + if value, ok := hu.mutation.AddedSeasonNum(); ok { + _spec.AddField(history.FieldSeasonNum, field.TypeInt, value) + } + if hu.mutation.SeasonNumCleared() { + _spec.ClearField(history.FieldSeasonNum, field.TypeInt) + } if value, ok := hu.mutation.SourceTitle(); ok { _spec.SetField(history.FieldSourceTitle, field.TypeString, value) } @@ -432,6 +498,51 @@ func (huo *HistoryUpdateOne) ClearEpisodeID() *HistoryUpdateOne { return huo } +// SetEpisodeNums sets the "episode_nums" field. +func (huo *HistoryUpdateOne) SetEpisodeNums(i []int) *HistoryUpdateOne { + huo.mutation.SetEpisodeNums(i) + return huo +} + +// AppendEpisodeNums appends i to the "episode_nums" field. +func (huo *HistoryUpdateOne) AppendEpisodeNums(i []int) *HistoryUpdateOne { + huo.mutation.AppendEpisodeNums(i) + return huo +} + +// ClearEpisodeNums clears the value of the "episode_nums" field. +func (huo *HistoryUpdateOne) ClearEpisodeNums() *HistoryUpdateOne { + huo.mutation.ClearEpisodeNums() + return huo +} + +// SetSeasonNum sets the "season_num" field. +func (huo *HistoryUpdateOne) SetSeasonNum(i int) *HistoryUpdateOne { + huo.mutation.ResetSeasonNum() + huo.mutation.SetSeasonNum(i) + return huo +} + +// SetNillableSeasonNum sets the "season_num" field if the given value is not nil. +func (huo *HistoryUpdateOne) SetNillableSeasonNum(i *int) *HistoryUpdateOne { + if i != nil { + huo.SetSeasonNum(*i) + } + return huo +} + +// AddSeasonNum adds i to the "season_num" field. +func (huo *HistoryUpdateOne) AddSeasonNum(i int) *HistoryUpdateOne { + huo.mutation.AddSeasonNum(i) + return huo +} + +// ClearSeasonNum clears the value of the "season_num" field. +func (huo *HistoryUpdateOne) ClearSeasonNum() *HistoryUpdateOne { + huo.mutation.ClearSeasonNum() + return huo +} + // SetSourceTitle sets the "source_title" field. func (huo *HistoryUpdateOne) SetSourceTitle(s string) *HistoryUpdateOne { huo.mutation.SetSourceTitle(s) @@ -702,6 +813,26 @@ func (huo *HistoryUpdateOne) sqlSave(ctx context.Context) (_node *History, err e if huo.mutation.EpisodeIDCleared() { _spec.ClearField(history.FieldEpisodeID, field.TypeInt) } + if value, ok := huo.mutation.EpisodeNums(); ok { + _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) + } + if value, ok := huo.mutation.AppendedEpisodeNums(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, history.FieldEpisodeNums, value) + }) + } + if huo.mutation.EpisodeNumsCleared() { + _spec.ClearField(history.FieldEpisodeNums, field.TypeJSON) + } + if value, ok := huo.mutation.SeasonNum(); ok { + _spec.SetField(history.FieldSeasonNum, field.TypeInt, value) + } + if value, ok := huo.mutation.AddedSeasonNum(); ok { + _spec.AddField(history.FieldSeasonNum, field.TypeInt, value) + } + if huo.mutation.SeasonNumCleared() { + _spec.ClearField(history.FieldSeasonNum, field.TypeInt) + } if value, ok := huo.mutation.SourceTitle(); ok { _spec.SetField(history.FieldSourceTitle, field.TypeString, value) } diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index e856610..474936d 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -74,6 +74,8 @@ var ( {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "media_id", Type: field.TypeInt}, {Name: "episode_id", Type: field.TypeInt, Nullable: true}, + {Name: "episode_nums", Type: field.TypeJSON, Nullable: true}, + {Name: "season_num", Type: field.TypeInt, Nullable: true}, {Name: "source_title", Type: field.TypeString}, {Name: "date", Type: field.TypeTime}, {Name: "target_dir", Type: field.TypeString}, diff --git a/ent/mutation.go b/ent/mutation.go index 52e18fa..4227016 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -2336,6 +2336,10 @@ type HistoryMutation struct { addmedia_id *int episode_id *int addepisode_id *int + episode_nums *[]int + appendepisode_nums []int + season_num *int + addseason_num *int source_title *string date *time.Time target_dir *string @@ -2578,6 +2582,141 @@ func (m *HistoryMutation) ResetEpisodeID() { delete(m.clearedFields, history.FieldEpisodeID) } +// SetEpisodeNums sets the "episode_nums" field. +func (m *HistoryMutation) SetEpisodeNums(i []int) { + m.episode_nums = &i + m.appendepisode_nums = nil +} + +// EpisodeNums returns the value of the "episode_nums" field in the mutation. +func (m *HistoryMutation) EpisodeNums() (r []int, exists bool) { + v := m.episode_nums + if v == nil { + return + } + return *v, true +} + +// OldEpisodeNums returns the old "episode_nums" field's value of the History entity. +// If the History 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 *HistoryMutation) OldEpisodeNums(ctx context.Context) (v []int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEpisodeNums is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEpisodeNums requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEpisodeNums: %w", err) + } + return oldValue.EpisodeNums, nil +} + +// AppendEpisodeNums adds i to the "episode_nums" field. +func (m *HistoryMutation) AppendEpisodeNums(i []int) { + m.appendepisode_nums = append(m.appendepisode_nums, i...) +} + +// AppendedEpisodeNums returns the list of values that were appended to the "episode_nums" field in this mutation. +func (m *HistoryMutation) AppendedEpisodeNums() ([]int, bool) { + if len(m.appendepisode_nums) == 0 { + return nil, false + } + return m.appendepisode_nums, true +} + +// ClearEpisodeNums clears the value of the "episode_nums" field. +func (m *HistoryMutation) ClearEpisodeNums() { + m.episode_nums = nil + m.appendepisode_nums = nil + m.clearedFields[history.FieldEpisodeNums] = struct{}{} +} + +// EpisodeNumsCleared returns if the "episode_nums" field was cleared in this mutation. +func (m *HistoryMutation) EpisodeNumsCleared() bool { + _, ok := m.clearedFields[history.FieldEpisodeNums] + return ok +} + +// ResetEpisodeNums resets all changes to the "episode_nums" field. +func (m *HistoryMutation) ResetEpisodeNums() { + m.episode_nums = nil + m.appendepisode_nums = nil + delete(m.clearedFields, history.FieldEpisodeNums) +} + +// SetSeasonNum sets the "season_num" field. +func (m *HistoryMutation) SetSeasonNum(i int) { + m.season_num = &i + m.addseason_num = nil +} + +// SeasonNum returns the value of the "season_num" field in the mutation. +func (m *HistoryMutation) SeasonNum() (r int, exists bool) { + v := m.season_num + if v == nil { + return + } + return *v, true +} + +// OldSeasonNum returns the old "season_num" field's value of the History entity. +// If the History 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 *HistoryMutation) OldSeasonNum(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSeasonNum is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSeasonNum requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSeasonNum: %w", err) + } + return oldValue.SeasonNum, nil +} + +// AddSeasonNum adds i to the "season_num" field. +func (m *HistoryMutation) AddSeasonNum(i int) { + if m.addseason_num != nil { + *m.addseason_num += i + } else { + m.addseason_num = &i + } +} + +// AddedSeasonNum returns the value that was added to the "season_num" field in this mutation. +func (m *HistoryMutation) AddedSeasonNum() (r int, exists bool) { + v := m.addseason_num + if v == nil { + return + } + return *v, true +} + +// ClearSeasonNum clears the value of the "season_num" field. +func (m *HistoryMutation) ClearSeasonNum() { + m.season_num = nil + m.addseason_num = nil + m.clearedFields[history.FieldSeasonNum] = struct{}{} +} + +// SeasonNumCleared returns if the "season_num" field was cleared in this mutation. +func (m *HistoryMutation) SeasonNumCleared() bool { + _, ok := m.clearedFields[history.FieldSeasonNum] + return ok +} + +// ResetSeasonNum resets all changes to the "season_num" field. +func (m *HistoryMutation) ResetSeasonNum() { + m.season_num = nil + m.addseason_num = nil + delete(m.clearedFields, history.FieldSeasonNum) +} + // SetSourceTitle sets the "source_title" field. func (m *HistoryMutation) SetSourceTitle(s string) { m.source_title = &s @@ -3050,13 +3189,19 @@ func (m *HistoryMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *HistoryMutation) Fields() []string { - fields := make([]string, 0, 11) + fields := make([]string, 0, 13) if m.media_id != nil { fields = append(fields, history.FieldMediaID) } if m.episode_id != nil { fields = append(fields, history.FieldEpisodeID) } + if m.episode_nums != nil { + fields = append(fields, history.FieldEpisodeNums) + } + if m.season_num != nil { + fields = append(fields, history.FieldSeasonNum) + } if m.source_title != nil { fields = append(fields, history.FieldSourceTitle) } @@ -3096,6 +3241,10 @@ func (m *HistoryMutation) Field(name string) (ent.Value, bool) { return m.MediaID() case history.FieldEpisodeID: return m.EpisodeID() + case history.FieldEpisodeNums: + return m.EpisodeNums() + case history.FieldSeasonNum: + return m.SeasonNum() case history.FieldSourceTitle: return m.SourceTitle() case history.FieldDate: @@ -3127,6 +3276,10 @@ func (m *HistoryMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldMediaID(ctx) case history.FieldEpisodeID: return m.OldEpisodeID(ctx) + case history.FieldEpisodeNums: + return m.OldEpisodeNums(ctx) + case history.FieldSeasonNum: + return m.OldSeasonNum(ctx) case history.FieldSourceTitle: return m.OldSourceTitle(ctx) case history.FieldDate: @@ -3168,6 +3321,20 @@ func (m *HistoryMutation) SetField(name string, value ent.Value) error { } m.SetEpisodeID(v) return nil + case history.FieldEpisodeNums: + v, ok := value.([]int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEpisodeNums(v) + return nil + case history.FieldSeasonNum: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSeasonNum(v) + return nil case history.FieldSourceTitle: v, ok := value.(string) if !ok { @@ -3245,6 +3412,9 @@ func (m *HistoryMutation) AddedFields() []string { if m.addepisode_id != nil { fields = append(fields, history.FieldEpisodeID) } + if m.addseason_num != nil { + fields = append(fields, history.FieldSeasonNum) + } if m.addsize != nil { fields = append(fields, history.FieldSize) } @@ -3266,6 +3436,8 @@ func (m *HistoryMutation) AddedField(name string) (ent.Value, bool) { return m.AddedMediaID() case history.FieldEpisodeID: return m.AddedEpisodeID() + case history.FieldSeasonNum: + return m.AddedSeasonNum() case history.FieldSize: return m.AddedSize() case history.FieldDownloadClientID: @@ -3295,6 +3467,13 @@ func (m *HistoryMutation) AddField(name string, value ent.Value) error { } m.AddEpisodeID(v) return nil + case history.FieldSeasonNum: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSeasonNum(v) + return nil case history.FieldSize: v, ok := value.(int) if !ok { @@ -3327,6 +3506,12 @@ func (m *HistoryMutation) ClearedFields() []string { if m.FieldCleared(history.FieldEpisodeID) { fields = append(fields, history.FieldEpisodeID) } + if m.FieldCleared(history.FieldEpisodeNums) { + fields = append(fields, history.FieldEpisodeNums) + } + if m.FieldCleared(history.FieldSeasonNum) { + fields = append(fields, history.FieldSeasonNum) + } if m.FieldCleared(history.FieldDownloadClientID) { fields = append(fields, history.FieldDownloadClientID) } @@ -3356,6 +3541,12 @@ func (m *HistoryMutation) ClearField(name string) error { case history.FieldEpisodeID: m.ClearEpisodeID() return nil + case history.FieldEpisodeNums: + m.ClearEpisodeNums() + return nil + case history.FieldSeasonNum: + m.ClearSeasonNum() + return nil case history.FieldDownloadClientID: m.ClearDownloadClientID() return nil @@ -3382,6 +3573,12 @@ func (m *HistoryMutation) ResetField(name string) error { case history.FieldEpisodeID: m.ResetEpisodeID() return nil + case history.FieldEpisodeNums: + m.ResetEpisodeNums() + return nil + case history.FieldSeasonNum: + m.ResetSeasonNum() + return nil case history.FieldSourceTitle: m.ResetSourceTitle() return nil diff --git a/ent/runtime.go b/ent/runtime.go index 7839e63..351439f 100644 --- a/ent/runtime.go +++ b/ent/runtime.go @@ -66,7 +66,7 @@ func init() { historyFields := schema.History{}.Fields() _ = historyFields // historyDescSize is the schema descriptor for size field. - historyDescSize := historyFields[5].Descriptor() + historyDescSize := historyFields[7].Descriptor() // history.DefaultSize holds the default value on creation for the size field. history.DefaultSize = historyDescSize.Default.(int) indexersFields := schema.Indexers{}.Fields() diff --git a/ent/schema/history.go b/ent/schema/history.go index d0754ca..8c14c74 100644 --- a/ent/schema/history.go +++ b/ent/schema/history.go @@ -14,7 +14,9 @@ type History struct { func (History) Fields() []ent.Field { return []ent.Field{ field.Int("media_id"), - field.Int("episode_id").Optional(), + field.Int("episode_id").Optional().Comment("deprecated"), + field.Ints("episode_nums").Optional(), + field.Int("season_num").Optional(), field.String("source_title"), field.Time("date"), field.String("target_dir"), diff --git a/pkg/metadata/tv.go b/pkg/metadata/tv.go index fe7bf97..afeca18 100644 --- a/pkg/metadata/tv.go +++ b/pkg/metadata/tv.go @@ -20,6 +20,17 @@ type Info struct { IsSeasonPack bool } +func (m *Info) ParseExtraDescription(desc string) { + if m.IsSeasonPack { //try to parse episode number with description + mm := ParseTv(desc) + if mm.StartEpisode > 0 { //sometimes they put episode info in desc text + m.IsSeasonPack = false + m.StartEpisode = mm.StartEpisode + m.EndEpisode = mm.EndEpisode + } + } +} + func (m *Info) IsAcceptable(names ...string) bool { for _, name := range names { re := regexp.MustCompile(`[^\p{L}\w\s]`) diff --git a/server/core/resources.go b/server/core/resources.go index 1cf4047..45c999b 100644 --- a/server/core/resources.go +++ b/server/core/resources.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "fmt" "polaris/ent" "polaris/ent/episode" @@ -14,7 +15,7 @@ import ( "github.com/pkg/errors" ) -func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, episodeNum int) (*string, error) { +func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum int, episodeNums ...int) (*string, error) { trc, dlc, err := c.GetDownloadClient() if err != nil { return nil, errors.Wrap(err, "connect transmission") @@ -32,19 +33,6 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, return nil, errors.New("no enough space") } - var ep *ent.Episode - if episodeNum > 0 { - for _, e := range series.Episodes { - if e.SeasonNumber == seasonNum && e.EpisodeNumber == episodeNum { - ep = e - } - } - if ep == nil { - return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, episodeNum) - } - } else { //season package download - ep = &ent.Episode{} - } magnet, err := utils.Link2Magnet(r1.Link) if err != nil { return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", r1.Link, err) @@ -58,9 +46,31 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) + if len(episodeNums) > 0 { + for _, epNum := range episodeNums { + var ep *ent.Episode + for _, e := range series.Episodes { + if e.SeasonNumber == seasonNum && e.EpisodeNumber == epNum { + ep = e + } + } + if ep == nil { + return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, epNum) + } + + if ep.Status == episode.StatusMissing { + c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) + } + + } + } else { //season package download + c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading) + + } history, err := c.db.SaveHistoryRecord(ent.History{ MediaID: seriesId, - EpisodeID: ep.ID, + EpisodeNums: episodeNums, + SeasonNum: seasonNum, SourceTitle: r1.Name, TargetDir: dir, Status: history.StatusRunning, @@ -73,72 +83,112 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, if err != nil { return nil, errors.Wrap(err, "save record") } - if episodeNum > 0 { - if ep.Status == episode.StatusMissing { - c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) - } - } else { - c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading) - } c.tasks[history.ID] = &Task{Torrent: torrent} - c.sendMsg(fmt.Sprintf(message.BeginDownload, r1.Name)) + name := r1.Name + + if len(episodeNums) > 0 { + buff := &bytes.Buffer{} + for i, ep := range episodeNums { + if i != 0 { + buff.WriteString(",") + + } + buff.WriteString(fmt.Sprint(ep)) + } + name = fmt.Sprintf("第%s集 (%s)", buff.String(), name) + } else { + name = fmt.Sprintf("全集 (%s)", name) + } + + c.sendMsg(fmt.Sprintf(message.BeginDownload, name)) log.Infof("success add %s to download task", r1.Name) return &r1.Name, nil - } -func (c *Client) SearchAndDownload(seriesId, seasonNum, episodeNum int) (*string, error) { - var episodes []int - if episodeNum > 0 { - episodes = append(episodes, episodeNum) + +/* +tmdb 校验获取的资源名,如果用资源名在tmdb搜索出来的结果能匹配上想要的资源,则认为资源有效,否则无效 +解决名称过于简单的影视会匹配过多资源的问题, 例如:梦魇绝镇 FROM +*/ +func (c *Client) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool { + m := metadata.ParseTv(r.Name) + se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1) + if err != nil { + log.Warnf("tmdb search error, consider this torrent ok: ", err) + return true + } else { + if len(se.Results) == 0 { + log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate + return true + } + series := c.db.GetMediaDetails(seriesId) + + se0 := se.Results[0] + if se0.ID != int64(series.TmdbID) { + log.Warnf("bt reosurce name not match tmdb id: %s", r.Name) + return false + } else { //resource tmdb id match + return true + } } +} + +func (c *Client) SearchAndDownload(seriesId, seasonNum int, episodeNums ...int) ([]string, error) { + res, err := SearchTvSeries(c.db, &SearchParam{ MediaId: seriesId, SeasonNum: seasonNum, - Episodes: episodes, + Episodes: episodeNums, CheckFileSize: true, CheckResolution: true, }) if err != nil { return nil, err } - /* - tmdb 校验获取的资源名,如果用资源名在tmdb搜索出来的结果能匹配上想要的资源,则认为资源有效,否则无效 - 解决名称过于简单的影视会匹配过多资源的问题, 例如:梦魇绝镇 FROM - */ - var r1 *torznab.Result - for _, r := range res { // + wanted := make(map[int]bool, len(episodeNums)) + for _, ep := range episodeNums { + wanted[ep] = true + } + var torrentNames []string +lo: + for _, r := range res { + if !c.checkBtReourceWithTmdb(&r, seriesId) { + continue + } m := metadata.ParseTv(r.Name) - se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1) - if err != nil { - log.Warnf("tmdb search error, consider this torrent ok: ", err) - r1 = &r - break - } else { - if len(se.Results) == 0 { - log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate - r1 = &r - break + m.ParseExtraDescription(r.Description) + if len(episodeNums) == 0 { //want season pack + if m.IsSeasonPack { + name, err := c.DownloadEpisodeTorrent(r, seriesId, seasonNum) + if err != nil { + return nil, err + } + torrentNames = append(torrentNames, *name) } - series := c.db.GetMediaDetails(seriesId) + } else { + torrentEpisodes := make([]int, 0) + for i := m.StartEpisode; i <= m.EndEpisode; i++ { + if !wanted[i] { //torrent has episode not wanted + continue lo + } + torrentEpisodes = append(torrentEpisodes, i) + } + name, err := c.DownloadEpisodeTorrent(r, seriesId, seasonNum, torrentEpisodes...) + if err != nil { + return nil, err + } + torrentNames = append(torrentNames, *name) - se0 := se.Results[0] - if se0.ID != int64(series.TmdbID) { - log.Warnf("bt reosurce name not match tmdb id: %s", r.Name) - continue - } else { //resource tmdb id match - r1 = &r + for _, num := range torrentEpisodes { + delete(wanted, num) //delete downloaded episode from wanted } } } - if r1 != nil { - log.Infof("found resource to download: %+v", r1) - return c.DownloadEpisodeTorrent(*r1, seriesId, seasonNum, episodeNum) - - } else { - return nil, errors.Errorf("no resource") + if len(wanted) > 0 { + log.Warnf("still wanted but not downloaded episodes: %v", wanted) } + return torrentNames, nil } func (c *Client) DownloadMovie(m *ent.Media, link, name string, size int, indexerID int) (*string, error) { diff --git a/server/core/scheduler.go b/server/core/scheduler.go index 9fa6419..945674b 100644 --- a/server/core/scheduler.go +++ b/server/core/scheduler.go @@ -115,6 +115,38 @@ func (c *Client) postTaskProcessing(id int) { } } +func getSeasonNum(h *ent.History) int { + if h.SeasonNum != 0 { + return h.SeasonNum + } + seasonNum, err := utils.SeasonId(h.TargetDir) + if err != nil { + log.Errorf("no season id: %v", h.TargetDir) + seasonNum = -1 + } + return seasonNum +} + +func (c *Client) GetEpisodeIds(r *ent.History) []int { + var episodeIds []int + seasonNum := getSeasonNum(r) + + if r.EpisodeID > 0 { + episodeIds = append(episodeIds, r.EpisodeID) + } + if len(r.EpisodeNums) > 0 { + series := c.db.GetMediaDetails(r.MediaID) + for _, epNum := range r.EpisodeNums { + for _, ep := range series.Episodes { + if ep.SeasonNumber == seasonNum && ep.EpisodeNumber == epNum { + episodeIds = append(episodeIds, ep.ID) + } + } + } + } + return episodeIds +} + func (c *Client) moveCompletedTask(id int) (err1 error) { torrent := c.tasks[id] r := c.db.GetHistory(id) @@ -123,11 +155,7 @@ func (c *Client) moveCompletedTask(id int) (err1 error) { return nil } c.db.SetHistoryStatus(r.ID, history.StatusUploading) - seasonNum, err := utils.SeasonId(r.TargetDir) - if err != nil { - log.Errorf("no season id: %v", r.TargetDir) - seasonNum = -1 - } + downloadclient, err := c.db.GetDownloadClient(r.DownloadClientID) if err != nil { log.Errorf("get task download client error: %v, use default one", err) @@ -138,13 +166,19 @@ func (c *Client) moveCompletedTask(id int) (err1 error) { return err } + seasonNum := getSeasonNum(r) + + episodeIds := c.GetEpisodeIds(r) + defer func() { if err1 != nil { c.db.SetHistoryStatus(r.ID, history.StatusFail) - if r.EpisodeID != 0 { - if !c.db.IsEpisodeDownloadingOrDownloaded(r.EpisodeID) { - c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusMissing) + if len(episodeIds) > 0 { + for _, id := range episodeIds { + if !c.db.IsEpisodeDownloadingOrDownloaded(id) { + c.db.SetEpisodeStatus(id, episode.StatusMissing) + } } } else { c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing) @@ -175,8 +209,10 @@ func (c *Client) moveCompletedTask(id int) (err1 error) { } c.db.SetHistoryStatus(r.ID, history.StatusSeeding) - if r.EpisodeID != 0 { - c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusDownloaded) + if len(episodeIds) > 0 { + for _, id := range episodeIds { + c.db.SetEpisodeStatus(id, episode.StatusDownloaded) + } } else { c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded) } @@ -286,10 +322,10 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string { } } if wantedSeasonPack { - name, err := c.SearchAndDownload(id, seasonNum, -1) + names, err := c.SearchAndDownload(id, seasonNum) if err == nil { - allNames = append(allNames, *name) - log.Infof("begin download torrent resource: %v", name) + allNames = append(allNames, names...) + log.Infof("begin download torrent resource: %v", names) } else { log.Warnf("finding season pack error: %v", err) wantedSeasonPack = false @@ -297,6 +333,7 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string { } if !wantedSeasonPack { + seasonEpisodesWanted := make(map[int][]int, 0) for _, ep := range epsides { if !ep.Monitored { continue @@ -317,14 +354,16 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string { } } - - name, err := c.SearchAndDownload(id, ep.SeasonNumber, ep.EpisodeNumber) + seasonEpisodesWanted[ep.SeasonNumber] = append(seasonEpisodesWanted[ep.SeasonNumber], ep.EpisodeNumber) + } + for se, eps := range seasonEpisodesWanted { + names, err := c.SearchAndDownload(id, se, eps...) if err != nil { - log.Warnf("finding resoruces of season %d episode %d error: %v", ep.SeasonNumber, ep.EpisodeNumber, err) + log.Warnf("finding resoruces of season %d episode %v error: %v", se, eps, err) continue } else { - allNames = append(allNames, *name) - log.Infof("begin download torrent resource: %v", name) + allNames = append(allNames, names...) + log.Infof("begin download torrent resource: %v", names) } } diff --git a/server/core/torrent.go b/server/core/torrent.go index 0c3d439..8d2db85 100644 --- a/server/core/torrent.go +++ b/server/core/torrent.go @@ -40,16 +40,8 @@ lo: for _, r := range res { //log.Infof("torrent resource: %+v", r) meta := metadata.ParseTv(r.Name) + meta.ParseExtraDescription(r.Description) - if meta.IsSeasonPack { //try to parse episode number with description - mm := metadata.ParseTv(r.Description) - if mm.StartEpisode > 0 { //sometimes they put episode info in desc text - meta.IsSeasonPack = false - meta.StartEpisode = mm.StartEpisode - meta.EndEpisode = mm.EndEpisode - } - - } if isImdbidNotMatch(series.ImdbID, r.ImdbId) { //has imdb id and not match continue } @@ -70,6 +62,9 @@ lo: } if len(param.Episodes) > 0 { //not season pack, but episode number not equal + if meta.StartEpisode <= 0 { + continue lo + } for i := meta.StartEpisode; i <= meta.EndEpisode; i++ { if !slices.Contains(param.Episodes, i) { continue lo diff --git a/server/resources.go b/server/resources.go index 49e4967..88e279a 100644 --- a/server/resources.go +++ b/server/resources.go @@ -120,7 +120,7 @@ func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) { if err != nil { return nil, errors.Wrap(err, "download") } - name = *name1 + name = name1[0] } return gin.H{