Class: Store::Digest::Object
- Inherits:
-
Object
- Object
- Store::Digest::Object
- Defined in:
- lib/store/digest/object.rb
Instance Attribute Summary collapse
-
#charset ⇒ Object
Returns the value of attribute charset.
-
#ctime ⇒ Object
Returns the value of attribute ctime.
-
#digests ⇒ Object
readonly
XXX come up with a policy for these that isn’t stupid, plus input sanitation.
-
#dtime ⇒ Object
Returns the value of attribute dtime.
-
#encoding ⇒ Object
Returns the value of attribute encoding.
-
#flags ⇒ Object
Returns the value of attribute flags.
-
#language ⇒ Object
Returns the value of attribute language.
-
#mtime ⇒ Object
Returns the value of attribute mtime.
-
#ptime ⇒ Object
Returns the value of attribute ptime.
-
#size ⇒ Object
readonly
XXX come up with a policy for these that isn’t stupid, plus input sanitation.
-
#type ⇒ Object
Returns the value of attribute type.
Class Method Summary collapse
Instance Method Summary collapse
-
#algorithms ⇒ Array
Return the algorithms used in the object.
-
#charset_checked? ⇒ false, true
Returns true if the character set has been checked.
-
#charset_valid? ⇒ false, true
Returns true if the character set has been checked and is valid.
-
#content ⇒ IO
Returns the content stored in the object.
-
#content? ⇒ false, true
Determines if there is content embedded in the object.
-
#deleted? ⇒ false, true
Just a plain old predicate to determine whether the blob has been deleted from the store (but implicitly the metadata record remains).
-
#digest(symbol) ⇒ Symbol?
(also: #[])
Return a particular digest.
-
#encoding_checked? ⇒ false, true
Returns true if the content encoding (e.g. gzip, deflate) has been checked.
-
#encoding_valid? ⇒ false, true
Returns true if the content encoding has been checked and is valid.
-
#fresh?(state = nil) ⇒ Boolean
Determine (or set) whether the object is “fresh”, i.e.
-
#initialize(content = nil, digests: {}, size: 0, type: 'application/octet-stream', charset: nil, language: nil, encoding: nil, ctime: nil, mtime: nil, ptime: nil, dtime: nil, flags: 0, strict: true, fresh: false) ⇒ Store::Digest::Object
constructor
Create a new object, naively recording whatever is handed.
- #scan(content = nil, digests: URI::NI.algorithms, mtime: nil, type: nil, charset: nil, language: nil, encoding: nil, blocksize: BLOCKSIZE, strict: true, fresh: nil, &block) ⇒ Object
-
#scanned? ⇒ false, true
Determines if the object has been scanned.
-
#syntax_checked? ⇒ false, true
Returns true if the blob’s syntax has been checked.
-
#syntax_valid? ⇒ false, true
Returns true if the blob’s syntax has been checked and is valid.
-
#to_h(content: false) ⇒ Hash
Return the object as a hash.
-
#to_s ⇒ Object
Outputs a human-readable string representation of the object.
-
#type_charset ⇒ String
Returns the type and charset, suitable for an HTTP header.
-
#type_checked? ⇒ false, true
Returns true if the content type has been checked.
-
#type_valid? ⇒ false, true
Returns true if the content type has been checked and is valid.
Constructor Details
#initialize(content = nil, digests: {}, size: 0, type: 'application/octet-stream', charset: nil, language: nil, encoding: nil, ctime: nil, mtime: nil, ptime: nil, dtime: nil, flags: 0, strict: true, fresh: false) ⇒ Store::Digest::Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/store/digest/object.rb', line 152 def initialize content = nil, digests: {}, size: 0, type: 'application/octet-stream', charset: nil, language: nil, encoding: nil, ctime: nil, mtime: nil, ptime: nil, dtime: nil, flags: 0, strict: true, fresh: false # snag this immediately @fresh = !!fresh # check input on content @content = case content when nil then nil when IO, StringIO, Proc then content when String then StringIO.new content when Pathname then -> { content..open('rb') } when -> x { %i[read seek pos].all? { |m| x.respond_to? m } } content else raise ArgumentError, "Cannot accept content given as #{content.class}" end # check input on digests @digests = case digests when Hash # hash must be clean digests.map do |k, v| raise ArgumentError, 'Digest keys must be symbol-able' unless k.respond_to? :to_sym k = k.to_sym raise ArgumentError, 'Digest values must be URI::NI' unless v.is_a? URI::NI raise ArgumentError, 'Digest key must match value algorithm' unless k == v.algorithm [k.to_sym, v.dup.freeze] end.to_h when nil then {} # empty hash when Array # only accepts array of URI::NI digests.map do |x| raise ArgumentError, "Digests given as array can only be URI::NI, not #{x}" \ unless x.is_a? URI::NI [x.algorithm, x.dup.freeze] end.to_h when URI::NI then { digests.algorithm => digests.dup.freeze } else # everything else is invalid raise ArgumentError, "Cannot coerce digests given as #{digests.inspect}" end # ctime, mtime, ptime, dtime should be all nil or nonnegative # integers or Time or DateTime b = binding %i[ctime mtime ptime dtime].each do |k| v = coerce_time(b.local_variable_get(k), k) instance_variable_set "@#{k}", v end # size and flags should be non-negative integers %i[size flags].each do |k| x = b.local_variable_get k v = case x when nil then 0 when Integer raise ArgumentError, "#{k} must be non-negative" if x < 0 x else raise ArgumentError, "#{k} must be nil or an Integer" end instance_variable_set "@#{k}", v end # the following can be strings or symbols: TOKENS.keys.each do |k| if x = b.local_variable_get(k) x = if strict coerce_token(x, k) else coerce_token(x, k) rescue nil end instance_variable_set "@#{k}", x.freeze if x end end end |
Instance Attribute Details
#charset ⇒ Object
Returns the value of attribute charset.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def charset @charset end |
#ctime ⇒ Object
Returns the value of attribute ctime.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def ctime @ctime end |
#digests ⇒ Object (readonly)
XXX come up with a policy for these that isn’t stupid, plus input sanitation
242 243 244 |
# File 'lib/store/digest/object.rb', line 242 def digests @digests end |
#dtime ⇒ Object
Returns the value of attribute dtime.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def dtime @dtime end |
#encoding ⇒ Object
Returns the value of attribute encoding.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def encoding @encoding end |
#flags ⇒ Object
Returns the value of attribute flags.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def flags @flags end |
#language ⇒ Object
Returns the value of attribute language.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def language @language end |
#mtime ⇒ Object
Returns the value of attribute mtime.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def mtime @mtime end |
#ptime ⇒ Object
Returns the value of attribute ptime.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def ptime @ptime end |
#size ⇒ Object (readonly)
XXX come up with a policy for these that isn’t stupid, plus input sanitation
242 243 244 |
# File 'lib/store/digest/object.rb', line 242 def size @size end |
#type ⇒ Object
Returns the value of attribute type.
243 244 245 |
# File 'lib/store/digest/object.rb', line 243 def type @type end |
Class Method Details
.scan(content, digests: URI::NI.algorithms, mtime: nil, type: nil, language: nil, charset: nil, encoding: nil, blocksize: BLOCKSIZE, strict: true, fresh: false, &block) ⇒ Object
247 248 249 250 251 252 253 |
# File 'lib/store/digest/object.rb', line 247 def self.scan content, digests: URI::NI.algorithms, mtime: nil, type: nil, language: nil, charset: nil, encoding: nil, blocksize: BLOCKSIZE, strict: true, fresh: false, &block self.new.scan content, digests: digests, mtime: mtime, type: type, language: language, charset: charset, encoding: encoding, blocksize: blocksize, strict: strict, fresh: fresh, &block end |
Instance Method Details
#algorithms ⇒ Array
Return the algorithms used in the object.
347 348 349 |
# File 'lib/store/digest/object.rb', line 347 def algorithms (@digests || {}).keys.sort end |
#charset_checked? ⇒ false, true
Returns true if the character set has been checked.
402 403 404 |
# File 'lib/store/digest/object.rb', line 402 def charset_checked? 0 != @flags & CHARSET_CHECKED end |
#charset_valid? ⇒ false, true
Returns true if the character set has been checked and is valid.
408 409 410 |
# File 'lib/store/digest/object.rb', line 408 def charset_valid? 0 != @flags & (CHARSET_CHECKED|CHARSET_VALID) end |
#content ⇒ IO
Returns the content stored in the object.
364 365 366 |
# File 'lib/store/digest/object.rb', line 364 def content @content.is_a?(Proc) ? @content.call : @content end |
#content? ⇒ false, true
Determines if there is content embedded in the object.
370 371 372 |
# File 'lib/store/digest/object.rb', line 370 def content? !!@content end |
#deleted? ⇒ false, true
Just a plain old predicate to determine whether the blob has been deleted from the store (but implicitly the metadata record remains).
457 458 459 |
# File 'lib/store/digest/object.rb', line 457 def deleted? !!@dtime end |
#digest(symbol) ⇒ Symbol? Also known as: []
Return a particular digest. Returns nil if there is no match.
354 355 356 357 358 |
# File 'lib/store/digest/object.rb', line 354 def digest symbol raise ArgumentError, "This method takes a symbol" unless symbol.respond_to? :to_sym digests[symbol.to_sym] end |
#encoding_checked? ⇒ false, true
Returns true if the content encoding (e.g. gzip, deflate) has been checked.
415 416 417 |
# File 'lib/store/digest/object.rb', line 415 def encoding_checked? 0 != @flags & ENCODING_CHECKED end |
#encoding_valid? ⇒ false, true
Returns true if the content encoding has been checked and is valid.
421 422 423 |
# File 'lib/store/digest/object.rb', line 421 def encoding_valid? 0 != @flags & (ENCODING_CHECKED|ENCODING_VALID) end |
#fresh?(state = nil) ⇒ Boolean
Determine (or set) whether the object is “fresh”, i.e. whether it is new (or restored), or had been previously been in the store.
341 342 343 |
# File 'lib/store/digest/object.rb', line 341 def fresh? state = nil state.nil? ? @fresh : @fresh = !!state end |
#scan(content = nil, digests: URI::NI.algorithms, mtime: nil, type: nil, charset: nil, language: nil, encoding: nil, blocksize: BLOCKSIZE, strict: true, fresh: nil, &block) ⇒ Object
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/store/digest/object.rb', line 255 def scan content = nil, digests: URI::NI.algorithms, mtime: nil, type: nil, charset: nil, language: nil, encoding: nil, blocksize: BLOCKSIZE, strict: true, fresh: nil, &block # update freshness if there is something to update @fresh = !!fresh unless fresh.nil? # we put all the scanning stuff in here content = case content when nil then self.content when IO, StringIO then content when String then StringIO.new content when Pathname then content.open('rb') when Proc then content.call when -> x { %i[read seek pos].all? { |m| x.respond_to? m } } content else raise ArgumentError, "Cannot scan content of type #{content.class}" end content.binmode if content.respond_to? :binmode # sane default for mtime @mtime = coerce_time(mtime || @mtime || (content.respond_to?(:mtime) ? content.mtime : Time.now), :mtime) # eh, *some* code reuse b = binding TOKENS.keys.each do |k| if x = b.local_variable_get(k) x = if strict coerce_token(x, k) else coerce_token(x, k) rescue nil end instance_variable_set "@#{k}", x.freeze if x end end digests = case digests when Array then digests when Symbol then [digests] else raise ArgumentError, 'Digests must be one or more symbol' end raise ArgumentError, "Invalid digest list #{digests - URI::NI.algorithms}" unless (digests - URI::NI.algorithms).empty? # set up the contexts digests = digests.map { |d| [d, URI::NI.context(d)] }.to_h # sample for mime type checking sample = StringIO.new '' @size = 0 while buf = content.read(blocksize) @size += buf.size sample << buf if sample.pos < SAMPLE digests.values.each { |ctx| ctx << buf } block.call buf if block_given? end # seek the content back to the front and store it content.seek 0, 0 @content = content # set up the digests @digests = digests.map do |k, v| [k, URI::NI.compute(v, algorithm: k).freeze] end.to_h.freeze # obtain the sampled content type ts = MimeMagic.by_magic(sample) || MimeMagic.default_type(sample) if content.respond_to? :path # may as well use the path if it's available and more specific ps = MimeMagic.by_path(content.path) # XXX the need to do ts.to_s is a bug in mimemagic ts = ps if ps and ps.child_of?(ts.to_s) end @type = !type || ts.child_of?(type) ? ts.to_s : type self end |
#scanned? ⇒ false, true
Determines if the object has been scanned.
384 385 386 |
# File 'lib/store/digest/object.rb', line 384 def scanned? !@digests.empty? end |
#syntax_checked? ⇒ false, true
Returns true if the blob’s syntax has been checked.
427 428 429 |
# File 'lib/store/digest/object.rb', line 427 def syntax_checked? 0 != @flags & SYNTAX_CHECKED end |
#syntax_valid? ⇒ false, true
Returns true if the blob’s syntax has been checked and is valid.
433 434 435 |
# File 'lib/store/digest/object.rb', line 433 def syntax_valid? 0 != @flags & (SYNTAX_CHECKED|SYNTAX_VALID) end |
#to_h(content: false) ⇒ Hash
Return the object as a hash. Omits the content by default.
464 465 466 467 468 469 470 |
# File 'lib/store/digest/object.rb', line 464 def to_h content: false main = %i[content digests] main.shift unless content (main + MANDATORY + OPTIONAL + [:flags]).map do |k| [k, send(k).dup] end.to_h end |
#to_s ⇒ Object
Outputs a human-readable string representation of the object.
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'lib/store/digest/object.rb', line 473 def to_s out = "#{self.class}\n Digests:\n" # disgorge the digests digests.values.sort { |a, b| a.to_s <=> b.to_s }.each do |d| out << " #{d}\n" end # now the fields MANDATORY.each { |m| out << " #{LABELS[m]}: #{send m}\n" } OPTIONAL.each do |o| val = send o out << " #{LABELS[o]}: #{val}\n" if val end # now the validation statuses out << "Validation:\n" FLAG.each_index do |i| x = flags >> (3 - i) & 3 out << (" %-16s: %s\n" % [FLAG[i], STATE[x]]) end out end |
#type_charset ⇒ String
Returns the type and charset, suitable for an HTTP header.
376 377 378 379 380 |
# File 'lib/store/digest/object.rb', line 376 def type_charset out = type.to_s out += ";charset=#{charset}" if charset out end |
#type_checked? ⇒ false, true
Returns true if the content type has been checked.
390 391 392 |
# File 'lib/store/digest/object.rb', line 390 def type_checked? 0 != @flags & TYPE_CHECKED end |
#type_valid? ⇒ false, true
Returns true if the content type has been checked and is valid.
396 397 398 |
# File 'lib/store/digest/object.rb', line 396 def type_valid? 0 != @flags & (TYPE_CHECKED|TYPE_VALID) end |