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) }