Class: Oxygene::CARArchive
- Inherits:
-
Object
- Object
- Oxygene::CARArchive
- Defined in:
- lib/oxygene/car_archive.rb
Overview
Parses a Content Addressable Archive (CAR) bundle loaded e.g. from an ATProto firehose message or from a repo .car exported from a PDS. The header part is decoded immediately, while the subsequent body/data sections are lazily decoded only as requested.
Only CAR version v1 is supported, and only limited to the features or variants used in ATProto (e.g. CIDs only in the v1 version as defined in DASL).
Related specifications:
Direct Known Subclasses
Instance Attribute Summary collapse
-
#roots ⇒ Array<CID>
readonly
Array of root CIDs listed in the archive header.
Class Method Summary collapse
-
.convert_data(object) ⇒ Hash, Array
private
Converts decoded CBOR values to their ATProto-specific JSON representation.
-
.make_bytes(data) ⇒ Hash{String => String}
private
Converts a binary string to an ATProto JSON $bytes object.
-
.make_cid_link(cid) ⇒ Hash{String => CID}
private
Converts a CBOR CID tag to an ATProto JSON $link object.
Instance Method Summary collapse
-
#initialize(data) ⇒ CARArchive
constructor
Creates a CAR archive reader from an in-memory CAR file.
-
#inspect ⇒ String
Returns a string with a representation of the object for debugging purposes.
-
#parsed_sections ⇒ Array<CARSection>
Returns the list of archive sections that have been parsed so far, without parsing any more sections.
-
#section_with_cid(cid, use_map: false, return_body: true) ⇒ Hash, ...
Looks up a section with a given CID in the archive, decoding sections as needed.
-
#sections ⇒ Array<CARSection>
Parses all sections in the archive if they haven't been loaded yet, and returns the list of all sections in the order as listed in the archive.
Constructor Details
#initialize(data) ⇒ CARArchive
Creates a CAR archive reader from an in-memory CAR file.
46 47 48 49 50 51 52 53 |
# File 'lib/oxygene/car_archive.rb', line 46 def initialize(data) @sections = [] @section_map = {} @buffer = StringIO.new(data) @map_needs_update = false read_header(@buffer) end |
Instance Attribute Details
#roots ⇒ Array<CID> (readonly)
Returns array of root CIDs listed in the archive header.
37 38 39 |
# File 'lib/oxygene/car_archive.rb', line 37 def roots @roots end |
Class Method Details
.convert_data(object) ⇒ Hash, Array
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Converts decoded CBOR values to their ATProto-specific JSON representation.
The passed hash or array is converted recursively in place. The conversion involves:
- replacing binary strings with
$bytesobjects with Base64-encoded data - converting CBOR CID tags to
$linkobjects holding an Oxygene::CID
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/oxygene/car_archive.rb', line 134 def self.convert_data(object) if object.is_a?(Hash) object.each do |k, v| if v.is_a?(Hash) || v.is_a?(Array) convert_data(v) elsif v.is_a?(CBOR::Tagged) object[k] = make_cid_link(v) elsif v.is_a?(String) && v.encoding == Encoding::ASCII_8BIT object[k] = make_bytes(v) end end elsif object.is_a?(Array) object.each_with_index do |v, i| if v.is_a?(Hash) || v.is_a?(Array) convert_data(v) elsif v.is_a?(CBOR::Tagged) object[i] = make_cid_link(v) elsif v.is_a?(String) && v.encoding == Encoding::ASCII_8BIT object[i] = make_bytes(v) end end else raise DecodeError, "Unexpected value type in record: #{object}" end end |
.make_bytes(data) ⇒ Hash{String => String}
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Converts a binary string to an ATProto JSON $bytes object.
181 182 183 184 185 186 |
# File 'lib/oxygene/car_archive.rb', line 181 def self.make_bytes(data) string = Base64.strict_encode64(data) string.chomp!('=') while string.getbyte(-1) == 61 { '$bytes' => string } end |
.make_cid_link(cid) ⇒ Hash{String => CID}
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Converts a CBOR CID tag to an ATProto JSON $link object.
169 170 171 |
# File 'lib/oxygene/car_archive.rb', line 169 def self.make_cid_link(cid) { '$link' => CID.from_cbor_tag(cid) } end |
Instance Method Details
#inspect ⇒ String
Returns a string with a representation of the object for debugging purposes.
191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/oxygene/car_archive.rb', line 191 def inspect vars = (instance_variables - [:@section_map]).map { |v| if v == :@sections && @buffer "#{v}=[...]" else "#{v}=#{instance_variable_get(v).inspect}" end } "#<#{self.class}:0x#{object_id} #{vars.join(", ")}>" end |
#parsed_sections ⇒ Array<CARSection>
Returns the list of archive sections that have been parsed so far, without parsing any more sections.
95 96 97 |
# File 'lib/oxygene/car_archive.rb', line 95 def parsed_sections @sections.dup.freeze end |
#section_with_cid(cid, use_map: false, return_body: true) ⇒ Hash, ...
Looks up a section with a given CID in the archive, decoding sections as needed.
Sections are parsed lazily – if the requested section has already been loaded, it's returned without parsing any more parts of the archive, otherwise unread sections are parsed only until a match is found.
When making repeated lookups for sections in one archive, e.g. when walking
the MST tree of a CAR repo, pass the use_map: true option, which tells
CARArchive to build and use an index that maps CIDs to sections for quicker lookup
(otherwise, parsed sections are searched sequentially). For performance, this isn't
enabled by default, since the common case of processing firehose commit messages only
does a single lookup to find the record data.
The CID may be passed as either an Oxygene::CID object, or its Oxygene::CID#cbor_form string.
78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/oxygene/car_archive.rb', line 78 def section_with_cid(cid, use_map: false, return_body: true) if found_section = find_parsed_section(cid, use_map) return (return_body ? found_section.json_body : found_section) end if found_section = parse_sections_until_match(cid, use_map) return (return_body ? found_section.json_body : found_section) end nil end |
#sections ⇒ Array<CARSection>
Parses all sections in the archive if they haven't been loaded yet, and returns the list of all sections in the order as listed in the archive.
Once all sections have been read, the original archive data buffer is released so it can be garbage-collected.
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/oxygene/car_archive.rb', line 109 def sections if @buffer if !@buffer.eof? read_section(@buffer) while !@buffer.eof? @map_needs_update = true end @buffer = nil end @sections end |