Class: Offroad::CargoStreamer
- Inherits:
-
Object
- Object
- Offroad::CargoStreamer
- Defined in:
- lib/cargo_streamer.rb
Overview
Class for encoding data to, and extracting data from, specially-formatted HTML comments which are called “cargo sections”. Each such section has a name, an md5sum for verification, and some base64-encoded zlib-compressed json data. Multiple cargo sections can have the same name; when the cargo is later read, requests for that name will be yielded each section in turn. The data must always be in the form of arrays of ActiveRecord, or things that walk sufficiently like ActiveRecord
Instance Method Summary collapse
-
#cargo_section_names ⇒ Object
Returns a list of cargo section names available to be read.
-
#each_cargo_section(name) ⇒ Object
Reads, verifies, and decodes each cargo section with a given name, passing each section’s decoded data to the block.
-
#first_cargo_element(name) ⇒ Object
Returns the first element from the return value of first_cargo_section.
-
#first_cargo_section(name) ⇒ Object
Reads, verifies, decodes, and returns the first cargo section with a given name.
-
#has_cargo_named?(name) ⇒ Boolean
Returns true if cargo with a given name is available.
-
#initialize(ioh, mode) ⇒ CargoStreamer
constructor
Creates a new CargoStreamer on the given stream, which will be used in the given mode (must be “w” or “r”).
-
#write_cargo_section(name, value, options = {}) ⇒ Object
Writes a cargo section with the given name and value to the IO stream.
Constructor Details
#initialize(ioh, mode) ⇒ CargoStreamer
Creates a new CargoStreamer on the given stream, which will be used in the given mode (must be “w” or “r”). If the mode is “r”, the file is immediately scanned to determine what cargo it contains.
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/cargo_streamer.rb', line 21 def initialize(ioh, mode) raise CargoStreamerError.new("Invalid mode: must be 'w' or 'r'") unless ["w", "r"].include?(mode) @mode = mode if ioh.is_a? String raise CargoStreamerError.new("Cannot accept string as ioh in write mode") unless @mode == "r" @ioh = StringIO.new(ioh, "r") else @ioh = ioh end scan_for_cargo if @mode == "r" end |
Instance Method Details
#cargo_section_names ⇒ Object
Returns a list of cargo section names available to be read
101 102 103 |
# File 'lib/cargo_streamer.rb', line 101 def cargo_section_names return @cargo_locations.keys end |
#each_cargo_section(name) ⇒ Object
Reads, verifies, and decodes each cargo section with a given name, passing each section’s decoded data to the block
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/cargo_streamer.rb', line 124 def each_cargo_section(name) raise CargoStreamerError.new("Mode must be 'r' to read cargo data") unless @mode == "r" locations = @cargo_locations[name] or return locations.each do |seek_location| @ioh.seek(seek_location) digest = "" encoded_data = "" @ioh.each_line do |line| line.chomp! if line == CARGO_END break elsif digest == "" digest = line else encoded_data += line end end yield verify_and_decode_cargo(digest, encoded_data) end end |
#first_cargo_element(name) ⇒ Object
Returns the first element from the return value of first_cargo_section
118 119 120 121 |
# File 'lib/cargo_streamer.rb', line 118 def first_cargo_element(name) arr = first_cargo_section(name) return (arr && arr.size > 0) ? arr[0] : nil end |
#first_cargo_section(name) ⇒ Object
Reads, verifies, decodes, and returns the first cargo section with a given name
111 112 113 114 115 |
# File 'lib/cargo_streamer.rb', line 111 def first_cargo_section(name) each_cargo_section(name) do |data| return data end end |
#has_cargo_named?(name) ⇒ Boolean
Returns true if cargo with a given name is available
106 107 108 |
# File 'lib/cargo_streamer.rb', line 106 def has_cargo_named?(name) return @cargo_locations.has_key? name end |
#write_cargo_section(name, value, options = {}) ⇒ Object
Writes a cargo section with the given name and value to the IO stream. Options:
-
:human_readable => true - Before writing the cargo section, writes a comment with human-readable data.
-
:include => [:assoc, :other_assoc] - Includes these first-level associations in the encoded data
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/cargo_streamer.rb', line 39 def write_cargo_section(name, value, = {}) raise CargoStreamerError.new("Mode must be 'w' to write cargo data") unless @mode == "w" raise CargoStreamerError.new("CargoStreamer section names must be strings") unless name.is_a? String raise CargoStreamerError.new("Invalid cargo name '" + name + "'") unless name == clean_for_html_comment(name) raise CargoStreamerError.new("Cargo name cannot include newlines") if name.include?("\n") raise CargoStreamerError.new("Value must be an array") unless value.is_a? Array [:to_xml, :attributes=, :valid?].each do || unless value.all? { |e| e.respond_to? } raise CargoStreamerError.new("All elements must respond to #{}") end end unless value.all? { |e| e.class.respond_to?(:safe_to_load_from_cargo_stream?) && e.class.safe_to_load_from_cargo_stream? } raise CargoStreamerError.new("All element classes must be models which are safe_to_load_from_cargo_stream") end unless [:skip_validation] unless value.all?(&:valid?) raise CargoStreamerError.new("All elements must be valid") end end if [:human_readable] human_data = value.map{ |rec| rec.attributes.map{ |k, v| "#{k.to_s.titleize}: #{v.to_s}" }.join("\n") }.join("\n\n") @ioh.write "<!--\n" @ioh.write name.titleize + "\n" @ioh.write "\n" @ioh.write clean_for_html_comment(human_data) + "\n" @ioh.write "-->\n" end name = name.chomp assoc_list = [:include] || [] xml = Builder::XmlMarkup.new xml_data = "<records>%s</records>" % value.map { |r| r.to_xml( :skip_instruct => true, :skip_types => true, :root => "record", :indent => 0, :include => assoc_list ) do |xml| xml.cargo_streamer_type r.class.name assoc_info = assoc_list.reject{|a| r.send(a) == nil}.map{|a| "#{a.to_s}=#{r.send(a).class.name}"}.join(",") xml.cargo_streamer_includes assoc_info end }.join() deflated_data = Zlib::Deflate::deflate(xml_data) b64_data = Base64.encode64(deflated_data).chomp digest = Digest::MD5::hexdigest(deflated_data).chomp @ioh.write CARGO_BEGIN + "\n" @ioh.write name + "\n" @ioh.write digest + "\n" @ioh.write b64_data + "\n" @ioh.write CARGO_END + "\n" end |