Flash is dead. It's no longer supported by browsers, it's not well-supported by emulators, and only two Flash posts were uploaded in the last year anyway. Old Flash files will continue to exist, but new Flash uploads will no longer be allowed.
232 lines
6.8 KiB
Ruby
232 lines
6.8 KiB
Ruby
class Upload < ApplicationRecord
|
|
class Error < StandardError; end
|
|
|
|
MAX_VIDEO_DURATION = 140
|
|
|
|
class FileValidator < ActiveModel::Validator
|
|
def validate(record)
|
|
validate_file_ext(record)
|
|
validate_integrity(record)
|
|
validate_md5_uniqueness(record)
|
|
validate_video_duration(record)
|
|
validate_resolution(record)
|
|
end
|
|
|
|
def validate_file_ext(record)
|
|
if record.file_ext.in?(["bin", "swf"])
|
|
record.errors.add(:file_ext, "is invalid (only JPEG, PNG, GIF, MP4, and WebM files are allowed")
|
|
end
|
|
end
|
|
|
|
def validate_integrity(record)
|
|
if record.media_file.is_corrupt?
|
|
record.errors.add(:file, "is corrupted")
|
|
end
|
|
end
|
|
|
|
def validate_md5_uniqueness(record)
|
|
if record.md5.nil?
|
|
return
|
|
end
|
|
|
|
md5_post = Post.find_by_md5(record.md5)
|
|
|
|
if md5_post.nil?
|
|
return
|
|
end
|
|
|
|
if record.replaced_post && record.replaced_post == md5_post
|
|
return
|
|
end
|
|
|
|
record.errors.add(:md5, "duplicate: #{md5_post.id}")
|
|
end
|
|
|
|
def validate_resolution(record)
|
|
resolution = record.image_width.to_i * record.image_height.to_i
|
|
|
|
if resolution > Danbooru.config.max_image_resolution
|
|
record.errors.add(:base, "image resolution is too large (resolution: #{(resolution / 1_000_000.0).round(1)} megapixels (#{record.image_width}x#{record.image_height}); max: #{Danbooru.config.max_image_resolution / 1_000_000} megapixels)")
|
|
elsif record.image_width > Danbooru.config.max_image_width
|
|
record.errors.add(:image_width, "is too large (width: #{record.image_width}; max width: #{Danbooru.config.max_image_width})")
|
|
elsif record.image_height > Danbooru.config.max_image_height
|
|
record.errors.add(:image_height, "is too large (height: #{record.image_height}; max height: #{Danbooru.config.max_image_height})")
|
|
end
|
|
end
|
|
|
|
def validate_video_duration(record)
|
|
if !record.uploader.is_admin? && record.media_file.is_video? && record.media_file.duration > MAX_VIDEO_DURATION
|
|
record.errors.add(:base, "video must not be longer than #{MAX_VIDEO_DURATION.seconds.inspect}")
|
|
end
|
|
end
|
|
end
|
|
|
|
attr_accessor :as_pending, :replaced_post, :file
|
|
belongs_to :uploader, :class_name => "User"
|
|
belongs_to :post, optional: true
|
|
|
|
before_validation :initialize_attributes, on: :create
|
|
before_validation :assign_rating_from_tags
|
|
# validates :source, format: { with: /\Ahttps?/ }, if: ->(record) {record.file.blank?}, on: :create
|
|
validates :rating, inclusion: { in: %w(q e s) }, allow_nil: true
|
|
validates :md5, confirmation: true, if: ->(rec) { rec.md5_confirmation.present? }
|
|
validates_with FileValidator, on: :file
|
|
serialize :context, JSON
|
|
|
|
after_destroy_commit :delete_files
|
|
|
|
scope :pending, -> { where(status: "pending") }
|
|
scope :preprocessed, -> { where(status: "preprocessed") }
|
|
scope :completed, -> { where(status: "completed") }
|
|
scope :uploaded_by, ->(user_id) { where(uploader_id: user_id) }
|
|
|
|
def initialize_attributes
|
|
self.uploader_id = CurrentUser.id
|
|
self.uploader_ip_addr = CurrentUser.ip_addr
|
|
self.server = Socket.gethostname
|
|
end
|
|
|
|
def self.prune!
|
|
completed.where("created_at < ?", 1.hour.ago).lock.destroy_all
|
|
preprocessed.where("created_at < ?", 1.day.ago).lock.destroy_all
|
|
where("created_at < ?", 3.days.ago).lock.destroy_all
|
|
end
|
|
|
|
def self.visible(user)
|
|
if user.is_admin?
|
|
all
|
|
elsif user.is_anonymous?
|
|
completed
|
|
else
|
|
completed.or(where(uploader: user))
|
|
end
|
|
end
|
|
|
|
concerning :FileMethods do
|
|
def media_file
|
|
@media_file ||= MediaFile.open(file, frame_data: context.to_h.dig("ugoira", "frame_data"))
|
|
end
|
|
|
|
def delete_files
|
|
# md5 is blank if the upload errored out before downloading the file.
|
|
if is_completed? || md5.blank? || Upload.where(md5: md5).exists? || Post.where(md5: md5).exists?
|
|
return
|
|
end
|
|
|
|
DanbooruLogger.info("Uploads: Deleting files for upload md5=#{md5}")
|
|
Danbooru.config.storage_manager.delete_file(nil, md5, file_ext, :original)
|
|
Danbooru.config.storage_manager.delete_file(nil, md5, file_ext, :large)
|
|
Danbooru.config.storage_manager.delete_file(nil, md5, file_ext, :preview)
|
|
Danbooru.config.backup_storage_manager.delete_file(nil, md5, file_ext, :original)
|
|
Danbooru.config.backup_storage_manager.delete_file(nil, md5, file_ext, :large)
|
|
Danbooru.config.backup_storage_manager.delete_file(nil, md5, file_ext, :preview)
|
|
end
|
|
end
|
|
|
|
concerning :StatusMethods do
|
|
def is_pending?
|
|
status == "pending"
|
|
end
|
|
|
|
def is_processing?
|
|
status == "processing"
|
|
end
|
|
|
|
def is_completed?
|
|
status == "completed"
|
|
end
|
|
|
|
def is_preprocessed?
|
|
status == "preprocessed"
|
|
end
|
|
|
|
def is_preprocessing?
|
|
status == "preprocessing"
|
|
end
|
|
|
|
def is_duplicate?
|
|
status.match?(/duplicate: \d+/)
|
|
end
|
|
|
|
def is_errored?
|
|
status.match?(/error:/)
|
|
end
|
|
|
|
def sanitized_status
|
|
if is_errored?
|
|
status.sub(/DETAIL:.+/m, "...")
|
|
else
|
|
status
|
|
end
|
|
end
|
|
|
|
def duplicate_post_id
|
|
@duplicate_post_id ||= status[/duplicate: (\d+)/, 1]
|
|
end
|
|
end
|
|
|
|
concerning :SourceMethods do
|
|
def source=(source)
|
|
source = source.unicode_normalize(:nfc)
|
|
|
|
# percent encode unicode characters in urls
|
|
if source =~ %r!\Ahttps?://!i
|
|
source = Addressable::URI.normalized_encode(source) rescue source
|
|
end
|
|
|
|
super(source)
|
|
end
|
|
|
|
def source_url
|
|
return nil unless source =~ %r!\Ahttps?://!i
|
|
Addressable::URI.heuristic_parse(source) rescue nil
|
|
end
|
|
end
|
|
|
|
def self.search(params)
|
|
q = search_attributes(params, :id, :created_at, :updated_at, :source, :rating, :parent_id, :server, :md5, :server, :file_ext, :file_size, :image_width, :image_height, :referer_url, :uploader, :post)
|
|
|
|
if params[:source_matches].present?
|
|
q = q.where_like(:source, params[:source_matches])
|
|
end
|
|
|
|
if params[:has_post].to_s.truthy?
|
|
q = q.where.not(post_id: nil)
|
|
elsif params[:has_post].to_s.falsy?
|
|
q = q.where(post_id: nil)
|
|
end
|
|
|
|
if params[:status].present?
|
|
q = q.where_like(:status, params[:status])
|
|
end
|
|
|
|
if params[:backtrace].present?
|
|
q = q.where_like(:backtrace, params[:backtrace])
|
|
end
|
|
|
|
if params[:tag_string].present?
|
|
q = q.where_like(:tag_string, params[:tag_string])
|
|
end
|
|
|
|
q.apply_default_order(params)
|
|
end
|
|
|
|
def assign_rating_from_tags
|
|
if rating = PostQueryBuilder.new(tag_string).find_metatag(:rating)
|
|
self.rating = rating.downcase.first
|
|
end
|
|
end
|
|
|
|
def upload_as_pending?
|
|
as_pending.to_s.truthy?
|
|
end
|
|
|
|
def has_commentary?
|
|
artist_commentary_title.present? || artist_commentary_desc.present? || translated_commentary_title.present? || translated_commentary_desc.present?
|
|
end
|
|
|
|
def self.available_includes
|
|
[:uploader, :post]
|
|
end
|
|
end
|