Websites/app/models/api_image.rb
Donovan Daniels b1c702e3cd
Add image management ui
poorly tested but it worked well enough, I'm sure I'll be patching bugs over the next few weeks
Also remove turbo because it sucks
Also changed the way we handle hosts in dev
2024-05-06 03:25:17 -05:00

201 lines
5.3 KiB
Ruby

# frozen_string_literal: true
class APIImage < ApplicationRecord
belongs_to_creator
attr_accessor :file, :exception
validates :category, presence: true, inclusion: { in: -> { APIImage.categories.map(&:db) } }
validates :id, uniqueness: true, on: :file
validate on: :file do |image|
ext, mime = file_header_info(file.path)
image.errors.add(:file, "type is invalid (#{mime})") if ext == mime
end
validate do |image|
image.errors.add(:base, exception.message) if exception
end
after_create :invalidate_cache
after_update :update_files, if: :saved_change_to_category?
after_destroy :delete_files
def delete_files
Websites.config.yiffy2_storage.delete(path)
invalidate_cache
end
def update_files
file = Websites.config.yiffy2_storage.get(path_before_last_save)
Websites.config.yiffy2_storage.put(path, file)
Websites.config.yiffy2_storage.delete(path_before_last_save)
invalidate_cache
end
def file_header_info(file_path)
File.open(file_path) do |bin|
mime_type = Marcel::MimeType.for(bin)
ext = case mime_type
when "image/jpeg"
"jpg"
when "image/gif"
"gif"
when "image/png"
"png"
else
mime_type
end
[mime_type, ext]
end
end
def invalidate_cache
Cache.redis.del("yiffy2:images:#{category_before_last_save}")
Cache.redis.del("yiffy2:images:#{category}")
end
module SearchMethods
def random(category, limit, size_limit = nil)
q = where(category: category)
q = q.where(file_size: ..size_limit) if size_limit
q.ids.sample(limit).map(&method(:find))
end
def bulk(category_map, size_limit = nil)
data = {}
category_map.each do |category, limit|
data[category] = random(category, limit, size_limit)
end
data
end
end
extend SearchMethods
def md5
id.gsub("-", "")
end
alias stripped_md5 md5
def path_before_last_save
"/#{category_before_last_save.tr('.', '/')}/#{md5}.#{file_ext}"
end
def path
"/#{category.tr('.', '/')}/#{md5}.#{file_ext}"
end
def url
Websites.config.yiffy2_storage.url_for(self)
end
def short_url
ShortUrl.override(md5, url).shorturl
end
def sources_string
sources.join("\n")
end
def sources_string=(str)
self.sources = str.split("\n").map(&:strip)
end
def artists_string
artists.join("\n")
end
def artists_string=(str)
self.artists = str.split("\n").map(&:strip)
end
def serializable_hash(*)
{
artists: artists,
sources: sources,
width: width,
height: height,
url: url,
type: mime_type,
name: "#{md5}.#{file_ext}",
id: md5,
ext: file_ext,
size: file_size,
reportURL: nil,
shortURL: short_url,
}
end
def self.categories
sfw = %w[animals.birb animals.blep animals.dikdik furry.boop furry.cuddle furry.flop furry.fursuit furry.hold furry.howl furry.hug furry.kiss furry.lick furry.propose]
nsfw = %w[furry.butts furry.bulge furry.yiff.andromorph furry.yiff.gay furry.yiff.gynomorph furry.yiff.lesbian furry.yiff.straight]
titles = {
**sfw.index_with { |c| c.split(".").map(&:capitalize).join(" > ") },
"animals.dikdik" => "Animals > Dik Dik",
**nsfw.index_with { |c| c.split(".").map(&:capitalize).join(" > ") },
}
sfw.map { |cat| APICategory.new(titles[cat], cat, true) }
.concat(nsfw.map { |cat| APICategory.new(titles[cat], cat, false) })
end
def self.category_title(db)
categories.find { |k| k == db }.try(:db) || db.split(".").map(&:capitalize).join(" > ")
end
def self.cached_count(category)
count = Cache.redis.get("yiffy2:images:#{category}")
if count.nil?
count = APIImage.where(category: category).count
# images aren't changing so we really don't need any expiry, but I still want an expiry just in case
Cache.redis.set("yiffy2:images:#{category}", count.to_s, ex: 60 * 60 * 24 * 7)
end
count.to_i
end
def self.state
data = group(:category).count.map do |k, v|
{
count: v,
name: category_title(k),
category: k,
state: v < 5 ? "red" : v < 20 ? "yellow" : "green",
}
end
[*data, {
count: data.pluck(:count).reduce(:+),
name: "Total",
category: "total",
state: "total",
},]
end
def self.sync_all
sync_e621
end
# TODO
def self.sync_e621
images = joins("CROSS JOIN unnest(sources) AS source").where("source ILIKE ?", "%e621.net%").distinct.limit(300)
ids = []
md5s = []
images.each do |img|
img.sources.each do |source|
next unless source.include?("e621.net")
id = source.split("/").last
ids << id.to_i if id.to_i.to_s == id
end
md5s << img.md5
end
Requests::E621.get_all_posts(ids: ids, md5s: md5s) => { posts:, missing: }
Rails.logger.info("Failed to find #{missing[:ids].length} posts") unless missing[:ids].empty?
mismatch = images.select { |img| posts.none? { |post| post["file"]["md5"] == img.md5 } }
Rails.logger.info("Found #{mismatch.length} mismatched images") unless mismatch.empty?
posts
end
def sync
APIImageSyncJob.perform_later(self)
end
end