399 lines
9.4 KiB
Go
399 lines
9.4 KiB
Go
package shows
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/odwrtw/polochon/lib"
|
|
)
|
|
|
|
const (
|
|
upsertShowQuery = `
|
|
INSERT INTO shows (imdb_id, title, rating, plot, tvdb_id, year, first_aired)
|
|
VALUES (:imdb_id, :title, :rating, :plot, :tvdb_id, :year, :first_aired)
|
|
ON CONFLICT (imdb_id)
|
|
DO UPDATE
|
|
SET imdb_id=:imdb_id, title=:title, rating=:rating, plot=:plot,
|
|
tvdb_id=:tvdb_id, year=:year, first_aired=:first_aired
|
|
RETURNING id;`
|
|
|
|
getShowQueryByImdbID = `
|
|
SELECT *
|
|
FROM shows WHERE imdb_id=$1;`
|
|
|
|
getShowQueryByID = `
|
|
SELECT *
|
|
FROM shows WHERE id=$1;`
|
|
|
|
deleteShowQueryByID = `DELETE FROM shows WHERE id=$1;`
|
|
|
|
getShowWithUserQueryByImdbID = `
|
|
SELECT
|
|
shows.id,
|
|
shows.imdb_id,
|
|
shows.title,
|
|
shows.rating,
|
|
shows.plot,
|
|
shows.tvdb_id,
|
|
shows.year,
|
|
shows.first_aired,
|
|
shows_tracked.season,
|
|
shows_tracked.episode,
|
|
shows.updated_at,
|
|
shows.created_at
|
|
FROM shows LEFT JOIN shows_tracked
|
|
ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
|
|
WHERE shows.imdb_id=$1;`
|
|
|
|
getShowWithUserQueryByID = `
|
|
SELECT
|
|
shows.id,
|
|
shows.imdb_id,
|
|
shows.title,
|
|
shows.rating,
|
|
shows.plot,
|
|
shows.tvdb_id,
|
|
shows.year,
|
|
shows.first_aired,
|
|
shows_tracked.season,
|
|
shows_tracked.episode,
|
|
shows.updated_at,
|
|
shows.created_at
|
|
FROM shows LEFT JOIN shows_tracked ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
|
|
WHERE shows.id=$1;`
|
|
)
|
|
|
|
var (
|
|
// ErrNotFound error returned when show not found in database
|
|
ErrNotFound = fmt.Errorf("Not found")
|
|
)
|
|
|
|
// Show represents a show
|
|
type Show struct {
|
|
sqly.BaseModel
|
|
polochon.Show
|
|
Episodes []*Episode `json:"episodes"`
|
|
TrackedSeason *int `json:"tracked_season"`
|
|
TrackedEpisode *int `json:"tracked_episode"`
|
|
BannerURL string `json:"banner_url"`
|
|
FanartURL string `json:"fanart_url"`
|
|
PosterURL string `json:"poster_url"`
|
|
}
|
|
|
|
// ShowDB represents the Show in the DB
|
|
type ShowDB struct {
|
|
ID string `db:"id"`
|
|
ImdbID string `db:"imdb_id"`
|
|
TvdbID int `db:"tvdb_id"`
|
|
TrackedSeason *int `db:"season"`
|
|
TrackedEpisode *int `db:"episode"`
|
|
Title string `db:"title"`
|
|
Rating float32 `db:"rating"`
|
|
Plot string `db:"plot"`
|
|
Year int `db:"year"`
|
|
FirstAired time.Time `db:"first_aired"`
|
|
Created time.Time `db:"created_at"`
|
|
Updated time.Time `db:"updated_at"`
|
|
}
|
|
|
|
// NewShowDB returns a Show ready to be put in DB from a
|
|
// Show
|
|
func NewShowDB(s *Show) ShowDB {
|
|
sDB := ShowDB{
|
|
ID: s.ID,
|
|
ImdbID: s.ImdbID,
|
|
Title: s.Title,
|
|
Rating: s.Rating,
|
|
Plot: s.Plot,
|
|
TvdbID: s.TvdbID,
|
|
Year: s.Year,
|
|
Created: s.Created,
|
|
Updated: s.Updated,
|
|
}
|
|
if s.FirstAired != nil {
|
|
sDB.FirstAired = *s.FirstAired
|
|
}
|
|
return sDB
|
|
}
|
|
|
|
// FillFromDB returns a Show from a ShowDB extracted from the DB
|
|
func (s *Show) FillFromDB(sDB *ShowDB) {
|
|
s.ID = sDB.ID
|
|
s.ImdbID = sDB.ImdbID
|
|
s.Title = sDB.Title
|
|
s.Rating = sDB.Rating
|
|
s.Plot = sDB.Plot
|
|
s.TvdbID = sDB.TvdbID
|
|
s.Year = sDB.Year
|
|
s.FirstAired = &sDB.FirstAired
|
|
s.Created = sDB.Created
|
|
s.Updated = sDB.Updated
|
|
s.TrackedSeason = sDB.TrackedSeason
|
|
s.TrackedEpisode = sDB.TrackedEpisode
|
|
}
|
|
|
|
// New returns a new Show with a polochon ShowConfig
|
|
func New(imdbID string) *Show {
|
|
return &Show{
|
|
Show: polochon.Show{
|
|
ImdbID: imdbID,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Get returns a show with user info like tracked
|
|
func (s *Show) Get(env *web.Env, user *users.User) error {
|
|
var err error
|
|
var sDB ShowDB
|
|
if s.ID != "" {
|
|
err = env.Database.QueryRowx(getShowWithUserQueryByID, s.ID, user.ID).StructScan(&sDB)
|
|
} else if s.ImdbID != "" {
|
|
err = env.Database.QueryRowx(getShowWithUserQueryByImdbID, s.ImdbID, user.ID).StructScan(&sDB)
|
|
} else {
|
|
err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Set the poster url
|
|
s.PosterURL = s.GetPosterURL(env)
|
|
|
|
s.FillFromDB(&sDB)
|
|
return nil
|
|
}
|
|
|
|
// GetDetails retrieves details for the show, first try to
|
|
// get info from db, if not exists, use polochon.Detailer
|
|
// and save informations in the database for future use
|
|
func (s *Show) GetDetails(env *web.Env, user *users.User, force bool) error {
|
|
log := env.Log.WithFields(logrus.Fields{
|
|
"imdb_id": s.ImdbID,
|
|
"function": "shows.GetDetails",
|
|
})
|
|
log.Debugf("getting details")
|
|
|
|
if len(s.Detailers) == 0 {
|
|
s.Detailers = env.Config.ShowDetailers
|
|
}
|
|
|
|
var err error
|
|
err = s.Get(env, user)
|
|
switch err {
|
|
case nil:
|
|
log.Debug("show found in database")
|
|
case sql.ErrNoRows:
|
|
log.Debug("show not found in database")
|
|
default:
|
|
// Unexpected error
|
|
return err
|
|
}
|
|
// If force is not specified, don't go further
|
|
if !force {
|
|
// Will return ErrNoRows if the show wasn't found
|
|
return err
|
|
}
|
|
|
|
// GetDetail
|
|
err = s.Show.GetDetails(env.Log)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debug("got details from detailers")
|
|
s.Episodes = []*Episode{}
|
|
for _, pe := range s.Show.Episodes {
|
|
s.Episodes = append(s.Episodes, &Episode{ShowEpisode: *pe})
|
|
}
|
|
|
|
err = s.Upsert(env.Database)
|
|
if err != nil {
|
|
log.Debug("error while doing show upsert func", err)
|
|
return err
|
|
}
|
|
|
|
log.Debug("show added in database")
|
|
|
|
// Download show images
|
|
s.downloadImages(env)
|
|
|
|
log.Debug("images downloaded")
|
|
|
|
// Set the poster url
|
|
s.PosterURL = s.GetPosterURL(env)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
|
|
func (s *Show) GetPosterURL(env *web.Env) string {
|
|
// Check if the show image exists
|
|
if _, err := os.Stat(s.imgFile(env, "poster")); os.IsNotExist(err) {
|
|
// TODO image in the config ?
|
|
return "img/noimage.png"
|
|
}
|
|
return s.imgURL(env, "poster")
|
|
}
|
|
|
|
// downloadImages will download the show images
|
|
func (s *Show) downloadImages(env *web.Env) {
|
|
// Download the banner
|
|
err := web.Download(s.Show.Banner, s.imgFile(env, "banner"))
|
|
if err != nil {
|
|
env.Log.Errorf("failed to dowload banner: %s", err)
|
|
}
|
|
err = web.Download(s.Show.Fanart, s.imgFile(env, "fanart"))
|
|
if err != nil {
|
|
env.Log.Errorf("failed to dowload fanart: %s", err)
|
|
}
|
|
err = web.Download(s.Show.Poster, s.imgFile(env, "poster"))
|
|
if err != nil {
|
|
env.Log.Errorf("failed to dowload poster: %s", err)
|
|
}
|
|
}
|
|
|
|
// imgFile returns the image location on disk
|
|
func (s *Show) imgFile(env *web.Env, imgType string) string {
|
|
fileURL := fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType)
|
|
return filepath.Join(env.Config.PublicDir, fileURL)
|
|
}
|
|
|
|
// imgURL returns the default image url
|
|
func (s *Show) imgURL(env *web.Env, imgType string) string {
|
|
return fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType)
|
|
}
|
|
|
|
// Upsert a show in the database
|
|
func (s *Show) Upsert(db *sqlx.DB) error {
|
|
sDB := NewShowDB(s)
|
|
var id string
|
|
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for r.Next() {
|
|
r.Scan(&id)
|
|
}
|
|
s.ID = id
|
|
|
|
for _, e := range s.Episodes {
|
|
e.ShowImdbID = s.ImdbID
|
|
err = e.Upsert(db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete show from database
|
|
func (s *Show) Delete(db *sqlx.DB) error {
|
|
r, err := db.Exec(deleteShowQueryByID, s.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
count, _ := r.RowsAffected()
|
|
if count != 1 {
|
|
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetEpisodes from database
|
|
func (s *Show) GetEpisodes(env *web.Env, force bool) error {
|
|
// We retrive episode's info from database populate the s.Episodes member
|
|
var episodesDB = []*EpisodeDB{}
|
|
err := env.Database.Select(&episodesDB, getEpisodesQuery, s.ImdbID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(episodesDB) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, episodeDB := range episodesDB {
|
|
episode := NewEpisode()
|
|
episode.FillFromDB(episodeDB)
|
|
s.Episodes = append(s.Episodes, episode)
|
|
err = episode.GetTorrents(env, force)
|
|
if err != nil {
|
|
env.Log.Debugf("error while getting episode torrent: %q", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetEpisode from database
|
|
func (s *Show) GetEpisodeDetails(env *web.Env, seasonNb, episodeNb int, force bool) (*Episode, error) {
|
|
log := env.Log.WithFields(logrus.Fields{
|
|
"imdb_id": s.ImdbID,
|
|
"season": seasonNb,
|
|
"episode": episodeNb,
|
|
"function": "show.GetEpisodeDetails",
|
|
})
|
|
log.Debugf("getting episode details")
|
|
|
|
e := NewEpisode()
|
|
e.ShowImdbID = s.ImdbID
|
|
e.Season = seasonNb
|
|
e.Episode = episodeNb
|
|
|
|
if len(e.Detailers) == 0 {
|
|
e.Detailers = env.Config.ShowDetailers
|
|
}
|
|
|
|
var err error
|
|
err = e.Get(env)
|
|
switch err {
|
|
case nil:
|
|
log.Debug("episode found in database")
|
|
case sql.ErrNoRows:
|
|
log.Debug("episode not found in database")
|
|
default:
|
|
// Unexpected error
|
|
return nil, err
|
|
}
|
|
// If force is not specified, don't go further
|
|
if !force {
|
|
// Will return ErrNoRows if the episode wasn't found
|
|
return e, err
|
|
}
|
|
|
|
// GetDetail of the episode
|
|
err = e.ShowEpisode.GetDetails(env.Log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Upsert the episode
|
|
err = e.Upsert(env.Database)
|
|
if err != nil {
|
|
log.Debug("error while doing episode upsert func", err)
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("episode inserted/updated in database")
|
|
|
|
return e, nil
|
|
}
|
|
|
|
// GetTorrents from the database or fetch them if needed
|
|
func (s *Show) GetTorrents(env *web.Env, force bool) error {
|
|
for _, e := range s.Episodes {
|
|
err := e.GetTorrents(env, force)
|
|
if err != nil {
|
|
env.Log.Errorf("error while getting episode torrent: %s", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|