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
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 bytes and the metadata record).
-
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch a single Blob's metadata record (not its bytes — see Blob.content).
-
.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
find_many, history, 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.
172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/atlas_rb/blob.rb', line 172 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(JSON.parse( multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key) .post(ROUTE, payload)&.body ))['blob'] end end |
.destroy(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Delete a Blob (the bytes and the metadata record).
198 199 200 |
# File 'lib/atlas_rb/blob.rb', line 198 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 |
.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. An unknown id or version
yields a 404 (raw Faraday response).
343 344 345 346 347 348 |
# File 'lib/atlas_rb/blob.rb', line 343 def self.rollback(id, version_id, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .post("#{ROUTE}#{id}/rollback", JSON.dump(version_id: version_id))&.body ))['blob'] end |
.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash
234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/atlas_rb/blob.rb', line 234 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(JSON.parse( multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key) .patch(ROUTE + id, payload)&.body )) 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).
307 308 309 310 311 312 313 314 315 316 |
# File 'lib/atlas_rb/blob.rb', line 307 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.
274 275 276 277 278 |
# File 'lib/atlas_rb/blob.rb', line 274 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 |