Changeset - dcab422615ca
[Not reviewed]
default
0 5 0
drewp@bigasterisk.com - 12 years ago 2013-06-11 05:48:10
drewp@bigasterisk.com
working on local->global sub action. creation of new file in rdfdb is still a problem
Ignore-this: 28a0b0754f5fca6bef118f7e14758053
5 files changed with 125 insertions and 25 deletions:
0 comments (0 inline, 0 general)
bin/rdfdb
Show inline comments
 
@@ -193,12 +193,13 @@ class WatchedFiles(object):
 
                                        callbacks=[self.dirChange])
 
        finally:
 
            self.initialLoad = False
 

	
 
    def dirChange(self, watch, path, mask):
 
        if mask & IN_CREATE:
 
            log.debug("%s created; consider adding a watch", path)
 
            self.watchFile(path.path)
 
            
 
    def watchFile(self, inFile):
 
        """
 
        consider adding a GraphFile to self.graphFiles
 

	
 
@@ -235,31 +236,40 @@ class WatchedFiles(object):
 
            return
 
            
 
        ctx = self.uriFromFile(inFile)
 
        gf = GraphFile(self.notifier, inFile, ctx,
 
                       self.patch, self.getSubgraph)
 
        self.graphFiles[ctx] = gf
 
        log.info("%s do initial read", inFile)
 
        gf.reread()
 

	
 
    def aboutToPatch(self, ctx):
 
        """
 
        warn us that a patch is about to come to this context. it's more
 
        straightforward to create the new file now
 
        """
 
    
 
        g = self.getSubgraph(ctx)
 

	
 
        if ctx not in self.graphFiles:
 
            outFile = self.fileForUri(ctx)
 
            log.info("starting new file %r", outFile)
 
            self.graphFiles[ctx] = GraphFile(self.notifier, outFile, ctx,
 
                                             self.patch, self.getSubgraph)
 

	
 
    def dirtyFiles(self, ctxs):
 
        """mark dirty the files that we watch in these contexts.
 

	
 
        the ctx might not be a file that we already read; it might be
 
        for a new file we have to create, or it might be for a
 
        transient context that we're not going to save
 

	
 
        if it's a ctx with no file, error
 
        """
 
        for ctx in ctxs:
 
            g = self.getSubgraph(ctx)
 

	
 
            if ctx not in self.graphFiles:
 
                outFile = self.fileForUri(ctx)
 
                self.graphFiles[ctx] = GraphFile(self.notifier, outFile, ctx,
 
                                                 self.patch, self.getSubgraph)
 
            
 
            self.graphFiles[ctx].dirty(g)
 

	
 
    def uriFromFile(self, filename):
 
        assert filename.endswith('.n3'), filename
 
        if not any(filename.startswith(t) for t in self.topDirsToWatch):
 
            raise ValueError("filename %s doesn't start with any of %s" %
 
@@ -298,12 +308,15 @@ class Db(object):
 
        back to the sender with that updateUri
 
        """
 
        ctx = p.getContext()
 
        log.info("patching graph %s -%d +%d" % (
 
            ctx, len(p.delQuads), len(p.addQuads)))
 

	
 
        if hasattr(self, 'watchedFiles'): # not during startup
 
            self.watchedFiles.aboutToPatch(ctx)
 
        
 
        patchQuads(self.graph, p.delQuads, p.addQuads, perfect=True)
 
        senderUpdateUri = getattr(p, 'senderUpdateUri', None)
 

	
 
        for c in self.clients:
 
            if c.updateUri == senderUpdateUri:
 
                # this client has self-applied the patch already
bin/subcomposer
Show inline comments
 
@@ -15,23 +15,23 @@ subcomposer
 

	
 

	
 
