Changeset - d8b25edf6c74
[Not reviewed]
default
0 1 0
drewp@bigasterisk.com - 11 years ago 2014-06-14 07:21:32
drewp@bigasterisk.com
fix dropping of subs onto effecteval
Ignore-this: 7150db275055597cdb9a5347910b032b
1 file changed with 3 insertions and 3 deletions:
0 comments (0 inline, 0 general)
bin/effecteval
Show inline comments
 
#!bin/python
 
from __future__ import division
 
from run_local import log
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
import sys, optparse, logging, subprocess, json, time, traceback, itertools
 
from rdflib import URIRef, Literal
 

	
 
sys.path.append('/usr/lib/pymodules/python2.7/') # for numpy, on rpi
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For numpy
 
from light9 import networking, showconfig, Submaster, dmxclient
 
from light9.curvecalc.curve import CurveResource
 
from light9.effecteval.effect import EffectNode
 
from light9.effecteval.effectloop import makeEffectLoop
 
from light9.greplin_cyclone import StatsForCyclone
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.rdfdb.patch import Patch
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from greplin import scales
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 
class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        self.write(open("light9/effecteval/effect.html").read())
 
    def delete(self):
 
        graph = self.settings.graph
 
        uri = URIRef(self.get_argument('uri'))
 
        with graph.currentState(tripleFilter=(None, L9['effect'], uri)) as g:
 
            song = ctx = list(g.subjects(L9['effect'], uri))[0]
 
        self.settings.graph.patch(Patch(delQuads=[
 
            (song, L9['effect'], uri, ctx),
 
            ]))
 
        
 
