Donovan Daniels
b1c702e3cd
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
201 lines
5.3 KiB
Ruby
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
|