diff --git a/bin/ascoltami2 b/bin/ascoltami2 --- a/bin/ascoltami2 +++ b/bin/ascoltami2 @@ -1,4 +1,5 @@ #!bin/python +from twisted.internet import gtk2reactor, reactor import web, thread, gobject, sys, optparse, logging from rdflib import URIRef sys.path.append(".") @@ -8,10 +9,6 @@ from light9.ascoltami.webapp import make from light9 import networking, showconfig -import BaseHTTPServer -BaseHTTPServer.BaseHTTPRequestHandler.log_message = \ - lambda self, format, *args: None - class App: def __init__(self, graph, show): self.graph = graph @@ -22,12 +19,10 @@ class App: def run(self, musicPort): # the cherrypy server would wedge when vidref pounds on it; this # one seems to run - thread.start_new(web.httpserver.runbasic, - (makeWebApp(self).wsgifunc(), - ('0.0.0.0', musicPort))) - - mainloop = gobject.MainLoop() - mainloop.run() + #gtk2reactor.install(useGtk=False) + reactor.listenTCP(musicPort, makeWebApp(self)) + log.info("listening on %s" % musicPort) + reactor.run() def onEOS(self, song): self.player.pause() @@ -45,7 +40,7 @@ class App: if __name__ == "__main__": logging.basicConfig() log = logging.getLogger() - gobject.threads_init() + gobject.threads_init() # this is in gtk2reactor too parser = optparse.OptionParser() parser.add_option('--show', diff --git a/light9/ascoltami/webapp.py b/light9/ascoltami/webapp.py --- a/light9/ascoltami/webapp.py +++ b/light9/ascoltami/webapp.py @@ -1,12 +1,19 @@ -import web, json, socket, subprocess +import json, socket, subprocess, cyclone.web, os from twisted.python.util import sibpath +from twisted.python.filepath import FilePath from light9.namespaces import L9 from light9.showconfig import getSongsFromShow, songOnDisk from rdflib import URIRef from web.contrib.template import render_genshi render = render_genshi([sibpath(__file__, ".")]) -app = None +try: + import sys + sys.path.append("../homeauto/lib") + from cycloneerr import PrettyErrorHandler +except ImportError: + class PrettyErrorHandler(object): + pass _songUris = {} # locationUri : song def songLocation(graph, songUri): @@ -17,12 +24,12 @@ def songLocation(graph, songUri): def songUri(graph, locationUri): return _songUris[locationUri] -class root(object): - def GET(self): - web.header("Content-type", "application/xhtml+xml") +class root(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + self.set_header("Content-Type", "application/xhtml+xml") # todo: use a template; embed the show name and the intro/post # times into the page - return render.index(host=socket.gethostname()) + self.write(render.index(host=socket.gethostname())) def playerSongUri(graph, player): """or None""" @@ -33,100 +40,104 @@ def playerSongUri(graph, player): else: return None -class timeResource(object): - def GET(self): - player = app.player - graph = app.graph - web.header("content-type", "application/json") - return json.dumps({ +class timeResource(PrettyErrorHandler,cyclone.web.RequestHandler): + def get(self): + player = self.settings.app.player + graph = self.settings.app.graph + self.set_header("Content-Type", "application/json") + self.write(json.dumps({ "song" : playerSongUri(graph, player), "started" : player.playStartTime, "duration" : player.duration(), "playing" : player.isPlaying(), "t" : player.currentTime(), "state" : player.states(), - }) + })) - def POST(self): + def post(self): """ post a json object with {pause: true} or {resume: true} if you want those actions. Use {t: } to seek, optionally with a pause/resume command too. """ - params = json.loads(web.data()) - player = app.player + params = json.loads(self.request.body) + player = self.settings.app.player if params.get('pause', False): player.pause() if params.get('resume', False): player.resume() if 't' in params: player.seek(params['t']) - web.header("content-type", "text/plain") - return "ok" + self.set_header("Content-Type", "text/plain") + self.write("ok") -class songs(object): - def GET(self): - graph = app.graph +class songs(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + graph = self.settings.app.graph - songs = getSongsFromShow(graph, app.show) + songs = getSongsFromShow(graph, self.settings.app.show) - web.header("Content-type", "application/json") - return json.dumps({"songs" : [ + self.set_header("Content-Type", "application/json") + self.write(json.dumps({"songs" : [ {"uri" : s, "path" : graph.value(s, L9['showPath']), - "label" : graph.label(s)} for s in songs]}) + "label" : graph.label(s)} for s in songs]})) -class songResource(object): - def POST(self): +class songResource(PrettyErrorHandler, cyclone.web.RequestHandler): + def post(self): """post a uri of song to switch to (and start playing)""" - graph = app.graph + graph = self.settings.app.graph - app.player.setSong(songLocation(graph, URIRef(web.data()))) - web.header("content-type", "text/plain") - return "ok" + self.settings.app.player.setSong(songLocation(graph, URIRef(self.request.body))) + self.set_header("Content-Type", "text/plain") + self.write("ok") -class seekPlayOrPause(object): - def POST(self): - player = app.player +class seekPlayOrPause(PrettyErrorHandler, cyclone.web.RequestHandler): + def post(self): + player = self.settings.app.player - data = json.loads(web.data()) + data = json.loads(self.request.body) if player.isPlaying(): player.pause() else: player.seek(data['t']) player.resume() -class output(object): - def POST(self): - d = json.loads(web.data()) +class output(PrettyErrorHandler, cyclone.web.RequestHandler): + def post(self): + d = json.loads(self.request.body) subprocess.check_call(["bin/movesinks", str(d['sink'])]) -class goButton(object): - def POST(self): +class goButton(PrettyErrorHandler, cyclone.web.RequestHandler): + def post(self): """ if music is playing, this silently does nothing. """ - graph, player = app.graph, app.player + graph, player = self.settings.app.graph, self.settings.app.player if player.isPlaying(): pass else: player.resume() - web.header("content-type", "text/plain") - return "ok" + self.set_header("Content-Type", "text/plain") + self.write("ok") -def makeWebApp(theApp): - global app - app = theApp +class static(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self, name): + if name.endswith('.css'): + self.set_header("Content-Type", "text/css") + self.write(FilePath("static").preauthChild(name).open().read()) - urls = (r"/", "root", - r"/time", "timeResource", - r"/song", "songResource", - r"/songs", "songs", - r"/seekPlayOrPause", "seekPlayOrPause", - r"/output", "output", - r"/go", "goButton", - ) +def makeWebApp(app): + return cyclone.web.Application(handlers=[ + (r"/", root), + (r"/time", timeResource), + (r"/song", songResource), + (r"/songs", songs), + (r"/seekPlayOrPause", seekPlayOrPause), + (r"/output", output), + (r"/go", goButton), + (r"/static/(.*)", static), + ], app=app) - return web.application(urls, globals(), autoreload=False)