#!/usr/bin/env python2.4
import py
from lxml import etree
command = 'xmame.SDL'
switches = '-fullscreen'
rompath = '/usr/share/games/xmame/roms'
datapath = '/home/johnny/.xmamerunner'
browserpath = '/usr/bin/mozilla'
http_port = 10001
# if this is filled, only the games that have their name in this list will be
# displayed
list_override = []
class XMameRunner(object):
"""A graphical user interface for XMAME
combined with a small database for roms and images, and a data
collector
"""
def __init__(self):
self.db = RomDB(rompath, datapath)
def get_items(self):
"""return an list of dictionaries with item data"""
xml = self.db.get_data()
dom = etree.fromstring(xml)
ret = []
for node in dom.xpath('//game'):
name = node.attrib['name']
if list_override and name not in list_override:
continue
ret.append({
'name': name,
'cloneof': node.attrib.get('cloneof', ''),
'description': node[0].text,
'year': node[1].text,
'manufacturer': node[2].text,
})
return ret
def run(self, name):
"""run a game
name is the name of the game from the dict (key 'name'), which
can be fed to xmame as a game name
"""
py.std.os.system('%s %s %s' % (command, switches, name))
class RomDB(object):
def __init__(self, rompath, datapath):
self.rompath = py.path.local(rompath)
self.datapath = dp = py.path.local(datapath)
if not dp.check():
dp.mkdir()
elif not dp.check(dir=1):
raise IOError, 'datapath is not a directory'
def get_data(self):
cache = self.datapath / '.mamerunner_cache'
if cache.check():
# if cache is still valid, use it, else create new
valid = True
for p in self.rompath.listdir():
if p.basename.endswith('.zip'):
print 'p:', p
print 'mtime:', p.mtime()
print 'cache mtime:', cache.mtime()
if p.mtime() > cache.mtime():
data = self.gather_data()
cache.write(data)
break
else:
data = cache.read()
else:
data = self.gather_data()
cache.write(data)
return data
def gather_data(self):
print 'going to gather data, this can take a long time!'
print 'first reading XML for all ROMs'
outfp, infp, errfp = py.std.popen2.popen3('%s -listxml' % command)
try:
xml = outfp.read()
finally:
outfp.close()
infp.close()
errfp.close()
print 'XML read, finding out which files are installed (slow!!)'
tree = etree.fromstring(xml)
outfp, infp, errfp = py.std.popen2.popen3('%s -verifyromsets' %
command)
usedgameels = []
try:
for line in outfp.readlines():
line = line.strip()
while ' ' in line:
line = line.replace(' ', ' ')
if line and line.split(' ')[1] == 'correct':
print 'found:', line.split(' ')[0]
usedgameels.append(
tree.xpath('//game[@name="%s"]' %
line.split(' ')[0])[0])
finally:
outfp.close()
infp.close()
errfp.close()
data = ['']
for el in usedgameels:
data.append(etree.tostring(el))
print 'done gathering data'
return '%s\n' % '\t'.join(data)
class CGIXMameRunner(object):
"""show the XMAMERunner menu in a browser
boot a simple HTTP server and point a browser to it, a page with
all the games is shown and the user can start one by clicking on
its entry
"""
def __init__(self):
self.runner = XMameRunner()
self.quit = False
def run(self):
self.lock = py.std.thread.allocate_lock()
this = self # make it available to the handler code
class XMameHTTPRequestHandler(
py.std.BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
"""show the listing page, run a game, or quit"""
path = self.path
query = ''
if '?' in path:
path, query = path.split('?')
if path == '/':
self.show_list()
elif path == '/run':
self.run_game(query)
elif path == '/images':
self.serve_image(query)
elif path == '/quit':
self.quit()
else:
data = 'no such url, please visit the root'
self.serve_data('text/html', data)
def serve_data(self, content_type, data):
self.wfile.write(
('HTTP/1.0 200 OK\r\n'
'Content-Type: %s; charset=UTF-8\r\n'
'Content-Length: %s\r\n'
'\r\n') % (content_type, len(data)))
self.wfile.write(data)
def show_list(self):
"""show the list of games"""
from templess.templess import template
t = template(py.magic.autopath().dirpath() /
'templates/list.html')
games = this.runner.get_items()
games.sort(lambda a, b: cmp(a['name'], b['name']))
for item in games:
item['link'] = '/run?%s' % item['name']
imgspath = py.path.local(datapath) / 'images'
if (imgspath / ('%s.png' % item['name'])).check():
item['imgsrc'] = '/images?%s' % item['name']
item['showimage'] = True
item['showlink'] = False
elif (imgspath / ('%s.png' % item['cloneof'])).check():
item['imgsrc'] = '/images?%s' % item['cloneof']
item['showimage'] = True
item['showlink'] = False
else:
item['showimage'] = False
item['showlink'] = True
html = t.render_to_string({
'games': games,
})
self.serve_data('text/html', html)
def run_game(self, query):
"""run a game"""
print 'going to run', query
this.runner.run(query)
def serve_image(self, query):
img = py.path.local(datapath) / ('images/%s.png' % query)
self.serve_data('image/png', img.read())
def quit(self):
"""quit the server and app"""
this.quit = 1
from templess.templess import template
t = template(py.magic.autopath().dirpath() /
'templates/quit.html')
self.serve_data('text/html', t.render_to_string({}))
thread = py.std.thread.start_new_thread(self.run_server,
(XMameHTTPRequestHandler,))
self.start_browser()
# wait for the lock to be set
while not self.lock.locked():
py.std.time.sleep(0.1)
# lock until the server has quit
self.lock.acquire()
def run_server(self, handler):
server = py.std.BaseHTTPServer.HTTPServer(('', http_port), handler)
self.lock.acquire()
while 1:
server.handle_request()
if self.quit:
print 'quitting'
break
self.lock.release()
def start_browser(self):
url = 'http://localhost:%s/' % http_port
py.std.os.system('%s "%s" > /dev/null 2>&1 &' % (browserpath, url))
if __name__ == '__main__':
r = CGIXMameRunner()
r.run()