# HG changeset patch
# User drewp@bigasterisk.com
# Date 2012-09-29 21:00:34
# Node ID 5ff9d9e264bb8f5021c74719b9bf578fb9232fa4
# Parent b19cd005a4916aab02a9769d2b6c451f2f29ba52
Patch.fromDiff and its usage in graphfile changes
Ignore-this: 650eaba5a71a5e838d14af5110b04e34
diff --git a/light9/rdfdb/graphfile.py b/light9/rdfdb/graphfile.py
--- a/light9/rdfdb/graphfile.py
+++ b/light9/rdfdb/graphfile.py
@@ -3,6 +3,7 @@ from twisted.python.filepath import File
from twisted.internet.inotify import IN_CLOSE_WRITE, IN_MOVED_FROM
from rdflib import Graph
from light9.rdfdb.patch import Patch
+from light9.rdfdb.rdflibpatch import inContext
log = logging.getLogger()
@@ -40,17 +41,48 @@ class GraphFile(object):
log.error("syntax error in %s" % self.path)
return
- adds = [(s, p, o, self.uri) for s, p, o in new - old]
- dels = [(s, p, o, self.uri) for s, p, o in old - new]
+ old = inContext(old, self.uri)
+ new = inContext(new, self.uri)
+ print "old %s new %s" % (old, new)
+
+ p = Patch.fromDiff(old, new)
+ if p:
+ self.patch(p, dueToFileChange=True)
- print "file dels"
- for s in dels:
- print s
- print "file adds"
- for s in adds:
- print s
- print ""
+ def dirty(self, graph):
+ """
+ there are new contents for 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
+ between files? i don't think so. the timer might be kind of
+ 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)))
+ self.graphToWrite = graph
+ if self.writeCall:
+ self.writeCall.reset(self.flushDelay)
+ else:
+ self.writeCall = reactor.callLater(self.flushDelay, self.flush)
+
+ def flush(self):
+ self.writeCall = None
+
+ tmpOut = self.path + ".rdfdb-temp"
+ f = open(tmpOut, 'w')
+ 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)
- if adds or dels:
- self.patch(Patch(addQuads=adds, delQuads=dels))
diff --git a/light9/rdfdb/patch.py b/light9/rdfdb/patch.py
--- a/light9/rdfdb/patch.py
+++ b/light9/rdfdb/patch.py
@@ -1,5 +1,5 @@
import json, unittest
-from rdflib import ConjunctiveGraph, URIRef
+from rdflib import ConjunctiveGraph, URIRef, URIRef as U
from light9.rdfdb.rdflibpatch import graphFromNQuad, graphFromQuads, serializeQuad
ALLSTMTS = (None, None, None)
@@ -28,6 +28,24 @@ class Patch(object):
if 'senderUpdateUri' in body:
self.senderUpdateUri = body['senderUpdateUri']
+ @classmethod
+ def fromDiff(cls, oldGraph, newGraph):
+ """
+ make a patch that changes oldGraph to newGraph
+ """
+ old = set(oldGraph.quads(ALLSTMTS))
+ new = set(newGraph.quads(ALLSTMTS))
+ return cls(addQuads=list(new - old), delQuads=list(old - new))
+
+ def __nonzero__(self):
+ """
+ does this patch do anything to a graph?
+ """
+ if self._jsonRepr and self._jsonRepr.strip():
+ raise NotImplementedError()
+ return bool(self._addQuads or self._delQuads or
+ self._addGraph or self._delGraph)
+
@property
def addQuads(self):
if self._addQuads is None:
@@ -97,3 +115,44 @@ class Patch(object):
else:
adds.add(q)
return Patch(delQuads=dels, addQuads=adds)
+
+ def getContext(self):
+ """assumes that all the edits are on the same context"""
+ ctx = None
+ for q in self.addQuads + self.delQuads:
+ if ctx is None:
+ ctx = q[3]
+
+ if ctx != q[3]:
+ raise ValueError("patch applies to multiple contexts, at least %r and %r" % (ctx, q[3]))
+ return ctx
+
+stmt1 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx1')
+
+class TestPatchFromDiff(unittest.TestCase):
+ def testEmpty(self):
+ g = ConjunctiveGraph()
+ p = Patch.fromDiff(g, g)
+ self.assert_(not p)
+
+ def testNonEmpty(self):
+ g1 = ConjunctiveGraph()
+ g2 = graphFromQuads([stmt1])
+ p = Patch.fromDiff(g1, g2)
+ self.assert_(p)
+
+ def testNoticesAdds(self):
+ g1 = ConjunctiveGraph()
+ g2 = graphFromQuads([stmt1])
+ p = Patch.fromDiff(g1, g2)
+ self.assertEqual(p.addQuads, [stmt1])
+ self.assertEqual(p.delQuads, [])
+
+ def testNoticesDels(self):
+ g1 = graphFromQuads([stmt1])
+ g2 = ConjunctiveGraph()
+ p = Patch.fromDiff(g1, g2)
+ self.assertEqual(p.addQuads, [])
+ self.assertEqual(p.delQuads, [stmt1])
+
+
diff --git a/light9/rdfdb/rdflibpatch.py b/light9/rdfdb/rdflibpatch.py
--- a/light9/rdfdb/rdflibpatch.py
+++ b/light9/rdfdb/rdflibpatch.py
@@ -67,6 +67,14 @@ def serializeQuad(g):
c.n3())
return out
+def inContext(graph, newContext):
+ """
+ make a ConjunctiveGraph where all the triples in the given graph
+ are in newContext
+ """
+ return graphFromQuads([(s,p,o,newContext) for s,p,o in graph])
+
+
class TestGraphFromQuads(unittest.TestCase):
nqOut = ' .\n'
def testSerializes(self):
@@ -82,7 +90,6 @@ class TestGraphFromQuads(unittest.TestCa
self.assertEqual(out.strip(), self.nqOut.strip())
-
stmt1 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx1')
stmt2 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx2')
class TestPatchQuads(unittest.TestCase):