"""
 
from __future__ import division, nested_scopes
 
import time, logging
 
from optparse import OptionParser
 
import logging
 
import logging, urllib
 
import Tkinter as tk
 
import louie as dispatcher
 
from twisted.internet import reactor, tksupport, task
 
from rdflib import URIRef, RDF
 
from rdflib import URIRef, RDF, RDFS, Literal
 

	
 
from run_local import log
 
log.setLevel(logging.DEBUG)
 

	
 
from light9.dmxchanedit import Levelbox
 
from light9 import dmxclient, Submaster, prof
 
from light9 import dmxclient, Submaster, prof, showconfig
 
from light9.Patch import get_channel_name
 
from light9.uihelpers import toplevelat
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9.rdfdb import clientsession
 
from light9.tkdnd import initTkdnd
 
from light9.namespaces import L9
 
@@ -68,26 +68,88 @@ class Subcomposer(tk.Frame):
 
    """
 
    def __init__(self, master, graph, session):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.graph = graph
 
        self.session = session
 

	
 
        # this is a URIRef or Local
 
        # this is a URIRef or Local. Strangely, the Local case still
 
        # has a uri, which you can get from
 
        # self.currentSub.uri. Probably that should be on the Local
 
        # object too, or maybe Local should be a subclass of URIRef
 
        self._currentChoice = Observable(Local)
 

	
 
        # this is a PersistentSubmaster (even for local)
 
        self.currentSub = Observable(Submaster.PersistentSubmaster(
 
                    graph, self.makeLocal()))
 

	
 
        def pc(val):
 
            log.info("change viewed sub to %s", val)
 
        self._currentChoice.subscribe(pc)
 

	
 
        EditChoice(self, self.graph, self._currentChoice).frame.pack(side='top')
 
        ec = self.editChoice = EditChoice(self, self.graph, self._currentChoice)
 
        ec.frame.pack(side='top')
 

	
 
        ec.subIcon.bind("<ButtonPress-1>", self.clickSubIcon)
 
        self.setupSubChoiceLinks()
 
        self.setupLevelboxUi()
 

	
 
    def clickSubIcon(self, *args):
 
        box = tk.Toplevel(self.editChoice.frame)
 
        box.wm_transient(self.editChoice.frame)
 
        tk.Label(box, text="Name this sub:").pack()
 
        e = tk.Entry(box)
 
        e.pack()
 
        b = tk.Button(box, text="Make global")
 
        b.pack()
 
        def clicked(*args):
 
            self.makeGlobal(newName=e.get())
 
            box.destroy()
 
            
 
        b.bind("<Button-1>", clicked)
 
        e.focus()
 
        
 
    def makeGlobal(self, newName):
 
        """promote our local submaster into a non-local, named one"""
 
        uri = self.currentSub().uri
 
        newUri = showconfig.showUri() + ("/sub/%s" %
 
                                         urllib.quote(newName, safe=''))
 
        with self.graph.currentState(
 
                tripleFilter=(uri, None, None)) as current:
 
            if (uri, RDF.type, L9['LocalSubmaster']) not in current:
 
                raise ValueError("%s is not a local submaster" % uri)
 
            if (newUri, None, None) in current:
 
                raise ValueError("new uri %s is in use" % newUri)
 
                
 
        # the local submaster was storing in ctx=self.session, but now
 
        # we want it to be in ctx=uri
 

	
 
        self.relocateSub(newUri, newName)
 

	
 
        # these are in separate patches for clarity as i'm debugging this
 
        self.graph.patch(Patch(addQuads=[
 
            (newUri, RDFS.label, Literal(newName), newUri),
 
        ], delQuads=[
 
            (newUri, RDF.type, L9['LocalSubmaster'], newUri),
 
                           ]))
 
        self.graph.patchObject(self.session,
 
                               self.session, L9['currentSub'], newUri)
 
        
 
    def relocateSub(self, newUri, newName):
 
        # maybe this goes in Submaster
 
        uri = self.currentSub().uri
 

	
 
        def repl(u):
 
            if u == uri:
 
                return newUri
 
            return u
 
        delQuads = self.currentSub().allQuads()
 
        addQuads = [(repl(s), p, repl(o), newUri) for s,p,o,c in delQuads]
 
        # patch can't span contexts yet
 
        self.graph.patch(Patch(addQuads=addQuads, delQuads=[]))
 
        #self.graph.patch(Patch(addQuads=[], delQuads=delQuads))
 
        
 
        
 
    def setupSubChoiceLinks(self):
 
        graph = self.graph
 
        def ann():
 
            print "currently: session=%s currentSub=%r _currentChoice=%r" % (
 
                self.session, self.currentSub(), self._currentChoice())
 
