Files
redux-scraper/app/models/blob_entry.rb
2025-01-01 03:29:53 +00:00

85 lines
2.1 KiB
Ruby

# typed: true
class BlobEntry < ReduxApplicationRecord
self.table_name = "blob_entries_p"
include ImmutableModel
before_destroy { raise ActiveRecord::ReadOnlyRecord }
self.primary_key = :sha256
EMPTY_FILE_SHA256 =
HexUtil.hex2bin(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
)
belongs_to :base,
optional: true,
foreign_key: :base_sha256,
class_name: "::BlobEntry"
after_create do
actual_sha256 = Digest::SHA256.digest(contents)
raise("digest mismatch for BlobEntry") if sha256 != actual_sha256
end
def base
@base_model ||= super || self.class.ensure(base_sha256) if base_sha256
end
validates_presence_of(:sha256, :content_type, :size)
validates :contents,
length: {
minimum: 0,
allow_nil: false,
message: "can't be nil",
}
validates :sha256, length: { is: 32 }
validates :base_sha256, length: { is: 32 }, if: :base_sha256
def self.ensure(sha256)
find_by(sha256: sha256) ||
raise("blob #{HexUtil.bin2hex(sha256)} does not exist")
end
def sha256_hex
HexUtil.bin2hex(sha256) if sha256
end
def contents
@contents ||=
begin
contents_raw = self.read_attribute(:contents)
self.base ? XDiff.patch(self.base.contents, contents_raw) : contents_raw
end
end
def bytes_stored
self.read_attribute(:contents).size
end
def self.find_or_build(content_type:, contents:)
sha256 = Digest::SHA256.digest(contents)
BlobEntry.find_by(sha256: sha256) ||
begin
build_record(
content_type: content_type,
sha256: sha256,
contents: contents,
)
end
end
DIFFABLE_CONTENT_TYPES = [%r{text/html}, %r{text/plain}, %r{application/json}]
def self.build_record(content_type:, sha256: nil, contents:)
sha256 ||= Digest::SHA256.digest(contents)
record =
self.new(
sha256: sha256,
content_type: content_type,
size: contents.size,
contents: contents,
)
record
end
end