Class: AtlasRb::Blob
Overview
The binary content backing a FileSet (or attached directly to a Work).
Blobs are the bytes-on-disk layer of the hierarchy. Operations on this class deal with raw octet streams: uploading new content, replacing content on an existing Blob, and streaming downloads via a chunk handler so very large files don't have to be buffered in memory.
Constant Summary collapse
- ROUTE =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Atlas REST endpoint prefix for this resource.
"/files/"
Constants included from FaradayHelper
FaradayHelper::ASSERTION_AUDIENCE, FaradayHelper::ASSERTION_ISSUER, FaradayHelper::ASSERTION_TTL, FaradayHelper::INSTRUMENTATION_EVENT
Class Method Summary collapse
-
.ancestry(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Resolve a content Blob to its parent FileSet and containing Work noids.
-
.content(id, range: nil, nuid: nil, on_behalf_of: nil) {|chunk| ... } ⇒ Hash
Stream the Blob's binary content through a caller-supplied block.
-
.create(id, blob_path, original_filename, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash
Upload a new Blob attached to a Work.
-
.destroy(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Delete a Blob: the metadata record and the bytes.
-
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch a single Blob's metadata record (not its bytes — see Blob.content).
-
.find_many_versions(ids, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
Read binary version history for many Blobs in one round-trip.
-
.rollback(id, version_id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Roll a Blob back to a prior version.
-
.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash
Replace the bytes of an existing Blob in-place.
-
.version_content(id, version_id, nuid: nil, on_behalf_of: nil) {|chunk| ... } ⇒ Hash
Stream the bytes of a prior version of a Blob through a block.
-
.versions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
List a Blob's retained binary version history.
-
.work(id, nuid: nil, on_behalf_of: nil) ⇒ String?
Convenience over Blob.ancestry: the containing Work's noid for a content Blob (or
nilwhen unresolvable).
Methods inherited from Resource
descendant_works, find_many, history, mods, mods_version, mods_versions, permissions, preview
Methods included from FaradayHelper
#connection, #multipart, #system_connection, #with_file_part
Class Method Details
.ancestry(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Resolve a content Blob to its parent FileSet and containing Work noids.
Wraps GET /files/<id>/ancestry. The download path is keyed only by the
blob id, so a consumer recording a download/stream impression against the
containing Work resolves it here — instead of threading the work noid
through the download URL. Reads on the Blob floor (no admin gate). An
unknown id yields a 404 (raw Faraday response); either value is nil
when unresolvable (e.g. an orphan blob with no FileSet parent).
66 67 68 69 70 |
# File 'lib/atlas_rb/blob.rb', line 66 def self.ancestry(id, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/ancestry")&.body )) end |
.content(id, range: nil, nuid: nil, on_behalf_of: nil) {|chunk| ... } ⇒ Hash
Stream the Blob's binary content through a caller-supplied block.
The body is not buffered — each chunk Faraday receives is yielded
to chunk_handler immediately, making this safe for files larger than
available memory.
Pass range: (e.g. "bytes=0-1048575") to forward an HTTP Range
header; Atlas answers 206 Partial Content and the chunks yielded are
just the requested slice. The returned hash exposes both the response
status (200 vs 206) and the response headers — so a caller
proxying to a browser media element can relay Content-Range,
Content-Length and Accept-Ranges verbatim and reproduce the 206.
123 124 125 126 127 128 129 |
# File 'lib/atlas_rb/blob.rb', line 123 def self.content(id, range: nil, nuid: nil, on_behalf_of: nil, &chunk_handler) response = connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/content") do |req| req.headers["Range"] = range if range req..on_data = proc { |chunk, _bytes_received, _env| chunk_handler.call(chunk) } end { status: response.status, headers: response.headers } end |
.create(id, blob_path, original_filename, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash
Streams the file (FD closed deterministically); a multi-GB upload is not buffered in memory. See FaradayHelper#with_file_part.
Upload a new Blob attached to a Work.
original_filename is preserved separately from the upload's
File.basename(blob_path) because the on-disk path is often a temp
file name (RackMultipart...tmp) — Atlas needs the user-facing name
for download UX.
176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/atlas_rb/blob.rb', line 176 def self.create(id, blob_path, original_filename, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) with_file_part(blob_path) do |part| payload = { work_id: id, original_filename: original_filename, binary: part } payload[:expected_digest] = expected_digest if expected_digest AtlasRb::Mash.new(write_resource( multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key) .post(ROUTE, payload) ))['blob'] end end |
.destroy(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Delete a Blob: the metadata record and the bytes.
Atlas removes the whole OCFL object, so every retained revision goes, not only the current one — versions and rollback have nothing left to work with afterwards. Unrecoverable, and admin-only. The Blob is also unlinked from its FileSet, whose METS is rebuilt.
207 208 209 |
# File 'lib/atlas_rb/blob.rb', line 207 def self.destroy(id, nuid: nil, on_behalf_of: nil) connection({}, nuid, on_behalf_of: on_behalf_of).delete(ROUTE + id) end |
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch a single Blob's metadata record (not its bytes — see content).
39 40 41 42 |
# File 'lib/atlas_rb/blob.rb', line 39 def self.find(id, nuid: nil, on_behalf_of: nil) body = fetch_resource(ROUTE + id, nuid: nuid, on_behalf_of: on_behalf_of) body && AtlasRb::Mash.new(body)['blob'] end |
.find_many_versions(ids, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
Read binary version history for many Blobs in one round-trip.
Wraps Atlas's POST /files/find_many_versions — the batch counterpart to
versions, returning one envelope of exactly that shape per Blob. Use it
anywhere a set of Blob noids would otherwise be resolved with a
versions-per-noid fan-out (the admin file-manage listing, which reads
every replaceable Blob on a Work): one HTTP call instead of N.
The ids travel in the request body, so the list is not bounded by URL
length. The result is unordered and may be shorter than the input —
an id that resolves to nothing, or to a resource that is not a Blob, is
dropped silently. Index by "blob_id"; do not assume positional
correspondence with ids.
Server admin-gates this exactly like versions (the descriptors expose
the same edit attribution), so 401 / 403 surface as raw Faraday
responses. The grant is class-wide, so nothing is dropped for
authorization — a dropped id is an unresolvable one.
328 329 330 331 332 333 |
# File 'lib/atlas_rb/blob.rb', line 328 def self.find_many_versions(ids, nuid: nil, on_behalf_of: nil) JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .post("#{ROUTE}find_many_versions", JSON.dump(ids: Array(ids)))&.body ).map { |envelope| AtlasRb::Mash.new(envelope) } end |
.rollback(id, version_id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Roll a Blob back to a prior version.
Wraps POST /files/<id>/rollback. Atlas promotes the given version to
current by appending its bytes again as a NEW revision — so rollback is
itself non-destructive (it becomes vN+1 with the bytes of vN) and the Blob
NOID is preserved. OCFL dedups the identical content, so no bytes are
recopied. Avoids a full round-trip of the bytes back through the caller
(vs. re-streaming version_content into update).
Pass a version_id obtained from versions. Atlas answers an unknown id
or version with a 404, which raises NotFoundError — a write
that did not happen must not read like one that did.
403 404 405 406 407 408 |
# File 'lib/atlas_rb/blob.rb', line 403 def self.rollback(id, version_id, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(write_resource( connection({}, nuid, on_behalf_of: on_behalf_of) .post("#{ROUTE}#{id}/rollback", JSON.dump(version_id: version_id)) ))['blob'] end |
.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash
247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/atlas_rb/blob.rb', line 247 def self.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) with_file_part(blob_path) do |part| payload = { binary: part } payload[:expected_digest] = expected_digest if expected_digest AtlasRb::Mash.new(write_resource( multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key) .patch(ROUTE + id, payload) )) end end |
.version_content(id, version_id, nuid: nil, on_behalf_of: nil) {|chunk| ... } ⇒ Hash
Stream the bytes of a prior version of a Blob through a block.
Wraps GET /files/<id>/versions/<version_id>/content — the version-pinned
twin of content, and the read half of "download the superseded file".
Like content, the body is not buffered: each chunk is yielded to
chunk_handler immediately (safe for files larger than memory), and the
response headers are captured and returned.
Pass a version_id obtained from versions (an opaque OCFL vN label);
only labels the history surfaced are addressable. An unknown id or version
yields a 404 (raw Faraday response).
362 363 364 365 366 367 368 369 370 371 |
# File 'lib/atlas_rb/blob.rb', line 362 def self.version_content(id, version_id, nuid: nil, on_behalf_of: nil, &chunk_handler) headers = {} connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions/#{version_id}/content") do |req| req..on_data = proc do |chunk, _bytes_received, env| headers = env.response_headers if headers.empty? && env chunk_handler.call(chunk) end end headers end |
.versions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
List a Blob's retained binary version history.
Wraps Atlas's GET /files/<id>/versions — the binary counterpart to
Resource.mods_versions. Returns a reverse-chronological (newest first)
envelope: one descriptor per retained content revision, each carrying its
OCFL version_id label, the file_identifier appended for that revision,
the created timestamp, the digest/size recorded at that version,
the stable original_filename, and actor attribution (actor_nuid /
on_behalf_of_nuid, null when no audit event correlates).
Server admin-gates this endpoint (it exposes edit attribution), so
401 / 403 surface as raw Faraday responses, matching
Resource.mods_versions. An unknown Blob id yields a 404.
287 288 289 290 291 |
# File 'lib/atlas_rb/blob.rb', line 287 def self.versions(id, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions")&.body )) end |
.work(id, nuid: nil, on_behalf_of: nil) ⇒ String?
Convenience over ancestry: the containing Work's noid for a content
Blob (or nil when unresolvable). The shape Cerberus's impression-capture
job wants — roll a download up to its Work from the blob id alone.
83 84 85 |
# File 'lib/atlas_rb/blob.rb', line 83 def self.work(id, nuid: nil, on_behalf_of: nil) ancestry(id, nuid: nuid, on_behalf_of: on_behalf_of)['work'] end |