changeset 924:dcab422615ca

working on local->global sub action. creation of new file in rdfdb is still a problem Ignore-this: 28a0b0754f5fca6bef118f7e14758053
author drewp@bigasterisk.com
date Tue, 11 Jun 2013 05:48:10 +0000
parents 8b95d2865643
children e668f93460d4
files bin/rdfdb bin/subcomposer light9/Submaster.py light9/editchoice.py light9/rdfdb/graphfile.py
diffstat 5 files changed, 125 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/bin/rdfdb	Tue Jun 11 00:02:35 2013 +0000
+++ b/bin/rdfdb	Tue Jun 11 05:48:10 2013 +0000
@@ -196,6 +196,7 @@
 
     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):
@@ -238,8 +239,23 @@
         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.
 
@@ -251,12 +267,6 @@
         """
         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):
@@ -301,6 +311,9 @@
         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)
 
--- a/bin/subcomposer	Tue Jun 11 00:02:35 2013 +0000
+++ b/bin/subcomposer	Tue Jun 11 05:48:10 2013 +0000
@@ -18,17 +18,17 @@
 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
@@ -71,7 +71,10 @@
         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)
@@ -82,9 +85,68 @@
             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
@@ -133,7 +195,9 @@
             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?
 
--- a/light9/Submaster.py	Tue Jun 11 00:02:35 2013 +0000
+++ b/light9/Submaster.py	Tue Jun 11 05:48:10 2013 +0000
@@ -187,6 +187,8 @@
         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)
@@ -212,6 +214,17 @@
         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:
--- a/light9/editchoice.py	Tue Jun 11 00:02:35 2013 +0000
+++ b/light9/editchoice.py	Tue Jun 11 05:48:10 2013 +0000
@@ -7,7 +7,7 @@
     manage. Set resourceObservable to Local to indicate that you're
     unlinked"""
 
-class EditChoice(tk.Frame):
+class EditChoice(object):
     """
     Observable <-> linker UI
 
--- a/light9/rdfdb/graphfile.py	Tue Jun 11 00:02:35 2013 +0000
+++ b/light9/rdfdb/graphfile.py	Tue Jun 11 05:48:10 2013 +0000
@@ -20,6 +20,8 @@
         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:
@@ -28,11 +30,12 @@
                 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
         
@@ -49,19 +52,25 @@
       
     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:
@@ -79,10 +88,10 @@
         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)
@@ -90,11 +99,12 @@
 
         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
@@ -108,7 +118,7 @@
         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:
@@ -127,5 +137,5 @@
         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)