app/models/movie.rb
class Movie < ApplicationRecord
include Base64Images
include PgSearch::Model
multisearchable against: [:title]
has_many :releases, class_name: "MovieRelease", dependent: :destroy, autosave: true
has_many :news_items, as: :newsworthy, dependent: :destroy
has_many :release_dates, class_name: "MovieReleaseDate", dependent: :destroy
before_create :add_key
after_commit :fetch_details, :on => :create
after_commit on: :create do
FetchMovieReleaseDatesJob.perform_later self
end
scope :downloadable, (lambda do
where("(waitlist = false AND download_at IS NULL) OR download_at < current_timestamp")
.includes(:releases)
.order(Arel.sql("download_at IS NOT NULL DESC, download_at desc, id desc"))
end)
scope :on_waitlist, ->{where("waitlist = true AND (download_at IS NULL OR download_at > current_timestamp)")}
scope :watched, ->{where(watched: true)}
scope :unwatched, ->{where(watched: false)}
scope :upcoming, (lambda do
unwatched
.where("movies.id IN (select movie_id from movie_release_dates where release_date > current_timestamp)")
.where("release_date >= ?", 2.years.ago)
end)
scope :with_better_release_than_downloaded, (lambda do
where.not(id: MovieRelease.where(resolution: "2160p", downloaded: true, source: "blu-ray").select(:movie_id))
.where(watched: false)
.where("download_at >= ?", 12.months.ago)
end)
base64_image :backdrop_image, :poster_image
def self.check_for_better_releases_than_downloaded
with_better_release_than_downloaded.each do |movie|
movie.check_for_better_releases_than_downloaded
# TODO: Replace with some kind of rate limiter
sleep 10 unless Rails.env.test?
end
end
def check_for_better_releases_than_downloaded
fetch_new_releases
save
if has_better_release_than_downloaded?
update(download_at: DateTime.now)
# TODO: Change message to include quality of release
NotifyHuginnJob.perform_later "A better release for #{title} has been found and will be downloaded."
end
end
def download
release = best_release
return if release.blank?
best_release.update downloaded: true
best_release.download_url
end
def fetch_images
update tmdb_images: Tmdb::Movie.images(imdb_id)
end
def fetch_new_releases
return if ptp_movie.blank?
ptp_movie_releases = ptp_movie.releases
ptp_movie_releases.each do |ptp_release|
existing_release = releases.find do |release|
release.ptp_movie_id == ptp_release.id
end
release = existing_release || releases.build(ptp_movie_id: ptp_release.id, auth_key: ptp_movie.auth_key)
release.attributes = ptp_release.to_h.except(:id)
end
self.releases = releases.select do |release|
ptp_movie_releases.map(&:id).include?(release.ptp_movie_id)
end
end
# TODO: Refactoring opportunity. This is kind of gunky
def fetch_release_dates
qualities = ["Unknown", "Digital HD", "Blu-ray", "4K UHD"]
struct = Struct.new(:release_date, :release_type, :quality)
release_dates.delete_all
data = Services::N8n::Api.new.release_dates(title)
data.map do |item|
type = if item.type.include? "4K UHD"
"4K UHD"
elsif item.type.include? "Blu-ray"
"Blu-ray"
elsif item.type.include? "Digital HD"
"Digital HD"
end
next nil unless type.present?
quality = qualities.index(type)
struct.new(item.release_date, type, quality)
end.compact.uniq.group_by(&:release_date).flat_map do |_type, group|
group.max_by(&:quality)
end.each do |item|
release_dates.create(release_date: item.release_date, release_type: item.release_type)
end
update available_date: release_dates.minimum(:release_date)
end
# TODO: Refactor
def has_better_release_than_downloaded?
downloaded_release = best_release(&:downloaded?)
return true if downloaded_release.blank? && acceptable_releases.any?
better_source = downloaded_release.try(:source_points).to_i < best_release.try(:source_points).to_i
better_resolution = downloaded_release.try(:resolution_points).to_i < best_release.try(:resolution_points).to_i
equal_resolution = downloaded_release.try(:resolution_points).to_i <= best_release.try(:resolution_points).to_i
better_resolution || (equal_resolution && better_source)
end
def deletable?
waitlist? && (download_at.blank? || download_at >= DateTime.now)
end
def poster_image(size = 1280)
return nil unless tmdb_images.key?("posters") && tmdb_images["posters"].any?
image = tmdb_images["posters"][0]["file_path"]
"#{Broad.tmdb_configuration.secure_base_url}w#{size}/#{image}"
end
def backdrop_image
return nil unless tmdb_images.key?("backdrops") && tmdb_images["backdrops"].any?
image = best_image(tmdb_images["backdrops"])["file_path"]
"#{Broad.tmdb_configuration.secure_base_url}original#{image}"
end
def ptp_movie
@ptp_movie ||= ptp_api.search(imdb_id).movie
end
def acceptable_releases(rule_klass: Domain::Ptp::ReleaseRules::Default)
Domain::AcceptableReleases.new(releases, rule_klass: rule_klass).select do |release|
block_given? ? (yield release) : true
end
end
def waitlist_releases(rule_klass: Domain::Ptp::ReleaseRules::Waitlist)
Domain::AcceptableReleases.new(releases, rule_klass: rule_klass).select do |release|
block_given? ? (yield release) : true
end
end
def killer_releases(rule_klass: Domain::Ptp::ReleaseRules::Killer)
Domain::AcceptableReleases.new(releases, rule_klass: rule_klass)
end
def has_acceptable_release?(&block)
acceptable_releases(&block).any?
end
def has_waitlist_release?(&block)
waitlist_releases(&block).any?
end
def has_killer_release?
killer_releases.any?
end
def best_release(rule_klass: Domain::Ptp::ReleaseRules::Default, &block)
acceptable_releases(rule_klass: rule_klass, &block).max
end
private
# Not used
def has_release?(ptp_release)
releases.find_by(ptp_movie_id: ptp_release.id).present?
end
def best_image(images)
images_4k = images.select{|image| image["width"] == 3840}
images_4k.first || images.first
end
def add_key
self.key = SecureRandom.urlsafe_base64
end
def fetch_details
FetchMovieDetailsJob.perform_later self
FetchMovieImagesJob.perform_later self
end
def ptp_api
@ptp_api ||= Services::Ptp::Api.new
end
end