@@ -130,13 +192,15 @@ class Subcomposer(tk.Frame):
 

	
 
    def levelsChanged(self, sub):
 
        if sub == self.currentSub():
 
            self.sendupdate()
 
        
 
    def makeLocal(self):
 
        # todo: put a type on this, so subChanged can identify it right
 
        """
 
        change our display to a local submaster
 
        """
 
        # todo: where will these get stored, or are they local to this
 
        # subcomposer process and don't use PersistentSubmaster at all?
 

	
 
        new = URIRef("http://local/%s" % time.time())
 
        self.graph.patch(Patch(addQuads=[
 
            (new, RDF.type, L9['Submaster'], self.session),
light9/Submaster.py
Show inline comments
 
@@ -184,12 +184,14 @@ class PersistentSubmaster(Submaster):
 
    def _saveContext(self):
 
        """the context in which we should save all the lightLevel triples for
 
        this sub"""
 
        typeStmt = (self.uri, RDF.type, L9['Submaster'])
 
        with self.graph.currentState(tripleFilter=typeStmt) as current:
 
            try:
 
                log.debug("submaster's type statement is in %r" %
 
                          list(current.contextsForStatement(typeStmt)))
 
                ctx = current.contextsForStatement(typeStmt)[0]
 
            except IndexError:
 
                log.info("declaring %s to be a submaster" % self.uri)
 
                ctx = self.uri
 
                self.graph.patch(Patch(addQuads=[
 
                    (self.uri, RDF.type, L9['Submaster'], ctx),
 
@@ -209,12 +211,23 @@ class PersistentSubmaster(Submaster):
 
        """set all levels to 0"""
 
        with self.graph.currentState() as g:
 
            levs = list(g.objects(self.uri, L9['lightLevel']))
 
        for lev in levs:
 
            self.graph.removeMappingNode(self._saveContext(), lev)
 

	
 
    def allQuads(self):
 
        """all the quads for this sub"""
 
        quads = []
 
        with self.graph.currentState() as current:
 
            quads.extend(current.quads((self.uri, None, None)))
 
            for s,p,o,c in quads:
 
                if p == L9['lightLevel']:
 
                    quads.extend(current.quads((o, None, None)))
 
        return quads
 

	
 

	
 
    def save(self):
 
        raise NotImplementedError("obsolete?")
 
        if self.temporary:
 
            log.info("not saving temporary sub named %s",self.name)
 
            return
 

	
light9/editchoice.py
Show inline comments
 
@@ -4,13 +4,13 @@ from light9.tkdnd import dragSourceRegis
 

	
 
class Local(object):
 
    """placeholder for the local uri that EditChoice does not
 
    manage. Set resourceObservable to Local to indicate that you're
 
    unlinked"""
 

	
 
class EditChoice(tk.Frame):
 
class EditChoice(object):
 
    """
 
    Observable <-> linker UI
 

	
 
    widget for tying some UI to a shared resource for editing, or
 
    unlinking it (which means associating it with a local resource
 
    that's not named or shared). This object does not own the choice
light9/rdfdb/graphfile.py
Show inline comments
 
@@ -17,25 +17,28 @@ class GraphFile(object):
 
        """
 
        this does not include an initial reread() call
 
        """
 
        self.path, self.uri = path, uri
 
        self.patch, self.getSubgraph = patch, getSubgraph
 

	
 
        self.lastWriteTimestamp = 0 # mtime from the last time _we_ wrote
 

	
 
        if not os.path.exists(path):
 
            # can't start notify until file exists
 
            try:
 
                os.makedirs(os.path.dirname(path))
 
            except OSError:
 
                pass
 
            f = open(path, "w")
 
            f.close()
 
            iolog.info("created %s", path)
 
            iolog.info("%s created", path)
 
            self.lastWriteTimestamp = os.path.getmtime(path)
 

	
 

	
 
        self.flushDelay = 2 # seconds until we have to call flush() when dirty
 
        self.writeCall = None # or DelayedCall
 
        self.lastWriteTimestamp = 0 # mtime from the last time _we_ wrote
 

	
 
        # emacs save comes in as IN_MOVE_SELF, maybe
 
        
 
        # I was hoping not to watch IN_CHANGED and get lots of
 
        # half-written files, but emacs doesn't close its files after
 
        # a write, so there's no other event. I could try to sleep
 
@@ -46,25 +49,31 @@ class GraphFile(object):
 
        from twisted.internet.inotify import IN_CLOSE_WRITE, IN_MOVED_FROM, IN_MODIFY, IN_DELETE, IN_DELETE_SELF, IN_CHANGED
 

	
 
        notifier.watch(FilePath(path), callbacks=[self.notify])
 
      
 
    def notify(self, notifier, filepath, mask):
 
        maskNames = humanReadableMask(mask)
 
        if maskNames[0] in ['open', 'access', 'close_nowrite', 'attrib', 'delete_self']:
 
            log.debug("file %s changed, ignoring %s" % (filepath, maskNames))
 
        if maskNames[0] == 'delete_self':
 
            log.warn("%s delete_self event: need to dump the stmts from "
 
                     "this file", filepath)
 
            # this is happening surprisingly often, even to files that
 
            # are still there
 
            return
 
        if maskNames[0] in ['open', 'access', 'close_nowrite', 'attrib']:
 
            log.debug("%s %s event, ignoring" % (filepath, maskNames))
 
            return
 

	
 
        try:
 
            if filepath.getModificationTime() == self.lastWriteTimestamp:
 
                log.debug("file %s changed, but we did this write", filepath)
 
                log.debug("%s changed, but we did this write", filepath)
 
                return
 
        except OSError as e:
 
            log.error("watched file %s: %r" % (filepath, e))
 
            log.error("%s: %r" % (filepath, e))
 
            return
 
            
 
        log.info("file %s changed (%s)", filepath, maskNames)
 
        log.info("%s needs reread because of %s event", filepath, maskNames)
 
        try:
 
            self.reread()
 
        except Exception:
 
            traceback.print_exc()
 

	
 
    def reread(self):
 
@@ -76,28 +85,29 @@ class GraphFile(object):
 
        new = Graph()
 
        try:
 
            new.parse(location=self.path, format='n3')
 
        except SyntaxError as e:
 
            print e
 
            traceback.print_exc()
 
            log.error("syntax error in %s" % self.path)
 
            log.error("%s syntax error", self.path)
 
            return
 
        except IOError as e:
 
            log.error("rereading %s: %r" % (self.uri, e))
 
            log.error("%s rereading %s: %r", self.path, self.uri, e)
 
            return
 

	
 
        old = inContext(old, self.uri)
 
        new = inContext(new, self.uri)
 

	
 
        p = Patch.fromDiff(old, new)
 
        if p:
 
            log.debug("%s applying patch for changes in file", self.path)
 
            self.patch(p, dueToFileChange=True)
 

	
 
    def dirty(self, graph):
 
        """
 
        there are new contents for our file
 
        there are new contents to write to our file
 
        
 
        graph is the rdflib.Graph that contains the contents of the
 
        file. It is allowed to change. Note that dirty() will probably
 
        do the save later when the graph might be different.
 
        
 
        after a timer has passed, write it out. Any scheduling issues
 
@@ -105,13 +115,13 @@ class GraphFile(object):
 
        huge, and then we might want to take a hint from a client that
 
        it's a good time to save the files that it was editing, like
 
        when the mouse moves out of the client's window and might be
 
        going towards a text file editor
 
        
 
        """
 
        log.info("%s dirty, %s stmt" % (self.uri, len(graph)))
 
        log.info("%s dirty, needs write", self.path)
 

	
 
        self.graphToWrite = graph
 
        if self.writeCall:
 
            self.writeCall.reset(self.flushDelay)
 
        else:
 
            self.writeCall = reactor.callLater(self.flushDelay, self.flush)
 
@@ -124,8 +134,8 @@ class GraphFile(object):
 
        t1 = time.time()
 
        self.graphToWrite.serialize(destination=f, format='n3')
 
        serializeTime = time.time() - t1
 
        f.close()
 
        self.lastWriteTimestamp = os.path.getmtime(tmpOut)
 
        os.rename(tmpOut, self.path)
 
        iolog.info("rewrote %s in %.1f ms", self.path, serializeTime * 1000)
 
        iolog.info("%s rewrote in %.1f ms", self.path, serializeTime * 1000)
 
        
0 comments (0 inline, 0 general)