Class: Distillery::Vault

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/distillery/vault.rb

Constant Summary collapse

CHECKSUMS =

List of ROM checksums

ROM::CHECKSUMS
ARCHIVES =

List of archives extensions

ROMArchive::EXTENSIONS
IGNORE_FILES =

List of files to be ignored

Set[ '.dat', '.missing', '.baddump', '.extra' ]
IGNORE_DIRS =

List of directories to be ignored

Set[ '.roms', '.games', '.trash' ]
DIR_PRUNING =

Directory pruning

Set[ '.dat' ]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(roms = []) ⇒ Vault

Returns a new instance of Vault.



116
117
118
119
120
121
# File 'lib/distillery/vault.rb', line 116

def initialize(roms = [])
    @cksum    = Hash[CHECKSUMS.map {|k| [ k, {} ] }]
    @roms     = []
        
    Array(roms).each {|rom| add_rom(rom) }
end

Class Method Details

.from_dir(dir, depth: nil) {|file, dir:| ... } ⇒ Object

Note:

file in IGNORE_FILES, directory in IGNORE_DIRS, directories holding a DIR_PRUNING file or starting with a dot are ignored

Potential ROM from directory.

Parameters:

  • dir (String)

    path to directory

  • depth (Integer, nil) (defaults to: nil)

    exploration depth

Yield Parameters:

  • file (String)

    file being processed

  • dir: (String)

    directory relative to



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/distillery/vault.rb', line 46

def self.from_dir(dir, depth: nil)
    Find.find(dir) do |path|
        basename = File.basename(path)
        subpath  = Pathname(path).relative_path_from(dir).to_s
        if    FileTest.directory?(path)
            next       if path == dir
            Find.prune if IGNORE_DIRS.include?(basename)
            Find.prune if basename.start_with?('.')
            Find.prune if !depth.nil? &&
                          subpath.split(File::Separator).size > depth
            Find.prune if DIR_PRUNING.any? {|f| File.exists?(f) }
        elsif FileTest.file?(path)
            next if IGNORE_FILES.include?(basename)                
            yield(subpath, dir: dir) if block_given?
        end
    end
end

.from_glob(glob, basedir: :guess) {|file, dir:| ... } ⇒ Object

Note:

file in IGNORE_FILES, directory in IGNORE_DIRS, directories holding a DIR_PRUNING file or starting with a dot are ignored

Potential ROM from glob

Parameters:

  • glob (String)

    ruby glob

  • basedir (:guess, nil) (defaults to: :guess)

    basedir to use when interpreting glob matching

Yield Parameters:

  • file (String)

    file being processed

  • dir: (String)

    directory relative to



77
78
79
80
81
82
83
84
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
# File 'lib/distillery/vault.rb', line 77

def self.from_glob(glob, basedir: :guess)
    if basedir == :guess
        gentry  = glob.split(File::SEPARATOR)
        idx     = gentry.find_index{|entry| entry =~ GLOB_PATTERN_REGEX }
        gentry  = gentry[0,idx]
        basedir = if    gentry.empty?       then nil
                  elsif gentry.first.empty? then '/'
                  else                      File.join(gentry)
                  end
    end

    # Build file list (reject ignored files and dirs)
    lst = Dir[glob].reject {|path|
        (! FileTest.file?(path)) 				||
        IGNORE_FILES.include?(File.basename(path)) 		||
        path.split(File::SEPARATOR)[0..-1].any? {|dir|
            IGNORE_DIRS.include?(dir) || dir.start_with?('.')
        }
    }
    # Build cut list based on directory prunning
    cutlst = lst.map {|f| File.dirname(f) }.uniq.select {|f|
        DIR_PRUNING.any? {|p| FileTest.exist?(File.join(f,p)) }
    }
    # Apply cut list
    lst.reject! {|path|
        cutlst.any? {|cut| path.start_with?("#{cut}#{File::SEPARATOR}") }
    }

    # Iterate on list
    lst.each do |path|
        subpath = if basedir.nil?
                  then path
                  else Pathname(path).relative_path_from(basedir).to_s
                  end
        yield(subpath, dir: basedir) if block_given?
    end
end

Instance Method Details

#&(o) ⇒ Vault

Construct a new ROM vault as the intersection

Parameters:

  • o (Vault)

    ROM vault to intersect with self

Returns:



149
150
151
# File 'lib/distillery/vault.rb', line 149

def &(o)
    Vault::new(@roms.select {|rom| o.match(rom) })
end

#-(o) ⇒ Vault

Constuct a new ROM vault as the difference

Parameters:

  • o (Vault)

    ROM vault to substract to self

Returns:



159
160
161
# File 'lib/distillery/vault.rb', line 159

