Changeset - fcf95ff23cc5
[Not reviewed]
default
0 5 0
drewp@bigasterisk.com - 12 years ago 2012-07-16 21:51:04
drewp@bigasterisk.com
PersistentSubmaster split. keyboardcomposer now notices submaster changes
Ignore-this: 2ea847e25af9b784cfec6aa4335dcc70
5 files changed with 302 insertions and 239 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -4,7 +4,7 @@ from __future__ import division, nested_
 
import cgi, os, sys, time, subprocess, logging
 
from optparse import OptionParser
 
import webcolors, colorsys
 

	
 
from louie import dispatcher
 
from twisted.internet import reactor, tksupport
 
from twisted.web import xmlrpc, server, resource
 
from Tix import *
 
@@ -19,6 +19,8 @@ from light9 import dmxclient, showconfig
 
from light9.uihelpers import toplevelat, bindkeys
 
from light9.namespaces import L9
 
from light9.tkdnd import initTkdnd, dragSourceRegister
 
from light9.rdfdb.syncedgraph import SyncedGraph
 

	
 
from bcf2000 import BCF2000
 

	
 
nudge_keys = {
 
@@ -55,6 +57,7 @@ class SubScale(Scale, Fadable):
 

	
 
class SubmasterTk(Frame):
 
    def __init__(self, master, sub, current_level):
 
        self.sub = sub
 
        bg = sub.graph.value(sub.uri, L9.color, default='#000000')
 
        rgb = webcolors.hex_to_rgb(bg)
 
        hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb])
 
