357 lines
8.5 KiB
Go

package movies
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
"github.com/Sirupsen/logrus"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/odwrtw/polochon/lib"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/torrents"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
)
const (
upsertMovieQuery = `
INSERT INTO movies (imdb_id, title, rating, votes, plot, tmdb_id, year,
genres, original_title, runtime, sort_title, tagline)
VALUES (:imdb_id, :title, :rating, :votes, :plot, :tmdb_id, :year, :genres,
:original_title, :runtime, :sort_title, :tagline)
ON CONFLICT (imdb_id)
DO UPDATE
SET imdb_id=:imdb_id, title=:title, rating=:rating, votes=:votes,
plot=:plot, tmdb_id=:tmdb_id, year=:year, genres=:genres,
original_title=:original_title, runtime=:runtime, sort_title=:sort_title,
tagline=:tagline
RETURNING id;`
getMovieQueryByImdbID = `
SELECT
movies.id,
movies.title,
movies.imdb_id,
movies.tmdb_id,
movies.votes,
movies.rating,
movies.plot,
movies.year,
movies.original_title,
movies.runtime,
movies.genres,
movies.sort_title,
movies.tagline,
movies.created_at,
movies.updated_at,
movies_tracked.user_id
FROM movies LEFT JOIN movies_tracked
ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2
WHERE movies.imdb_id=$1;`
getMovieQueryByID = `
SELECT
movies.id,
movies.title,
movies.imdb_id,
movies.tmdb_id,
movies.votes,
movies.rating,
movies.plot,
movies.year,
movies.original_title,
movies.runtime,
movies.genres,
movies.sort_title,
movies.tagline,
movies.created_at,
movies.updated_at,
movies_tracked.user_id
FROM movies LEFT JOIN movies_tracked
ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2
WHERE movies.id=$1;`
deleteMovieQuery = `DELETE FROM movies WHERE id=$1;`
)
// MovieDB represents the Movie in the DB
type MovieDB struct {
ID string `db:"id"`
ImdbID string `db:"imdb_id"`
TmdbID int `db:"tmdb_id"`
UserID *string `db:"user_id"`
Title string `db:"title"`
OriginalTitle string `db:"original_title"`
SortTitle string `db:"sort_title"`
Rating float32 `db:"rating"`
Votes int `db:"votes"`
Plot string `db:"plot"`
Year int `db:"year"`
Runtime int `db:"runtime"`
Tagline string `db:"tagline"`
Genres pq.StringArray `db:"genres"`
Created time.Time `db:"created_at"`
Updated time.Time `db:"updated_at"`
}
// NewMovieDB returns a Movie ready to be put in DB from a
// Movie
func NewMovieDB(m *Movie) MovieDB {
genres := []string{}
if m.Genres != nil {
genres = m.Genres
}
return MovieDB{
ID: m.ID,
ImdbID: m.ImdbID,
Title: m.Title,
Rating: m.Rating,
Votes: m.Votes,
Plot: m.Plot,
TmdbID: m.TmdbID,
Year: m.Year,
OriginalTitle: m.OriginalTitle,
Runtime: m.Runtime,
SortTitle: m.SortTitle,
Tagline: m.Tagline,
Genres: genres,
Created: m.Created,
Updated: m.Updated,
}
}
// FillFromDB returns a Movie from a MovieDB extracted from the DB
func (m *Movie) FillFromDB(mDB *MovieDB) {
m.Created = mDB.Created
m.Updated = mDB.Updated
m.ID = mDB.ID
m.ImdbID = mDB.ImdbID
m.Title = mDB.Title
m.Rating = mDB.Rating
m.Votes = mDB.Votes
m.Plot = mDB.Plot
m.TmdbID = mDB.TmdbID
m.Year = mDB.Year
m.OriginalTitle = mDB.OriginalTitle
m.Runtime = mDB.Runtime
m.Genres = mDB.Genres
m.SortTitle = mDB.SortTitle
m.Tagline = mDB.Tagline
if mDB.UserID != nil {
m.Wishlisted = true
}
}
// Movie represents a movie
type Movie struct {
sqly.BaseModel
polochon.Movie
Wishlisted bool `json:"wishlisted"`
PolochonURL string `json:"polochon_url"`
PosterURL string `json:"poster_url"`
}
// New returns a new Movie with an ImDB id
func New(imdbID string) *Movie {
return &Movie{
Movie: polochon.Movie{
ImdbID: imdbID,
},
}
}
// Get returns show details in database from id or imdbid or an error
func (m *Movie) Get(env *web.Env, user *users.User) error {
var mDB MovieDB
var err error
if m.ID != "" {
err = env.Database.QueryRowx(getMovieQueryByID, m.ID, user.ID).StructScan(&mDB)
} else if m.ImdbID != "" {
err = env.Database.QueryRowx(getMovieQueryByImdbID, m.ImdbID, user.ID).StructScan(&mDB)
} else {
err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID")
}
if err != nil {
return err
}
// Set the poster url
m.PosterURL = m.GetPosterURL(env)
m.FillFromDB(&mDB)
return nil
}
// GetDetails retrieves details for the movie, first try to get info from db,
// if not exists, use polochon.Detailer and save informations in the database
// for future use
//
// If force is used, the detailer will be used even if the movie is found in
// database
func (m *Movie) GetDetails(env *web.Env, user *users.User, force bool) error {
log := env.Log.WithFields(logrus.Fields{
"imdb_id": m.ImdbID,
"function": "movies.GetDetails",
})
log.Debugf("getting details")
if len(m.Detailers) == 0 {
m.Detailers = env.Config.MovieDetailers
}
var err error
err = m.Get(env, user)
switch err {
case nil:
log.Debug("movie found in database")
case sql.ErrNoRows:
log.Debug("movie 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 movie wasn't found
return err
}
// GetDetail
err = m.Movie.GetDetails(env.Log)
if err != nil {
return err
}
log.Debug("got details from detailers")
err = m.Upsert(env.Database)
if err != nil {
log.Debug("error while doing db func")
return err
}
log.Debug("movie added in database")
// Download poster
err = web.Download(m.Thumb, m.imgFile(env))
if err != nil {
return err
}
log.Debug("poster downloaded")
// Set the poster url
m.PosterURL = m.GetPosterURL(env)
return nil
}
// GetTorrents retrieves torrents for the movie, first try to get info from db,
// if not exists, use polochon.Torrenter and save informations in the database
// for future use
//
// If force is used, the torrenter will be used even if the torrent is found in
// database
func (m *Movie) GetTorrents(env *web.Env, force bool) error {
log := env.Log.WithFields(logrus.Fields{
"imdb_id": m.ImdbID,
"function": "movies.GetTorrents",
})
log.Debugf("getting torrents")
if len(m.Torrenters) == 0 {
m.Torrenters = env.Config.MovieTorrenters
}
movieTorrents, err := torrents.GetMovieTorrents(env.Database, m.ImdbID)
switch err {
case nil:
log.Debug("torrents found in database")
case sql.ErrNoRows:
log.Debug("torrent not found in database")
default:
// Unexpected error
return err
}
if !force {
log.Debugf("returning %d torrents from db", len(movieTorrents))
// Add the torrents to the movie
for _, t := range movieTorrents {
m.Torrents = append(m.Torrents, t.Torrent)
}
return nil
}
err = m.Movie.GetTorrents(env.Log)
if err != nil {
return err
}
log.Debugf("got %d torrents from torrenters", len(m.Movie.Torrents))
for _, t := range m.Movie.Torrents {
torrent := torrents.NewMovie(m.ImdbID, t)
err = torrent.Upsert(env.Database)
if err != nil {
log.Error("error while adding torrent", err)
continue
}
}
return nil
}
// Upsert a movie in the database
func (m *Movie) Upsert(db *sqlx.DB) error {
mDB := NewMovieDB(m)
var id string
r, err := db.NamedQuery(upsertMovieQuery, mDB)
if err != nil {
return err
}
for r.Next() {
r.Scan(&id)
}
m.ID = id
return nil
}
// Delete movie from database
func (m *Movie) Delete(db *sqlx.DB) error {
r, err := db.Exec(deleteMovieQuery, m.ID)
if err != nil {
return err
}
count, _ := r.RowsAffected()
if count != 1 {
return fmt.Errorf("Unexpected number of row deleted: %d", count)
}
return nil
}
// imgURL returns the default image url
func (m *Movie) imgURL(env *web.Env) string {
return fmt.Sprintf("img/movies/%s.jpg", m.ImdbID)
}
// imgFile returns the image location on disk
func (m *Movie) imgFile(env *web.Env) string {
return filepath.Join(env.Config.PublicDir, m.imgURL(env))
}
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
func (m *Movie) GetPosterURL(env *web.Env) string {
// Check if the movie image exists
if _, err := os.Stat(m.imgFile(env)); os.IsNotExist(err) {
// TODO image in the config ?
return "img/noimage.png"
}
return m.imgURL(env)
}