From 081338df2456b06dfab42f14f2fa158999520cb1 Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Mon, 12 Aug 2024 22:19:57 +0800 Subject: [PATCH] feat: code refactor and support season pack write .plexmatch file --- db/db.go | 8 +++ ent/episode.go | 13 ++++- ent/episode/episode.go | 8 +++ ent/episode/where.go | 80 ++++++++++++++++++++++++++ ent/episode_create.go | 18 ++++++ ent/episode_update.go | 52 +++++++++++++++++ ent/migrate/schema.go | 3 +- ent/mutation.go | 75 +++++++++++++++++++++++- ent/schema/episode.go | 9 +-- server/core/integration.go | 114 ++++++++++++++++++++++++++++++++----- server/core/scheduler.go | 25 ++++---- 11 files changed, 374 insertions(+), 31 deletions(-) diff --git a/db/db.go b/db/db.go index 575a9df..78c9234 100644 --- a/db/db.go +++ b/db/db.go @@ -571,3 +571,11 @@ func (c *Client) EditMediaMetadata(in EditMediaData) error { return c.ent.Media.Update().Where(media.ID(in.ID)).SetResolution(in.Resolution).SetTargetDir(in.TargetDir).SetLimiter(in.Limiter). Exec(context.Background()) } + +func (c *Client) UpdateEpisodeTargetFile(id int, filename string) error { + return c.ent.Episode.Update().Where(episode.ID(id)).SetTargetFile(filename).Exec(context.Background()) +} + +func (c *Client) GetSeasonEpisodes(mediaId, seasonNum int) ([]*ent.Episode, error) { + return c.ent.Episode.Query().Where(episode.MediaID(mediaId), episode.SeasonNumber(seasonNum)).All(context.Background()) +} diff --git a/ent/episode.go b/ent/episode.go index 4d1c55a..f159326 100644 --- a/ent/episode.go +++ b/ent/episode.go @@ -33,6 +33,8 @@ type Episode struct { Status episode.Status `json:"status,omitempty"` // Monitored holds the value of the "monitored" field. Monitored bool `json:"monitored"` + // TargetFile holds the value of the "target_file" field. + TargetFile string `json:"target_file,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the EpisodeQuery when eager-loading is set. Edges EpisodeEdges `json:"edges"` @@ -68,7 +70,7 @@ func (*Episode) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullBool) case episode.FieldID, episode.FieldMediaID, episode.FieldSeasonNumber, episode.FieldEpisodeNumber: values[i] = new(sql.NullInt64) - case episode.FieldTitle, episode.FieldOverview, episode.FieldAirDate, episode.FieldStatus: + case episode.FieldTitle, episode.FieldOverview, episode.FieldAirDate, episode.FieldStatus, episode.FieldTargetFile: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) @@ -139,6 +141,12 @@ func (e *Episode) assignValues(columns []string, values []any) error { } else if value.Valid { e.Monitored = value.Bool } + case episode.FieldTargetFile: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field target_file", values[i]) + } else if value.Valid { + e.TargetFile = value.String + } default: e.selectValues.Set(columns[i], values[i]) } @@ -203,6 +211,9 @@ func (e *Episode) String() string { builder.WriteString(", ") builder.WriteString("monitored=") builder.WriteString(fmt.Sprintf("%v", e.Monitored)) + builder.WriteString(", ") + builder.WriteString("target_file=") + builder.WriteString(e.TargetFile) builder.WriteByte(')') return builder.String() } diff --git a/ent/episode/episode.go b/ent/episode/episode.go index 16713ae..231c283 100644 --- a/ent/episode/episode.go +++ b/ent/episode/episode.go @@ -30,6 +30,8 @@ const ( FieldStatus = "status" // FieldMonitored holds the string denoting the monitored field in the database. FieldMonitored = "monitored" + // FieldTargetFile holds the string denoting the target_file field in the database. + FieldTargetFile = "target_file" // EdgeMedia holds the string denoting the media edge name in mutations. EdgeMedia = "media" // Table holds the table name of the episode in the database. @@ -54,6 +56,7 @@ var Columns = []string{ FieldAirDate, FieldStatus, FieldMonitored, + FieldTargetFile, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -146,6 +149,11 @@ func ByMonitored(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMonitored, opts...).ToFunc() } +// ByTargetFile orders the results by the target_file field. +func ByTargetFile(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTargetFile, opts...).ToFunc() +} + // ByMediaField orders the results by media field. func ByMediaField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/ent/episode/where.go b/ent/episode/where.go index 15b51da..99fe362 100644 --- a/ent/episode/where.go +++ b/ent/episode/where.go @@ -89,6 +89,11 @@ func Monitored(v bool) predicate.Episode { return predicate.Episode(sql.FieldEQ(FieldMonitored, v)) } +// TargetFile applies equality check predicate on the "target_file" field. It's identical to TargetFileEQ. +func TargetFile(v string) predicate.Episode { + return predicate.Episode(sql.FieldEQ(FieldTargetFile, v)) +} + // MediaIDEQ applies the EQ predicate on the "media_id" field. func MediaIDEQ(v int) predicate.Episode { return predicate.Episode(sql.FieldEQ(FieldMediaID, v)) @@ -424,6 +429,81 @@ func MonitoredNEQ(v bool) predicate.Episode { return predicate.Episode(sql.FieldNEQ(FieldMonitored, v)) } +// TargetFileEQ applies the EQ predicate on the "target_file" field. +func TargetFileEQ(v string) predicate.Episode { + return predicate.Episode(sql.FieldEQ(FieldTargetFile, v)) +} + +// TargetFileNEQ applies the NEQ predicate on the "target_file" field. +func TargetFileNEQ(v string) predicate.Episode { + return predicate.Episode(sql.FieldNEQ(FieldTargetFile, v)) +} + +// TargetFileIn applies the In predicate on the "target_file" field. +func TargetFileIn(vs ...string) predicate.Episode { + return predicate.Episode(sql.FieldIn(FieldTargetFile, vs...)) +} + +// TargetFileNotIn applies the NotIn predicate on the "target_file" field. +func TargetFileNotIn(vs ...string) predicate.Episode { + return predicate.Episode(sql.FieldNotIn(FieldTargetFile, vs...)) +} + +// TargetFileGT applies the GT predicate on the "target_file" field. +func TargetFileGT(v string) predicate.Episode { + return predicate.Episode(sql.FieldGT(FieldTargetFile, v)) +} + +// TargetFileGTE applies the GTE predicate on the "target_file" field. +func TargetFileGTE(v string) predicate.Episode { + return predicate.Episode(sql.FieldGTE(FieldTargetFile, v)) +} + +// TargetFileLT applies the LT predicate on the "target_file" field. +func TargetFileLT(v string) predicate.Episode { + return predicate.Episode(sql.FieldLT(FieldTargetFile, v)) +} + +// TargetFileLTE applies the LTE predicate on the "target_file" field. +func TargetFileLTE(v string) predicate.Episode { + return predicate.Episode(sql.FieldLTE(FieldTargetFile, v)) +} + +// TargetFileContains applies the Contains predicate on the "target_file" field. +func TargetFileContains(v string) predicate.Episode { + return predicate.Episode(sql.FieldContains(FieldTargetFile, v)) +} + +// TargetFileHasPrefix applies the HasPrefix predicate on the "target_file" field. +func TargetFileHasPrefix(v string) predicate.Episode { + return predicate.Episode(sql.FieldHasPrefix(FieldTargetFile, v)) +} + +// TargetFileHasSuffix applies the HasSuffix predicate on the "target_file" field. +func TargetFileHasSuffix(v string) predicate.Episode { + return predicate.Episode(sql.FieldHasSuffix(FieldTargetFile, v)) +} + +// TargetFileIsNil applies the IsNil predicate on the "target_file" field. +func TargetFileIsNil() predicate.Episode { + return predicate.Episode(sql.FieldIsNull(FieldTargetFile)) +} + +// TargetFileNotNil applies the NotNil predicate on the "target_file" field. +func TargetFileNotNil() predicate.Episode { + return predicate.Episode(sql.FieldNotNull(FieldTargetFile)) +} + +// TargetFileEqualFold applies the EqualFold predicate on the "target_file" field. +func TargetFileEqualFold(v string) predicate.Episode { + return predicate.Episode(sql.FieldEqualFold(FieldTargetFile, v)) +} + +// TargetFileContainsFold applies the ContainsFold predicate on the "target_file" field. +func TargetFileContainsFold(v string) predicate.Episode { + return predicate.Episode(sql.FieldContainsFold(FieldTargetFile, v)) +} + // HasMedia applies the HasEdge predicate on the "media" edge. func HasMedia() predicate.Episode { return predicate.Episode(func(s *sql.Selector) { diff --git a/ent/episode_create.go b/ent/episode_create.go index 9458c97..648f18d 100644 --- a/ent/episode_create.go +++ b/ent/episode_create.go @@ -92,6 +92,20 @@ func (ec *EpisodeCreate) SetNillableMonitored(b *bool) *EpisodeCreate { return ec } +// SetTargetFile sets the "target_file" field. +func (ec *EpisodeCreate) SetTargetFile(s string) *EpisodeCreate { + ec.mutation.SetTargetFile(s) + return ec +} + +// SetNillableTargetFile sets the "target_file" field if the given value is not nil. +func (ec *EpisodeCreate) SetNillableTargetFile(s *string) *EpisodeCreate { + if s != nil { + ec.SetTargetFile(*s) + } + return ec +} + // SetMedia sets the "media" edge to the Media entity. func (ec *EpisodeCreate) SetMedia(m *Media) *EpisodeCreate { return ec.SetMediaID(m.ID) @@ -224,6 +238,10 @@ func (ec *EpisodeCreate) createSpec() (*Episode, *sqlgraph.CreateSpec) { _spec.SetField(episode.FieldMonitored, field.TypeBool, value) _node.Monitored = value } + if value, ok := ec.mutation.TargetFile(); ok { + _spec.SetField(episode.FieldTargetFile, field.TypeString, value) + _node.TargetFile = value + } if nodes := ec.mutation.MediaIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/ent/episode_update.go b/ent/episode_update.go index 5f54f8e..bc26f08 100644 --- a/ent/episode_update.go +++ b/ent/episode_update.go @@ -160,6 +160,26 @@ func (eu *EpisodeUpdate) SetNillableMonitored(b *bool) *EpisodeUpdate { return eu } +// SetTargetFile sets the "target_file" field. +func (eu *EpisodeUpdate) SetTargetFile(s string) *EpisodeUpdate { + eu.mutation.SetTargetFile(s) + return eu +} + +// SetNillableTargetFile sets the "target_file" field if the given value is not nil. +func (eu *EpisodeUpdate) SetNillableTargetFile(s *string) *EpisodeUpdate { + if s != nil { + eu.SetTargetFile(*s) + } + return eu +} + +// ClearTargetFile clears the value of the "target_file" field. +func (eu *EpisodeUpdate) ClearTargetFile() *EpisodeUpdate { + eu.mutation.ClearTargetFile() + return eu +} + // SetMedia sets the "media" edge to the Media entity. func (eu *EpisodeUpdate) SetMedia(m *Media) *EpisodeUpdate { return eu.SetMediaID(m.ID) @@ -252,6 +272,12 @@ func (eu *EpisodeUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := eu.mutation.Monitored(); ok { _spec.SetField(episode.FieldMonitored, field.TypeBool, value) } + if value, ok := eu.mutation.TargetFile(); ok { + _spec.SetField(episode.FieldTargetFile, field.TypeString, value) + } + if eu.mutation.TargetFileCleared() { + _spec.ClearField(episode.FieldTargetFile, field.TypeString) + } if eu.mutation.MediaCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -433,6 +459,26 @@ func (euo *EpisodeUpdateOne) SetNillableMonitored(b *bool) *EpisodeUpdateOne { return euo } +// SetTargetFile sets the "target_file" field. +func (euo *EpisodeUpdateOne) SetTargetFile(s string) *EpisodeUpdateOne { + euo.mutation.SetTargetFile(s) + return euo +} + +// SetNillableTargetFile sets the "target_file" field if the given value is not nil. +func (euo *EpisodeUpdateOne) SetNillableTargetFile(s *string) *EpisodeUpdateOne { + if s != nil { + euo.SetTargetFile(*s) + } + return euo +} + +// ClearTargetFile clears the value of the "target_file" field. +func (euo *EpisodeUpdateOne) ClearTargetFile() *EpisodeUpdateOne { + euo.mutation.ClearTargetFile() + return euo +} + // SetMedia sets the "media" edge to the Media entity. func (euo *EpisodeUpdateOne) SetMedia(m *Media) *EpisodeUpdateOne { return euo.SetMediaID(m.ID) @@ -555,6 +601,12 @@ func (euo *EpisodeUpdateOne) sqlSave(ctx context.Context) (_node *Episode, err e if value, ok := euo.mutation.Monitored(); ok { _spec.SetField(episode.FieldMonitored, field.TypeBool, value) } + if value, ok := euo.mutation.TargetFile(); ok { + _spec.SetField(episode.FieldTargetFile, field.TypeString, value) + } + if euo.mutation.TargetFileCleared() { + _spec.ClearField(episode.FieldTargetFile, field.TypeString) + } if euo.mutation.MediaCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index 373f0cf..aa85afd 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -39,6 +39,7 @@ var ( {Name: "air_date", Type: field.TypeString}, {Name: "status", Type: field.TypeEnum, Enums: []string{"missing", "downloading", "downloaded"}, Default: "missing"}, {Name: "monitored", Type: field.TypeBool, Default: false}, + {Name: "target_file", Type: field.TypeString, Nullable: true}, {Name: "media_id", Type: field.TypeInt, Nullable: true}, } // EpisodesTable holds the schema information for the "episodes" table. @@ -49,7 +50,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "episodes_media_episodes", - Columns: []*schema.Column{EpisodesColumns[8]}, + Columns: []*schema.Column{EpisodesColumns[9]}, RefColumns: []*schema.Column{MediaColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/ent/mutation.go b/ent/mutation.go index 8825486..d51c9d5 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -923,6 +923,7 @@ type EpisodeMutation struct { air_date *string status *episode.Status monitored *bool + target_file *string clearedFields map[string]struct{} media *int clearedmedia bool @@ -1370,6 +1371,55 @@ func (m *EpisodeMutation) ResetMonitored() { m.monitored = nil } +// SetTargetFile sets the "target_file" field. +func (m *EpisodeMutation) SetTargetFile(s string) { + m.target_file = &s +} + +// TargetFile returns the value of the "target_file" field in the mutation. +func (m *EpisodeMutation) TargetFile() (r string, exists bool) { + v := m.target_file + if v == nil { + return + } + return *v, true +} + +// OldTargetFile returns the old "target_file" field's value of the Episode entity. +// If the Episode 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 *EpisodeMutation) OldTargetFile(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTargetFile is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTargetFile requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTargetFile: %w", err) + } + return oldValue.TargetFile, nil +} + +// ClearTargetFile clears the value of the "target_file" field. +func (m *EpisodeMutation) ClearTargetFile() { + m.target_file = nil + m.clearedFields[episode.FieldTargetFile] = struct{}{} +} + +// TargetFileCleared returns if the "target_file" field was cleared in this mutation. +func (m *EpisodeMutation) TargetFileCleared() bool { + _, ok := m.clearedFields[episode.FieldTargetFile] + return ok +} + +// ResetTargetFile resets all changes to the "target_file" field. +func (m *EpisodeMutation) ResetTargetFile() { + m.target_file = nil + delete(m.clearedFields, episode.FieldTargetFile) +} + // ClearMedia clears the "media" edge to the Media entity. func (m *EpisodeMutation) ClearMedia() { m.clearedmedia = true @@ -1431,7 +1481,7 @@ func (m *EpisodeMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *EpisodeMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 9) if m.media != nil { fields = append(fields, episode.FieldMediaID) } @@ -1456,6 +1506,9 @@ func (m *EpisodeMutation) Fields() []string { if m.monitored != nil { fields = append(fields, episode.FieldMonitored) } + if m.target_file != nil { + fields = append(fields, episode.FieldTargetFile) + } return fields } @@ -1480,6 +1533,8 @@ func (m *EpisodeMutation) Field(name string) (ent.Value, bool) { return m.Status() case episode.FieldMonitored: return m.Monitored() + case episode.FieldTargetFile: + return m.TargetFile() } return nil, false } @@ -1505,6 +1560,8 @@ func (m *EpisodeMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldStatus(ctx) case episode.FieldMonitored: return m.OldMonitored(ctx) + case episode.FieldTargetFile: + return m.OldTargetFile(ctx) } return nil, fmt.Errorf("unknown Episode field %s", name) } @@ -1570,6 +1627,13 @@ func (m *EpisodeMutation) SetField(name string, value ent.Value) error { } m.SetMonitored(v) return nil + case episode.FieldTargetFile: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTargetFile(v) + return nil } return fmt.Errorf("unknown Episode field %s", name) } @@ -1630,6 +1694,9 @@ func (m *EpisodeMutation) ClearedFields() []string { if m.FieldCleared(episode.FieldMediaID) { fields = append(fields, episode.FieldMediaID) } + if m.FieldCleared(episode.FieldTargetFile) { + fields = append(fields, episode.FieldTargetFile) + } return fields } @@ -1647,6 +1714,9 @@ func (m *EpisodeMutation) ClearField(name string) error { case episode.FieldMediaID: m.ClearMediaID() return nil + case episode.FieldTargetFile: + m.ClearTargetFile() + return nil } return fmt.Errorf("unknown Episode nullable field %s", name) } @@ -1679,6 +1749,9 @@ func (m *EpisodeMutation) ResetField(name string) error { case episode.FieldMonitored: m.ResetMonitored() return nil + case episode.FieldTargetFile: + m.ResetTargetFile() + return nil } return fmt.Errorf("unknown Episode field %s", name) } diff --git a/ent/schema/episode.go b/ent/schema/episode.go index 8cddf78..7bd1789 100644 --- a/ent/schema/episode.go +++ b/ent/schema/episode.go @@ -22,16 +22,17 @@ func (Episode) Fields() []ent.Field { field.String("air_date"), field.Enum("status").Values("missing", "downloading", "downloaded").Default("missing"), field.Bool("monitored").Default(false).StructTag("json:\"monitored\""), //whether this episode is monitored + field.String("target_file").Optional(), } } // Edges of the Episode. func (Episode) Edges() []ent.Edge { return []ent.Edge{ - edge.From("media", Media.Type). - Ref("episodes"). - Unique(). + edge.From("media", Media.Type). + Ref("episodes"). + Unique(). Field("media_id"), - } + } } diff --git a/server/core/integration.go b/server/core/integration.go index 64540db..528878a 100644 --- a/server/core/integration.go +++ b/server/core/integration.go @@ -3,24 +3,30 @@ package core import ( "bytes" "fmt" + "github.com/pkg/errors" + "os" "path/filepath" "polaris/db" "polaris/ent/media" storage1 "polaris/ent/storage" "polaris/log" + "polaris/pkg/metadata" "polaris/pkg/notifier" "polaris/pkg/storage" + "polaris/pkg/utils" + "slices" "strings" - - "github.com/pkg/errors" ) -func (c *Client) writePlexmatch(seriesId int, episodeId int, targetDir, name string) error { +func (c *Client) writePlexmatch(historyId int) error { if !c.plexmatchEnabled() { return nil } - series, err := c.db.GetMedia(seriesId) + + his := c.db.GetHistory(historyId) + + series, err := c.db.GetMedia(his.MediaID) if err != nil { return err } @@ -47,24 +53,44 @@ func (c *Client) writePlexmatch(seriesId int, episodeId int, targetDir, name str } } - //season plexmatch file - ep, err := c.db.GetEpisodeByID(episodeId) - if err != nil { - return errors.Wrap(err, "query episode") - } buff := bytes.Buffer{} - seasonPlex := filepath.Join(targetDir, ".plexmatch") + seasonPlex := filepath.Join(his.TargetDir, ".plexmatch") data, err := st.ReadFile(seasonPlex) if err != nil { log.Infof("read season plexmatch: %v", err) } else { buff.Write(data) } - if strings.Contains(buff.String(), name) { - log.Debugf("already write plex episode line: %v", name) - return nil + + if his.EpisodeID > 0 { + //single episode download + ep, err := c.db.GetEpisodeByID(his.EpisodeID) + if err != nil { + return errors.Wrap(err, "query episode") + } + if strings.Contains(buff.String(), ep.TargetFile) { + log.Debugf("already write plex episode line: %v", ep.TargetFile) + return nil + } + buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, ep.TargetFile)) + } else { + seasonNum, err := utils.SeasonId(his.TargetDir) + if err != nil { + return errors.Wrap(err, "no season id") + } + allEpisodes, err := c.db.GetSeasonEpisodes(his.MediaID, seasonNum) + if err != nil { + return errors.Wrap(err, "query season episode") + } + for _, ep := range allEpisodes { + if strings.Contains(buff.String(), ep.TargetFile) { + log.Debugf("already write plex episode line: %v", ep.TargetFile) + continue + } + buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, ep.TargetFile)) + } + } - buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, name)) log.Infof("write season plexmatch file content: %s", buff.String()) return st.WriteFile(seasonPlex, buff.Bytes()) } @@ -128,3 +154,63 @@ func (c *Client) sendMsg(msg string) { log.Debugf("send message to %s success, msg is %s", cl.Name, msg) } } + +func (c *Client) findEpisodeFilesPreMoving(historyId int) error { + his := c.db.GetHistory(historyId) + + isSingleEpisode := his.EpisodeID > 0 + downloadDir := c.db.GetDownloadDir() + task := c.tasks[historyId] + target := filepath.Join(downloadDir, task.Name()) + fi, err := os.Stat(target) + if err != nil { + return errors.Wrapf(err, "read dir %v", target) + } + if isSingleEpisode { + if fi.IsDir() { + //download single episode in dir + //TODO + } else { + //is file + if err := c.db.UpdateEpisodeTargetFile(his.EpisodeID, fi.Name()); err != nil { + log.Errorf("writing downloaded file name to db error: %v", err) + } + } + } else { + if fi.IsDir() { + return fmt.Errorf("not season pack downloaded") + } + seasonNum, err := utils.SeasonId(his.TargetDir) + if err != nil { + return errors.Wrap(err, "no season id") + } + + files, err := os.ReadDir(target) + if err != nil { + return err + } + for _, f := range files { + if f.IsDir() { //want media file + continue + } + excludedExt := []string{".txt", ".srt", ".ass", ".sub"} + ext := filepath.Ext(f.Name()) + if slices.Contains(excludedExt, strings.ToLower(ext)) { + continue + } + + meta := metadata.ParseTv(f.Name()) + if meta.Episode > 0 { + //episode exists + ep, err := c.db.GetEpisode(his.MediaID, seasonNum, meta.Episode) + if err != nil { + return err + } + if err := c.db.UpdateEpisodeTargetFile(ep.ID, f.Name()); err != nil { + return errors.Wrap(err, "update episode file") + } + } + } + } + return nil +} diff --git a/server/core/scheduler.go b/server/core/scheduler.go index c63e09a..25be4a9 100644 --- a/server/core/scheduler.go +++ b/server/core/scheduler.go @@ -61,15 +61,25 @@ func (c *Client) checkTasks() { } log.Infof("task is done: %v", t.Name()) c.sendMsg(fmt.Sprintf(message.DownloadComplete, t.Name())) - go func() { - if err := c.moveCompletedTask(id); err != nil { - log.Infof("post tasks for id %v fail: %v", id, err) - } - }() + + go c.postTaskProcessing(id) } } } +func (c *Client) postTaskProcessing(id int) { + if err := c.findEpisodeFilesPreMoving(id); err != nil { + log.Errorf("finding all episode file error: %v", err) + } else { + if err := c.writePlexmatch(id); err != nil { + log.Errorf("write plexmatch file error: %v", err) + } + } + if err := c.moveCompletedTask(id); err != nil { + log.Infof("post tasks for id %v fail: %v", id, err) + } +} + func (c *Client) moveCompletedTask(id int) (err1 error) { torrent := c.tasks[id] r := c.db.GetHistory(id) @@ -119,11 +129,6 @@ func (c *Client) moveCompletedTask(id int) (err1 error) { return err } - // .plexmatch file - if err := c.writePlexmatch(r.MediaID, r.EpisodeID, r.TargetDir, torrentName); err != nil { - log.Errorf("create .plexmatch file error: %v", err) - } - //如果种子是路径,则会把路径展开,只移动文件,类似 move dir/* dir2/, 如果种子是文件,则会直接移动文件,类似 move file dir/ if err := stImpl.Copy(filepath.Join(c.db.GetDownloadDir(), torrentName), r.TargetDir); err != nil { return errors.Wrap(err, "move file")