mirror of
https://github.com/simon-ding/polaris.git
synced 2026-03-10 11:40:47 +08:00
feat: add season package download ability
This commit is contained in:
21
db/db.go
21
db/db.go
@@ -175,14 +175,14 @@ func (c *Client) GetMediaDetails(id int) *MediaDetails {
|
||||
var md = &MediaDetails{
|
||||
Media: se,
|
||||
}
|
||||
if se.MediaType == media.MediaTypeTv {
|
||||
ep, err := se.QueryEpisodes().All(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("get episodes %d: %v", id, err)
|
||||
return nil
|
||||
}
|
||||
md.Episodes = ep
|
||||
|
||||
ep, err := se.QueryEpisodes().All(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("get episodes %d: %v", id, err)
|
||||
return nil
|
||||
}
|
||||
md.Episodes = ep
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
@@ -325,7 +325,6 @@ func (c *Client) AddStorage(st *StorageInfo) error {
|
||||
st.Settings["movie_path"] += "/"
|
||||
}
|
||||
|
||||
|
||||
data, err := json.Marshal(st.Settings)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "json marshal")
|
||||
@@ -477,6 +476,10 @@ func (c *Client) SetEpisodeStatus(id int, status episode.Status) error {
|
||||
return c.ent.Episode.Update().Where(episode.ID(id)).SetStatus(status).Exec(context.TODO())
|
||||
}
|
||||
|
||||
func (c *Client) SetSeasonAllEpisodeStatus(mediaID, seasonNum int, status episode.Status) error {
|
||||
return c.ent.Episode.Update().Where(episode.MediaID(mediaID), episode.SeasonNumber(seasonNum)).SetStatus(status).Exec(context.TODO())
|
||||
}
|
||||
|
||||
func (c *Client) TmdbIdInWatchlist(tmdb_id int) bool {
|
||||
return c.ent.Media.Query().Where(media.TmdbID(tmdb_id)).CountX(context.TODO()) > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ func NewWebdavStorage(url, user, password, path string) (*WebdavStorage, error)
|
||||
}
|
||||
|
||||
func (w *WebdavStorage) Move(local, remote string) error {
|
||||
baseLocal := filepath.Base(local)
|
||||
remoteBase := filepath.Join(w.dir,remote, baseLocal)
|
||||
|
||||
remoteBase := filepath.Join(w.dir,remote)
|
||||
|
||||
//log.Infof("remove all content in %s", remoteBase)
|
||||
//w.fs.RemoveAll(remoteBase)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -9,8 +8,10 @@ import (
|
||||
|
||||
"github.com/adrg/strutil"
|
||||
"github.com/adrg/strutil/metrics"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/exp/rand"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func isASCII(s string) bool {
|
||||
@@ -84,3 +85,64 @@ func FindSeasonEpisodeNum(name string) (se int, ep int, err error) {
|
||||
seNum1, _ := strconv.Atoi(seNum)
|
||||
return seNum1, epNum1, nil
|
||||
}
|
||||
|
||||
func FindSeasonPackageInfo(name string) (se int, err error) {
|
||||
seRe := regexp.MustCompile(`S\d+`)
|
||||
epRe := regexp.MustCompile(`E\d+`)
|
||||
nameUpper := strings.ToUpper(name)
|
||||
matchEp := epRe.FindAllString(nameUpper, -1)
|
||||
if len(matchEp) != 0 {
|
||||
err = errors.New("episode number should not exist")
|
||||
}
|
||||
matchSe := seRe.FindAllString(nameUpper, -1)
|
||||
if len(matchSe) == 0 {
|
||||
err = errors.New("no season num")
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
seNum := strings.TrimPrefix(matchSe[0], "S")
|
||||
se, _ = strconv.Atoi(seNum)
|
||||
return se, err
|
||||
}
|
||||
|
||||
func IsSeasonPackageName(name string) bool {
|
||||
seRe := regexp.MustCompile(`S\d+`)
|
||||
epRe := regexp.MustCompile(`E\d+`)
|
||||
nameUpper := strings.ToUpper(name)
|
||||
matchEp := epRe.FindAllString(nameUpper, -1)
|
||||
if len(matchEp) != 0 {
|
||||
return false //episode number should not exist
|
||||
}
|
||||
matchSe := seRe.FindAllString(nameUpper, -1)
|
||||
if len(matchSe) == 0 {
|
||||
return false //no season num
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ContainsIgnoreCase(s, substr string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
||||
func SeasonId(seasonName string) (int, error) {
|
||||
//Season 01
|
||||
seRe := regexp.MustCompile(`\d+`)
|
||||
matchSe := seRe.FindAllString(seasonName, -1)
|
||||
if len(matchSe) == 0 {
|
||||
return 0, errors.New("no season number") //no season num
|
||||
}
|
||||
num, err := strconv.Atoi(matchSe[0])
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "convert")
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func AvailableSpace(dir string) uint64 {
|
||||
var stat unix.Statfs_t
|
||||
|
||||
unix.Statfs(dir, &stat)
|
||||
return stat.Bavail * uint64(stat.Bsize)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"polaris/ent"
|
||||
"polaris/ent/episode"
|
||||
"polaris/log"
|
||||
"polaris/pkg/utils"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
|
||||
type Activity struct {
|
||||
*ent.History
|
||||
Progress int `json:"progress"`
|
||||
Progress int `json:"progress"`
|
||||
}
|
||||
|
||||
func (s *Server) GetAllActivities(c *gin.Context) (interface{}, error) {
|
||||
@@ -51,7 +52,18 @@ func (s *Server) RemoveActivity(c *gin.Context) (interface{}, error) {
|
||||
}
|
||||
delete(s.tasks, his.ID)
|
||||
}
|
||||
s.db.SetEpisodeStatus(his.EpisodeID, episode.StatusMissing)
|
||||
if his.EpisodeID != 0 {
|
||||
s.db.SetEpisodeStatus(his.EpisodeID, episode.StatusMissing)
|
||||
|
||||
} else {
|
||||
seasonNum, err := utils.SeasonId(his.TargetDir)
|
||||
if err != nil {
|
||||
log.Errorf("no season id: %v", his.TargetDir)
|
||||
seasonNum = -1
|
||||
}
|
||||
s.db.SetSeasonAllEpisodeStatus(his.MediaID, seasonNum, episode.StatusMissing)
|
||||
|
||||
}
|
||||
|
||||
err = s.db.DeleteHistory(id)
|
||||
if err != nil {
|
||||
|
||||
159
server/core/torrent.go
Normal file
159
server/core/torrent.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"polaris/db"
|
||||
"polaris/ent"
|
||||
"polaris/ent/media"
|
||||
"polaris/log"
|
||||
"polaris/pkg/torznab"
|
||||
"polaris/pkg/utils"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func SearchSeasonPackage(db1 *db.Client, seriesId, seasonNum int) ([]torznab.Result, error) {
|
||||
series := db1.GetMediaDetails(seriesId)
|
||||
if series == nil {
|
||||
return nil, fmt.Errorf("no tv series of id %v", seriesId)
|
||||
}
|
||||
q := fmt.Sprintf("%s S%02d", series.NameEn, seasonNum)
|
||||
|
||||
res := searchWithTorznab(db1, q)
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("no resource found")
|
||||
}
|
||||
var filtered []torznab.Result
|
||||
for _, r := range res {
|
||||
if !isNameAcceptable(r.Name, series.Media, seasonNum, -1) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, r)
|
||||
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
return nil, errors.New("no resource found")
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func SearchEpisode(db1 *db.Client, seriesId, seasonNum, episodeNum int) ([]torznab.Result, error) {
|
||||
series := db1.GetMediaDetails(seriesId)
|
||||
if series == nil {
|
||||
return nil, fmt.Errorf("no tv series of id %v", seriesId)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf("%s S%02dE%02d", series.NameEn, seasonNum, episodeNum)
|
||||
res := searchWithTorznab(db1, q)
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("no resource found")
|
||||
}
|
||||
|
||||
var filtered []torznab.Result
|
||||
for _, r := range res {
|
||||
if !isNameAcceptable(r.Name, series.Media, seasonNum, episodeNum) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
|
||||
}
|
||||
|
||||
func SearchMovie(db1 *db.Client, movieId int) ([]torznab.Result, error) {
|
||||
movieDetail := db1.GetMediaDetails(movieId)
|
||||
if movieDetail == nil {
|
||||
return nil, errors.New("no media found of id")
|
||||
}
|
||||
|
||||
res := searchWithTorznab(db1, movieDetail.NameEn)
|
||||
|
||||
res1 := searchWithTorznab(db1, movieDetail.NameCn)
|
||||
res = append(res, res1...)
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("no resource found")
|
||||
}
|
||||
var filtered []torznab.Result
|
||||
for _, r := range res {
|
||||
if !isNameAcceptable(r.Name, movieDetail.Media, -1, -1) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, r)
|
||||
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return nil, errors.New("no resource found")
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
|
||||
}
|
||||
|
||||
func searchWithTorznab(db *db.Client, q string) []torznab.Result {
|
||||
|
||||
var res []torznab.Result
|
||||
allTorznab := db.GetAllTorznabInfo()
|
||||
for _, tor := range allTorznab {
|
||||
resp, err := torznab.Search(tor.URL, tor.ApiKey, q)
|
||||
if err != nil {
|
||||
log.Errorf("search %s error: %v", tor.Name, err)
|
||||
continue
|
||||
}
|
||||
res = append(res, resp...)
|
||||
}
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
var s1 = res[i]
|
||||
var s2 = res[j]
|
||||
return s1.Seeders > s2.Seeders
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func isNameAcceptable(torrentName string, m *ent.Media, seasonNum, episodeNum int) bool {
|
||||
if !utils.ContainsIgnoreCase(torrentName,m.NameCn) && !utils.ContainsIgnoreCase(torrentName,m.NameEn) &&
|
||||
!utils.ContainsIgnoreCase(torrentName,m.OriginalName) {
|
||||
return false
|
||||
}
|
||||
if !utils.IsNameAcceptable(torrentName, m.NameCn) && !utils.IsNameAcceptable(torrentName, m.NameEn) && !utils.IsNameAcceptable(torrentName, m.OriginalName){
|
||||
return false //name not match
|
||||
}
|
||||
|
||||
ss := strings.Split(m.AirDate, "-")[0]
|
||||
year, _ := strconv.Atoi(ss)
|
||||
if m.MediaType == media.MediaTypeMovie {
|
||||
if !strings.Contains(torrentName, strconv.Itoa(year)) && !strings.Contains(torrentName, strconv.Itoa(year+1)) && !strings.Contains(torrentName, strconv.Itoa(year-1)) {
|
||||
return false //not the same movie, if year is not correct
|
||||
}
|
||||
}
|
||||
|
||||
if m.MediaType == media.MediaTypeTv {
|
||||
if episodeNum != -1 {
|
||||
se := fmt.Sprintf("S%02dE%02d", seasonNum, episodeNum)
|
||||
if !utils.ContainsIgnoreCase(torrentName, se) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
//season package
|
||||
if !utils.IsSeasonPackageName(torrentName) {
|
||||
return false
|
||||
}
|
||||
|
||||
seNum, err := utils.FindSeasonPackageInfo(torrentName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if seNum != seasonNum {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -7,31 +7,15 @@ import (
|
||||
"polaris/ent/episode"
|
||||
"polaris/ent/history"
|
||||
"polaris/log"
|
||||
"polaris/pkg/torznab"
|
||||
"polaris/pkg/transmission"
|
||||
"polaris/pkg/utils"
|
||||
"polaris/server/core"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *Server) searchWithTorznab(q string) []torznab.Result {
|
||||
|
||||
var res []torznab.Result
|
||||
allTorznab := s.db.GetAllTorznabInfo()
|
||||
for _, tor := range allTorznab {
|
||||
resp, err := torznab.Search(tor.URL, tor.ApiKey, q)
|
||||
if err != nil {
|
||||
log.Errorf("search %s error: %v", tor.Name, err)
|
||||
continue
|
||||
}
|
||||
res = append(res, resp...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type addTorznabIn struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
@@ -83,6 +67,59 @@ func (s *Server) getDownloadClient() (*transmission.Client, error) {
|
||||
}
|
||||
return trc, nil
|
||||
}
|
||||
|
||||
func (s *Server) searchAndDownloadSeasonPackage(seriesId, seasonNum int) (*string, error) {
|
||||
trc, err := s.getDownloadClient()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "connect transmission")
|
||||
}
|
||||
|
||||
res, err := core.SearchSeasonPackage(s.db, seriesId, seasonNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r1 := res[0]
|
||||
log.Infof("found resource to download: %v", r1)
|
||||
|
||||
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.Magnet, 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(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "save record")
|
||||
}
|
||||
s.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading)
|
||||
|
||||
s.tasks[history.ID] = &Task{Torrent: torrent}
|
||||
return &r1.Name, nil
|
||||
}
|
||||
|
||||
func (s *Server) searchAndDownload(seriesId, seasonNum, episodeNum int) (*string, error) {
|
||||
trc, err := s.getDownloadClient()
|
||||
if err != nil {
|
||||
@@ -102,13 +139,11 @@ func (s *Server) searchAndDownload(seriesId, seasonNum, episodeNum int) (*string
|
||||
return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, episodeNum)
|
||||
}
|
||||
|
||||
q := fmt.Sprintf("%s S%02dE%02d", series.OriginalName, seasonNum, episodeNum)
|
||||
|
||||
res := s.searchWithTorznab(q)
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("no resource found")
|
||||
res, err := core.SearchEpisode(s.db, seriesId, seasonNum, episodeNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r1 := s.findBestMatchTv(res, seasonNum, episodeNum, series)
|
||||
r1 := res[0]
|
||||
log.Infof("found resource to download: %v", r1)
|
||||
torrent, err := trc.Download(r1.Magnet, s.db.GetDownloadDir())
|
||||
if err != nil {
|
||||
@@ -116,7 +151,7 @@ func (s *Server) searchAndDownload(seriesId, seasonNum, episodeNum int) (*string
|
||||
}
|
||||
torrent.Start()
|
||||
|
||||
dir := fmt.Sprintf("%s/Season %02d", series.TargetDir, ep.SeasonNumber)
|
||||
dir := fmt.Sprintf("%s/Season %02d", series.TargetDir, seasonNum)
|
||||
|
||||
history, err := s.db.SaveHistoryRecord(ent.History{
|
||||
MediaID: ep.MediaID,
|
||||
@@ -127,64 +162,75 @@ func (s *Server) searchAndDownload(seriesId, seasonNum, episodeNum int) (*string
|
||||
Size: r1.Size,
|
||||
Saved: torrent.Save(),
|
||||
})
|
||||
s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "save record")
|
||||
}
|
||||
s.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
|
||||
|
||||
s.tasks[history.ID] = &Task{Torrent: torrent}
|
||||
|
||||
log.Infof("success add %s to download task", r1.Name)
|
||||
return &r1.Name, nil
|
||||
}
|
||||
|
||||
func (s *Server) findBestMatchTv(resources []torznab.Result, season, episode int, series *db.MediaDetails) torznab.Result {
|
||||
var filtered []torznab.Result
|
||||
for _, r := range resources {
|
||||
if !(series.NameEn != "" && strings.Contains(r.Name, series.NameEn)) && !strings.Contains(r.Name, series.OriginalName) {
|
||||
//name not match
|
||||
continue
|
||||
}
|
||||
|
||||
se := fmt.Sprintf("S%02dE%02d", season, episode)
|
||||
if !strings.Contains(r.Name, se) {
|
||||
//season or episode not match
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(r.Name), series.Resolution) {
|
||||
//resolution not match
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
|
||||
// sort.Slice(filtered, func(i, j int) bool {
|
||||
// var s1 = filtered[i]
|
||||
// var s2 = filtered[j]
|
||||
// return s1.Seeders > s2.Seeders
|
||||
// })
|
||||
|
||||
return filtered[0]
|
||||
}
|
||||
|
||||
type searchAndDownloadIn struct {
|
||||
ID int `json:"id" binding:"required"`
|
||||
Season int `json:"season"`
|
||||
Episode int `json:"episode"`
|
||||
}
|
||||
|
||||
func (s *Server) SearchAvailableEpisodeResource(c *gin.Context) (interface{}, error) {
|
||||
var in searchAndDownloadIn
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
return nil, errors.Wrap(err, "bind json")
|
||||
}
|
||||
log.Infof("search episode resources link: %v", in)
|
||||
res, err := core.SearchEpisode(s.db, in.ID, in.Season, in.Episode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "search episode")
|
||||
}
|
||||
var searchResults []TorznabSearchResult
|
||||
for _, r := range res {
|
||||
searchResults = append(searchResults, TorznabSearchResult{
|
||||
Name: r.Name,
|
||||
Size: r.Size,
|
||||
Seeders: r.Seeders,
|
||||
Peers: r.Peers,
|
||||
Link: r.Magnet,
|
||||
})
|
||||
}
|
||||
if len(searchResults) == 0 {
|
||||
return nil, errors.New("no resource found")
|
||||
}
|
||||
return searchResults, nil
|
||||
}
|
||||
|
||||
func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) {
|
||||
var in searchAndDownloadIn
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
return nil, errors.Wrap(err, "bind json")
|
||||
}
|
||||
log.Infof("search episode resources link: %v", in)
|
||||
name, err := s.searchAndDownload(in.ID, in.Season, in.Episode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "download")
|
||||
var name string
|
||||
if in.Episode == 0 {
|
||||
log.Infof("season package search")
|
||||
//search season package
|
||||
name1, err := s.searchAndDownloadSeasonPackage(in.ID, in.Season)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "download")
|
||||
}
|
||||
name = *name1
|
||||
} else {
|
||||
log.Infof("season episode search")
|
||||
name1, err := s.searchAndDownload(in.ID, in.Season, in.Episode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "download")
|
||||
}
|
||||
name = *name1
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"name": *name,
|
||||
"name": name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -208,26 +254,13 @@ func (s *Server) SearchAvailableMovies(c *gin.Context) (interface{}, error) {
|
||||
return nil, errors.New("no media found of id " + ids)
|
||||
}
|
||||
|
||||
res := s.searchWithTorznab(movieDetail.NameEn)
|
||||
|
||||
res1 := s.searchWithTorznab(movieDetail.NameCn)
|
||||
res = append(res, res1...)
|
||||
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("no resource found")
|
||||
res, err := core.SearchMovie(s.db, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss := strings.Split(movieDetail.AirDate, "-")[0]
|
||||
year, _ := strconv.Atoi(ss)
|
||||
|
||||
var searchResults []TorznabSearchResult
|
||||
for _, r := range res {
|
||||
if !strings.Contains(r.Name, strconv.Itoa(year)) && !strings.Contains(r.Name, strconv.Itoa(year+1)) && !strings.Contains(r.Name, strconv.Itoa(year-1)) {
|
||||
continue //not the same movie, if year is not correct
|
||||
}
|
||||
|
||||
if !utils.IsNameAcceptable(r.Name, movieDetail.NameCn) && !utils.IsNameAcceptable(r.Name, movieDetail.NameEn) {
|
||||
continue //name not match
|
||||
}
|
||||
searchResults = append(searchResults, TorznabSearchResult{
|
||||
Name: r.Name,
|
||||
Size: r.Size,
|
||||
@@ -239,9 +272,7 @@ func (s *Server) SearchAvailableMovies(c *gin.Context) (interface{}, error) {
|
||||
if len(searchResults) == 0 {
|
||||
return nil, errors.New("no resource found")
|
||||
}
|
||||
|
||||
return searchResults, nil
|
||||
|
||||
}
|
||||
|
||||
type downloadTorrentIn struct {
|
||||
|
||||
@@ -58,10 +58,18 @@ func (s *Server) moveCompletedTask(id int) (err error) {
|
||||
s.db.SetHistoryStatus(r.ID, history.StatusUploading)
|
||||
|
||||
defer func() {
|
||||
seasonNum, err := utils.SeasonId(r.TargetDir)
|
||||
if err != nil {
|
||||
log.Errorf("no season id: %v", r.TargetDir)
|
||||
seasonNum = -1
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.db.SetHistoryStatus(r.ID, history.StatusFail)
|
||||
if r.EpisodeID != 0 {
|
||||
s.db.SetEpisodeStatus(r.EpisodeID, episode.StatusMissing)
|
||||
} else {
|
||||
s.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -69,6 +77,8 @@ func (s *Server) moveCompletedTask(id int) (err error) {
|
||||
s.db.SetHistoryStatus(r.ID, history.StatusSuccess)
|
||||
if r.EpisodeID != 0 {
|
||||
s.db.SetEpisodeStatus(r.EpisodeID, episode.StatusDownloaded)
|
||||
} else {
|
||||
s.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded)
|
||||
}
|
||||
|
||||
torrent.Remove()
|
||||
@@ -108,8 +118,16 @@ func (s *Server) moveCompletedTask(id int) (err error) {
|
||||
stImpl = storageImpl
|
||||
|
||||
}
|
||||
if err := stImpl.Move(filepath.Join(s.db.GetDownloadDir(), torrent.Name()), r.TargetDir); err != nil {
|
||||
return errors.Wrap(err, "move file")
|
||||
if r.EpisodeID == 0 {
|
||||
//season package download
|
||||
if err := stImpl.Move(filepath.Join(s.db.GetDownloadDir(), torrent.Name()), r.TargetDir); err != nil {
|
||||
return errors.Wrap(err, "move file")
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := stImpl.Move(filepath.Join(s.db.GetDownloadDir(), torrent.Name()), filepath.Join(r.TargetDir, torrent.Name())); err != nil {
|
||||
return errors.Wrap(err, "move file")
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("move downloaded files to target dir success, file: %v, target dir: %v", torrent.Name(), r.TargetDir)
|
||||
|
||||
@@ -69,6 +69,7 @@ func (s *Server) Serve() error {
|
||||
tv.GET("/search", HttpHandler(s.SearchMedia))
|
||||
tv.POST("/tv/watchlist", HttpHandler(s.AddTv2Watchlist))
|
||||
tv.GET("/tv/watchlist", HttpHandler(s.GetTvWatchlist))
|
||||
tv.POST("/tv/torrents", HttpHandler(s.SearchAvailableEpisodeResource))
|
||||
tv.POST("/movie/watchlist", HttpHandler(s.AddMovie2Watchlist))
|
||||
tv.GET("/movie/watchlist", HttpHandler(s.GetMovieWatchlist))
|
||||
tv.GET("/movie/resources/:id", HttpHandler(s.SearchAvailableMovies))
|
||||
|
||||
@@ -56,14 +56,16 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
||||
Opacity(
|
||||
opacity: 0.7,
|
||||
child: ep.status == "downloading"
|
||||
? const Icon(Icons.downloading)
|
||||
? const Tooltip(message: "下载中",child: Icon(Icons.downloading),)
|
||||
: (ep.status == "downloaded"
|
||||
? const Icon(Icons.download_done)
|
||||
: const Icon(Icons.warning_amber_rounded))),
|
||||
? const Tooltip(message: "已下载",child: Icon(Icons.download_done),)
|
||||
: const Tooltip(message: "未下载",child: Icon(Icons.warning_amber_rounded),) )),
|
||||
),
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
Tooltip(
|
||||
message: "搜索下载对应剧集",
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
var f = ref
|
||||
.read(mediaDetailsProvider(widget.seriesId)
|
||||
@@ -79,6 +81,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.search)),
|
||||
)
|
||||
,
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
@@ -103,14 +107,31 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
||||
title: k == 0 ? const Text("特别篇") : Text("第 $k 季"),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DataTable(columns: const [
|
||||
DataColumn(label: Text("#")),
|
||||
DataColumn(
|
||||
DataTable(columns: [
|
||||
const DataColumn(label: Text("#")),
|
||||
const DataColumn(
|
||||
label: Text("标题"),
|
||||
),
|
||||
DataColumn(label: Text("播出时间")),
|
||||
DataColumn(label: Text("状态")),
|
||||
DataColumn(label: Text("操作"))
|
||||
const DataColumn(label: Text("播出时间")),
|
||||
const DataColumn(label: Text("状态")),
|
||||
DataColumn(label: Tooltip(
|
||||
message: "搜索下载全部剧集",
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
var f = ref
|
||||
.read(mediaDetailsProvider(widget.seriesId)
|
||||
.notifier)
|
||||
.searchAndDownload(widget.seriesId,
|
||||
k, 0);
|
||||
setState(() {
|
||||
_pendingFuture = f;
|
||||
});
|
||||
if (!Utils.showError(context, snapshot)) {
|
||||
var name = await f;
|
||||
Utils.showSnakeBar("开始下载: $name");
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.search)),) )
|
||||
], rows: m[k]!),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user