app/models/binary_blob.rb
class BinaryBlob < ApplicationRecord
include Purging
belongs_to :resource, :polymorphic => true
has_many :binary_blob_parts, -> { order(:id) }, :dependent => :delete_all
def delete_binary
self.md5 = self.size = self.part_size = nil
binary_blob_parts.delete_all
save!
end
# Get binary file from database into a raw String
def binary
data = binary_blob_parts.pluck(:data).join
unless size.nil? || size == data.bytesize
raise _("size of %{name} id [%{number}] is incorrect") % {:name => self.class.name, :number => id}
end
unless md5.nil? || md5 == Digest::MD5.hexdigest(data)
raise _("md5 of %{name} id [%{number}] is incorrect") % {:name => self.class.name, :number => id}
end
data
end
# Set binary file into the database from a raw String
def binary=(data)
data.force_encoding('ASCII-8BIT')
delete_binary unless parts == 0
return self if data.bytesize == 0
self.part_size ||= BinaryBlobPart.default_part_size
self.md5 = Digest::MD5.hexdigest(data)
self.size = data.bytesize
until data.bytesize == 0
buf = data.slice!(0..self.part_size)
binary_blob_parts << BinaryBlobPart.new(:data => buf)
end
save!
self
end
# Write binary file from the database into a file
def dump_binary(path_or_io)
dump_size = 0
hasher = Digest::MD5.new
begin
fd = path_or_io.respond_to?(:write) ? path_or_io : File.open(path_or_io, "wb")
# TODO: Change this to collect the binary_blob_parts in batches, so we are not pulling in every row into memory at once
binary_blob_parts.each do |b|
data = b.data
dump_size += data.bytesize
hasher.update(data)
fd.write(data)
end
ensure
fd.close unless path_or_io.respond_to?(:write)
end
unless size.nil? || size == dump_size
raise _("size of %{name} id [%{number}] is incorrect") % {:name => self.class.name, :number => id}
end
unless md5.nil? || md5 == hasher.hexdigest
raise _("md5 of %{name} id [%{number}] is incorrect") % {:name => self.class.name, :number => id}
end
true
end
# Set binary file into the database from a file
def store_binary(path)
delete_binary unless parts == 0
self.part_size ||= BinaryBlobPart.default_part_size
self.md5 = nil
self.size = 0
hasher = Digest::MD5.new
File.open(path, "rb") do |f|
until f.eof?
buf = f.read(self.part_size)
self.size += buf.length
hasher.update(buf)
binary_blob_parts << BinaryBlobPart.new(:data => buf)
end
end
self.md5 = hasher.hexdigest
save!
self
end
def parts
binary_blob_parts.size
end
def expected_parts
part_size = self.part_size || BinaryBlobPart.default_part_size
(self.size.to_f / part_size).ceil
end
def serializer
data_type == "YAML" ? YAML : Marshal
end
def data
serializer.respond_to?(:unsafe_load) ? serializer.unsafe_load(binary) : serializer.load(binary)
end
def store_data(data_type, the_data)
self.data_type = data_type
self.binary = serializer.dump(the_data)
end
end