class SongEffects(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def post(self):
 
        song = URIRef(self.get_argument('uri'))
 
        dropped = URIRef(self.get_argument('drop'))
 
        ctx = song
 
        graph = self.settings.graph
 
        effect = graph.sequentialUri(song + "/effect-")
 
        quads = [
 
            (song, L9['effect'], effect, ctx),
 
            (effect, RDF.type, L9['Effect'], ctx),
 
        ]
 

	
 
        with graph.currentState(
 
                tripleFilter=(dropped, None, None)) as g:
 
            droppedTypes = g.objects(dropped, RDF.type)
 
            droppedTypes = list(g.objects(dropped, RDF.type))
 
            droppedLabel = g.label(dropped)
 
            droppedCodes = g.objects(dropped, L9['code'])
 
            droppedCodes = list(g.objects(dropped, L9['code']))
 

	
 
        if L9['EffectClass'] in droppedTypes:
 
            quads.extend([
 
                (effect, RDFS.label, droppedLabel, ctx),
 
                (effect, RDF.type, dropped, ctx),
 
                ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
        elif L9['Curve'] in droppedTypes:
 
        elif L9['Submaster'] in droppedTypes:
 
            curve = graph.sequentialUri(song + "/curve-")
 
            cr = CurveResource(graph, curve)
 
            cr.newCurve(ctx, label=Literal('sub %s' % droppedLabel))
 
            cr.saveCurve()
 
            quads.extend([
 
                (song, L9['curve'], curve, ctx),                
 
                (effect, L9['code'],
 
                 Literal('out = %s * %s' % (dropped.n3(), curve.n3())),
 
                 ctx),
 
                ])
 
        else:
 
            raise NotImplementedError(
 
                "don't know how to add an effect from %r (types=%r)" %
 
                (dropped, droppedTypes))
 
            
 
        graph.patch(Patch(addQuads=quads))
 
        
 
class SongEffectsUpdates(cyclone.websocket.WebSocketHandler):
 
    def connectionMade(self, *args, **kwargs):
 
        self.graph = self.settings.graph
 
        self.graph.addHandler(self.updateClient)
 
        
 
    def updateClient(self):
 
        # todo: abort if client is gone
 
        playlist = self.graph.value(showconfig.showUri(), L9['playList'])
 
        songs = list(self.graph.items(playlist))
 
        out = []
 
        for s in songs:
 
            out.append({'uri': s, 'label': self.graph.label(s)})
 
            out[-1]['effects'] = [{'uri': uri} for uri in sorted(self.graph.objects(s, L9['effect']))]
 
        self.sendMessage({'songs': out})
 
        
 
        
 
class EffectUpdates(cyclone.websocket.WebSocketHandler):
 
    """
 
    stays alive for the life of the effect page
 
    """
 
    def connectionMade(self, *args, **kwargs):
 
        log.info("websocket opened")
 
        self.uri = URIRef(self.get_argument('uri'))
 
        self.sendMessage({'hello': repr(self)})
 

	
 
        self.graph = self.settings.graph
 
        self.graph.addHandler(self.updateClient)
 

	
 
    def updateClient(self):
 
        # todo: if client has dropped, abort and don't get any more
 
        # graph updates
 

	
 
        # EffectNode knows how to put them in order. Somehow this is
 
        # not triggering an update when the order changes.
 
        en = EffectNode(self.graph, self.uri)
 
        codeLines = [c.code for c in en.codes]
 
        self.sendMessage({'codeLines': codeLines})
 
        
 
    def connectionLost(self, reason):
 
        log.info("websocket closed")
 

	
 
    def messageReceived(self, message):
 
        log.info("got message %s" % message)
 
        # write a patch back to the graph
 

	
 
def replaceObjects(graph, c, s, p, newObjs):
 
    patch = graph.getObjectPatch(
 
        context=c,
 
        subject=s,
 
        predicate=p,
 
        newObject=newObjs[0])
 

	
 
    moreAdds = []
 
    for line in newObjs[1:]:
 
        moreAdds.append((s, p, line, c))
 
    fullPatch = Patch(delQuads=patch.delQuads,
 
                      addQuads=patch.addQuads + moreAdds)
 
    graph.patch(fullPatch)
 

	
 
        
 
class Code(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        codeLines = []
 
        for i in itertools.count(0):
 
            k = 'codeLines[%s][text]' % i
 
            v = self.get_argument(k, None)
 
            if v is not None:
 
                codeLines.append(Literal(v))
 
            else:
 
                break
 
        if not codeLines:
 
            log.info("no codelines received on PUT /code")
 
            return
 
        with self.settings.graph.currentState(
 
                tripleFilter=(None, L9['effect'], effect)) as g:
 
            song = g.subjects(L9['effect'], effect).next()
 
            
 
        replaceObjects(self.settings.graph, song, effect, L9['code'], codeLines)
 
            
 
        # right here we could tell if the code has a python error and return it
 
        self.send_error(202)
 
        
 
class EffectEval(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    @inlineCallbacks
 
    def get(self):
 
        # return dmx list for that effect
 
        uri = URIRef(self.get_argument('uri'))
 
        response = yield cyclone.httpclient.fetch(
 
            networking.musicPlayer.path('time'))
 
        songTime = json.loads(response.body)['t']
 

	
 
        node = EffectNode(self.settings.graph, uri)
 
        outSub = node.eval(songTime)
 
        self.write(json.dumps(outSub.get_dmx_list()))
 

	
 

	
 
# Completely not sure where the effect background loop should
 
# go. Another process could own it, and get this request repeatedly:
 
class SongEffectsEval(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        song = URIRef(self.get_argument('song'))
 
        effects = effectsForSong(self.settings.graph, song)
 
        raise NotImplementedError
 
        self.write(maxDict(effectDmxDict(e) for e in effects))
 
        # return dmx dict for all effects in the song, already combined
 

	
 
class App(object):
 
    def __init__(self, show, outputWhere):
 
        self.show = show
 
        self.outputWhere = outputWhere
 
        self.graph = SyncedGraph(networking.rdfdb.url, "effectEval")
 
        self.graph.initiallySynced.addCallback(self.launch)
 

	
 
        self.stats = scales.collection('/',
 
                                       scales.PmfStat('sendLevels'),
 
                                       scales.PmfStat('getMusic'),
 
                                       scales.PmfStat('evals'),
 
                                       scales.PmfStat('sendOutput'),
 
                                       scales.IntStat('errors'),
 
                                       )
 

	
 
    def launch(self, *args):
 
        self.loop = makeEffectLoop(self.graph, self.stats, self.outputWhere)
 
        self.loop.startLoop()
 
        
 
        SFH = cyclone.web.StaticFileHandler
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/()', SFH,
 
             {'path': 'light9/effecteval', 'default_filename': 'index.html'}),
 
            (r'/effect', EffectEdit),
 
            (r'/(websocket\.js)', SFH, {'path': 'light9/rdfdb/web/'}),
 
            (r'/effect\.js', StaticCoffee, {'src': 'light9/effecteval/effect.coffee'}),
 
            (r'/index\.js', StaticCoffee, {'src': 'light9/effecteval/index.coffee'}),
 
            (r'/effectUpdates', EffectUpdates),
 
            (r'/code', Code),
 
            (r'/songEffectsUpdates', SongEffectsUpdates),
 
            (r'/static/(.*)', SFH, {'path': 'static/'}),
 
            (r'/effect/eval', EffectEval),
 
            (r'/songEffects', SongEffects),
 
            (r'/songEffects/eval', SongEffectsEval),
 
            (r'/stats', StatsForCyclone),
 
        ], debug=True, graph=self.graph, stats=self.stats)
 
        reactor.listenTCP(networking.effectEval.port, self.cycloneApp)
 
        log.info("listening on %s" % networking.effectEval.port)
 
        
 
class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def initialize(self, src):
 
        super(StaticCoffee, self).initialize()
 
        self.src = src
 
    def get(self):
 
        self.set_header('Content-Type', 'application/javascript')
 
        self.write(subprocess.check_output([
 
            '/usr/bin/coffee', '--compile', '--print', self.src]))
 

	
 
        
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog", action="store_true",
 
                      help="twisted logging")
 
    parser.add_option("--output", metavar="WHERE", help="dmx or leds")
 
    (options, args) = parser.parse_args()
 
    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
            
 
    app = App(URIRef(options.show), options.output)
 
    if options.twistedlog:
 
        from twisted.python import log as twlog
0 comments (0 inline, 0 general)