@@ -65,28 +68,34 @@ class SubmasterTk(Frame):
 
        self.slider_var = DoubleVar()
 
        self.slider_var.set(current_level)
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 
        namelabel = Label(self, text=sub.name, font="Arial 7", bg=darkBg,
 
        
 
        self.namelabel = Label(self, font="Arial 7", bg=darkBg,
 
            fg='white', pady=0)
 
        namelabel.pack(side=TOP)
 
        self.sub.graph.addHandler(self.updateName)
 
        
 
        self.namelabel.pack(side=TOP)
 
        levellabel = Label(self, textvariable=self.slider_var, font="Arial 7",
 
            bg='black', fg='white', pady=0)
 
        levellabel.pack(side=TOP)
 
        self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
 
        bindkeys(self, "<Control-Key-l>", self.launch_subcomposer)
 

	
 
        for w in [self, namelabel, levellabel]:
 
        for w in [self, self.namelabel, levellabel]:
 
            dragSourceRegister(w, 'copy', 'text/uri-list', sub.uri)
 

	
 
    def updateName(self):
 
        self.namelabel.config(text=self.sub.graph.label(self.sub.uri))
 

	
 
    def launch_subcomposer(self, *args):
 
        subprocess.Popen(["bin/subcomposer", "--no-geometry", self.name])
 

	
 
class KeyboardComposer(Frame, SubClient):
 
    def __init__(self, root, graph, submasters, current_sub_levels=None,
 
    def __init__(self, root, graph, current_sub_levels=None,
 
                 hw_sliders=True):
 
        Frame.__init__(self, root, bg='black')
 
        SubClient.__init__(self)
 
        self.graph = graph
 
        self.submasters = submasters
 
        self.submasters = Submasters(graph)
 
        self.name_to_subtk = {}
 
        self.current_sub_levels = {}
 
        self.current_row = 0
 
@@ -101,21 +110,14 @@ class KeyboardComposer(Frame, SubClient)
 

	
 
        self.use_hw_sliders = hw_sliders
 
        self.connect_to_hw(hw_sliders)
 
        self.draw_ui()
 

	
 
        self.make_key_hints()
 
        self.make_buttons()
 

	
 
        self.graph.addHandler(self.redraw_sliders)
 
        self.send_levels_loop()
 

	
 
    def draw_ui(self):
 
        self.rows = [] # this holds Tk Frames for each row
 
        self.slider_vars = {} # this holds subname:sub Tk vars
 
        self.slider_table = {} # this holds coords:sub Tk vars
 
        self.name_to_subtk.clear() # subname : SubmasterTk instance
 

	
 
        self.make_key_hints()
 
        self.draw_sliders()
 
        if len(self.rows):
 
            self.change_row(self.current_row)
 
            self.rows[self.current_row].focus()
 

	
 
    def make_buttons(self):
 
        self.buttonframe = Frame(self, bg='black')
 
        self.buttonframe.pack(side=BOTTOM)
 

	
 
@@ -131,10 +133,6 @@ class KeyboardComposer(Frame, SubClient)
 
            command=self.alltozero, bg='black', fg='white')
 
        self.alltozerobutton.pack(side='left')
 

	
 
        self.refreshbutton = Button(self.buttonframe, text="Refresh", 
 
            command=self.refresh, bg='black', fg='white')
 
        self.refreshbutton.pack(side=LEFT)
 

	
 
        self.save_stage_button = Button(self.buttonframe, text="Save", 
 
            command=lambda: self.save_current_stage(self.sub_name.get()), 
 
            bg='black', fg='white')
 
@@ -142,18 +140,53 @@ class KeyboardComposer(Frame, SubClient)
 
        self.sub_name = Entry(self.buttonframe, bg='black', fg='white')
 
        self.sub_name.pack(side=LEFT)
 

	
 

	
 
    def redraw_sliders(self):
 
        self.slider_vars = {} # this holds subname:sub Tk vars
 
        self.slider_table = {} # this holds coords:sub Tk vars
 
        self.name_to_subtk.clear() # subname : SubmasterTk instance
 

	
 
        self.graph.addHandler(self.draw_sliders)
 
        if len(self.rows):
 
            self.change_row(self.current_row)
 
            self.rows[self.current_row].focus()
 

	
 
        self.stop_frequent_update_time = 0
 
                
 
    def onNewSub(self, sub):
 
        log.info("new %s", sub)
 
        self.graph.addHandler(self.draw_sliders)
 

	
 
    def onLostSub(self, subUri):
 
        log.info("lost %s", subUri)
 
        self.graph.addHandler(self.draw_sliders)
 
    
 
    def draw_sliders(self):
 

	
 

	
 
        if hasattr(self, 'rows'):
 
            for r in self.rows:
 
                r.destroy()
 
        self.rows = [] # this holds Tk Frames for each row
 

	
 
        
 
        self.tk_focusFollowsMouse()
 

	
 
        rowcount = -1
 
        col = 0
 
        last_group = None
 

	
 
        # there are unlikely to be any subs at startup because we
 
        # probably haven't been called back with the graph data yet
 
        
 
        #read get_all_subs then watch 'new submaster' 'lost submaster' signals
 
        withgroups = sorted((self.graph.value(sub.uri, L9['group']), 
 
                             self.graph.value(sub.uri, L9['order']), 
 
                             sub)
 
            for sub in self.submasters.get_all_subs())
 
        dispatcher.connect(self.onNewSub, "new submaster")
 
        dispatcher.connect(self.onLostSub, "lost submaster")
 
        log.info("withgroups %s", withgroups)
 

	
 
        for group, order, sub in withgroups:
 
            group = self.graph.value(sub.uri, L9['group'])
 
@@ -203,7 +236,7 @@ class KeyboardComposer(Frame, SubClient)
 
            try:
 
                self.sliders = Sliders(self)
 
            except IOError:
 
                print "Couldn't actually find any sliders (but really, it's no problem)"
 
                log.info("no hardware sliders")
 
                self.sliders = DummySliders()
 
                self.use_hw_sliders = False
 
        else:
 
@@ -256,6 +289,7 @@ class KeyboardComposer(Frame, SubClient)
 
        if event.keysym in ('Prior', 'p', 'bracketright'):
 
            diff = -1
 
        self.change_row(self.current_row + diff)
 

	
 
    def change_row(self, row):
 
        old_row = self.current_row
 
        self.current_row = row
 
@@ -328,20 +362,24 @@ class KeyboardComposer(Frame, SubClient)
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        return dict([(name, slidervar.get()) 
 
            for name, slidervar in self.slider_vars.items()])
 

	
 
    def get_levels_as_sub(self):
 
        scaledsubs = [self.submasters.get_sub_by_name(sub) * level \
 
            for sub, level in self.get_levels().items() if level > 0.0]
 

	
 
        maxes = sub_maxes(*scaledsubs)
 
        return maxes
 

	
 
    def save_current_stage(self, subname):
 
        print "saving current levels as", subname
 
        log.info("saving current levels as %s", subname)
 
        sub = self.get_levels_as_sub()
 
        sub.name = subname
 
        sub.temporary = 0
 
@@ -356,20 +394,6 @@ class KeyboardComposer(Frame, SubClient)
 
            self.send_levels()
 
            self.after(10, self.send_frequent_updates)
 

	
 
    def refresh(self):
 
        self.save()
 
        graph = showconfig.getGraph()
 
        self.submasters = Submasters(graph)
 
        self.current_sub_levels, self.current_row = \
 
            pickle.load(file('.keyboardcomposer.savedlevels'))
 
        for r in self.rows:
 
            r.destroy()
 
        self.keyhints.destroy()
 
        self.buttonframe.destroy()
 
        self.draw_ui()
 
        # possibly paranoia (but possibly not)
 
        self.change_row(self.current_row)
 

	
 
    def alltozero(self):
 
        for name, subtk in self.name_to_subtk.items():
 
            if subtk.scale.scale_var.get() != 0:
 
@@ -463,10 +487,9 @@ if __name__ == "__main__":
 
    opts, args = parser.parse_args()
 

	
 
    logging.basicConfig(level=logging.INFO if opts.v else logging.WARN)
 
    log = logging.getLogger()
 
    log = logging.getLogger('keyboardcomposer')
 

	
 
    graph = showconfig.getGraph()
 
    s = Submasters(graph)
 
    graph = SyncedGraph("keyboardcomposer")
 

	
 
    root = Tk()
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 
@@ -476,7 +499,7 @@ if __name__ == "__main__":
 
    startLevels = None
 
    if opts.nonpersistent:
 
        startLevels = {}
 
    kc = KeyboardComposer(tl, graph, s, startLevels,
 
    kc = KeyboardComposer(tl, graph, startLevels,
 
                          hw_sliders=not opts.no_sliders)
 
    kc.pack(fill=BOTH, expand=1)
 

	
 
@@ -489,8 +512,8 @@ if __name__ == "__main__":
 
        reactor.listenTCP(networking.keyboardComposer.port,
 
                          server.Site(LevelServerHttp(kc.name_to_subtk)))
 
    except twisted.internet.error.CannotListenError, e:
 
        print "Can't (and won't!) start level server:"
 
        print e
 
        log.warn("Can't (and won't!) start level server:")
 
        log.warn(e)
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    if not opts.nonpersistent:
bin/rdfdb
Show inline comments
 
@@ -70,7 +70,9 @@ Our web ui:
 
registered clients
 

	
 
recent edits, each one says what client it came from. You can reverse
 
them here.
 
them here. We should be able to take patches that are close in time
 
and keep updating the same data (e.g. a stream of changes as the user
 
drags a slider) and collapse them into a single edit for clarity.
 

	
 
"""
 
from twisted.internet import reactor
 
@@ -131,7 +133,9 @@ class Db(object):
 

	
 
        for inFile in [#"show/dance2012/config.n3",
 
                       "show/dance2012/subs/bcools",
 
                       #"demo.n3",
 
                       "show/dance2012/subs/bwarm",
 
                       "show/dance2012/subs/house",
 
                       "demo.n3",
 
                       ]:
 
            self.g = GraphFile(notifier,
 
                               inFile,
light9/Submaster.py
Show inline comments
 
from __future__ import division
 
import os, logging, time
 
from rdflib import Graph
 
from rdflib import Graph, RDF
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import Patch, showconfig
 
try:
 
    import dispatch.dispatcher as dispatcher
 
except ImportError:
 
    from louie import dispatcher
 
log = logging.getLogger()
 
log = logging.getLogger('submaster')
 

	
 
class Submaster:
 
class Submaster(object):
 
    "Contain a dictionary of levels, but you didn't need to know that"
 
    def __init__(self,
 
                 name=None,
 
                 graph=None, sub=None,
 
                 leveldict=None, temporary=False):
 
        """sub is the URI for this submaster, graph is a graph where
 
        we can learn about the sub. If graph is not provided, we look
 
        in a file named name.
 
    def __init__(self, name, levels):
 
        """this sub has a name just for debugging. It doesn't get persisted. 
 
        See PersistentSubmaster.
 

	
 
        levels is a dict
 
        """
 
        self.name = name
 
        self.levels = levels
 

	
 
        self.temporary = True
 
        
 
        if not self.temporary:
 
            # obsolete
 
            dispatcher.connect(log.error, 'reload all subs')
 
            
 
        log.debug("%s initial levels %s", self.name, self.levels)
 

	
 
    def _editedLevels(self):
 
        pass
 
    
 
    def set_level(self, channelname, level, save=True):
 
        self.levels[Patch.resolve_name(channelname)] = level
 
        self._editedLevels()
 

	
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
            # this may call _editedLevels too many times
 
            self.set_level(k, v, save=0)
 

	
 
        name is the filename where we can load a graph about this URI
 
        (see showconfig.subFile)
 
    def get_levels(self):
 
        return self.levels
 
    
 
    def no_nonzero(self):
 
        return (not self.levels.values()) or not (max(self.levels.values()) > 0)
 

	
 
    def __mul__(self, scalar):
 
        return Submaster("%s*%s" % (self.name, scalar), 
 
                         levels=dict_scale(self.levels, scalar))
 
    __rmul__ = __mul__
 
    def max(self, *othersubs):
 
        return sub_maxes(self, *othersubs)
 

	
 
    def __add__(self, other):
 
        return self.max(other)
 

	
 
        passing name alone makes a new empty sub
 
    def ident(self):
 
        return (self.name, tuple(sorted(self.levels.items())))
 

	
 
    def __repr__(self):
 
        items = getattr(self, 'levels', {}).items()
 
        items.sort()
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (getattr(self, 'name', 'no name yet'), levels)
 

	
 
        temporary means the sub won't get saved or loaded
 
    def __cmp__(self, other):
 
        # not sure how useful this is
 
        return cmp(self.ident(), other.ident())
 
    
 
    def __hash__(self):
 
        return hash(self.ident())
 

	
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels() # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in leveldict.items():
 
            if v == 0:
 
                continue
 
            try:
 
                dmxchan = Patch.get_dmx_channel(k) - 1
 
            except ValueError:
 
                log.error("error trying to compute dmx levels for submaster %s" % self.name)
 
                raise
 
            if dmxchan >= len(levels):
 
                levels.extend([0] * (dmxchan - len(levels) + 1))
 
            levels[dmxchan] = max(v, levels[dmxchan])
 

	
 
        pass:
 
          name, temporary=True  -  no rdf involved
 
          sub, filename         -  read sub URI from graph at filename
 
        return levels
 
    
 
    def normalize_patch_names(self):
 
        """Use only the primary patch names."""
 
        # possibly busted -- don't use unless you know what you're doing
 
        self.set_all_levels(self.levels.copy())
 
          
 
          name - new sub
 
          sub - n
 
          name, sub - new 
 
    def get_normalized_copy(self):
 
        """Get a copy of this sumbaster that only uses the primary patch 
 
        names.  The levels will be the same."""
 
        newsub = Submaster("%s (normalized)" % self.name, {})
 
        newsub.set_all_levels(self.levels)
 
        return newsub
 
    
 
    def crossfade(self, othersub, amount):
 
        """Returns a new sub that is a crossfade between this sub and
 
        another submaster.  
 
        
 
        NOTE: You should only crossfade between normalized submasters."""
 
        otherlevels = othersub.get_levels()
 
        keys_set = {}
 
        for k in self.levels.keys() + otherlevels.keys():
 
            keys_set[k] = 1
 
        all_keys = keys_set.keys()
 
        
 
        """
 
        if name is sub is leveldict is None:
 
            raise TypeError("more args are needed")
 
        if sub is not None and name is None:
 
            name = graph.label(sub)
 
        if graph is not None:
 
            # old code was passing leveldict as second positional arg
 
            assert isinstance(graph, Graph)
 
        self.name = name
 
        self.uri = sub
 
        xfaded_sub = Submaster("xfade", {})
 
        for k in all_keys:
 
            xfaded_sub.set_level(k, 
 
                                 linear_fade(self.levels.get(k, 0),
 
                                             otherlevels.get(k, 0),
 
                                             amount))
 

	
 
        return xfaded_sub
 

	
 
class PersistentSubmaster(Submaster):
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
        self.graph.addHandler(self.setName)
 
        self.graph.addHandler(self.setLevels)
 
        Submaster.__init__(self, self.name, self.levels)
 
        self.graph = graph
 
        self.temporary = temporary
 
        if leveldict:
 
            self.levels = leveldict
 
        self.uri = uri
 
        self.temporary = False
 

	
 
    def ident(self):
 
        return self.uri
 
        
 
    def _editedLevels(self):
 
        self.save()
 
        
 
    def setName(self):
 
        log.info("sub update name %s %s", self.uri, self.graph.label(self.uri))
 
        self.name = self.graph.label(self.uri)
 
        
 
    def setLevels(self):
 
        log.info("sub update levels")
 
        oldLevels = getattr(self, 'levels', {}).copy()
 
        self.setLevelsFromGraph()
 
        if oldLevels != self.levels:
 
            log.info("sub %s changed" % self.name)
 
        
 
    def setLevelsFromGraph(self):
 
        patchGraph = showconfig.getGraph() # going away
 
        if hasattr(self, 'levels'):
 
            self.levels.clear()
 
        else:
 
            self.levels = {}
 
            self.reload(quiet=True, graph=graph)
 
        if not self.temporary:
 
            dispatcher.connect(self.reload, 'reload all subs')
 
        log.debug("%s initial levels %s", self.name, self.levels)
 
        
 
    def reload(self, quiet=False, graph=None):
 
        if self.temporary:
 
            return
 
        try:
 
            oldlevels = self.levels.copy()
 
            self.levels.clear()
 
            patchGraph = showconfig.getGraph()
 
            if 1 or graph is None:
 
                # need to read the sub graph to build the levels, not
 
                # use the main one! The sub graphs will eventually
 
                # just be part of the one and only shared graph
 
                graph = Graph()
 
                if not self.name:
 
                    # anon sub, maybe for a chase
 
                    pass
 
                else:
 
                    inFile = showconfig.subFile(self.name)
 

	
 
                    t1 = time.time()
 
                    graph.parse(inFile, format="n3")
 
                    log.info("reading %s in %.1fms", inFile, 1000 * (time.time() - t1))
 
                    
 
                self.setLevelsFromGraph(graph, patchGraph)
 
                
 
            if (not quiet) and (oldlevels != self.levels):
 
                log.info("sub %s changed" % self.name)
 
        except IOError, e:
 
            log.error("Can't read file for sub: %r (%s)" % (self.name, e))
 

	
 
    def setLevelsFromGraph(self, graph, patchGraph):
 
        self.uri = L9['sub/%s' % self.name]
 
        for lev in graph.objects(self.uri, L9['lightLevel']):
 
            chan = graph.value(lev, L9['channel'])
 
            val = graph.value(lev, L9['level'])
 
        for lev in self.graph.objects(self.uri, L9['lightLevel']):
 
            chan = self.graph.value(lev, L9['channel'])
 
            val = self.graph.value(lev, L9['level'])
 
            name = patchGraph.label(chan)
 
            if not name:
 
                log.error("sub %r has channel %r with no name- leaving out that channel" % (self.name, chan))
 
                #log.error("sub %r has channel %r with no name- leaving out that channel" % (self.name, chan))
 
                continue
 
            self.levels[name] = float(val)
 

	
 
@@ -120,87 +186,6 @@ class Submaster:
 

	
 
        graph.serialize(showconfig.subFile(self.name), format="nt")
 

	
 
    def set_level(self, channelname, level, save=True):
 
        self.levels[Patch.resolve_name(channelname)] = level
 
        if save:
 
            self.save()
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
            self.set_level(k, v, save=0)
 
        self.save()
 
    def get_levels(self):
 
        return self.levels
 
    def no_nonzero(self):
 
        return (not self.levels.values()) or not (max(self.levels.values()) > 0)
 
    def __mul__(self, scalar):
 
        return Submaster("%s*%s" % (self.name, scalar), 
 
                         leveldict=dict_scale(self.levels, scalar),
 
                         temporary=True)
 
    __rmul__ = __mul__
 
    def max(self, *othersubs):
 
        return sub_maxes(self, *othersubs)
 

	
 
    def __add__(self, other):
 
        return self.max(other)
 

	
 
    def __repr__(self):
 
        items = self.levels.items()
 
        items.sort()
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (self.name, levels)
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels() # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in leveldict.items():
 
            if v == 0:
 
                continue
 
            try:
 
                dmxchan = Patch.get_dmx_channel(k) - 1
 
            except ValueError:
 
                log.error("error trying to compute dmx levels for submaster %s" % self.name)
 
                raise
 
            if dmxchan >= len(levels):
 
                levels.extend([0] * (dmxchan - len(levels) + 1))
 
            levels[dmxchan] = max(v, levels[dmxchan])
 

	
 
        return levels
 
    def normalize_patch_names(self):
 
        """Use only the primary patch names."""
 
        # possibly busted -- don't use unless you know what you're doing
 
        self.set_all_levels(self.levels.copy())
 
    def get_normalized_copy(self):
 
        """Get a copy of this sumbaster that only uses the primary patch 
 
        names.  The levels will be the same."""
 
        newsub = Submaster("%s (normalized)" % self.name, temporary=1)
 
        newsub.set_all_levels(self.levels)
 
        return newsub
 
    def crossfade(self, othersub, amount):
 
        """Returns a new sub that is a crossfade between this sub and
 
        another submaster.  
 
        
 
        NOTE: You should only crossfade between normalized submasters."""
 
        otherlevels = othersub.get_levels()
 
        keys_set = {}
 
        for k in self.levels.keys() + otherlevels.keys():
 
            keys_set[k] = 1
 
        all_keys = keys_set.keys()
 

	
 
        xfaded_sub = Submaster("xfade", temporary=1)
 
        for k in all_keys:
 
            xfaded_sub.set_level(k, 
 
                                 linear_fade(self.levels.get(k, 0),
 
                                             otherlevels.get(k, 0),
 
                                             amount))
 

	
 
        return xfaded_sub
 
    def __cmp__(self, other):
 
        """Compare by sub repr (name, hopefully)"""
 
        return cmp(repr(self), repr(other))
 
    def __hash__(self):
 
        raise NotImplementedError
 
        return hash(repr(self))
 
                                            
 
def linear_fade(start, end, amount):
 
    """Fades between two floats by an amount.  amount is a float between
 
@@ -213,8 +198,7 @@ def sub_maxes(*subs):
 
    nonzero_subs = [s for s in subs if not s.no_nonzero()]
 
    name = "max(%s)" % ", ".join([repr(s) for s in nonzero_subs])
 
    return Submaster(name,
 
                     leveldict=dict_max(*[sub.levels for sub in nonzero_subs]),
 
                     temporary=1)
 
                     levels=dict_max(*[sub.levels for sub in nonzero_subs]))
 

	
 
def combine_subdict(subdict, name=None, permanent=False):
 
    """A subdict is { Submaster objects : levels }.  We combine all
 
@@ -236,16 +220,23 @@ class Submasters:
 
    "Collection o' Submaster objects"
 
    def __init__(self, graph):
 
        self.submasters = {}
 
        self.graph = graph
 

	
 
        files = os.listdir(showconfig.subsDir())
 
        t1 = time.time()
 
        for filename in files:
 
            # we don't want these files
 
            if filename.startswith('.') or filename.endswith('~') or \
 
               filename.startswith('CVS'):
 
                continue
 
            self.submasters[filename] = Submaster(filename, graph=graph)
 
        log.info("loaded all submasters in %.1fms" % ((time.time() - t1) * 1000))
 
        graph.addHandler(self.findSubs)
 

	
 
    def findSubs(self):
 
        current = set()
 

	
 
        for s in self.graph.subjects(RDF.type, L9['Submaster']):
 
            log.info("found sub %s", s)
 
            if s not in self.submasters:
 
                sub = self.submasters[s] = PersistentSubmaster(self.graph, s)
 
                dispatcher.send("new submaster", sub=sub)
 
                current.add(s)
 
        for s in set(self.submasters.keys()) - current:
 
            del self.submasters[s]
 
            dispatcher.send("lost submaster", subUri=s)
 
        log.info("findSubs finished %s", self.submasters)
 
        
 
    def get_all_subs(self):
 
        "All Submaster objects"
 
@@ -255,7 +246,7 @@ class Submasters:
 
        songs = []
 
        notsongs = []
 
        for s in l:
 
            if s.name.startswith('song'):
 
            if s.name and s.name.startswith('song'):
 
                songs.append(s)
 
            else:
 
                notsongs.append(s)
light9/rdfdb/graphfile.py
Show inline comments
 
import logging
 
import logging, traceback
 
from twisted.python.filepath import FilePath
 
from rdflib import Graph
 
from light9.rdfdb.patch import Patch
 
@@ -18,7 +18,10 @@ class GraphFile(object):
 
      
 
    def notify(self, notifier, filepath, mask):
 
        log.info("file %s changed" % filepath)
 
        try:
 
        self.reread()
 
        except Exception:
 
            traceback.print_exc()
 

	
 
    def reread(self):
 
        """update the graph with any diffs from this file"""
light9/rdfdb/syncedgraph.py
Show inline comments
 
from rdflib import ConjunctiveGraph, RDFS
 
from rdflib import ConjunctiveGraph, RDFS, RDF
 
import logging, cyclone.httpclient, traceback, urllib
 
from twisted.internet import reactor
 
log = logging.getLogger()
 
log = logging.getLogger('syncedgraph')
 
from light9.rdfdb.patch import Patch, ALLSTMTS
 
from light9.rdfdb.rdflibpatch import patchQuads
 

	
 
@@ -44,6 +44,7 @@ class GraphWatchers(object):
 
    """
 
    def __init__(self):
 
        self._handlersSp = {} # (s,p): set(handlers)
 
        self._handlersPo = {} # (p,o): set(handlers)
 

	
 
    def addSubjPredWatcher(self, func, s, p):
 
        if func is None:
 
@@ -52,19 +53,34 @@ class GraphWatchers(object):
 
        try:
 
            self._handlersSp.setdefault(key, set()).add(func)
 
        except Exception:
 
            print "with key %r and func %r" % (key, func)
 
            log.error("with key %r and func %r" % (key, func))
 
            raise
 

	
 
    def addPredObjWatcher(self, func, p, o):
 
        self._handlersPo.setdefault((p, o), set()).add(func)
 

	
 
    def whoCares(self, patch):
 
        """what handler functions would care about the changes in this patch"""
 
        """what handler functions would care about the changes in this patch?
 

	
 
        this removes the handlers that it gives you
 
        """
 
        self.dependencies()
 
        affectedSubjPreds = set([(s, p) for s, p, o, c in patch.addQuads]+
 
                                [(s, p) for s, p, o, c in patch.delQuads])
 
        affectedPredObjs = set([(p, o) for s, p, o, c in patch.addQuads]+
 
                                [(p, o) for s, p, o, c in patch.delQuads])
 
        
 
        ret = set()
 
        for (s,p), func in self._handlersSp.iteritems():
 
        for (s, p), funcs in self._handlersSp.iteritems():
 
            if (s,p) in affectedSubjPreds:
 
                ret.update(func)
 
                ret.update(funcs)
 
                funcs.clear()
 
                
 
        for (p, o), funcs in self._handlersPo.iteritems():
 
            if (p, o) in affectedPredObjs:
 
                ret.update(funcs)
 
                funcs.clear()
 

	
 
        return ret
 

	
 
    def dependencies(self):
 
@@ -73,7 +89,7 @@ class GraphWatchers(object):
 
        data they depend on. This is meant for showing on the web ui
 
        for browsing.
 
        """
 
        print "whocares"
 
        log.info("whocares:")
 
        from pprint import pprint
 
        pprint(self._handlersSp)
 
        
 
@@ -111,12 +127,12 @@ class SyncedGraph(object):
 
        self.updateResource = 'http://localhost:%s/update' % port
 
        log.info("listening on %s" % port)
 
        self.register(label)
 
        self.currentFunc = None
 
        self.currentFuncs = [] # stack of addHandler callers
 

	
 
    def register(self, label):
 

	
 
        def done(x):
 
            print "registered", x.body
 
            log.debug("registered with rdfdb")
 

	
 
        cyclone.httpclient.fetch(
 
            url='http://localhost:8051/graphClients',
 
@@ -153,11 +169,11 @@ class SyncedGraph(object):
 
        # new? Cache their results, so if i make the query again and
 
        # it gives the same result, I don't call the handler?
 
        
 
        self.currentFunc = func
 
        self.currentFuncs.append(func)
 
        try:
 
            func()
 
        finally:
 
            self.currentFunc = None
 
            self.currentFuncs.pop()
 

	
 
    def updateOnPatch(self, p):
 
        """
 
@@ -165,26 +181,52 @@ class SyncedGraph(object):
 
        might care, and then notice what data they depend on now
 
        """
 
        for func in self._watchers.whoCares(p):
 
            # and forget the old one!
 
            # todo: forget the old handlers for this func
 
            self.addHandler(func)
 

	
 
    def _assertCurrent(self):
 
        if self.currentFunc is None:
 
    def _getCurrentFunc(self):
 
        if not self.currentFuncs:
 
            # this may become a warning later
 
            raise ValueError("asked for graph data outside of a handler")
 

	
 
        # we add the watcher to the deepest function, since that
 
        # should be the cheapest way to update when this part of the
 
        # data changes
 
        return self.currentFuncs[-1]
 

	
 
    # these just call through to triples() so it might be possible to
 
    # watch just that one
 
    def value(self, subj, pred):
 
        self._assertCurrent()
 
        self._watchers.addSubjPredWatcher(self.currentFunc, subj, pred)
 
        return self._graph.value(subj, pred)
 
    # watch just that one.
 

	
 
    # if you get a bnode in your response, maybe the answer to
 
    # dependency tracking is to say that you depended on the triple
 
    # that got you that bnode, since it is likely to change to another
 
    # bnode later. This won't work if the receiver stores bnodes
 
    # between calls, but probably most of them don't do that (they
 
    # work from a starting uri)
 
    
 
    def value(self, subject=None, predicate=RDF.value, object=None,
 
              default=None, any=True):
 
        if object is not None:
 
            raise NotImplementedError()
 
        func = self._getCurrentFunc()
 
        self._watchers.addSubjPredWatcher(func, subject, predicate)
 
        return self._graph.value(subject, predicate, object=object,
 
                                 default=default, any=any)
 

	
 
    def objects(self, subject=None, predicate=None):
 
        self._assertCurrent()
 
        self._watchers.addSubjPredWatcher(self.currentFunc, subject, predicate)
 
        func = self._getCurrentFunc()
 
        self._watchers.addSubjPredWatcher(func, subject, predicate)
 
        return self._graph.objects(subject, predicate)
 
    
 
    def label(self, uri):
 
        self._assertCurrent()
 
        return self.value(uri, RDFS.label)
 

	
 
    def subjects(self, predicate=None, object=None):
 
        func = self._getCurrentFunc()
 
        self._watchers.addPredObjWatcher(func, predicate, object)
 
        return self._graph.subjects(predicate, object)
 

	
 
    # i find myself wanting 'patch' versions of these calls that tell
 
    # you only what results have just appeared or disappeared. I think
 
    # I'm going to be repeating that logic a lot. Maybe just for the
 
    # subjects(RDF.type, t) call
0 comments (0 inline, 0 general)