changeset 793:cbfed4e684ef

redo asco web server to use twisted/cyclone. mainloop is now glib inside twisted. seems ok (and lower cpu usage) but not well tested Ignore-this: 24ae0b173599a0d99e465f1b5a10f39b
author drewp@bigasterisk.com
date Sat, 30 Jun 2012 06:29:23 +0000
parents 351cac9512c1
children 1a6ad502c05c
files bin/ascoltami2 light9/ascoltami/webapp.py
diffstat 2 files changed, 72 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/bin/ascoltami2	Sat Jun 30 05:58:27 2012 +0000
+++ b/bin/ascoltami2	Sat Jun 30 06:29:23 2012 +0000
@@ -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 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 @@
     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 @@
 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',
--- a/light9/ascoltami/webapp.py	Sat Jun 30 05:58:27 2012 +0000
+++ b/light9/ascoltami/webapp.py	Sat Jun 30 06:29:23 2012 +0000
@@ -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 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 @@
     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: <seconds>} 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)