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, #read_body, #read_raw, #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).
71 72 73 74 75 |
# File 'lib/atlas_rb/blob.rb', line 71 def self.ancestry(id, nuid: nil, on_behalf_of: nil) read_body(connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/ancestry")) do |body| AtlasRb::Mash.new(body) end 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.
128 129 130 131 132 133 134 |
# File 'lib/atlas_rb/blob.rb', line 128 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.
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/atlas_rb/blob.rb', line 181 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.
212 213 214 |
# File 'lib/atlas_rb/blob.rb', line 212 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.
343 344 345 346 347 348 |
# File 'lib/atlas_rb/blob.rb', line 343 def self.find_many_versions(ids, nuid: nil, on_behalf_of: nil) read_body( connection({}, nuid, on_behalf_of: on_behalf_of) .post("#{ROUTE}find_many_versions", JSON.dump(ids: Array(ids))) ) { |body| 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.
425 426 427 428 429 430 |
# File 'lib/atlas_rb/blob.rb', line 425 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
252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/atlas_rb/blob.rb', line 252 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).
383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/atlas_rb/blob.rb', line 383 def self.version_content(id, version_id, nuid: nil, on_behalf_of: nil, &chunk_handler) headers = {} response = 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 { status: response.status, headers: 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.
297 298 299 300 301 |
# File 'lib/atlas_rb/blob.rb', line 297 def self.versions(id, nuid: nil, on_behalf_of: nil) read_body(connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions")) do |body| AtlasRb::Mash.new(body) end 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.
88 89 90 |
# File 'lib/atlas_rb/blob.rb', line 88 def self.work(id, nuid: nil, on_behalf_of: nil) ancestry(id, nuid: nuid, on_behalf_of: on_behalf_of)['work'] end |