Skip to content

Instantly share code, notes, and snippets.

@savetheclocktower
Last active September 6, 2017 03:25

Revisions

  1. savetheclocktower revised this gist Sep 6, 2017. 1 changed file with 27 additions and 0 deletions.
    27 changes: 27 additions & 0 deletions quit-emulationstation
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    #!/bin/bash

    print_help_and_exit() {
    echo "Usage: quit-emulationstation [options]"
    echo ""
    echo "Exits EmulationStation gracefully (if it is running)."
    exit 0
    }

    while true ; do
    case "$1" in
    -h) print_help_and_exit ;;
    *) break ;;
    esac
    shift
    done

    pid=`ps -ef | awk '/emulationstation\/emulationstation$/ {print $2}'`

    if [ -z $pid ]; then
    echo "EmulationStation doesn't appear to be running."
    exit 1
    else
    printf "Quitting EmulationStation..."
    kill $pid && echo -e "done.\n"
    exit 0
    fi
  2. savetheclocktower created this gist Sep 5, 2017.
    71 changes: 71 additions & 0 deletions assign-categories
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,71 @@
    #!/usr/bin/env ruby

    require 'pathname'

    begin
    require 'inifile'
    require 'nokogiri'
    rescue LoadError => e
    puts "This script requires nokogiri and inifile:"
    puts " $ gem install nokogiri inifile"
    exit 1
    end

    CATVER_PATH = Pathname.new(ARGV[0] || '/home/pi/catver.ini')
    CATVER = IniFile::load(CATVER_PATH)
    CATEGORIES = CATVER['Category']

    GAMELIST_DIR = Pathname.new('/home/pi/.emulationstation/gamelists/arcade')

    GAMELIST_BACKUP_PATH = GAMELIST_DIR.join("gamelist.xml.#{Time.now.to_i}.bak")
    GAMELIST_CURRENT_PATH = GAMELIST_DIR.join('gamelist.xml')

    # Backup current gamelist.xml.
    GAMELIST_BACKUP_PATH.open('w') do |f|
    f.write(GAMELIST_CURRENT_PATH.read)
    end

    GAMELIST_XML = Nokogiri::XML(GAMELIST_CURRENT_PATH.read)

    # Traverse the gamelist.
    GAMELIST_XML.css('gameList > game').each do |g|
    path_node = g.at_css('path')
    basename = Pathname.new(path_node).basename('.zip').to_s

    genre_node = g.at_css('genre')
    current_genre = g.at_css('genre').content rescue "(none)"
    new_genre = CATEGORIES[basename]

    puts "GAME: #{basename}"

    # Do we need to do anything for this game?
    if new_genre == current_genre
    puts " genre is up to date"
    next
    end

    # Do we have a genre to give it?
    unless new_genre && !new_genre.empty?
    puts " no genre found in INI"
    next
    end

    # This game has a genre. Write it to the XML.
    # Create the node if it didn't exist before.
    if !genre_node
    genre_node = Nokogiri::XML::Node.new('genre', GAMELIST_XML)
    g.add_child(genre_node)
    end

    genre_node.content = new_genre

    puts " Current genre: #{current_genre}"
    puts " New genre: #{new_genre}"
    end

    # Write out the XML to a
    GAMELIST_CURRENT_PATH.open('w') do |f|
    GAMELIST_XML.write_xml_to(f)
    end

    puts "...done."
    71 changes: 71 additions & 0 deletions assign-screenshots
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,71 @@
    #!/usr/bin/env ruby

    require 'pathname'

    begin
    require 'nokogiri'
    rescue LoadError => e
    puts "This script requires nokogiri:"
    puts " $ gem install nokogiri"
    exit 1
    end

    SYSTEM = 'arcade'

    def screenshot_path_for_game(system, game)
    Pathname.new("/home/pi/screens/#{system}/#{game}.png")
    end

    GAMELIST_DIR = Pathname.new("/home/pi/.emulationstation/gamelists/#{SYSTEM}")

    GAMELIST_BACKUP_PATH = GAMELIST_DIR.join("gamelist.xml.#{Time.now.to_i}.bak")
    GAMELIST_CURRENT_PATH = GAMELIST_DIR.join('gamelist.xml')

    # Backup current gamelist.xml.
    GAMELIST_BACKUP_PATH.open('w') do |f|
    f.write(GAMELIST_CURRENT_PATH.read)
    end

    GAMELIST_XML = Nokogiri::XML(GAMELIST_CURRENT_PATH.read)

    # Traverse the gamelist.
    GAMELIST_XML.css('gameList > game').each do |g|
    path_node = g.at_css('path')
    basename = Pathname.new(path_node).basename('.zip').to_s

    art_node = g.at_css('image')
    current_art = art_node.content rescue nil
    new_art = screenshot_path_for_game(SYSTEM, basename)

    puts "GAME: #{basename}"
    if new_art == current_art
    puts " no change needed"
    next
    end

    # Does the screenshot exist?
    if new_art.file?
    # Update the artwork's modified time so we can more easily tell which
    # artwork files we aren't using later on.
    path.touch
    else
    puts " could not find screenshot at #{new_art}"
    next
    end

    # Create the `image` element if needed.
    if !art_node
    art_node = Nokogiri::XML::Node.new('image', GAMELIST_XML)
    g.add_child(art_node)
    end

    art_node.content = new_art.to_s
    puts " changed art path to #{new_art}"
    end

    # Write out the XML.
    GAMELIST_CURRENT_PATH.open('w') do |f|
    GAMELIST_XML.write_xml_to(f)
    end

    puts "...done."
    195 changes: 195 additions & 0 deletions make-game-list
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,195 @@
    #!/usr/bin/env ruby

    require 'nokogiri'
    require 'optparse'
    require 'pathname'

    SYSTEMS = ARGV

    NAME_MAP = {
    'arcade' => 'Arcade Games',
    'daphne' => 'Laserdisc Games'
    }

    GAMELIST_ROOT = Pathname.new('/home/pi/.emulationstation/gamelists/')

    output = []

    $options = {
    require: ['name', 'genre', 'developer']
    }

    opts = OptionParser.new do |o|
    o.banner = "Usage: make-game-list [options] [systems]"
    o.separator ""

    o.on('-r', '--require=FOO,BAR', "Skip games that lack any of these fields (default: name, genre, developer)") do |value|
    $options[:require] = value.split(',')
    end
    end

    begin
    opts.parse!
    rescue OptionParser::InvalidArgument => e
    STDERR.puts("#{e.message}\n\n")
    STDERR.puts(opts)
    exit 1
    end

    def fails_requirements?(meta)
    $options[:require].any? { |k| meta[k.to_sym].nil? }
    end

    def html_for_game(game)
    id = File.basename( game.at_css('path').content, '.zip' )
    name = game.at_css('name').content
    date = game.at_css('releasedate').content rescue nil
    year = date.nil? ? nil : date[0..3]
    genre = game.at_css('genre').content rescue nil
    developer = game.at_css('developer').content rescue nil
    description = game.at_css('desc').content rescue nil

    return '' if fails_requirements?({
    name: name,
    year: year,
    genre: genre,
    developer: developer
    })

    %Q[
    <tr>
    <td data-game="#{id}" data-value="#{name}">
    <a href="#" class="game-link" data-toggle="popover" data-title="#{name}">#{name}</a>
    <div class="game-description">#{description}</div>
    </td>
    <td>#{year || '?'}</td>
    <td>#{genre || '?'}</td>
    <td>#{developer || '?'}</td>
    </tr>
    ]

    end

    def html_for_system(path)
    xml = Nokogiri::XML( path.open )
    system = path.dirname.basename.to_s

    games = xml.css('gameList > game')


    %Q[
    <h2 class="system-title">#{NAME_MAP[system] || system}</h2>
    <table class="table table-bordered table-collapsed table-striped sortable">
    <thead>
    <tr>
    <th data-defaultsort="asc">Game</th>
    <th>Year</th>
    <th>Genre</th>
    <th>Manufacturer</th>
    </tr>
    </thead>
    <tbody>
    #{games.map(&method(:html_for_game)).join("\n")}
    </tbody>
    </table>
    ]
    end

    SYSTEMS.each do |system|
    path = GAMELIST_ROOT.join(system, 'gamelist.xml')
    output << html_for_system(path)
    end

    output = output.join("\n")

    content = <<-HTML
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Nostalgia-Tron Games List</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=News+Cycle:700|Oxygen">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <style type="text/css" media="screen">
    h1, h2, h3:not(.popover-title) {
    font-family: 'News Cycle', sans-serif;
    }
    h3.popover-title {
    font-weight: bold;
    }
    h2.system-title {
    margin: 2.5rem 0 2rem;
    }
    body {
    font-family: 'Oxygen', sans-serif;
    padding-top: 3rem;
    padding-bottom: 3rem;
    }
    .popover {
    font-family: 'Oxygen', sans-serif;
    }
    .game-description {
    display: none;
    }
    </style>
    </head>
    <body>
    <div class="container">
    #{output}
    </div>
    <script type="text/javascript">
    $(function () {
    // When we open a popover, hide any others that may be open.
    $('a.game-link').click(function (e) {
    e.preventDefault();
    var $others = $('[data-toggle="popover"]').not(e.target);
    $others.popover('hide');
    $(e.target).popover('toggle');
    });
    // Make popovers wider.
    $('a.game-link').on('show.bs.popover', function () {
    $(this).data("bs.popover").tip().css("max-width", "600px");
    });
    // Hide a popover whenever someone clicks off.
    $('body').click(function (e) {
    var $anchor = $(e.target).closest('a.game-link');
    var $popover = $(e.target).closest('.popover');
    if ($anchor.length > 0 || $popover.length > 0) return;
    $('[data-toggle="popover"]').popover('hide');
    });
    $('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'manual',
    container: 'body',
    placement: 'auto bottom',
    content: function () {
    var text = $(this).closest('td').find('.game-description').text();
    text = "<p>" + text + "</p>";
    text = text.replace(/\\n\\s*\\n/g, "</p>\\n<p>");
    return text;
    }
    });
    });
    </script>
    </body>
    </html>
    HTML

    puts content