Class: Distillery::ROM
- Inherits:
-
Object
- Object
- Distillery::ROM
- Defined in:
- lib/distillery/rom.rb,
lib/distillery/rom/path.rb,
lib/distillery/rom/path/file.rb,
lib/distillery/rom/path/archive.rb,
lib/distillery/rom/path/virtual.rb
Overview
ROM representation. It will typically have a name (entry) and hold information about it’s content (size and checksums). If physical content is present it is referenced by it’s path
Defined Under Namespace
Classes: HeaderLookupError, Path
Constant Summary collapse
- CHECKSUMS_WEAK =
List of supported weak checksums sorted by strength order (a subset of CHECKSUMS)
[ :crc32 ].freeze
- CHECKSUMS_STRONG =
List of supported strong checksums sorted by strength order (a subset of CHECKSUMS)
[ :sha256, :sha1, :md5 ].freeze
- CHECKSUMS =
List of all supported checksums sorted by strength order
(CHECKSUMS_STRONG + CHECKSUMS_WEAK).freeze
- CHECKSUMS_DAT =
List of all DAT supported checksums sorted by strengh order
[ :sha1, :md5, :crc32 ].freeze
- FS_CHECKSUM =
Checksum used when saving to file-system
:sha1
Class Method Summary collapse
-
.filecopy(from, to, length = nil, offset = 0, force: false, link: :hard) ⇒ Boolean
Copy file, possibly using link if requested.
-
.from_file(file, root = nil, headers: nil) ⇒ ROM
Create ROM object from file definition.
-
.headered?(data, ext: nil, headers: HEADERS) ⇒ Integer?
Check if an header is detected.
-
.info(io, bufsize: 32, headers: nil) ⇒ Hash{Symbol=>Object}
Get information about ROM file (size, checksum, header, …).
Instance Method Summary collapse
-
#cksum(type, fmt = :bin) ⇒ String
Get the ROM specific checksum.
-
#cksums(fmt = :bin) ⇒ Hash{Symbol=>String}
Get the ROM checksums.
-
#copy(to, part: :all, force: false, link: :hard) ⇒ Boolean
Copy ROM content to the filesystem, possibly using link if requested.
-
#crc32 ⇒ String?
Get ROM crc32 as hexadcimal string (if defined).
-
#delete! ⇒ Boolean
Delete physical content.
-
#fshash ⇒ String
Checksum to be used for naming on filesystem.
-
#has_content? ⇒ Boolean
Check if ROM hold content.
-
#header ⇒ String
Get ROM header.
-
#headered? ⇒ Boolean
Does this ROM have an header?.
-
#initialize(path, logger: nil, offset: nil, size: nil, **cksums) ⇒ ROM
constructor
Create ROM representation.
-
#md5 ⇒ String?
Get ROM md5 as hexadecimal string (if defined).
-
#missing_checksums?(checksums = CHECKSUMS_DAT) ⇒ Boolean
Are some checksums missing?.
-
#missing_size? ⇒ Boolean
Is size information missing?.
-
#name ⇒ String
Get ROM name.
-
#path ⇒ String
Get ROM path.
-
#reader {|io| ... } ⇒ Object
ROM reader.
-
#rename(path, force: false) {|old, new| ... } ⇒ Boolean
Rename ROM and physical content.
-
#same?(o, weak: true) ⇒ Boolean?
Compare ROMs using their checksums.
-
#sha1 ⇒ String?
Get ROM sha1 as hexadecimal string (if defined).
-
#size ⇒ Integer?
Get ROM size in bytes.
-
#to_s(prefered = :name) ⇒ String
String representation.
Constructor Details
#initialize(path, logger: nil, offset: nil, size: nil, **cksums) ⇒ ROM
Create ROM representation.
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 |
# File 'lib/distillery/rom.rb', line 256 def initialize(path, logger: nil, offset: nil, size: nil, **cksums) # Sanity check if path.nil? raise ArgumentError, "ROM path is required" end unsupported_cksums = cksums.keys - CHECKSUMS if ! unsupported_cksums.empty? raise ArgumentError, "unsupported checksums <#{unsupported_cksums.join(',')}>" end # Ensure checksum for nul-size ROM if size == 0 cksums = Hash[CHECKSUMS_DEF.map {|k, (_, z)| [k, z] } ] end # Initialize @offset = offset @path = path @size = size @cksum = Hash[CHECKSUMS_DEF.map {|k, (s, _)| [k, case val = cksums[k] # No checksum when '', '-', nil # Checksum as hexstring or binary string when String case val.size when s/4 then [val].pack('H*') when s/8 then val else raise ArgumentError, "wrong size #{val.size} for hash string #{k}" end # Checksum as integer when Integer raise ArgumentError if (val < 0) || (val > 2**s) ["%0#{s/4}x" % val].pack('H*') # Oops else raise ArgumentError, "unsupported hash value type" end ] }].compact # Warns warns = [] # warns << 'nul size' if @size == 0 warns << 'no checksum' if @cksum.empty? if !warns.empty? warn "ROM <#{self.to_s}> has #{warns.join(', ')}" end end |
Class Method Details
.filecopy(from, to, length = nil, offset = 0, force: false, link: :hard) ⇒ Boolean
Copy file, possibly using link if requested.
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 |
# File 'lib/distillery/rom.rb', line 174 def self.filecopy(from, to, length = nil, offset = 0, force: false, link: :hard) # Ensure sub-directories are created FileUtils.mkpath(File.dirname(to)) # If whole file is to be copied try optimisation if length.nil? && offset.zero? # If we are on the same filesystem, we can use hardlink f_stat = File.stat(from) f_dev = [ f_stat.dev_major, f_stat.dev_minor ] t_stat = File.stat(File.dirname(to)) t_dev = [ t_stat.dev_major, t_stat.dev_minor ] if f_dev == t_dev # If file already exists we will need to unlink it before # but we will try to create hardlink before to not remove # it unnecessarily if hardlinks are not supported begin File.link(from, to) return true rescue Errno::EEXIST raise if !force # File exist and we need to unlink it # if unlink or link fails, something is wrong begin File.unlink(to) File.link(from, to) return true rescue Errno::ENOENT end rescue Errno::EOPNOTSUPP # If link are not supported fallback to copy end end end # Copy file op = force ? File::TRUNC : File::EXCL File.open(from, File::RDONLY) {|i| i.seek(offset) File.open(to, File::CREAT|File::WRONLY|op) {|o| IO.copy_stream(i, o, length) } } return true rescue Errno::EEXIST return false end |
.from_file(file, root = nil, headers: nil) ⇒ ROM
Create ROM object from file definition.
If ‘file` is an absolute path or `root` is not specified, ROM will be created with basename/dirname of entry.
235 236 237 238 239 240 241 242 243 244 |
# File 'lib/distillery/rom.rb', line 235 def self.from_file(file, root=nil, headers: nil) basedir, entry = if root.nil? then File.split(file) elsif file.start_with?('/') then File.split(file) else [ root, file ] end file = File.join(basedir, entry) rominfo = File.open(file) {|io| ROM.info(io, headers: headers) } self.new(ROM::Path::File.new(entry, basedir), **rominfo) end |
.headered?(data, ext: nil, headers: HEADERS) ⇒ Integer?
Check if an header is detected
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/distillery/rom.rb', line 146 def self.headered?(data, ext: nil, headers: HEADERS) # Normalize ext = ext[1..-1] if ext && (ext[0] == ?.) size = data.size hdr = headers.find {| rules:, ** | rules.all? {|offset, string| if (offset + string.size) > size raise HeaderLookupError end data[offset, string.size] == string } } hdr&.[](:offset) end |
.info(io, bufsize: 32, headers: nil) ⇒ Hash{Symbol=>Object}
Get information about ROM file (size, checksum, header, …)
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/distillery/rom.rb', line 85 def self.info(io, bufsize: 32, headers: nil) # Sanity check if bufsize <= 0 raise ArgumentError, "bufsize argument must be > 0" end # Apply default headers ||= HEADERS # Adjust bufsize (from kB to B) bufsize <<= 10 # Initialize info offset = 0 size = 0 sha256 = Digest::SHA256.new sha1 = Digest::SHA1.new md5 = Digest::MD5.new crc32 = 0 # Process whole data if x = io.read(bufsize) if headers != false begin if offset = self.headered?(x, headers: headers) x = x[offset..-1] end rescue HeaderLookupError end end loop do size += x.length sha256 << x sha1 << x md5 << x crc32 = Zlib::crc32(x, crc32) break unless x = io.read(bufsize) end end # Return info { :offset => offset, :size => size, :sha256 => sha256.digest, :sha1 => sha1.digest, :md5 => md5.digest, :crc32 => crc32, }.compact end |
Instance Method Details
#cksum(type, fmt = :bin) ⇒ String
Get the ROM specific checksum
393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/distillery/rom.rb', line 393 def cksum(type, fmt=:bin) raise ArgumentError unless CHECKSUMS.include?(type) if ckobj = @cksum[type] case fmt when :bin then ckobj when :hex then ckobj.unpack1('H*') else raise ArgumentError end end end |
#cksums(fmt = :bin) ⇒ Hash{Symbol=>String}
Get the ROM checksums
415 416 417 418 419 420 421 |
# File 'lib/distillery/rom.rb', line 415 def cksums(fmt=:bin) case fmt when :bin then @cksum when :hex then @cksum.transform_values {|v| v.unpack1('H*') } else raise ArgumentError end end |
#copy(to, part: :all, force: false, link: :hard) ⇒ Boolean
Copy ROM content to the filesystem, possibly using link if requested.
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/distillery/rom.rb', line 526 def copy(to, part: :all, force: false, link: :hard) # Sanity check unless [ :all, :rom, :header ].include?(part) raise ArgumenetError, "unsupported part (#{part})" end # Copy length, offset = case part when :all [ nil, 0 ] when :rom [ nil, @offset || 0 ] when :header return false if !self.headered? [ @offset, 0 ] end @path.copy(to, length, offset, force: force, link: link) end |
#crc32 ⇒ String?
Get ROM crc32 as hexadcimal string (if defined)
472 473 474 |
# File 'lib/distillery/rom.rb', line 472 def crc32 cksum(:crc32, :hex) end |
#delete! ⇒ Boolean
Delete physical content.
551 552 553 554 555 |
# File 'lib/distillery/rom.rb', line 551 def delete! if @path.delete! @path == ROM::Path::Virtual.new(@path.entry) end end |
#fshash ⇒ String
Checksum to be used for naming on filesystem
428 429 430 |
# File 'lib/distillery/rom.rb', line 428 def fshash cksum(FS_CHECKSUM, :hex) end |
#has_content? ⇒ Boolean
Check if ROM hold content
336 337 338 |
# File 'lib/distillery/rom.rb', line 336 def has_content? ! @path.storage.nil? end |
#header ⇒ String
Get ROM header
376 377 378 379 |
# File 'lib/distillery/rom.rb', line 376 def header return nil if !headered? @path.reader {|io| io.read(@offset) } end |
#headered? ⇒ Boolean
Does this ROM have an header?
367 368 369 |
# File 'lib/distillery/rom.rb', line 367 def headered? !@offset.nil? && (@offset > 0) end |
#md5 ⇒ String?
Get ROM md5 as hexadecimal string (if defined)
463 464 465 |
# File 'lib/distillery/rom.rb', line 463 def md5 cksum(:md5, :hex) end |
#missing_checksums?(checksums = CHECKSUMS_DAT) ⇒ Boolean
Are some checksums missing?
483 484 485 |
# File 'lib/distillery/rom.rb', line 483 def missing_checksums?(checksums = CHECKSUMS_DAT) @cksum.keys != checksums end |
#missing_size? ⇒ Boolean
Is size information missing?
435 436 437 |
# File 'lib/distillery/rom.rb', line 435 def missing_size? @size.nil? end |
#name ⇒ String
Get ROM name.
492 493 494 |
# File 'lib/distillery/rom.rb', line 492 def name @path.basename end |
#path ⇒ String
Get ROM path.
501 502 503 |
# File 'lib/distillery/rom.rb', line 501 def path @path end |
#reader {|io| ... } ⇒ Object
ROM reader
512 513 514 |
# File 'lib/distillery/rom.rb', line 512 def reader(&block) @path.reader(&block) end |
#rename(path, force: false) {|old, new| ... } ⇒ Boolean
Renaming could lead to silent removing if same ROM is on its way
Rename ROM and physical content.
571 572 573 574 575 576 577 578 579 580 581 |
# File 'lib/distillery/rom.rb', line 571 def rename(path, force: false) # Deal with renaming ok = @path.rename(path, force: force) if ok @entry = entry yield(old_entry, entry) if block_given? end ok end |
#same?(o, weak: true) ⇒ Boolean?
Compare ROMs using their checksums.
317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/distillery/rom.rb', line 317 def same?(o, weak: true) return true if self.equal?(o) decidable = false (weak ? CHECKSUMS : CHECKSUMS_STRONG).each {|type| s_cksum = self.cksum(type) o_cksum = o.cksum(type) if s_cksum.nil? || o_cksum.nil? then next elsif s_cksum != o_cksum then return false else decidable = true end } decidable ? true : nil end |
#sha1 ⇒ String?
Get ROM sha1 as hexadecimal string (if defined)
454 455 456 |
# File 'lib/distillery/rom.rb', line 454 def sha1 cksum(:sha1, :hex) end |
#size ⇒ Integer?
Get ROM size in bytes.
445 446 447 |
# File 'lib/distillery/rom.rb', line 445 def size @size end |
#to_s(prefered = :name) ⇒ String
String representation.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/distillery/rom.rb', line 346 def to_s(prefered = :name) case prefered when :checksum if key = CHECKSUMS.find {|k| @cksum.include?(k) } then cksum(key, :hex) else self.name end when :name self.name when :entry self.entry else self.name end end |