initial import
--HG-- branch : toybocks
This commit is contained in:
commit
053f435a0b
10 changed files with 946 additions and 0 deletions
238
backend.py
Executable file
238
backend.py
Executable file
|
@ -0,0 +1,238 @@
|
||||||
|
import dblayer
|
||||||
|
from dblayer import db, cur
|
||||||
|
import subprocess, shlex
|
||||||
|
import os
|
||||||
|
from zipfile import ZipFile
|
||||||
|
from threading import Thread
|
||||||
|
from ringbuffer import SplitRingBuffer
|
||||||
|
import time
|
||||||
|
|
||||||
|
class EmulatorHandlerThread(Thread):
|
||||||
|
def __init__(self, game, proc):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.buffer = SplitRingBuffer(1024*1024, 512*1024)
|
||||||
|
self.game = game
|
||||||
|
self.proc = proc
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
data = self.proc.stdout.read(4096)
|
||||||
|
self.proc.poll()
|
||||||
|
if not data and self.proc.returncode != None:
|
||||||
|
break
|
||||||
|
self.buffer.write(data)
|
||||||
|
time.sleep(0.4)
|
||||||
|
|
||||||
|
|
||||||
|
emulator_table = {}
|
||||||
|
gamesystem_table = {}
|
||||||
|
playlist_list = []
|
||||||
|
playlist_table = {}
|
||||||
|
|
||||||
|
class GamesystemInfo(object):
|
||||||
|
def __init__(self, id, name, fileext):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.fileexts = fileext.split(';')
|
||||||
|
self.emu_id = None
|
||||||
|
|
||||||
|
class EmulatorInfo(object):
|
||||||
|
def __init__(self, id, name, path, options):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.options = options
|
||||||
|
self.system_id = None
|
||||||
|
|
||||||
|
def start_game(self, game):
|
||||||
|
args = shlex.split(self.options)
|
||||||
|
emu = subprocess.Popen([self.path] + args + [game.path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
return emu
|
||||||
|
|
||||||
|
class GameInfo(object):
|
||||||
|
def __init__(self, id, name, path, emu, syst):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.emu_id = emu
|
||||||
|
self.system_id = syst
|
||||||
|
|
||||||
|
class AllGames_Playlist(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.id = None
|
||||||
|
self.name = '(All Games)'
|
||||||
|
|
||||||
|
def lookup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def gamelist(self):
|
||||||
|
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game order by game.name")
|
||||||
|
|
||||||
|
gl = []
|
||||||
|
|
||||||
|
for r in cur:
|
||||||
|
i, n, p, e, s = r
|
||||||
|
gl.append(GameInfo(i, n, p, e, s))
|
||||||
|
|
||||||
|
return gl
|
||||||
|
|
||||||
|
class Playlist(object):
|
||||||
|
def __init__(self, id=None, name=None):
|
||||||
|
if id == None and name == None:
|
||||||
|
raise ValueError, "Either name or id must be specified"
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.gamelist_cache = None
|
||||||
|
|
||||||
|
def lookup(self):
|
||||||
|
if self.name != None:
|
||||||
|
cur.execute("select id from playlist where name = ?", [self.name])
|
||||||
|
else:
|
||||||
|
cur.execute("select name from playlist where id = ?", [self.id])
|
||||||
|
|
||||||
|
rs = cur.fetchall()
|
||||||
|
if len(rs) > 1:
|
||||||
|
raise ValueError, "Non unique name for playlist"
|
||||||
|
else:
|
||||||
|
raise ValueError, "Playlist not found"
|
||||||
|
|
||||||
|
if self.name == None:
|
||||||
|
self.name = rs[0][0]
|
||||||
|
else:
|
||||||
|
self.id = rs[0][0]
|
||||||
|
|
||||||
|
def gamelist(self):
|
||||||
|
if self.gamelist_cache != None:
|
||||||
|
return self.gamelist_cache
|
||||||
|
|
||||||
|
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game, playlist_game where playlist_game.game_id = game.id and playlist_game.playlist_id = ? order by game.name", [self.id])
|
||||||
|
|
||||||
|
gl = []
|
||||||
|
|
||||||
|
for r in cur:
|
||||||
|
i, n, p, e, s = r
|
||||||
|
gl.append(GameInfo(i, n, p, e, s))
|
||||||
|
|
||||||
|
# enabling this could cause huge memory usage, but make things faster
|
||||||
|
#self.gamelist_cache = gl
|
||||||
|
return gl
|
||||||
|
|
||||||
|
def load_playlists():
|
||||||
|
global playlist_list
|
||||||
|
dblayer.connect()
|
||||||
|
cur.execute("select id, name from playlist order by name")
|
||||||
|
pl = [AllGames_Playlist()]
|
||||||
|
playlist_table[-1] = pl[0]
|
||||||
|
for r in cur:
|
||||||
|
id, name = r
|
||||||
|
p = Playlist(id, name)
|
||||||
|
pl.append(p)
|
||||||
|
playlist_table[id] = p
|
||||||
|
|
||||||
|
playlist_list = pl
|
||||||
|
|
||||||
|
def load_emulators():
|
||||||
|
dblayer.connect()
|
||||||
|
cur.execute("select id, name, path, options from emulator")
|
||||||
|
for r in cur:
|
||||||
|
id, name, path, options = r
|
||||||
|
ei = EmulatorInfo(id, name, path, options)
|
||||||
|
emulator_table[id] = ei
|
||||||
|
|
||||||
|
cur.execute("select id, name, fileext from gamesystem")
|
||||||
|
for r in cur:
|
||||||
|
id, name, fileext = r
|
||||||
|
gsi = GamesystemInfo(id, name, fileext)
|
||||||
|
gamesystem_table[id] = gsi
|
||||||
|
|
||||||
|
cur.execute("select gamesystem_id, emulator_id from gamesystem_emulator")
|
||||||
|
for r in cur:
|
||||||
|
gsid, eid = r
|
||||||
|
gamesystem_table[gsid].emu_id = eid
|
||||||
|
emulator_table[eid].system_id = gsid
|
||||||
|
|
||||||
|
def find_emu(emu):
|
||||||
|
if not os.environ.has_key('PATH'):
|
||||||
|
paths = ['.']
|
||||||
|
else:
|
||||||
|
paths = os.environ['PATH'].split(os.pathsep) + ['.']
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
if os.path.exists(os.path.join(path, emu)):
|
||||||
|
return os.path.abspath(os.path.join(path, emu))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def initialize_emulator_row(emulator, gamesystem):
|
||||||
|
cur.execute("insert into emulator (name, path, options) values (?, ?, ?)", [emulator.name, emulator.path, emulator.options])
|
||||||
|
emulator.id = cur.lastrowid
|
||||||
|
cur.execute("insert into gamesystem (name, fileext) values (?, ?)", [gamesystem.name, ';'.join(gamesystem.fileexts)])
|
||||||
|
gamesystem.id = cur.lastrowid
|
||||||
|
cur.execute("insert into gamesystem_emulator (gamesystem_id, emulator_id) values (?, ?)", [gamesystem.id, emulator.id])
|
||||||
|
|
||||||
|
def initialize_emulator_table():
|
||||||
|
emulist = [(('SNES','.SMC'),'snes9x','snes9x-sdl','snes9x-x11','snes9x-gtk','zsnes'),(('NES','.NES'),'fceu'),(('Genesis','.BIN'),'dgen'),(('MAME','**MAME**'),'xmame')]
|
||||||
|
for emutype in emulist:
|
||||||
|
system, fileext = emutype[0]
|
||||||
|
for emu in emutype[1:]:
|
||||||
|
emupath = find_emu(emu)
|
||||||
|
if emupath:
|
||||||
|
initialize_emulator_row(EmulatorInfo(None, emu, emupath, ''), GamesystemInfo(None, system, fileext))
|
||||||
|
|
||||||
|
def scan_zip(path):
|
||||||
|
zf = ZipFile(path, "r")
|
||||||
|
zfl = zf.namelist()
|
||||||
|
zf.close()
|
||||||
|
if len(zfl) > 1:
|
||||||
|
return "**MAME**"
|
||||||
|
elif zfl:
|
||||||
|
return os.path.splitext(zfl[0])[1].upper()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def autoload_data(path, playlist, system=None, emulator=None):
|
||||||
|
fileext_map = {}
|
||||||
|
if system == None:
|
||||||
|
for gsi in gamesystem_table.values():
|
||||||
|
for fe in gsi.fileexts:
|
||||||
|
fileext_map[fe.upper()] = gsi
|
||||||
|
print fileext_map
|
||||||
|
for base, dirs, files in os.walk(path):
|
||||||
|
for file in files:
|
||||||
|
fn, fe = os.path.splitext(file)
|
||||||
|
feu = fe.upper()
|
||||||
|
|
||||||
|
if feu == '.ZIP':
|
||||||
|
feu = scan_zip(os.path.join(path, base, file))
|
||||||
|
if not feu:
|
||||||
|
continue
|
||||||
|
autosystem = None
|
||||||
|
if feu in fileext_map:
|
||||||
|
autosystem = fileext_map[feu]
|
||||||
|
if system != None:
|
||||||
|
autosystem = system
|
||||||
|
if autosystem != None:
|
||||||
|
"We have a valid game, we know what system it's for, and we will use that system's default emulator"
|
||||||
|
emuid = None
|
||||||
|
if emulator != None:
|
||||||
|
emuid = emulator.id
|
||||||
|
sysid = None
|
||||||
|
if autosystem != None:
|
||||||
|
sysid = autosystem.id
|
||||||
|
cur.execute("insert into game (name, path, emu_id, gamesystem_id) values (?, ?, ?, ?)", [fn, os.path.join(path, base, file), emuid, sysid])
|
||||||
|
gameid = cur.lastrowid
|
||||||
|
cur.execute("insert into playlist_game (game_id, playlist_id) values (?, ?)", [gameid, playlist.id])
|
||||||
|
|
||||||
|
|
||||||
|
def get_game(idnum):
|
||||||
|
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game where game.id = ? order by game.name", [idnum])
|
||||||
|
|
||||||
|
rs = cur.fetchall()
|
||||||
|
if len(rs) > 1:
|
||||||
|
raise ValueError, "Non unique id for game, multiple games matched"
|
||||||
|
elif len(rs) == 0:
|
||||||
|
raise ValueError, "Game not found"
|
||||||
|
i, n, p, e, s = rs[0]
|
||||||
|
gi = GameInfo(i, n, p, e, s)
|
||||||
|
|
||||||
|
return gi
|
25
bootstrap_autoload.py
Executable file
25
bootstrap_autoload.py
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
import backend
|
||||||
|
import dblayer
|
||||||
|
import os
|
||||||
|
|
||||||
|
if os.path.exists('games.db'):
|
||||||
|
os.remove('games.db')
|
||||||
|
dblayer.connect()
|
||||||
|
dblayer.create()
|
||||||
|
backend.initialize_emulator_table()
|
||||||
|
backend.load_emulators()
|
||||||
|
|
||||||
|
|
||||||
|
base = '/mnt/gamez/Games/Emulators/ROMs/'
|
||||||
|
|
||||||
|
for d in ('SNES', 'NES', 'Genesis'):
|
||||||
|
system = None
|
||||||
|
for gs in backend.gamesystem_table.values():
|
||||||
|
if gs.name == d:
|
||||||
|
system = gs
|
||||||
|
backend.cur.execute("insert into playlist (name) values (?)", [d])
|
||||||
|
path = os.path.join(base, d + ' ROMs')
|
||||||
|
assert os.path.exists(path)
|
||||||
|
|
||||||
|
backend.autoload_data(path, backend.Playlist(backend.cur.lastrowid, d))
|
||||||
|
backend.db.commit()
|
5
config.py
Executable file
5
config.py
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
from utils import program_path
|
||||||
|
from inifile import inifile
|
||||||
|
|
||||||
|
config = inifile('settings.ini', path=[program_path()])
|
||||||
|
config.read()
|
38
dblayer.py
Executable file
38
dblayer.py
Executable file
|
@ -0,0 +1,38 @@
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
class ObjectProxy(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._realobj = None
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name in ("_realobj",) or hasattr(self, name):
|
||||||
|
object.__setattr__(self, name, value)
|
||||||
|
else:
|
||||||
|
self._realobj.__setattr__(name, value)
|
||||||
|
def __getattribute__(self, name):
|
||||||
|
try:
|
||||||
|
return object.__getattribute__(self, name)
|
||||||
|
except:
|
||||||
|
return self._realobj.__getattribute__(name)
|
||||||
|
def __repr__(self):
|
||||||
|
return """<Proxy for "%s">""" % (repr(self._realobj),)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self._realobj.__iter__()
|
||||||
|
|
||||||
|
db = ObjectProxy()
|
||||||
|
cur = ObjectProxy()
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
if db._realobj != None:
|
||||||
|
return
|
||||||
|
db._realobj = sqlite3.connect("games.db")
|
||||||
|
cur._realobj = db.cursor()
|
||||||
|
|
||||||
|
def create():
|
||||||
|
cur.execute("""create table playlist (id integer primary key autoincrement, name text)""")
|
||||||
|
cur.execute("""create table emulator (id integer primary key autoincrement, name text, path text, options text)""")
|
||||||
|
cur.execute("""create table gamesystem (id integer primary key autoincrement, name text, fileext text)""")
|
||||||
|
cur.execute("""create table gamesystem_emulator (id integer primary key autoincrement, emulator_id integer, gamesystem_id integer, foreign key (emulator_id) references emulator(id), foreign key (gamesystem_id) references gamesystem(id))""")
|
||||||
|
cur.execute("""create table game (id integer primary key autoincrement, name text, path text, emu_id integer, gamesystem_id integer, foreign key (emu_id) references emulator(id), foreign key (gamesystem_id) references gamesystem(id))""")
|
||||||
|
cur.execute("""create table playlist_game (id integer primary key autoincrement, game_id integer, playlist_id integer, foreign key (game_id) references game(id), foreign key (playlist_id) references playlist(id))""")
|
||||||
|
|
267
inifile.py
Executable file
267
inifile.py
Executable file
|
@ -0,0 +1,267 @@
|
||||||
|
# ini reader/writer
|
||||||
|
__doc__ = """
|
||||||
|
reads and writes config files using the microsoft windows 'ini' format.
|
||||||
|
(author's note: I don't think there IS any official 'ini' format. Some
|
||||||
|
artistic licence may have been used)
|
||||||
|
|
||||||
|
Provides class 'inifile', which is a dict-like object that can be read
|
||||||
|
from a file or written out to a file (or both!)
|
||||||
|
"""
|
||||||
|
|
||||||
|
__license__ = """
|
||||||
|
Copyright (c) 2006-2007 Bradley Lawrence <bradley@eltanin.net>
|
||||||
|
|
||||||
|
This is licensed under the Modified BSD License, as follows:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
from utils import program_path
|
||||||
|
|
||||||
|
def sanitize_for_subst(key):
|
||||||
|
"""
|
||||||
|
replaces open and close brackets with tokens, to ensure the brackets
|
||||||
|
do not interfere with the %(dict_key)s string formatting syntax.
|
||||||
|
|
||||||
|
'=' is used as a token delimiter because we know an equals can never
|
||||||
|
appear in a keyname, since it is interpreted by this reader as the
|
||||||
|
end of the key and the start of the value.
|
||||||
|
"""
|
||||||
|
return key.replace('(', '=o=').replace(')', '=c=')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class inisubsec(object):
|
||||||
|
def __init__(self, name, owner):
|
||||||
|
self.owner = owner
|
||||||
|
self.name = name
|
||||||
|
self.data = {}
|
||||||
|
self.verbatim_hdr = "[%(sec_title)s]\n"
|
||||||
|
self.verbatim = ["%(=new_data=)s"]
|
||||||
|
self.verbatim_ftr = "\n"
|
||||||
|
self.verbatim_fields = []
|
||||||
|
self.new_fields = []
|
||||||
|
self.initializing = True
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.data[key.lower()]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if not self.data.has_key(key.lower()):
|
||||||
|
if self.initializing:
|
||||||
|
self.verbatim_fields += [key]
|
||||||
|
else:
|
||||||
|
self.new_fields += [key]
|
||||||
|
self.data[key.lower()] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.data[key.lower()]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.data.__iter__()
|
||||||
|
def items(self):
|
||||||
|
return self.data.items()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class inifile(object):
|
||||||
|
def __init__(self, fname, path=None):
|
||||||
|
self.data = {}
|
||||||
|
sep = os.path.sep
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
fs = os.path.split(fname)
|
||||||
|
self.fname = os.path.split(fname)[1]
|
||||||
|
if not fs[0]:
|
||||||
|
self.path = (program_path() + sep, '')
|
||||||
|
else:
|
||||||
|
self.path = (fs[0] + sep,)
|
||||||
|
else:
|
||||||
|
if type(path) == str:
|
||||||
|
self.path = (path,)
|
||||||
|
else:
|
||||||
|
self.path = []
|
||||||
|
for p in path:
|
||||||
|
if p and (p[-1] != sep):
|
||||||
|
p += sep
|
||||||
|
self.path += [p]
|
||||||
|
self.fname = fname
|
||||||
|
|
||||||
|
self.last_opened_file = None
|
||||||
|
self.cur_section = None
|
||||||
|
self.activepath = None
|
||||||
|
self.verbatim_hdr = []
|
||||||
|
self.section_order = []
|
||||||
|
|
||||||
|
def set_section(self, section):
|
||||||
|
self.cur_section = section.lower()
|
||||||
|
|
||||||
|
def unset_section(self):
|
||||||
|
self.cur_section = None
|
||||||
|
|
||||||
|
def get_current_file(self):
|
||||||
|
return
|
||||||
|
def read(self):
|
||||||
|
fd = None
|
||||||
|
for p in self.path:
|
||||||
|
if os.path.exists(p + self.fname):
|
||||||
|
self.activepath = p
|
||||||
|
fd = file(p + self.fname, 'r')
|
||||||
|
break
|
||||||
|
if not fd:
|
||||||
|
return False
|
||||||
|
|
||||||
|
act_section = 'default'
|
||||||
|
section_init = False
|
||||||
|
for line in fd:
|
||||||
|
verbatimline = line
|
||||||
|
keyvalue_line = False
|
||||||
|
line = line.lstrip()
|
||||||
|
if not line:
|
||||||
|
# blank line, ignore
|
||||||
|
pass
|
||||||
|
elif line[0] == ';':
|
||||||
|
# comment! ignore
|
||||||
|
pass
|
||||||
|
elif line[0] == '[':
|
||||||
|
# section header
|
||||||
|
line = line.rstrip()
|
||||||
|
if line[-1] == ']':
|
||||||
|
act_section = line[1:-1]
|
||||||
|
section_init = True
|
||||||
|
if not self.data.has_key(act_section.lower()):
|
||||||
|
"add specified section"
|
||||||
|
actsec = inisubsec(act_section, self)
|
||||||
|
self.data[act_section.lower()] = actsec
|
||||||
|
actsec.verbatim_hdr = verbatimline
|
||||||
|
actsec.verbatim_ftr = ""
|
||||||
|
verbatimline = None
|
||||||
|
self.section_order += [act_section]
|
||||||
|
else:
|
||||||
|
# garbage line, don't understand it
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
peq = line.find('=')
|
||||||
|
if peq == -1:
|
||||||
|
# garbage line, don't understand it
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
k = line[:peq]
|
||||||
|
v = line[peq+1:].rstrip('\n\r')
|
||||||
|
if not self.data.has_key(act_section.lower()):
|
||||||
|
"add default section"
|
||||||
|
assert not section_init
|
||||||
|
|
||||||
|
actsec = inisubsec('default', self)
|
||||||
|
self.data[act_section.lower()] = actsec
|
||||||
|
|
||||||
|
|
||||||
|
self.data[act_section.lower()][k] = v
|
||||||
|
verbatimline = "".join([k, '=%(', sanitize_for_subst(k.lower()), ')s\n'])
|
||||||
|
keyvalue_line = True
|
||||||
|
|
||||||
|
if verbatimline:
|
||||||
|
if section_init or keyvalue_line:
|
||||||
|
"keyvalue lines always go under the default section, regardless of initialized state"
|
||||||
|
actsec = self.data[act_section.lower()]
|
||||||
|
actsec.verbatim += [verbatimline]
|
||||||
|
else:
|
||||||
|
self.verbatim_hdr += [verbatimline]
|
||||||
|
|
||||||
|
|
||||||
|
if section_init:
|
||||||
|
actsec = self.data[act_section.lower()]
|
||||||
|
actsec.verbatim += ["\n"]
|
||||||
|
else:
|
||||||
|
self.verbatim_hdr += ["\n"]
|
||||||
|
|
||||||
|
if self.data.has_key('default'):
|
||||||
|
"has a default section. make sure it's added to the section_order"
|
||||||
|
try:
|
||||||
|
self.section_order.index('default')
|
||||||
|
except:
|
||||||
|
self.section_order += ['default']
|
||||||
|
|
||||||
|
for secdict in self.data.values():
|
||||||
|
secdict.initializing = False
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
p = self.path[0]
|
||||||
|
if self.activepath:
|
||||||
|
p = self.activepath
|
||||||
|
|
||||||
|
fd = file(p + self.fname, 'w')
|
||||||
|
first = True
|
||||||
|
|
||||||
|
fd.write("".join(self.verbatim_hdr))
|
||||||
|
|
||||||
|
for sec in self.section_order:
|
||||||
|
secdict = self.data[sec.lower()]
|
||||||
|
|
||||||
|
new_lines = []
|
||||||
|
for k in secdict.new_fields:
|
||||||
|
new_lines += ["%s=%s\n" % (k, secdict[k])]
|
||||||
|
new_lines = "".join(new_lines)
|
||||||
|
verbatim_dict = {'=new_data=': new_lines}
|
||||||
|
for k in secdict.verbatim_fields:
|
||||||
|
verbatim_dict[sanitize_for_subst(k.lower())] = secdict[k]
|
||||||
|
|
||||||
|
fd.write(secdict.verbatim_hdr % {'sec_title': secdict.name})
|
||||||
|
fd.write("".join(secdict.verbatim) % verbatim_dict)
|
||||||
|
fd.write(secdict.verbatim_ftr)
|
||||||
|
|
||||||
|
def add_section(self, key):
|
||||||
|
if not self.data.has_key(key):
|
||||||
|
self.data[key.lower()] = inisubsec(key, self)
|
||||||
|
self.section_order += [key]
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if type(key) != str:
|
||||||
|
raise TypeError, "Ini files can only contain string keys"
|
||||||
|
|
||||||
|
if self.cur_section != None:
|
||||||
|
if self.data[self.cur_section].has_key(key.lower()):
|
||||||
|
return self.data[self.cur_section][key.lower()]
|
||||||
|
else:
|
||||||
|
raise KeyError, "Section '%s' does not contain a key named '%s'" % (self.cur_section, key)
|
||||||
|
else:
|
||||||
|
#if not self.data.has_key(key.lower()):
|
||||||
|
# self.data[key.lower()] = inisubsec(key, self)
|
||||||
|
return self.data[key.lower()]
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if self.cur_section != None:
|
||||||
|
if not self.data.has_key(self.cur_section):
|
||||||
|
raise KeyError, "Section '%s' does not exist" % (self.cur_section)
|
||||||
|
self.data[self.cur_section][key.lower()] = value
|
||||||
|
else:
|
||||||
|
raise ValueError, "Cannot set the value of a section header"
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.data[key.lower()]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.data.__iter__()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.data.items()
|
||||||
|
|
88
mainwnd.py
Executable file
88
mainwnd.py
Executable file
|
@ -0,0 +1,88 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: iso-8859-15 -*-
|
||||||
|
# generated by wxGlade 0.6.3 on Sun Feb 21 18:24:20 2010
|
||||||
|
|
||||||
|
import wx
|
||||||
|
|
||||||
|
# begin wxGlade: extracode
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(wx.Frame):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
# begin wxGlade: MainWindow.__init__
|
||||||
|
kwds["style"] = wx.DEFAULT_FRAME_STYLE
|
||||||
|
wx.Frame.__init__(self, *args, **kwds)
|
||||||
|
self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER)
|
||||||
|
self.playlists = customListCtrl(self.splitter, -1, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.SUNKEN_BORDER)
|
||||||
|
self.games = customListCtrl(self.splitter, -1, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL|wx.LC_NO_HEADER|wx.SUNKEN_BORDER)
|
||||||
|
|
||||||
|
self.__set_properties()
|
||||||
|
self.__do_layout()
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
def __set_properties(self):
|
||||||
|
# begin wxGlade: MainWindow.__set_properties
|
||||||
|
self.SetTitle("frame_2")
|
||||||
|
self.SetSize((723, 569))
|
||||||
|
self.SetMenuBar(MainMenu())
|
||||||
|
self.playlists.SetBackgroundColour(wx.Colour(20, 20, 20))
|
||||||
|
self.playlists.SetForegroundColour(wx.Colour(192, 192, 192))
|
||||||
|
self.playlists.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
|
||||||
|
self.playlists.SetFocus()
|
||||||
|
self.games.SetBackgroundColour(wx.Colour(20, 20, 20))
|
||||||
|
self.games.SetForegroundColour(wx.Colour(192, 192, 192))
|
||||||
|
self.games.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
def __do_layout(self):
|
||||||
|
# begin wxGlade: MainWindow.__do_layout
|
||||||
|
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.splitter.SplitVertically(self.playlists, self.games, 250)
|
||||||
|
sizer.Add(self.splitter, 1, wx.EXPAND, 0)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
self.Layout()
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
# end of class MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
class MainMenu(wx.MenuBar):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
# begin wxGlade: MainMenu.__init__
|
||||||
|
wx.MenuBar.__init__(self, *args, **kwds)
|
||||||
|
wxglade_tmp_menu = wx.Menu()
|
||||||
|
wxglade_tmp_menu.Append(wx.NewId(), "E&xit", "", wx.ITEM_NORMAL)
|
||||||
|
self.Append(wxglade_tmp_menu, "&File")
|
||||||
|
|
||||||
|
self.__set_properties()
|
||||||
|
self.__do_layout()
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
def __set_properties(self):
|
||||||
|
# begin wxGlade: MainMenu.__set_properties
|
||||||
|
pass
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
def __do_layout(self):
|
||||||
|
# begin wxGlade: MainMenu.__do_layout
|
||||||
|
pass
|
||||||
|
# end wxGlade
|
||||||
|
|
||||||
|
# end of class MainMenu
|
||||||
|
|
||||||
|
|
||||||
|
class MyApp(wx.App):
|
||||||
|
def OnInit(self):
|
||||||
|
wx.InitAllImageHandlers()
|
||||||
|
mainwnd = MainWindow(None, -1, "")
|
||||||
|
self.SetTopWindow(mainwnd)
|
||||||
|
mainwnd.Show()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# end of class MyApp
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
Toybocks = MyApp(0)
|
||||||
|
Toybocks.MainLoop()
|
137
ringbuffer.py
Executable file
137
ringbuffer.py
Executable file
|
@ -0,0 +1,137 @@
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RingBuffer(object):
|
||||||
|
def __init__(self, size):
|
||||||
|
self.size = size
|
||||||
|
self.buffer = StringIO()
|
||||||
|
self.buffer_pos = 0
|
||||||
|
self.read_pos = 0
|
||||||
|
self.bytes_written = 0
|
||||||
|
self.first_read = True
|
||||||
|
self.fullclass = RingBufferFull
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
if self.buffer_pos + len(data) >= self.size:
|
||||||
|
self.__class__ = self.fullclass
|
||||||
|
split = self.size - self.buffer_pos
|
||||||
|
self.buffer.write(data[:split])
|
||||||
|
self.buffer.seek(0, 0)
|
||||||
|
self.buffer_pos = 0
|
||||||
|
self.bytes_written += split
|
||||||
|
self.write(data[split:])
|
||||||
|
else:
|
||||||
|
self.buffer.write(data)
|
||||||
|
self.buffer_pos += len(data)
|
||||||
|
self.bytes_written += len(data)
|
||||||
|
|
||||||
|
def read(self, bytes=0):
|
||||||
|
self.buffer.seek(self.read_pos, 0)
|
||||||
|
rb = self.buffer.read(bytes)
|
||||||
|
self.read_pos = self.buffer.tell()
|
||||||
|
self.buffer.seek(self.buffer_pos, 0)
|
||||||
|
return rb
|
||||||
|
|
||||||
|
|
||||||
|
class SplitRingBuffer(RingBuffer):
|
||||||
|
def __init__(self, size, split):
|
||||||
|
RingBuffer.__init__(self, size)
|
||||||
|
self.fullclass = SplitRingBufferFull
|
||||||
|
self.splitpos = split
|
||||||
|
self.read_pos = split
|
||||||
|
|
||||||
|
def read_head():
|
||||||
|
self.buffer.seek(0, 0)
|
||||||
|
rb = self.buffer.read()
|
||||||
|
self.buffer.seek(self.buffer_pos, 0)
|
||||||
|
return (True, rb)
|
||||||
|
|
||||||
|
|
||||||
|
class RingBufferFull(object):
|
||||||
|
def __init__(self, size):
|
||||||
|
raise NotImplementedError, "You should not create this class manually, use RingBuffer() instead"
|
||||||
|
|
||||||
|
def overflow_buffer():
|
||||||
|
self.buffer_pos = 0
|
||||||
|
self.seek_to_start()
|
||||||
|
|
||||||
|
def seek_to_start():
|
||||||
|
self.buffer.seek(0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
di = 0
|
||||||
|
ld = len(data)
|
||||||
|
while (ld - di) + self.buffer_pos >= self.size:
|
||||||
|
self.buffer.write(data[di:di + (self.size - self.buffer_pos)])
|
||||||
|
if self.read_pos != None and self.read_pos > self.buffer_pos:
|
||||||
|
# our read pos has been overwritten, we've lost our place
|
||||||
|
self.read_pos = None
|
||||||
|
self.overflow_buffer()
|
||||||
|
di += (self.size - self.buffer_pos)
|
||||||
|
self.buffer.write(data[di:])
|
||||||
|
if self.read_pos != None and self.buffer_pos <= self.read_pos and (self.buffer_pos + ld - di) > self.read_pos:
|
||||||
|
self.read_pos = None
|
||||||
|
self.buffer_pos += ld - di
|
||||||
|
self.bytes_written += ld
|
||||||
|
|
||||||
|
def read(self, bytes=0):
|
||||||
|
pos = self.read_pos
|
||||||
|
fullread = False
|
||||||
|
if pos == None:
|
||||||
|
pos = self.buffer_pos
|
||||||
|
fullread = True
|
||||||
|
|
||||||
|
if pos == self.buffer_pos and fullread:
|
||||||
|
maxlen = self.size
|
||||||
|
elif pos == self.buffer_pos and not fullread:
|
||||||
|
maxlen = 0
|
||||||
|
else:
|
||||||
|
maxlen = self.buffer_pos - pos
|
||||||
|
if maxlen < 0:
|
||||||
|
maxlen += self.size
|
||||||
|
self.buffer.seek(pos, 0)
|
||||||
|
if bytes > 0 and maxlen > bytes:
|
||||||
|
maxlen = bytes
|
||||||
|
|
||||||
|
if maxlen == 0:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
split = self.size - pos
|
||||||
|
if split >= maxlen:
|
||||||
|
self.buffer.seek(pos, 0)
|
||||||
|
rb = self.buffer.read(maxlen)
|
||||||
|
self.read_pos = self.buffer.tell()
|
||||||
|
self.buffer.seek(self.buffer_pos, 0)
|
||||||
|
else:
|
||||||
|
self.buffer.seek(pos, 0)
|
||||||
|
rb = self.buffer.read(split)
|
||||||
|
self.seek_to_start()
|
||||||
|
rb += self.buffer.read(maxlen - split)
|
||||||
|
self.read_pos = self.buffer.tell()
|
||||||
|
self.buffer.seek(self.buffer_pos, 0)
|
||||||
|
|
||||||
|
return rb
|
||||||
|
|
||||||
|
|
||||||
|
class SplitRingBufferFull(RingBufferFull):
|
||||||
|
def read(self, bytes=0):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def overflow_buffer():
|
||||||
|
self.buffer_pos = self.split_pos
|
||||||
|
|
||||||
|
def seek_to_start():
|
||||||
|
self.buffer.seek(self.split_pos, 0)
|
||||||
|
|
||||||
|
def read_head():
|
||||||
|
self.buffer.seek(0, 0)
|
||||||
|
rb = self.buffer.read(self.split_pos)
|
||||||
|
self.buffer.seek(self.buffer_pos, 0)
|
||||||
|
return (False, rb)
|
||||||
|
|
||||||
|
|
2
settings.ini
Executable file
2
settings.ini
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
[Paths]
|
||||||
|
Roms=S:/Games/Emulators/ROMs/SNES:S:/Games/Emulators/ROMs/SNES
|
138
toybocks.py
Executable file
138
toybocks.py
Executable file
|
@ -0,0 +1,138 @@
|
||||||
|
import mainwnd
|
||||||
|
import wx
|
||||||
|
import backend
|
||||||
|
|
||||||
|
class customListCtrl(wx.ListCtrl):
|
||||||
|
def GetItemBackgroundColour(self, item):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def OnGetItemText(self, item, col):
|
||||||
|
return self.gamelist[item].name
|
||||||
|
|
||||||
|
def GetItemData(self, item):
|
||||||
|
if hasattr(self, "gamelist"):
|
||||||
|
return self.gamelist[item].id
|
||||||
|
else:
|
||||||
|
return wx.ListCtrl.GetItemData(self, item)
|
||||||
|
|
||||||
|
mainwnd.customListCtrl = customListCtrl
|
||||||
|
|
||||||
|
class ToybocksMainWindow(mainwnd.MainWindow):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
mainwnd.MainWindow.__init__(self, *args, **kwds)
|
||||||
|
|
||||||
|
self.emulator_thread = None
|
||||||
|
|
||||||
|
|
||||||
|
self.playlists.InsertColumn(0, "Selection")
|
||||||
|
# self.playlists.InsertStringItem(0, "(All Games)")
|
||||||
|
# self.playlists.InsertStringItem(1, "(None)")
|
||||||
|
self.games.InsertColumn(0, "Game")
|
||||||
|
self.games.SetColumnWidth(0, 1800)
|
||||||
|
# self.games.InsertStringItem(0, "Game One")
|
||||||
|
# self.games.InsertStringItem(1, "Testing 123")
|
||||||
|
# self.games.InsertStringItem(2, "Entry Test")
|
||||||
|
# self.games.InsertStringItem(3, "Listing Items")
|
||||||
|
# self.games.InsertStringItem(4, "Game 789")
|
||||||
|
|
||||||
|
backend.load_emulators()
|
||||||
|
backend.load_playlists()
|
||||||
|
pl = backend.playlist_list
|
||||||
|
c = 0
|
||||||
|
for p in pl:
|
||||||
|
self.playlists.InsertStringItem(c, p.name)
|
||||||
|
if p.id != None:
|
||||||
|
self.playlists.SetItemData(c, int(p.id))
|
||||||
|
else:
|
||||||
|
self.playlists.SetItemData(c, -1)
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
p = pl[0]
|
||||||
|
|
||||||
|
self.games.playlist = p
|
||||||
|
self.games.gamelist = p.gamelist()
|
||||||
|
self.games.SetItemCount(len(self.games.gamelist))
|
||||||
|
self.games.RefreshItems(0, len(self.games.gamelist))
|
||||||
|
self.playlists.SetColumnWidth(0, 200)
|
||||||
|
self.playlists.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||||
|
|
||||||
|
self.playlists.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnChangePlaylist)
|
||||||
|
self.playlists.Bind(wx.EVT_LIST_KEY_DOWN, self.OnKeyUp)
|
||||||
|
#wx.EVT_LIST_KEY_DOWN(self.playlists, self.OnKeyUp)
|
||||||
|
self.playlists.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
||||||
|
self.games.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
||||||
|
|
||||||
|
TIMER_ID = 150
|
||||||
|
self.timer = wx.Timer(self, TIMER_ID)
|
||||||
|
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
||||||
|
|
||||||
|
def OnChangePlaylist(self, event):
|
||||||
|
name, id, item = self.GetSelItem(self.playlists)
|
||||||
|
p = backend.playlist_table[id]
|
||||||
|
self.games.playlist = p
|
||||||
|
self.games.gamelist = p.gamelist()
|
||||||
|
self.games.SetItemCount(len(self.games.gamelist))
|
||||||
|
self.games.RefreshItems(0, len(self.games.gamelist))
|
||||||
|
#self.games.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||||
|
|
||||||
|
|
||||||
|
def OnTimer(self, event):
|
||||||
|
ep = self.emulator_thread
|
||||||
|
if ep and ep != True:
|
||||||
|
if not ep.is_alive():
|
||||||
|
self.timer.Stop()
|
||||||
|
ep.buffer.write("\nEmulator exited with return code %d\n" % (ep.proc.returncode,))
|
||||||
|
self.emulator_thread = None
|
||||||
|
item = self.games.FindItemData(-1, ep.game.id)
|
||||||
|
self.games.SetItemBackgroundColour(item, self.games.GetBackgroundColour())
|
||||||
|
|
||||||
|
def OnKeyUp(self, event):
|
||||||
|
key = event.GetKeyCode()
|
||||||
|
|
||||||
|
if key == wx.WXK_RETURN:
|
||||||
|
self.OnStartGame(None)
|
||||||
|
elif key == wx.WXK_LEFT:
|
||||||
|
self.playlists.SetFocus()
|
||||||
|
elif key == wx.WXK_RIGHT:
|
||||||
|
self.games.SetFocus()
|
||||||
|
else:
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
def GetSelItem(self, box):
|
||||||
|
item = box.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
|
||||||
|
if item == -1:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return (box.GetItemText(item), box.GetItemData(item), item)
|
||||||
|
|
||||||
|
def OnStartGame(self, event):
|
||||||
|
if self.emulator_thread:
|
||||||
|
return
|
||||||
|
self.emulator_thread = True
|
||||||
|
name, id, item = self.GetSelItem(self.games)
|
||||||
|
print name, id, item
|
||||||
|
game = backend.get_game(id)
|
||||||
|
emuid = game.emu_id
|
||||||
|
if emuid == None:
|
||||||
|
emuid = backend.gamesystem_table[game.system_id].emu_id
|
||||||
|
emu = backend.emulator_table[emuid]
|
||||||
|
proc = emu.start_game(game)
|
||||||
|
self.emulator_thread = backend.EmulatorHandlerThread(game, proc)
|
||||||
|
self.emulator_thread.start()
|
||||||
|
self.timer.Start(1000)
|
||||||
|
self.games.SetItemBackgroundColour(item, wx.Colour(196,0,0))
|
||||||
|
|
||||||
|
class ToybocksApp(wx.App):
|
||||||
|
def OnInit(self):
|
||||||
|
wx.InitAllImageHandlers()
|
||||||
|
mainwnd = ToybocksMainWindow(None, -1, "")
|
||||||
|
self.SetTopWindow(mainwnd)
|
||||||
|
mainwnd.Show()
|
||||||
|
state = wx.LIST_STATE_FOCUSED|wx.LIST_STATE_SELECTED
|
||||||
|
mainwnd.playlists.SetItemState(0, state, state)
|
||||||
|
mainwnd.games.SetItemState(0, state, state)
|
||||||
|
mainwnd.playlists.SetFocus()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
app = ToybocksApp(0)
|
||||||
|
app.MainLoop()
|
8
utils.py
Executable file
8
utils.py
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def program_path():
|
||||||
|
p = os.path.split(sys.argv[0])[0]
|
||||||
|
p = os.path.abspath(p)
|
||||||
|
p = os.path.realpath(p)
|
||||||
|
return p
|
Loading…
Add table
Reference in a new issue