Files
manyfold/app/models/model_file.rb
T

183 lines
4.4 KiB
Ruby

class ModelFile < ApplicationRecord
include LibraryUploader::Attachment(:attachment)
include Listable
extend Memoist
belongs_to :model
has_many :problems, as: :problematic, dependent: :destroy
after_create :attach_existing_file!
belongs_to :presupported_version, class_name: "ModelFile", optional: true
has_one :unsupported_version, class_name: "ModelFile", foreign_key: "presupported_version_id",
inverse_of: :presupported_version, dependent: :nullify
validates :filename, presence: true, uniqueness: {scope: :model}
validate :presupported_version_is_presupported
validate :presupported_files_cannot_have_presupported_version
default_scope { order(:filename) }
scope :unsupported, -> { where(presupported: false) }
scope :presupported, -> { where(presupported: true) }
# Explicitly explain serialization for MariaDB
attribute :attachment_data, :json
SUPPORT_KEYWORDS = %w[
presupported
presup
supported
sup
wsupports
withsupports
]
def size
attachment.size
rescue
attributes["size"]
end
def size=(value)
ActiveSupport::Deprecation.warn("size is now set by Shrine")
end
def extension
attachment.extension
rescue
File.extname(filename).delete(".").downcase
end
def is_image?
SupportedMimeTypes.image_extensions.include? extension
end
def is_3d_model?
SupportedMimeTypes.model_extensions.include? extension
end
def mime_type
Mime::Type.lookup_by_extension(extension)
end
def basename(include_extension: false)
File.basename(filename, include_extension ? "" : ".*")
end
def name
basename.humanize.titleize
end
def path_within_library
File.join(model.path, filename)
end
def attach_existing_file!(refresh: true)
return if attachment.present? || !exists_on_storage?
attachment_attacher.set Shrine.uploaded_file(
storage: model.library.storage_key,
id: path_within_library,
metadata: {
filename: basename(include_extension: true),
size: attributes["size"],
mime_type: Mime::Type.lookup_by_extension(File.extname(filename).delete(".").downcase).to_s
}
)
attachment_attacher.refresh_metadata! if refresh
save!
end
def exists_on_storage?
model.library.has_file? path_within_library
end
def file_last_modified
model.library.file_last_modified path_within_library
end
def head(bytes)
io = attachment.open
result = io.read(bytes)
io.close
result
end
# TODO: this should move to Shrine metadata processing to be more efficient
def calculate_digest
Digest::SHA512.new.update(attachment.read).hexdigest
rescue Errno::ENOENT
nil
end
def bounding_box
return nil unless mesh
bbox = Mittsu::Box3.new.set_from_object(mesh)
bbox.size.to_a
end
def duplicates
return ModelFile.none if digest.nil?
ModelFile.where(digest: digest).where.not(id: id)
end
def duplicate?
duplicates.count > 0
end
# Used for ETag in conditional GETs
# See https://guides.rubyonrails.org/caching_with_rails.html#conditional-get-support
def cache_key_with_version
digest
end
def delete_from_disk_and_destroy
# Rescan any duplicates
duplicates.each { |x| Analysis::AnalyseModelFileJob.perform_later(x.id) }
# Remove the db record
destroy
end
def self.ransackable_attributes(_auth_object = nil)
["caption", "created_at", "digest", "filename", "id", "notes", "presupported", "size", "updated_at", "y_up", "presupported_version_id", "unsupported_version_id"]
end
def self.ransackable_associations(_auth_object = nil)
["favorited", "model", "problems"]
end
def mesh
# TODO: This can be better, but needs changes upstream in Mittsu to allow loaders to parse from an IO object
loader&.new&.parse(attachment.read)
end
memoize :mesh
def reattach!
attachment_attacher.attach attachment, storage: model.library.storage_key
save!
end
private
def presupported_files_cannot_have_presupported_version
if presupported_version && presupported
errors.add(:presupported_version, :already_presupported)
end
end
def presupported_version_is_presupported
if presupported_version && !presupported_version.presupported
errors.add(:presupported_version, :not_supported)
end
end
def loader
case extension
when "stl"
Mittsu::STLLoader
when "obj"
Mittsu::OBJLoader
end
end
end