def -(o)
    Vault::new(@roms.reject {|rom| o.match(rom) })
end

#<<(rom) ⇒ Object

Add ROM

Parameters:

  • *roms (ROM)

    ROM to add

Returns:

  • self



170
171
172
# File 'lib/distillery/vault.rb', line 170

def <<(rom)
    add_rom(rom)
end

#add_from_dir(dir, depth: nil, archives: ARCHIVES) {|file, dir| ... } ⇒ self

Note:

file in IGNORE_FILES, directory in IGNORE_DIRS, directories holding a DIR_PRUNING file or starting with a dot are ignored

Add ROM from directory.

Parameters:

  • dir (String)

    path to directory

  • depth (Integer, nil) (defaults to: nil)

    exploration depth

  • archives (#include?) (defaults to: ARCHIVES)

    archives tester

Yield Parameters:

  • file (String)

    file being processed

  • dir (String)

    directory relative to

Returns:

  • (self)


238
239
240
241
242
243
244
# File 'lib/distillery/vault.rb', line 238

def add_from_dir(dir, depth: nil, archives: ARCHIVES)
    Vault.from_dir(dir, depth: depth) do | file, dir: |
        yield(file, dir: dir) if block_given?
        add_from_file(file, dir, archives: archives)
    end
    self
end

#add_from_file(file, basedir = nil, archives: ARCHIVES) ⇒ self

Add ROM from file

Parameters:

  • file (String)

    path to files relative to basedir

  • basedir (String, nil) (defaults to: nil)

    base directory

  • archives (#include?) (defaults to: ARCHIVES)

    archives tester

Returns:

  • (self)


213
214
215
216
217
218
219
220
221
# File 'lib/distillery/vault.rb', line 213

def add_from_file(file, basedir = nil, archives: ARCHIVES)
    filepath = File.join(*[ basedir, file ].compact)
    romlist  = if ROMArchive.archive?(filepath, archives: archives)
               then ROMArchive.from_file(filepath).to_a
               else ROM.from_file(file, basedir)
               end

    Array(romlist).each {|rom| add_rom(rom) }
end

#add_from_glob(glob, basedir: :guess, archives: ARCHIVES) {|file, dir| ... } ⇒ self

Note:

file in IGNORE_FILES, directory in IGNORE_DIRS, directories holding a DIR_PRUNING file or starting with a dot are ignored

Add ROM from glob

Parameters:

  • glob (String)

    ruby glob

  • basedir (:guess, nil) (defaults to: :guess)

    basedir to use when interpreting glob matching

  • archives (#include?) (defaults to: ARCHIVES)

    archives tester

Yield Parameters:

  • file (String)

    file being processed

  • dir (String)

    directory relative to

Returns:

  • (self)


262
263
264
265
266
267
268
# File 'lib/distillery/vault.rb', line 262

def add_from_glob(glob, basedir: :guess, archives: ARCHIVES)
    Vault.from_dir(glob, basedir: basedir) do | file, dir: |
        yield(file, dir: dir) if block_given?
        add_from_file(file, dir, archives: archives)
    end
    self
end

#add_rom(rom) ⇒ Object

Add ROM

Parameters:

  • rom (ROM)

    ROM to add

Returns:

  • self



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/distillery/vault.rb', line 181

def add_rom(rom)
    # Sanity check
    unless ROM === rom
        raise ArgumentError, "not a ROM" 
    end

    # Add it to the list
    @roms << rom

    # Keep track of checksums
    @cksum.each {|type, hlist|
        hlist.merge!(rom.cksum(type) => rom) {|key, old, new|
            if Array(old).any? {|r| r.path == new.path}
            then old
            else Array(old) + [ new ]
            end
        }
    }
    
    # Chainable
    self
end

#cksummatch(query) ⇒ Array<ROM>?

Return list of matching ROMs.

Parameters:

  • query (Hash{Symbol=>String})

    Hash of checksums to match with

Returns:

  • (Array<ROM>)

    list of matching ROMs

  • (nil)

    if no match



302
303
304
305
306
307
308
309
# File 'lib/distillery/vault.rb', line 302

def cksummatch(query)
    CHECKSUMS.each {|type|
        if (q = query[type]) && (r = @cksum[type][q])
            return Array(r)
        end
    }
    return nil
end

#dump(compact: false) {|group, entries| ... } ⇒ self

Dumping of ROM vault entries

Parameters:

  • compact (Boolean) (defaults to: false)

Yield Parameters:

  • group (String)
  • entries (Array<String>)

Returns:

  • (self)


406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/distillery/vault.rb', line 406

def dump(compact: false, &block)
    self.each.inject({}) {|grp, rom|
        grp.merge(rom.path.storage => [rom]) {|key, old, new| old + new }
    }.each {|storage, roms|
        size = if ROM::Path::Archive === roms.first.path
                   roms.first.path.archive.size
               end
        
        if storage.nil?
            roms.each {|rom| block.call(rom.path.entry, nil) }
        else
            if compact && (size == roms.size)
            then block.call(storage)
            else block.call(storage, roms.map {|r| r.path.entry })
            end
        end
    }
    self
end

#each {|rom| ... } ⇒ self, Enumerator

Iterate over each ROM

Yield Parameters:

Returns:

  • (self, Enumerator)


139
140
141
142
# File 'lib/distillery/vault.rb', line 139

def each
    block_given? ? @roms.each {|r| yield(r) }
                 : @roms.each
end

#empty?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/distillery/vault.rb', line 124

def empty?
    @roms.empty?
end

#headeredInteger, ...

Check if we have some headered ROM.

Returns:

  • (Integer)

    only some ROMs are headered

  • (true)

    all ROMs are headered

  • (false)

    no headered ROM



286
287
288
289
290
291
292
293
# File 'lib/distillery/vault.rb', line 286

def headered
    size = @roms.select {|rom| rom.headered? }.size
    
    if    size == 0          then false
    elsif size == @roms.size then true
    else                          size
    end
end

#match(query) {|rom| ... } ⇒ Array<ROM>?

Return list of matching ROMs.

Parameters:

  • query (Hash{Symbol=>String}, ROM)

    Hash of checksums or ROM to match with

Yield Parameters:

  • rom (ROM)

    ROM that has been saved

Returns:

  • (Array<ROM>)

    list of matching ROMs

  • (nil)

    if no match



333
334
335
336
337
338
339
# File 'lib/distillery/vault.rb', line 333

def match(query)
    case query
    when Hash then self.cksummatch(query)
    when ROM  then self.rommatch(query)
    else raise ArgumentError
    end
end

#rommatch(rom) ⇒ Array<ROM>?

Return list of matching ROMs.

Parameters:

  • rom (ROM)

    ROM to match with

Returns:

  • (Array<ROM>)

    list of matching ROMs

  • (nil)

    if no match



318
319
320
# File 'lib/distillery/vault.rb', line 318

def rommatch(rom)
    self.cksummatch(rom.cksums)
end

#save(dir, part: :all, subdir: false, pristine: false, force: false) {|rom| ... } ⇒ self

Save ROM to filesystem

Parameters:

  • dir (String)

    directory used for saving

  • part (:all, :header, :rom) (defaults to: :all)

    wich part of the ROM file to save

  • subdir (Boolean, Integer, Proc) (defaults to: false)

    use subdirectory

  • pristine (Boolean) (defaults to: false)

    should existing directory be removed

  • force (Boolean) (defaults to: false)

    remove previous file if necessary

Yield Parameters:

  • rom (ROM)

    ROM saved

Returns:

  • (self)


354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/distillery/vault.rb', line 354

def save(dir, part: :all, subdir: false, pristine: false, force: false,
         &block)
    # Directory
    FileUtils.remove_dir(dir) if     pristine	 # Create clean env
    Dir.mkdir(dir)            unless Dir.exist?(dir) # Ensure directory

    # Fill directory.
    # -> We have the physical ROMs, so we have all the checksums
    #    except if the file is an header without rom content
    @roms.select {|rom| rom.has_content? && !rom.fshash.nil? }
         .each   {|rom|
        hash    = rom.fshash
        destdir = dir
        dirpart = case subdir
                  when nil, false then nil
                  when true       then hash[0..3]
                  when Integer    then hash[0..subdir]
                  when Proc       then subdir.call(rom)
                  else raise ArgumentError, "unsupported subdir type"
                  end
                      
        if dirpart
            # Update destination directory
            destdir = File.join(destdir, *dirpart)
            # Ensure destination directory exists
            FileUtils.mkdir_p(destdir)
        end

        # Destination file
        dest = File.join(destdir, hash)
        
        # If the file exist, it is the right file, as it is
        # named from it's hash (ie: content)
        if force || !File.exists?(dest)
            rom.copy(dest, part: part, force: force)
        end
        
        block.call(rom) if block
    }
    self
end

#sizeInteger

Returns:

  • (Integer)


129
130
131
# File 'lib/distillery/vault.rb', line 129

def size
    @roms.size
end

#with_partial_checksumArray<ROM>?

List of ROM with loosely defined (ie: with some missing checksum)

Returns:

  • (Array<ROM>, nil)


275
276
277
# File 'lib/distillery/vault.rb', line 275

def with_partial_checksum
    @roms.select {|rom| rom.missing_checksums? }
end