From c787d71fbd3e52b9a8205656fffe2c831a3bd171 Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Fri, 2 Aug 2024 10:08:26 +0800 Subject: [PATCH] code refactor --- ent/schema/episode.go | 1 + server/activity.go | 18 ++-- server/core/client.go | 94 +++++++++++++++++++ server/core/integration.go | 121 ++++++++++++++++++++++++ server/core/resources.go | 165 +++++++++++++++++++++++++++++++++ server/{ => core}/scheduler.go | 141 ++++++++++++++-------------- server/integration.go | 62 ------------- server/notify_client.go | 31 ------- server/resources.go | 160 +------------------------------- server/server.go | 36 ++----- server/storage.go | 30 +----- server/watchlist.go | 2 +- 12 files changed, 475 insertions(+), 386 deletions(-) create mode 100644 server/core/client.go create mode 100644 server/core/integration.go create mode 100644 server/core/resources.go rename server/{ => core}/scheduler.go (66%) delete mode 100644 server/integration.go diff --git a/ent/schema/episode.go b/ent/schema/episode.go index 9f530b9..fcca4b2 100644 --- a/ent/schema/episode.go +++ b/ent/schema/episode.go @@ -21,6 +21,7 @@ func (Episode) Fields() []ent.Field { field.String("overview"), field.String("air_date"), field.Enum("status").Values("missing", "downloading", "downloaded").Default("missing"), + field.Bool("monitored").Default(true).StructTag("json:\"monitored\""), //whether this episode is monitored } } diff --git a/server/activity.go b/server/activity.go index 2e61a95..2c3134e 100644 --- a/server/activity.go +++ b/server/activity.go @@ -32,7 +32,7 @@ func (s *Server) GetAllActivities(c *gin.Context) (interface{}, error) { a := Activity{ History: h, } - for id, task := range s.tasks { + for id, task := range s.core.GetTasks() { if h.ID == id && task.Exists() { a.Progress = task.Progress() } @@ -54,13 +54,11 @@ func (s *Server) RemoveActivity(c *gin.Context) (interface{}, error) { log.Errorf("no record of id: %d", id) return nil, nil } - torrent := s.tasks[his.ID] - if torrent != nil { - if err := torrent.Remove(); err != nil { - return nil, errors.Wrap(err, "remove torrent") - } - delete(s.tasks, his.ID) + + if err := s.core.RemoveTaskAndTorrent(his.ID); err != nil { + return nil, errors.Wrap(err, "remove torrent") } + if his.EpisodeID != 0 { s.db.SetEpisodeStatus(his.EpisodeID, episode.StatusMissing) @@ -96,7 +94,7 @@ func (s *Server) GetMediaDownloadHistory(c *gin.Context) (interface{}, error) { type TorrentInfo struct { Name string `json:"name"` - ID int64 `json:"id"` + ID int64 `json:"id"` SeedRatio float32 `json:"seed_ratio"` Progress int `json:"progress"` } @@ -116,8 +114,8 @@ func (s *Server) GetAllTorrents(c *gin.Context) (interface{}, error) { continue } infos = append(infos, TorrentInfo{ - Name: t.Name(), - ID: t.ID, + Name: t.Name(), + ID: t.ID, Progress: t.Progress(), }) } diff --git a/server/core/client.go b/server/core/client.go new file mode 100644 index 0000000..b4b2f8e --- /dev/null +++ b/server/core/client.go @@ -0,0 +1,94 @@ +package core + +import ( + "polaris/db" + "polaris/ent" + "polaris/log" + "polaris/pkg/tmdb" + "polaris/pkg/transmission" + + "github.com/pkg/errors" + "github.com/robfig/cron" +) + +func NewClient(db *db.Client, language string) *Client { + return &Client{ + db: db, + cron: cron.New(), + tasks: make(map[int]*Task, 0), + language: language, + } +} + +type Client struct { + db *db.Client + cron *cron.Cron + tasks map[int]*Task + language string +} + +func (c *Client) Init() { + c.reloadTasks() + c.addSysCron() +} + +func (c *Client) reloadTasks() { + allTasks := c.db.GetHistories() + for _, t := range allTasks { + torrent, err := transmission.ReloadTorrent(t.Saved) + if err != nil { + log.Errorf("relaod task %s failed: %v", t.SourceTitle, err) + continue + } + if !torrent.Exists() { //只要种子还存在于客户端中,就重新加载,有可能是还在做种中 + continue + } + log.Infof("reloading task: %d %s", t.ID, t.SourceTitle) + c.tasks[t.ID] = &Task{Torrent: torrent} + } +} + +func (c *Client) getDownloadClient() (*transmission.Client, *ent.DownloadClients, error) { + tr := c.db.GetTransmission() + trc, err := transmission.NewClient(transmission.Config{ + URL: tr.URL, + User: tr.User, + Password: tr.Password, + }) + if err != nil { + return nil, nil, errors.Wrap(err, "connect transmission") + } + return trc, tr, nil +} + +func (c *Client) TMDB() (*tmdb.Client, error) { + api := c.db.GetSetting(db.SettingTmdbApiKey) + if api == "" { + return nil, errors.New("TMDB apiKey not set") + } + return tmdb.NewClient(api) +} + +func (c *Client) MustTMDB() *tmdb.Client { + t, err := c.TMDB() + if err != nil { + log.Panicf("get tmdb: %v", err) + } + return t +} + + +func (c *Client) RemoveTaskAndTorrent(id int)error { + torrent := c.tasks[id] + if torrent != nil { + if err := torrent.Remove(); err != nil { + return errors.Wrap(err, "remove torrent") + } + delete(c.tasks, id) + } + return nil +} + +func (c *Client) GetTasks() map[int]*Task { + return c.tasks +} \ No newline at end of file diff --git a/server/core/integration.go b/server/core/integration.go new file mode 100644 index 0000000..f4b7e60 --- /dev/null +++ b/server/core/integration.go @@ -0,0 +1,121 @@ +package core + +import ( + "bytes" + "fmt" + "path/filepath" + "polaris/db" + "polaris/ent/media" + storage1 "polaris/ent/storage" + "polaris/log" + "polaris/pkg/notifier" + "polaris/pkg/storage" + + "github.com/pkg/errors" +) + +func (c *Client) writePlexmatch(seriesId int, episodeId int, targetDir, name string) error { + + if !c.plexmatchEnabled() { + return nil + } + series, err := c.db.GetMedia(seriesId) + if err != nil { + return err + } + if series.MediaType != media.MediaTypeTv { + return nil + } + st, err := c.getStorage(series.StorageID, media.MediaTypeTv) + if err != nil { + return errors.Wrap(err, "get storage") + } + + //series plexmatch file + _, err = st.ReadFile(filepath.Join(series.TargetDir, ".plexmatch")) + if err != nil { + //create new + log.Warnf(".plexmatch file not found, create new one: %s", series.NameEn) + if err := st.WriteFile(filepath.Join(series.TargetDir, ".plexmatch"), + []byte(fmt.Sprintf("tmdbid: %d\n", series.TmdbID))); err != nil { + return errors.Wrap(err, "series plexmatch") + } + } + + //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") + data, err := st.ReadFile(seasonPlex) + if err != nil { + log.Infof("read season plexmatch: %v", err) + } else { + buff.Write(data) + } + 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()) +} + +func (c *Client) plexmatchEnabled() bool { + return c.db.GetSetting(db.SettingPlexMatchEnabled) == "true" +} + +func (c *Client) getStorage(storageId int, mediaType media.MediaType) (storage.Storage, error) { + st := c.db.GetStorage(storageId) + targetPath := st.TvPath + if mediaType == media.MediaTypeMovie { + targetPath = st.MoviePath + } + + switch st.Implementation { + case storage1.ImplementationLocal: + + storageImpl1, err := storage.NewLocalStorage(targetPath) + if err != nil { + return nil, errors.Wrap(err, "new local") + } + return storageImpl1, nil + + case storage1.ImplementationWebdav: + ws := st.ToWebDavSetting() + storageImpl1, err := storage.NewWebdavStorage(ws.URL, ws.User, ws.Password, targetPath, ws.ChangeFileHash == "true") + if err != nil { + return nil, errors.Wrap(err, "new webdav") + } + return storageImpl1, nil + } + return nil, errors.New("no storage found") +} + +func (c *Client) sendMsg(msg string) { + clients, err := c.db.GetAllNotificationClients2() + if err != nil { + log.Errorf("query notification clients: %v", err) + return + } + for _, cl := range clients { + if !cl.Enabled { + continue + } + handler, ok := notifier.Gethandler(cl.Service) + if !ok { + log.Errorf("no notification implementation of service %s", cl.Service) + continue + } + noCl, err := handler(cl.Settings) + if err != nil { + log.Errorf("handle setting for name %s error: %v", cl.Name, err) + continue + } + err = noCl.SendMsg(msg) + if err != nil { + log.Errorf("send message error: %v", err) + continue + } + log.Debugf("send message to %s success, msg is %s", cl.Name, msg) + } +} diff --git a/server/core/resources.go b/server/core/resources.go new file mode 100644 index 0000000..53ca176 --- /dev/null +++ b/server/core/resources.go @@ -0,0 +1,165 @@ +package core + +import ( + "fmt" + "polaris/ent" + "polaris/ent/episode" + "polaris/ent/history" + "polaris/log" + "polaris/pkg/notifier/message" + "polaris/pkg/torznab" + "polaris/pkg/utils" + + "github.com/pkg/errors" +) + +func (c *Client) DownloadSeasonPackage(r1 torznab.Result, seriesId, seasonNum int) (*string, error) { + trc, dlClient, err := c.getDownloadClient() + if err != nil { + return nil, errors.Wrap(err, "connect transmission") + } + downloadDir := c.db.GetDownloadDir() + size := utils.AvailableSpace(downloadDir) + if size < uint64(r1.Size) { + log.Errorf("space available %v, space needed %v", size, r1.Size) + return nil, errors.New("no enough space") + } + + torrent, err := trc.Download(r1.Link, c.db.GetDownloadDir()) + if err != nil { + return nil, errors.Wrap(err, "downloading") + } + torrent.Start() + + series := c.db.GetMediaDetails(seriesId) + if series == nil { + return nil, fmt.Errorf("no tv series of id %v", seriesId) + } + dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) + + history, err := c.db.SaveHistoryRecord(ent.History{ + MediaID: seriesId, + EpisodeID: 0, + SourceTitle: r1.Name, + TargetDir: dir, + Status: history.StatusRunning, + Size: r1.Size, + Saved: torrent.Save(), + DownloadClientID: dlClient.ID, + IndexerID: r1.IndexerId, + }) + if err != nil { + return nil, errors.Wrap(err, "save record") + } + c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading) + + c.tasks[history.ID] = &Task{Torrent: torrent} + + c.sendMsg(fmt.Sprintf(message.BeginDownload, r1.Name)) + return &r1.Name, nil + +} + + +func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, episodeNum int) (*string, error) { + trc, dlc, err := c.getDownloadClient() + if err != nil { + return nil, errors.Wrap(err, "connect transmission") + } + series := c.db.GetMediaDetails(seriesId) + if series == nil { + return nil, fmt.Errorf("no tv series of id %v", seriesId) + } + var ep *ent.Episode + 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) + } + torrent, err := trc.Download(r1.Link, c.db.GetDownloadDir()) + if err != nil { + return nil, errors.Wrap(err, "downloading") + } + torrent.Start() + + dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) + + history, err := c.db.SaveHistoryRecord(ent.History{ + MediaID: ep.MediaID, + EpisodeID: ep.ID, + SourceTitle: r1.Name, + TargetDir: dir, + Status: history.StatusRunning, + Size: r1.Size, + Saved: torrent.Save(), + DownloadClientID: dlc.ID, + IndexerID: r1.IndexerId, + }) + if err != nil { + return nil, errors.Wrap(err, "save record") + } + c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) + + c.tasks[history.ID] = &Task{Torrent: torrent} + c.sendMsg(fmt.Sprintf(message.BeginDownload, r1.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) { + + res, err := SearchEpisode(c.db, seriesId, seasonNum, episodeNum, true) + if err != nil { + return nil, err + } + r1 := res[0] + log.Infof("found resource to download: %+v", r1) + return c.DownloadEpisodeTorrent(r1, seriesId, seasonNum, episodeNum) +} + +func (c *Client) DownloadMovie(m *ent.Media,link, name string, size int, indexerID int) (*string, error) { + trc, dlc, err := c.getDownloadClient() + if err != nil { + return nil, errors.Wrap(err, "connect transmission") + } + + torrent, err := trc.Download(link, c.db.GetDownloadDir()) + if err != nil { + return nil, errors.Wrap(err, "downloading") + } + torrent.Start() + + if name == "" { + name = m.OriginalName + } + go func() { + ep, _ := c.db.GetMovieDummyEpisode(m.ID) + history, err := c.db.SaveHistoryRecord(ent.History{ + MediaID: m.ID, + EpisodeID: ep.ID, + SourceTitle: name, + TargetDir: m.TargetDir, + Status: history.StatusRunning, + Size: size, + Saved: torrent.Save(), + DownloadClientID: dlc.ID, + IndexerID: indexerID, + }) + if err != nil { + log.Errorf("save history error: %v", err) + } + + c.tasks[history.ID] = &Task{Torrent: torrent} + + c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) + }() + + c.sendMsg(fmt.Sprintf(message.BeginDownload, name)) + log.Infof("success add %s to download task", name) + return &name, nil + +} \ No newline at end of file diff --git a/server/scheduler.go b/server/core/scheduler.go similarity index 66% rename from server/scheduler.go rename to server/core/scheduler.go index 38e900f..e81d815 100644 --- a/server/scheduler.go +++ b/server/core/scheduler.go @@ -1,4 +1,4 @@ -package server +package core import ( "fmt" @@ -11,44 +11,43 @@ import ( "polaris/pkg" "polaris/pkg/notifier/message" "polaris/pkg/utils" - "polaris/server/core" "time" "github.com/pkg/errors" ) -func (s *Server) scheduler() { - s.mustAddCron("@every 1m", s.checkTasks) - s.mustAddCron("0 0 * * * *", func() { - s.downloadTvSeries() - s.downloadMovie() +func (c *Client) addSysCron() { + c.mustAddCron("@every 1m", c.checkTasks) + c.mustAddCron("0 0 * * * *", func() { + c.downloadTvSeries() + c.downloadMovie() }) - s.mustAddCron("0 0 */12 * * *", s.checkAllSeriesNewSeason) - s.cron.Start() + c.mustAddCron("0 0 */12 * * *", c.checkAllSeriesNewSeason) + c.cron.Start() } -func (s *Server) mustAddCron(spec string, cmd func()) { - if err := s.cron.AddFunc(spec, cmd); err != nil { +func (c *Client) mustAddCron(spec string, cmd func()) { + if err := c.cron.AddFunc(spec, cmd); err != nil { log.Errorf("add func error: %v", err) panic(err) } } -func (s *Server) checkTasks() { +func (c *Client) checkTasks() { log.Debug("begin check tasks...") - for id, t := range s.tasks { + for id, t := range c.tasks { if !t.Exists() { log.Infof("task no longer exists: %v", id) - delete(s.tasks, id) + delete(c.tasks, id) continue } log.Infof("task (%s) percentage done: %d%%", t.Name(), t.Progress()) if t.Progress() == 100 { - r := s.db.GetHistory(id) + r := c.db.GetHistory(id) if r.Status == history.StatusSuccess { //task already success, check seed ratio - torrent := s.tasks[id] - ok, err := s.isSeedRatioLimitReached(r.IndexerID, torrent) + torrent := c.tasks[id] + ok, err := c.isSeedRatioLimitReached(r.IndexerID, torrent) if err != nil { log.Warnf("getting torrent seed ratio : %v", err) ok = false @@ -56,16 +55,16 @@ func (s *Server) checkTasks() { if ok { log.Infof("torrent file seed ratio reached, remove: %v", torrent.Name()) torrent.Remove() - delete(s.tasks, id) + delete(c.tasks, id) } else { log.Infof("torrent file still sedding: %v", torrent.Name()) } continue } log.Infof("task is done: %v", t.Name()) - s.sendMsg(fmt.Sprintf(message.DownloadComplete, t.Name())) + c.sendMsg(fmt.Sprintf(message.DownloadComplete, t.Name())) go func() { - if err := s.moveCompletedTask(id); err != nil { + if err := c.moveCompletedTask(id); err != nil { log.Infof("post tasks for id %v fail: %v", id, err) } }() @@ -73,20 +72,20 @@ func (s *Server) checkTasks() { } } -func (s *Server) moveCompletedTask(id int) (err1 error) { - torrent := s.tasks[id] - r := s.db.GetHistory(id) +func (c *Client) moveCompletedTask(id int) (err1 error) { + torrent := c.tasks[id] + r := c.db.GetHistory(id) if r.Status == history.StatusUploading { log.Infof("task %d is already uploading, skip", id) return nil } - s.db.SetHistoryStatus(r.ID, history.StatusUploading) + 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 := s.db.GetDownloadClient(r.DownloadClientID) + downloadclient, err := c.db.GetDownloadClient(r.DownloadClientID) if err != nil { log.Errorf("get task download client error: %v, use default one", err) downloadclient = &ent.DownloadClients{RemoveCompletedDownloads: true, RemoveFailedDownloads: true} @@ -96,59 +95,59 @@ func (s *Server) moveCompletedTask(id int) (err1 error) { defer func() { if err1 != nil { - s.db.SetHistoryStatus(r.ID, history.StatusFail) + c.db.SetHistoryStatus(r.ID, history.StatusFail) if r.EpisodeID != 0 { - s.db.SetEpisodeStatus(r.EpisodeID, episode.StatusMissing) + c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusMissing) } else { - s.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing) + c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing) } - s.sendMsg(fmt.Sprintf(message.ProcessingFailed, err)) + c.sendMsg(fmt.Sprintf(message.ProcessingFailed, err)) if downloadclient.RemoveFailedDownloads { log.Debugf("task failed, remove failed torrent and files related") - delete(s.tasks, r.ID) + delete(c.tasks, r.ID) torrent.Remove() } } }() - series := s.db.GetMediaDetails(r.MediaID) + series := c.db.GetMediaDetails(r.MediaID) if series == nil { return nil } - st := s.db.GetStorage(series.StorageID) + st := c.db.GetStorage(series.StorageID) log.Infof("move task files to target dir: %v", r.TargetDir) - stImpl, err := s.getStorage(st.ID, series.MediaType) + stImpl, err := c.getStorage(st.ID, series.MediaType) if err != nil { return err } //如果种子是路径,则会把路径展开,只移动文件,类似 move dir/* dir2/, 如果种子是文件,则会直接移动文件,类似 move file dir/ - if err := stImpl.Copy(filepath.Join(s.db.GetDownloadDir(), torrentName), r.TargetDir); err != nil { + if err := stImpl.Copy(filepath.Join(c.db.GetDownloadDir(), torrentName), r.TargetDir); err != nil { return errors.Wrap(err, "move file") } // .plexmatch file - if err := s.writePlexmatch(r.MediaID, r.EpisodeID, r.TargetDir, torrentName); err != nil { + if err := c.writePlexmatch(r.MediaID, r.EpisodeID, r.TargetDir, torrentName); err != nil { log.Errorf("create .plexmatch file error: %v", err) } - s.db.SetHistoryStatus(r.ID, history.StatusSuccess) + c.db.SetHistoryStatus(r.ID, history.StatusSuccess) if r.EpisodeID != 0 { - s.db.SetEpisodeStatus(r.EpisodeID, episode.StatusDownloaded) + c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusDownloaded) } else { - s.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded) + c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded) } - s.sendMsg(fmt.Sprintf(message.ProcessingComplete, torrentName)) + c.sendMsg(fmt.Sprintf(message.ProcessingComplete, torrentName)) //判断是否需要删除本地文件 - ok, err := s.isSeedRatioLimitReached(r.IndexerID, torrent) + ok, err := c.isSeedRatioLimitReached(r.IndexerID, torrent) if err != nil { log.Warnf("getting torrent seed ratio %s: %v", torrent.Name(), err) ok = false } if downloadclient.RemoveCompletedDownloads && ok { log.Debugf("download complete,remove torrent and files related") - delete(s.tasks, r.ID) + delete(c.tasks, r.ID) torrent.Remove() } @@ -156,13 +155,13 @@ func (s *Server) moveCompletedTask(id int) (err1 error) { return nil } -func (s *Server) checkDownloadedSeriesFiles(m *ent.Media) error { +func (c *Client) CheckDownloadedSeriesFiles(m *ent.Media) error { if m.MediaType != media.MediaTypeTv { return nil } log.Infof("check files in directory: %s", m.TargetDir) - var storageImpl, err = s.getStorage(m.StorageID, media.MediaTypeTv) + var storageImpl, err = c.getStorage(m.StorageID, media.MediaTypeTv) if err != nil { return err } @@ -190,12 +189,12 @@ func (s *Server) checkDownloadedSeriesFiles(m *ent.Media) error { continue } log.Infof("found match, season num %d, episode num %d", seNum, epNum) - ep, err := s.db.GetEpisode(m.ID, seNum, epNum) + ep, err := c.db.GetEpisode(m.ID, seNum, epNum) if err != nil { log.Error("update episode: %v", err) continue } - err = s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloaded) + err = c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloaded) if err != nil { log.Error("update episode: %v", err) continue @@ -212,11 +211,11 @@ type Task struct { pkg.Torrent } -func (s *Server) downloadTvSeries() { +func (c *Client) downloadTvSeries() { log.Infof("begin check all tv series resources") - allSeries := s.db.GetMediaWatchlist(media.MediaTypeTv) + allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv) for _, series := range allSeries { - tvDetail := s.db.GetMediaDetails(series.ID) + tvDetail := c.db.GetMediaDetails(series.ID) for _, ep := range tvDetail.Episodes { if !series.DownloadHistoryEpisodes { //设置不下载历史已播出剧集,只下载将来剧集 t, err := time.Parse("2006-01-02", ep.AirDate) @@ -232,7 +231,7 @@ func (s *Server) downloadTvSeries() { if ep.Status != episode.StatusMissing { //已经下载的不去下载 continue } - name, err := s.searchAndDownload(series.ID, ep.SeasonNumber, ep.EpisodeNumber) + name, err := c.SearchAndDownload(series.ID, ep.SeasonNumber, ep.EpisodeNumber) if err != nil { log.Infof("cannot find resource to download for %s: %v", ep.Title, err) } else { @@ -244,12 +243,12 @@ func (s *Server) downloadTvSeries() { } } -func (s *Server) downloadMovie() { +func (c *Client) downloadMovie() { log.Infof("begin check all movie resources") - allSeries := s.db.GetMediaWatchlist(media.MediaTypeMovie) + allSeries := c.db.GetMediaWatchlist(media.MediaTypeMovie) for _, series := range allSeries { - detail := s.db.GetMediaDetails(series.ID) + detail := c.db.GetMediaDetails(series.ID) if len(detail.Episodes) == 0 { log.Errorf("no related dummy episode: %v", detail.NameEn) continue @@ -259,32 +258,32 @@ func (s *Server) downloadMovie() { continue } - if err := s.downloadMovieSingleEpisode(ep); err != nil { + if err := c.downloadMovieSingleEpisode(ep); err != nil { log.Errorf("download movie error: %v", err) } } } -func (s *Server) downloadMovieSingleEpisode(ep *ent.Episode) error { - trc, dlc, err := s.getDownloadClient() +func (c *Client) downloadMovieSingleEpisode(ep *ent.Episode) error { + trc, dlc, err := c.getDownloadClient() if err != nil { return errors.Wrap(err, "connect transmission") } - res, err := core.SearchMovie(s.db, ep.MediaID, true) + res, err := SearchMovie(c.db, ep.MediaID, true) if err != nil { return errors.Wrap(err, "search movie") } r1 := res[0] log.Infof("begin download torrent resource: %v", r1.Name) - torrent, err := trc.Download(r1.Link, s.db.GetDownloadDir()) + torrent, err := trc.Download(r1.Link, c.db.GetDownloadDir()) if err != nil { return errors.Wrap(err, "downloading") } torrent.Start() - history, err := s.db.SaveHistoryRecord(ent.History{ + history, err := c.db.SaveHistoryRecord(ent.History{ MediaID: ep.MediaID, EpisodeID: ep.ID, SourceTitle: r1.Name, @@ -298,36 +297,36 @@ func (s *Server) downloadMovieSingleEpisode(ep *ent.Episode) error { log.Errorf("save history error: %v", err) } - s.tasks[history.ID] = &Task{Torrent: torrent} + c.tasks[history.ID] = &Task{Torrent: torrent} - s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) + c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) return nil } -func (s *Server) checkAllSeriesNewSeason() { +func (c *Client) checkAllSeriesNewSeason() { log.Infof("begin checking series all new season") - allSeries := s.db.GetMediaWatchlist(media.MediaTypeTv) + allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv) for _, series := range allSeries { - err := s.checkSeiesNewSeason(series) + err := c.checkSeiesNewSeason(series) if err != nil { log.Errorf("check series new season error: series name %v, error: %v", series.NameEn, err) } } } -func (s *Server) checkSeiesNewSeason(media *ent.Media) error { - d, err := s.MustTMDB().GetTvDetails(media.TmdbID, s.language) +func (c *Client) checkSeiesNewSeason(media *ent.Media) error { + d, err := c.MustTMDB().GetTvDetails(media.TmdbID, c.language) if err != nil { return errors.Wrap(err, "tmdb") } lastsSason := d.NumberOfSeasons - seasonDetail, err := s.MustTMDB().GetSeasonDetails(media.TmdbID, lastsSason, s.language) + seasonDetail, err := c.MustTMDB().GetSeasonDetails(media.TmdbID, lastsSason, c.language) if err != nil { return errors.Wrap(err, "tmdb season") } for _, ep := range seasonDetail.Episodes { - epDb, err := s.db.GetEpisode(media.ID, ep.SeasonNumber, ep.EpisodeNumber) + epDb, err := c.db.GetEpisode(media.ID, ep.SeasonNumber, ep.EpisodeNumber) if err != nil { if ent.IsNotFound(err) { log.Infof("add new episode: %+v", ep) @@ -340,20 +339,20 @@ func (s *Server) checkSeiesNewSeason(media *ent.Media) error { AirDate: ep.AirDate, Status: episode.StatusMissing, } - s.db.SaveEposideDetail2(episode) + c.db.SaveEposideDetail2(episode) } } else { //update episode if ep.Name != epDb.Title || ep.Overview != epDb.Overview || ep.AirDate != epDb.AirDate { log.Infof("update new episode: %+v", ep) - s.db.UpdateEpiode2(epDb.ID, ep.Name, ep.Overview, ep.AirDate) + c.db.UpdateEpiode2(epDb.ID, ep.Name, ep.Overview, ep.AirDate) } } } return nil } -func (s *Server) isSeedRatioLimitReached(indexId int, t pkg.Torrent) (bool, error) { - indexer, err := s.db.GetIndexer(indexId) +func (c *Client) isSeedRatioLimitReached(indexId int, t pkg.Torrent) (bool, error) { + indexer, err := c.db.GetIndexer(indexId) if err != nil { return false, err } diff --git a/server/integration.go b/server/integration.go deleted file mode 100644 index 22c9e39..0000000 --- a/server/integration.go +++ /dev/null @@ -1,62 +0,0 @@ -package server - -import ( - "bytes" - "fmt" - "path/filepath" - "polaris/db" - "polaris/ent/media" - "polaris/log" - - "github.com/pkg/errors" -) - -func (s *Server) writePlexmatch(seriesId int, episodeId int, targetDir, name string) error { - - if !s.plexmatchEnabled() { - return nil - } - series, err := s.db.GetMedia(seriesId) - if err != nil { - return err - } - if series.MediaType != media.MediaTypeTv { - return nil - } - st, err := s.getStorage(series.StorageID, media.MediaTypeTv) - if err != nil { - return errors.Wrap(err, "get storage") - } - - //series plexmatch file - _, err = st.ReadFile(filepath.Join(series.TargetDir, ".plexmatch")) - if err != nil { - //create new - log.Warnf(".plexmatch file not found, create new one: %s", series.NameEn) - if err := st.WriteFile(filepath.Join(series.TargetDir, ".plexmatch"), - []byte(fmt.Sprintf("tmdbid: %d\n",series.TmdbID))); err != nil { - return errors.Wrap(err, "series plexmatch") - } - } - - //season plexmatch file - ep, err := s.db.GetEpisodeByID(episodeId) - if err != nil { - return errors.Wrap(err, "query episode") - } - buff := bytes.Buffer{} - seasonPlex := filepath.Join(targetDir, ".plexmatch") - data, err := st.ReadFile(seasonPlex) - if err != nil { - log.Infof("read season plexmatch: %v", err) - } else { - buff.Write(data) - } - 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()) -} - -func (s *Server) plexmatchEnabled() bool { - return s.db.GetSetting(db.SettingPlexMatchEnabled) == "true" -} diff --git a/server/notify_client.go b/server/notify_client.go index c6aa468..56ed8dd 100644 --- a/server/notify_client.go +++ b/server/notify_client.go @@ -2,8 +2,6 @@ package server import ( "polaris/ent" - "polaris/log" - "polaris/pkg/notifier" "strconv" "github.com/gin-gonic/gin" @@ -44,32 +42,3 @@ func (s *Server) AddNotificationClient(c *gin.Context) (interface{}, error) { } return nil, nil } - -func (s *Server) sendMsg(msg string) { - clients, err := s.db.GetAllNotificationClients2() - if err != nil { - log.Errorf("query notification clients: %v", err) - return - } - for _, cl := range clients { - if !cl.Enabled { - continue - } - handler, ok := notifier.Gethandler(cl.Service) - if !ok { - log.Errorf("no notification implementation of service %s", cl.Service) - continue - } - noCl, err := handler(cl.Settings) - if err != nil { - log.Errorf("handle setting for name %s error: %v", cl.Name, err) - continue - } - err = noCl.SendMsg(msg) - if err != nil { - log.Errorf("send message error: %v", err) - continue - } - log.Debugf("send message to %s success, msg is %s", cl.Name, msg) - } -} \ No newline at end of file diff --git a/server/resources.go b/server/resources.go index dc24c73..9cc351c 100644 --- a/server/resources.go +++ b/server/resources.go @@ -2,14 +2,9 @@ package server import ( "fmt" - "polaris/ent" - "polaris/ent/episode" - "polaris/ent/history" "polaris/ent/media" "polaris/log" - "polaris/pkg/notifier/message" "polaris/pkg/torznab" - "polaris/pkg/utils" "polaris/server/core" "github.com/gin-gonic/gin" @@ -25,117 +20,10 @@ func (s *Server) searchAndDownloadSeasonPackage(seriesId, seasonNum int) (*strin r1 := res[0] log.Infof("found resource to download: %+v", r1) - return s.downloadSeasonPackage(r1, seriesId, seasonNum) + return s.core.DownloadSeasonPackage(r1, seriesId, seasonNum) } -func (s *Server) downloadSeasonPackage(r1 torznab.Result, seriesId, seasonNum int) (*string, error) { - trc, dlClient, err := s.getDownloadClient() - if err != nil { - return nil, errors.Wrap(err, "connect transmission") - } - downloadDir := s.db.GetDownloadDir() - size := utils.AvailableSpace(downloadDir) - if size < uint64(r1.Size) { - log.Errorf("space available %v, space needed %v", size, r1.Size) - return nil, errors.New("no enough space") - } - - torrent, err := trc.Download(r1.Link, s.db.GetDownloadDir()) - if err != nil { - return nil, errors.Wrap(err, "downloading") - } - torrent.Start() - - series := s.db.GetMediaDetails(seriesId) - if series == nil { - return nil, fmt.Errorf("no tv series of id %v", seriesId) - } - dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) - - history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: seriesId, - EpisodeID: 0, - SourceTitle: r1.Name, - TargetDir: dir, - Status: history.StatusRunning, - Size: r1.Size, - Saved: torrent.Save(), - DownloadClientID: dlClient.ID, - IndexerID: r1.IndexerId, - }) - if err != nil { - return nil, errors.Wrap(err, "save record") - } - s.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading) - - s.tasks[history.ID] = &Task{Torrent: torrent} - - s.sendMsg(fmt.Sprintf(message.BeginDownload, r1.Name)) - return &r1.Name, nil - -} - -func (s *Server) downloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, episodeNum int) (*string, error) { - trc, dlc, err := s.getDownloadClient() - if err != nil { - return nil, errors.Wrap(err, "connect transmission") - } - series := s.db.GetMediaDetails(seriesId) - if series == nil { - return nil, fmt.Errorf("no tv series of id %v", seriesId) - } - var ep *ent.Episode - 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) - } - torrent, err := trc.Download(r1.Link, s.db.GetDownloadDir()) - if err != nil { - return nil, errors.Wrap(err, "downloading") - } - torrent.Start() - - dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum) - - history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: ep.MediaID, - EpisodeID: ep.ID, - SourceTitle: r1.Name, - TargetDir: dir, - Status: history.StatusRunning, - Size: r1.Size, - Saved: torrent.Save(), - DownloadClientID: dlc.ID, - IndexerID: r1.IndexerId, - }) - if err != nil { - return nil, errors.Wrap(err, "save record") - } - s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) - - s.tasks[history.ID] = &Task{Torrent: torrent} - s.sendMsg(fmt.Sprintf(message.BeginDownload, r1.Name)) - - log.Infof("success add %s to download task", r1.Name) - return &r1.Name, nil - -} -func (s *Server) searchAndDownload(seriesId, seasonNum, episodeNum int) (*string, error) { - - res, err := core.SearchEpisode(s.db, seriesId, seasonNum, episodeNum, true) - if err != nil { - return nil, err - } - r1 := res[0] - log.Infof("found resource to download: %+v", r1) - return s.downloadEpisodeTorrent(r1, seriesId, seasonNum, episodeNum) -} - type searchAndDownloadIn struct { ID int `json:"id" binding:"required"` Season int `json:"season"` @@ -203,7 +91,7 @@ func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) { name = *name1 } else { log.Infof("season episode search") - name1, err := s.searchAndDownload(in.ID, in.Season, in.Episode) + name1, err := s.core.SearchAndDownload(in.ID, in.Season, in.Episode) if err != nil { return nil, errors.Wrap(err, "download") } @@ -241,55 +129,17 @@ func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) { name = fmt.Sprintf("%v S%02d", m.OriginalName, in.Season) } res := torznab.Result{Name: name, Link: in.Link, Size: in.Size} - return s.downloadSeasonPackage(res, in.MediaID, in.Season) + return s.core.DownloadSeasonPackage(res, in.MediaID, in.Season) } name := in.Name if name == "" { name = fmt.Sprintf("%v S%02dE%02d", m.OriginalName, in.Season, in.Episode) } res := torznab.Result{Name: name, Link: in.Link, Size: in.Size, IndexerId: in.IndexerId} - return s.downloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode) + return s.core.DownloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode) } else { //movie - trc, dlc, err := s.getDownloadClient() - if err != nil { - return nil, errors.Wrap(err, "connect transmission") - } - - torrent, err := trc.Download(in.Link, s.db.GetDownloadDir()) - if err != nil { - return nil, errors.Wrap(err, "downloading") - } - torrent.Start() - name := in.Name - if name == "" { - name = m.OriginalName - } - go func() { - ep, _ := s.db.GetMovieDummyEpisode(m.ID) - history, err := s.db.SaveHistoryRecord(ent.History{ - MediaID: m.ID, - EpisodeID: ep.ID, - SourceTitle: name, - TargetDir: m.TargetDir, - Status: history.StatusRunning, - Size: in.Size, - Saved: torrent.Save(), - DownloadClientID: dlc.ID, - IndexerID: in.IndexerId, - }) - if err != nil { - log.Errorf("save history error: %v", err) - } - - s.tasks[history.ID] = &Task{Torrent: torrent} - - s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading) - }() - - s.sendMsg(fmt.Sprintf(message.BeginDownload, in.Name)) - log.Infof("success add %s to download task", in.Name) - return in.Name, nil + return s.core.DownloadMovie(m, in.Link, in.Name, in.Size, in.IndexerId) } } diff --git a/server/server.go b/server/server.go index a0410ed..ea52c11 100644 --- a/server/server.go +++ b/server/server.go @@ -8,13 +8,12 @@ import ( "polaris/db" "polaris/log" "polaris/pkg/tmdb" - "polaris/pkg/transmission" + "polaris/server/core" "polaris/ui" ginzap "github.com/gin-contrib/zap" "github.com/gin-contrib/static" - "github.com/robfig/cron" "github.com/gin-gonic/gin" "github.com/pkg/errors" @@ -22,26 +21,25 @@ import ( func NewServer(db *db.Client) *Server { r := gin.Default() - return &Server{ - r: r, - db: db, - cron: cron.New(), - tasks: make(map[int]*Task), + s := &Server{ + r: r, + db: db, } + s.core = core.NewClient(db, s.language) + return s } type Server struct { r *gin.Engine db *db.Client - cron *cron.Cron + core *core.Client language string - tasks map[int]*Task jwtSerect string } func (s *Server) Serve() error { - s.scheduler() - s.reloadTasks() + s.core.Init() + s.restoreProxy() s.jwtSerect = s.db.GetSetting(db.JwtSerectKey) @@ -142,22 +140,6 @@ func (s *Server) MustTMDB() *tmdb.Client { return t } -func (s *Server) reloadTasks() { - allTasks := s.db.GetHistories() - for _, t := range allTasks { - torrent, err := transmission.ReloadTorrent(t.Saved) - if err != nil { - log.Errorf("relaod task %s failed: %v", t.SourceTitle, err) - continue - } - if !torrent.Exists() { //只要种子还存在于客户端中,就重新加载,有可能是还在做种中 - continue - } - log.Infof("reloading task: %d %s", t.ID, t.SourceTitle) - s.tasks[t.ID] = &Task{Torrent: torrent} - } -} - func (s *Server) proxyPosters(c *gin.Context) { remote, _ := url.Parse("https://image.tmdb.org") proxy := httputil.NewSingleHostReverseProxy(remote) diff --git a/server/storage.go b/server/storage.go index 6841d24..9a58ff1 100644 --- a/server/storage.go +++ b/server/storage.go @@ -3,8 +3,7 @@ package server import ( "fmt" "polaris/db" - "polaris/ent/media" - storage1 "polaris/ent/storage" + "polaris/log" "polaris/pkg/storage" "strconv" @@ -113,30 +112,3 @@ func (s *Server) SuggestedMovieFolderName(c *gin.Context) (interface{}, error) { log.Infof("tv series of tmdb id %v suggestting name is %v", id, name) return gin.H{"name": name}, nil } - -func (s *Server) getStorage(storageId int, mediaType media.MediaType) (storage.Storage, error) { - st := s.db.GetStorage(storageId) - targetPath := st.TvPath - if mediaType == media.MediaTypeMovie { - targetPath = st.MoviePath - } - - switch st.Implementation { - case storage1.ImplementationLocal: - - storageImpl1, err := storage.NewLocalStorage(targetPath) - if err != nil { - return nil, errors.Wrap(err, "new local") - } - return storageImpl1, nil - - case storage1.ImplementationWebdav: - ws := st.ToWebDavSetting() - storageImpl1, err := storage.NewWebdavStorage(ws.URL, ws.User, ws.Password, targetPath, ws.ChangeFileHash == "true") - if err != nil { - return nil, errors.Wrap(err, "new webdav") - } - return storageImpl1, nil - } - return nil, errors.New("no storage found") -} diff --git a/server/watchlist.go b/server/watchlist.go index cf0c8fc..d375eca 100644 --- a/server/watchlist.go +++ b/server/watchlist.go @@ -139,7 +139,7 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) { if err := s.downloadBackdrop(detail.BackdropPath, r.ID); err != nil { log.Errorf("download poster error: %v", err) } - if err := s.checkDownloadedSeriesFiles(r); err != nil { + if err := s.core.CheckDownloadedSeriesFiles(r); err != nil { log.Errorf("check downloaded files error: %v", err) }