Class: ModelFile
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ModelFile
- Includes:
- Listable, MemoWise, Problematic, PublicIDable, Relatable
- Defined in:
- app/models/model_file.rb
Constant Summary collapse
- SPECIAL_FILES =
[ "datapackage.json" ]
- SUPPORT_KEYWORDS =
%w[ presupported presup supported sup wsupports withsupports ]
Constants inherited from ApplicationRecord
ApplicationRecord::SAFE_NAME_LENGTH
Instance Method Summary collapse
- #alternative_formats ⇒ Object
- #analyse_geometry_later(delay: 0.seconds) ⇒ Object
- #analyse_later(delay: 5.seconds) ⇒ Object
- #attach_existing_file!(refresh: true, skip_validations: false) ⇒ Object
- #basename(include_extension: false) ⇒ Object
-
#cache_key_with_version ⇒ Object
Used for ETag in conditional GETs See https://guides.rubyonrails.org/caching_with_rails.html#conditional-get-support.
-
#calculate_digest ⇒ Object
TODO: this should move to Shrine metadata processing to be more efficient.
- #check_derivatives! ⇒ Object
- #convert_later(format, delay: 0.seconds) ⇒ Object
- #convertable?(to: nil) ⇒ Boolean
- #ctime ⇒ Object
- #delete_from_disk_and_destroy ⇒ Object
- #dimensions ⇒ Object
- #duplicate? ⇒ Boolean
- #duplicates ⇒ Object
- #exists_on_storage? ⇒ Boolean
- #extension ⇒ Object
- #file_last_modified ⇒ Object
- #filename_without_extension ⇒ Object
- #has_render? ⇒ Boolean
- #head(bytes) ⇒ Object
- #is_3d_model? ⇒ Boolean
- #is_document? ⇒ Boolean
- #is_image? ⇒ Boolean
- #is_video? ⇒ Boolean
- #mime_type ⇒ Object
- #mtime ⇒ Object
- #name ⇒ Object
- #name_and_filename ⇒ Object
- #parse_metadata_later(delay: 0.seconds) ⇒ Object
- #path_within_library(derivative: nil) ⇒ Object
- #planar? ⇒ Boolean
- #presupported_version ⇒ Object
- #presupported_version=(file) ⇒ Object
- #reattach! ⇒ Object
- #refresh_metadata! ⇒ Object
- #scene ⇒ Object
- #unsupported_version ⇒ Object
- #up_direction ⇒ Object
- #update_from_url!(url:) ⇒ Object
Methods included from PublicIDable
Methods included from Listable
Methods inherited from ApplicationRecord
Instance Method Details
#alternative_formats ⇒ Object
289 290 291 |
# File 'app/models/model_file.rb', line 289 def alternative_formats .where("relationships.predicate" => "alternative_format_of") end |
#analyse_geometry_later(delay: 0.seconds) ⇒ Object
221 222 223 |
# File 'app/models/model_file.rb', line 221 def analyse_geometry_later(delay: 0.seconds) Analysis::GeometricAnalysisJob.set(wait: delay).perform_later(id) end |
#analyse_later(delay: 5.seconds) ⇒ Object
217 218 219 |
# File 'app/models/model_file.rb', line 217 def analyse_later(delay: 5.seconds) Analysis::AnalyseModelFileJob.set(wait: delay).perform_later(id) end |
#attach_existing_file!(refresh: true, skip_validations: false) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/models/model_file.rb', line 106 def attach_existing_file!(refresh: true, skip_validations: false) return if .present? || !exists_on_storage? .set ModelFileUploader.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 } ) . if refresh save!(validate: !skip_validations) end |
#basename(include_extension: false) ⇒ Object
87 88 89 |
# File 'app/models/model_file.rb', line 87 def basename(include_extension: false) File.basename(filename, include_extension ? "" : ".*") end |
#cache_key_with_version ⇒ Object
Used for ETag in conditional GETs See https://guides.rubyonrails.org/caching_with_rails.html#conditional-get-support
173 174 175 |
# File 'app/models/model_file.rb', line 173 def cache_key_with_version digest end |
#calculate_digest ⇒ Object
TODO: this should move to Shrine metadata processing to be more efficient
137 138 139 140 141 142 143 144 145 |
# File 'app/models/model_file.rb', line 137 def calculate_digest sha = Digest::SHA512.new while (chunk = .read(8192)) sha.update(chunk) end sha.hexdigest rescue Errno::ENOENT nil end |
#check_derivatives! ⇒ Object
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'app/models/model_file.rb', line 264 def check_derivatives! ApplicationRecord.no_touching do regen = .empty? # Clear out missing derivatives .each do |k, v| unless model.library.has_file?(v.id) &.remove_derivative(k) regen = true end end if regen &.create_derivatives save!(validate: false, touch: false) end end end |
#convert_later(format, delay: 0.seconds) ⇒ Object
203 204 205 |
# File 'app/models/model_file.rb', line 203 def convert_later(format, delay: 0.seconds) Analysis::FileConversionJob.set(wait: delay).perform_later(id, format.to_sym) end |
#convertable?(to: nil) ⇒ Boolean
207 208 209 210 |
# File 'app/models/model_file.rb', line 207 def convertable?(to: nil) return false unless FileHandlers::Assimp.can_load? mime_type to.nil? || FileHandlers::Assimp.can_save?(to) end |
#ctime ⇒ Object
239 240 241 242 243 |
# File 'app/models/model_file.rb', line 239 def ctime &.ctime.present? ? Time.zone.parse(.ctime) : updated_at rescue NoMethodError created_at end |
#delete_from_disk_and_destroy ⇒ Object
212 213 214 215 |
# File 'app/models/model_file.rb', line 212 def delete_from_disk_and_destroy model.library.storage.delete path_within_library destroy end |
#dimensions ⇒ Object
147 148 149 150 151 152 153 154 155 |
# File 'app/models/model_file.rb', line 147 def dimensions bbox = ..dig("object", "bounding_box") return nil unless bbox bbox = Mittsu::Box3.new.set_from_points([ Mittsu::Vector3.new(bbox.dig("minimum", "x"), bbox.dig("minimum", "y"), bbox.dig("minimum", "z")), Mittsu::Vector3.new(bbox.dig("maximum", "x"), bbox.dig("maximum", "y"), bbox.dig("maximum", "z")) ]) bbox.size end |
#duplicate? ⇒ Boolean
167 168 169 |
# File 'app/models/model_file.rb', line 167 def duplicate? size && size > 0 && duplicates.exists? && !is_document? end |
#duplicates ⇒ Object
162 163 164 165 |
# File 'app/models/model_file.rb', line 162 def duplicates return ModelFile.none if digest.nil? # rubocop:todo Pundit/UsePolicyScope ModelFile.where(digest: digest).where.not(id: id) # rubocop:todo Pundit/UsePolicyScope end |
#exists_on_storage? ⇒ Boolean
121 122 123 |
# File 'app/models/model_file.rb', line 121 def exists_on_storage? model.library.has_file? path_within_library end |
#extension ⇒ Object
54 55 56 57 58 59 60 61 |
# File 'app/models/model_file.rb', line 54 def extension if has_attribute? :attachment_data &.extension else # DEPRECATED: for Pre-shrine migration File.extname(filename).delete(".").downcase end end |
#file_last_modified ⇒ Object
125 126 127 |
# File 'app/models/model_file.rb', line 125 def file_last_modified model.library.file_last_modified path_within_library end |
#filename_without_extension ⇒ Object
91 92 93 94 |
# File 'app/models/model_file.rb', line 91 def filename_without_extension dirname = File.dirname(filename) File.join([dirname, basename(include_extension: false)].reject { it == "." }) end |
#has_render? ⇒ Boolean
79 80 81 |
# File 'app/models/model_file.rb', line 79 def has_render? is_3d_model? && .derivatives.key?(:render) end |
#head(bytes) ⇒ Object
129 130 131 132 133 134 |
# File 'app/models/model_file.rb', line 129 def head(bytes) io = .open result = io.read(bytes) io.close result end |
#is_3d_model? ⇒ Boolean
75 76 77 |
# File 'app/models/model_file.rb', line 75 def is_3d_model? MediaType.model_extensions.include? extension end |
#is_document? ⇒ Boolean
71 72 73 |
# File 'app/models/model_file.rb', line 71 def is_document? MediaType.document_extensions.include? extension end |
#is_image? ⇒ Boolean
63 64 65 |
# File 'app/models/model_file.rb', line 63 def is_image? MediaType.image_extensions.include? extension end |
#is_video? ⇒ Boolean
67 68 69 |
# File 'app/models/model_file.rb', line 67 def is_video? MediaType.video_extensions.include? extension end |
#mime_type ⇒ Object
83 84 85 |
# File 'app/models/model_file.rb', line 83 def mime_type Mime::Type.lookup_by_extension(extension) end |
#mtime ⇒ Object
233 234 235 236 237 |
# File 'app/models/model_file.rb', line 233 def mtime &.mtime.present? ? Time.zone.parse(.mtime) : updated_at rescue NoMethodError updated_at end |
#name ⇒ Object
96 97 98 |
# File 'app/models/model_file.rb', line 96 def name basename.humanize.careful_titleize end |
#name_and_filename ⇒ Object
199 200 201 |
# File 'app/models/model_file.rb', line 199 def name_and_filename "#{name} (#{filename})" end |
#parse_metadata_later(delay: 0.seconds) ⇒ Object
225 226 227 |
# File 'app/models/model_file.rb', line 225 def (delay: 0.seconds) Scan::ModelFile::ParseMetadataJob.set(wait: delay).perform_later(id) end |
#path_within_library(derivative: nil) ⇒ Object
100 101 102 103 104 |
# File 'app/models/model_file.rb', line 100 def path_within_library(derivative: nil) derivative ? File.join(model.path, ".manyfold", "derivatives", filename, "#{derivative}.#{extension}") : File.join(model.path, filename) end |
#planar? ⇒ Boolean
157 158 159 160 |
# File 'app/models/model_file.rb', line 157 def planar? i = dimensions.to_a.index { it.zero? } i ? [:x, :y, :z].at(i) : nil end |
#presupported_version ⇒ Object
285 286 287 |
# File 'app/models/model_file.rb', line 285 def presupported_version reverse_relationships.where(predicate: "supported_version_of").first&.subject end |
#presupported_version=(file) ⇒ Object
293 294 295 296 297 298 299 |
# File 'app/models/model_file.rb', line 293 def presupported_version=(file) clear_presupported_relation and return if file.nil? return unless !presupported && file.presupported if file != presupported_version reverse_relationships.where(predicate: "supported_version_of").create_with(subject: file).first_or_create!.update(subject: file) end end |
#reattach! ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 |
# File 'app/models/model_file.rb', line 187 def reattach! if .id != path_within_library || .storage_key != model.library.storage_key old_path = .id old_storage = .storage # Reattach .attach , storage: model.library.storage_key # Remove previous file old_storage.delete old_path save! end end |
#refresh_metadata! ⇒ Object
257 258 259 260 261 262 |
# File 'app/models/model_file.rb', line 257 def ApplicationRecord.no_touching do &. save(touch: false, validate: false) end end |
#scene ⇒ Object
177 178 179 180 181 182 183 184 185 |
# File 'app/models/model_file.rb', line 177 def scene Shrine.with_file(.open) do scene = Assimp.import_file(it.path) scene.apply_post_processing(Assimp::PostProcessSteps[ :JoinIdenticalVertices, :Triangulate ]) end end |
#unsupported_version ⇒ Object
281 282 283 |
# File 'app/models/model_file.rb', line 281 def unsupported_version relationships.where(predicate: "supported_version_of").first&.objekt end |
#up_direction ⇒ Object
229 230 231 |
# File 'app/models/model_file.rb', line 229 def up_direction y_up ? "+y" : "+z" end |
#update_from_url!(url:) ⇒ Object
245 246 247 248 249 250 251 252 253 254 255 |
# File 'app/models/model_file.rb', line 245 def update_from_url!(url:) save! if .assign_remote_url( url, downloader: { headers: { "If-None-Match" => &.&.dig("remote_etag"), "If-Modified-Since" => &.&.dig("remote_last_modified") }.compact } ) end |