Files
polaris/server/watchlist.go
2024-08-03 12:31:53 +08:00

363 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"polaris/db"
"polaris/ent"
"polaris/ent/episode"
"polaris/ent/media"
"polaris/ent/schema"
"polaris/log"
"strconv"
"time"
tmdb "github.com/cyruzin/golang-tmdb"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
type searchTvParam struct {
Query string `form:"query"`
Page int `form:"page"`
}
func (s *Server) SearchTvSeries(c *gin.Context) (interface{}, error) {
var q searchTvParam
if err := c.ShouldBindQuery(&q); err != nil {
return nil, errors.Wrap(err, "bind query")
}
log.Infof("search tv series with keyword: %v", q.Query)
r, err := s.MustTMDB().SearchTvShow(q.Query, "")
if err != nil {
return nil, errors.Wrap(err, "search tv")
}
return r, nil
}
func (s *Server) SearchMedia(c *gin.Context) (interface{}, error) {
var q searchTvParam
if err := c.ShouldBindQuery(&q); err != nil {
return nil, errors.Wrap(err, "bind query")
}
log.Infof("search media with keyword: %v", q.Query)
tmdb, err := s.TMDB()
if err != nil {
return nil, err
}
r, err := tmdb.SearchMedia(q.Query, s.language, q.Page)
if err != nil {
return nil, errors.Wrap(err, "search tv")
}
for i, res := range r.Results {
if s.db.TmdbIdInWatchlist(int(res.ID)) {
r.Results[i].InWatchlist = true
}
}
return r, nil
}
type addWatchlistIn struct {
TmdbID int `json:"tmdb_id" binding:"required"`
StorageID int `json:"storage_id" `
Resolution string `json:"resolution" binding:"required"`
Folder string `json:"folder" binding:"required"`
DownloadHistoryEpisodes bool `json:"download_history_episodes"` //for tv
SizeMin int `json:"size_min"`
SizeMax int `json:"size_max"`
}
func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
var in addWatchlistIn
if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query")
}
log.Debugf("add tv watchlist input %+v", in)
if in.Folder == "" {
return nil, errors.New("folder should be provided")
}
detailCn, err := s.MustTMDB().GetTvDetails(in.TmdbID, db.LanguageCN)
if err != nil {
return nil, errors.Wrap(err, "get tv detail")
}
var nameCn = detailCn.Name
detailEn, _ := s.MustTMDB().GetTvDetails(in.TmdbID, db.LanguageEN)
var nameEn = detailEn.Name
var detail *tmdb.TVDetails
if s.language == "" || s.language == db.LanguageCN {
detail = detailCn
} else {
detail = detailEn
}
log.Infof("find detail for tv id %d: %v", in.TmdbID, detail)
var epIds []int
for _, season := range detail.Seasons {
seasonId := season.SeasonNumber
se, err := s.MustTMDB().GetSeasonDetails(int(detail.ID), seasonId, s.language)
if err != nil {
log.Errorf("get season detail (%s) error: %v", detail.Name, err)
continue
}
for _, ep := range se.Episodes {
shouldMonitor := false
//如果设置下载往期剧集则监控所有剧集。如果没有则监控未上映的剧集考虑时差等问题留24h余量
if in.DownloadHistoryEpisodes {
shouldMonitor = true
} else {
t, err := time.Parse("2006-01-02", ep.AirDate)
if err != nil {
log.Error("air date not known, will monitor: %v", ep.AirDate)
shouldMonitor = true
} else {
if time.Since(t) < 24*time.Hour { //monitor episode air 24h before now
shouldMonitor = true
}
}
}
epid, err := s.db.SaveEposideDetail(&ent.Episode{
SeasonNumber: seasonId,
EpisodeNumber: ep.EpisodeNumber,
Title: ep.Name,
Overview: ep.Overview,
AirDate: ep.AirDate,
Monitored: shouldMonitor,
})
if err != nil {
log.Errorf("save episode info error: %v", err)
continue
}
epIds = append(epIds, epid)
}
}
m := &ent.Media{
TmdbID: int(detail.ID),
MediaType: media.MediaTypeTv,
NameCn: nameCn,
NameEn: nameEn,
OriginalName: detail.OriginalName,
Overview: detail.Overview,
AirDate: detail.FirstAirDate,
Resolution: media.Resolution(in.Resolution),
StorageID: in.StorageID,
TargetDir: in.Folder,
DownloadHistoryEpisodes: in.DownloadHistoryEpisodes,
Limiter: &schema.MediaLimiter{SizeMin: in.SizeMin, SizeMax: in.SizeMax},
}
r, err := s.db.AddMediaWatchlist(m, epIds)
if err != nil {
return nil, errors.Wrap(err, "add to list")
}
go func() {
if err := s.downloadPoster(detail.PosterPath, r.ID); err != nil {
log.Errorf("download poster error: %v", err)
}
if err := s.downloadBackdrop(detail.BackdropPath, r.ID); err != nil {
log.Errorf("download poster error: %v", err)
}
if err := s.core.CheckDownloadedSeriesFiles(r); err != nil {
log.Errorf("check downloaded files error: %v", err)
}
}()
log.Infof("add tv %s to watchlist success", detail.Name)
return nil, nil
}
func (s *Server) AddMovie2Watchlist(c *gin.Context) (interface{}, error) {
var in addWatchlistIn
if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query")
}
log.Infof("add movie watchlist input: %+v", in)
detailCn, err := s.MustTMDB().GetMovieDetails(in.TmdbID, db.LanguageCN)
if err != nil {
return nil, errors.Wrap(err, "get movie detail")
}
var nameCn = detailCn.Title
detailEn, _ := s.MustTMDB().GetMovieDetails(in.TmdbID, db.LanguageEN)
var nameEn = detailEn.Title
var detail *tmdb.MovieDetails
if s.language == "" || s.language == db.LanguageCN {
detail = detailCn
} else {
detail = detailEn
}
log.Infof("find detail for movie id %d: %v", in.TmdbID, detail)
epid, err := s.db.SaveEposideDetail(&ent.Episode{
SeasonNumber: 1,
EpisodeNumber: 1,
Title: "dummy episode for movies",
Overview: "dummy episode for movies",
AirDate: detail.ReleaseDate,
Monitored: true,
})
if err != nil {
return nil, errors.Wrap(err, "add dummy episode")
}
log.Infof("added dummy episode for movie: %v", nameEn)
r, err := s.db.AddMediaWatchlist(&ent.Media{
TmdbID: int(detail.ID),
MediaType: media.MediaTypeMovie,
NameCn: nameCn,
NameEn: nameEn,
OriginalName: detail.OriginalTitle,
Overview: detail.Overview,
AirDate: detail.ReleaseDate,
Resolution: media.Resolution(in.Resolution),
StorageID: in.StorageID,
TargetDir: in.Folder,
Limiter: &schema.MediaLimiter{SizeMin: in.SizeMin, SizeMax: in.SizeMax},
}, []int{epid})
if err != nil {
return nil, errors.Wrap(err, "add to list")
}
go func() {
if err := s.downloadPoster(detail.PosterPath, r.ID); err != nil {
log.Errorf("download poster error: %v", err)
}
if err := s.downloadBackdrop(detail.BackdropPath, r.ID); err != nil {
log.Errorf("download backdrop error: %v", err)
}
}()
log.Infof("add movie %s to watchlist success", detail.Title)
return nil, nil
}
func (s *Server) downloadBackdrop(path string, mediaID int) error {
url := "https://image.tmdb.org/t/p/original" + path
return s.downloadImage(url, mediaID, "backdrop.jpg")
}
func (s *Server) downloadPoster(path string, mediaID int) error {
var url = "https://image.tmdb.org/t/p/original" + path
return s.downloadImage(url, mediaID, "poster.jpg")
}
func (s *Server) downloadImage(url string, mediaID int, name string) error {
log.Infof("try to download image: %v", url)
var resp, err = http.Get(url)
if err != nil {
return errors.Wrap(err, "http get")
}
targetDir := fmt.Sprintf("%v/%d", db.ImgPath, mediaID)
os.MkdirAll(targetDir, 0777)
//ext := filepath.Ext(path)
targetFile := filepath.Join(targetDir, name)
f, err := os.Create(targetFile)
if err != nil {
return errors.Wrap(err, "new file")
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
if err != nil {
return errors.Wrap(err, "copy http response")
}
log.Infof("image successfully downlaoded: %v", targetFile)
return nil
}
type MediaWithStatus struct {
*ent.Media
MonitoredNum int `json:"monitored_num"`
DownloadedNum int `json:"downloaded_num"`
}
//missing: episode aired missing
//downloaded: all monitored episode downloaded
//monitoring: episode aired downloaded, but still has not aired episode
//for movie, only monitoring/downloaded
func (s *Server) GetTvWatchlist(c *gin.Context) (interface{}, error) {
list := s.db.GetMediaWatchlist(media.MediaTypeTv)
res := make([]MediaWithStatus, len(list))
for i, item := range list {
var ms = MediaWithStatus{
Media: item,
MonitoredNum: 0,
DownloadedNum: 0,
}
details := s.db.GetMediaDetails(item.ID)
for _, ep := range details.Episodes {
if ep.Monitored {
ms.MonitoredNum++
if ep.Status == episode.StatusDownloaded {
ms.DownloadedNum++
}
}
}
res[i] = ms
}
return res, nil
}
func (s *Server) GetMovieWatchlist(c *gin.Context) (interface{}, error) {
list := s.db.GetMediaWatchlist(media.MediaTypeMovie)
res := make([]MediaWithStatus, len(list))
for i, item := range list {
var ms = MediaWithStatus{
Media: item,
MonitoredNum: 1,
DownloadedNum: 0,
}
dummyEp, err := s.db.GetMovieDummyEpisode(item.ID)
if err != nil {
log.Errorf("get dummy episode: %v", err)
} else {
if dummyEp.Status == episode.StatusDownloaded {
ms.DownloadedNum++
}
}
res[i] = ms
}
return res, nil
}
type MediaDetails struct {
*db.MediaDetails
Storage *ent.Storage `json:"storage"`
}
func (s *Server) GetMediaDetails(c *gin.Context) (interface{}, error) {
ids := c.Param("id")
id, err := strconv.Atoi(ids)
if err != nil {
return nil, errors.Wrap(err, "convert")
}
detail := s.db.GetMediaDetails(id)
st := s.db.GetStorage(detail.StorageID)
return MediaDetails{MediaDetails: detail, Storage: &st.Storage}, nil
}
func (s *Server) DeleteFromWatchlist(c *gin.Context) (interface{}, error) {
ids := c.Param("id")
id, err := strconv.Atoi(ids)
if err != nil {
return nil, errors.Wrap(err, "convert")
}
if err := s.db.DeleteMedia(id); err != nil {
return nil, errors.Wrap(err, "delete db")
}
os.RemoveAll(filepath.Join(db.ImgPath, ids)) //delete image related
return "success", nil
}