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 }