code refactor

This commit is contained in:
Simon Ding
2024-08-02 10:08:26 +08:00
parent c28e16805e
commit c787d71fbd
12 changed files with 475 additions and 386 deletions

View File

@@ -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
}
}

View File

@@ -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(),
})
}

94
server/core/client.go Normal file
View File

@@ -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
}

121
server/core/integration.go Normal file
View File

@@ -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)
}
}

165
server/core/resources.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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)
}