394 lines
9.3 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.created_at,
shows.updated_at,
COALESCE(shows_tracked.season,0),
COALESCE(shows_tracked.episode,0) AS trackedepisode
FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_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.created_at,
shows.updated_at,
COALESCE(shows_tracked.season,0),
COALESCE(shows_tracked.episode,0)
FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_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"`
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 {
return ShowDB{
ID: s.ID,
ImdbID: s.ImdbID,
Title: s.Title,
Rating: s.Rating,
Plot: s.Plot,
TvdbID: s.TvdbID,
Year: s.Year,
FirstAired: *s.FirstAired,
Created: s.Created,
Updated: s.Updated,
}
}
// 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
}
// New returns a new Show with a polochon ShowConfig
func New(imdbID string) *Show {
return &Show{
Show: polochon.Show{
ImdbID: imdbID,
},
}
}
// Get returns show details in database from id or imdbid or an error
func (s *Show) Get(env *web.Env) error {
var sDB ShowDB
var err error
if s.ID != "" {
err = env.Database.QueryRowx(getShowQueryByID, s.ID).StructScan(&sDB)
} else if s.ImdbID != "" {
err = env.Database.QueryRowx(getShowQueryByImdbID, s.ImdbID).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
}
// GetAsUser returns a show with user info like tracked
func (s *Show) GetAsUser(db *sqlx.DB, user *users.User) error {
var err error
if s.ID != "" {
err = db.QueryRowx(getShowWithUserQueryByID, s.ID, user.ID).StructScan(s)
} else if s.ImdbID != "" {
err = db.QueryRowx(getShowWithUserQueryByImdbID, s.ImdbID, user.ID).StructScan(s)
} else {
err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID")
}
if err != nil {
if err.Error() == "sql: no rows in result set" {
return ErrNotFound
}
return err
}
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, 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)
switch err {
case nil:
log.Debug("show found in database")
if !force {
log.Debug("returning show from db")
return nil
}
case sql.ErrNoRows:
log.Debug("show not found in database")
default:
// Unexpected error
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 movie 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.imgURL(env, "banner"))
if err != nil {
env.Log.Errorf("failed to dowload banner: %s", err)
}
err = web.Download(s.Show.Fanart, s.imgURL(env, "fanart"))
if err != nil {
env.Log.Errorf("failed to dowload fanart: %s", err)
}
err = web.Download(s.Show.Poster, s.imgURL(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)
}
// GetDetailsAsUser like GetDetails but with User context
func (s *Show) GetDetailsAsUser(db *sqlx.DB, user *users.User, log *logrus.Entry) error {
var err error
err = s.GetAsUser(db, user)
if err == nil {
// found ok
return nil
}
if err != ErrNotFound {
// Unexpected error
return err
}
err = s.Show.GetDetails(log)
if err != nil {
return err
}
s.Episodes = []*Episode{}
for _, pe := range s.Show.Episodes {
s.Episodes = append(s.Episodes, &Episode{ShowEpisode: *pe})
}
err = s.Upsert(db)
if err != nil {
return err
}
return nil
}
// IsTracked returns true if the show is tracked use this function
// after retrieve the show with GetAsUser or other *AsUser functions
func (s *Show) IsTracked() bool {
if s.TrackedSeason != 0 && s.TrackedEpisode != 0 {
return true
}
return false
}
// 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) 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, false)
if err != nil {
env.Log.Debugf("error while getting episode torrent: %q", err)
}
}
return 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
}