comparison lib/patchablegraph/patchablegraph.py @ 712:d98c3ffe7144

new graph output for browsers, with autorefresh Ignore-this: e4ff4dbed311b238d90988a1891ef640
author drewp@bigasterisk.com
date Mon, 03 Feb 2020 23:47:23 -0800
parents e404420a2343
children 58f1877780b9
comparison
equal deleted inserted replaced
711:e404420a2343 712:d98c3ffe7144
18 * https://www.w3.org/2009/12/rdf-ws/papers/ws07 Supporting Change Propagation in RDF 18 * https://www.w3.org/2009/12/rdf-ws/papers/ws07 Supporting Change Propagation in RDF
19 * https://www.w3.org/DesignIssues/lncs04/Diff.pdf Delta: an ontology for the distribution of 19 * https://www.w3.org/DesignIssues/lncs04/Diff.pdf Delta: an ontology for the distribution of
20 differences between RDF graphs 20 differences between RDF graphs
21 21
22 """ 22 """
23 import json, logging, itertools 23 import json, logging, itertools, html
24 24
25 from greplin import scales 25 from greplin import scales
26 from rdfdb.grapheditapi import GraphEditApi 26 from rdfdb.grapheditapi import GraphEditApi
27 from rdflib import ConjunctiveGraph 27 from rdflib import ConjunctiveGraph
28 from rdflib.namespace import NamespaceManager
28 from rdflib.parser import StringInputSource 29 from rdflib.parser import StringInputSource
29 from rdflib_jsonld.serializer import from_rdf 30 from rdflib_jsonld.serializer import from_rdf
30 import cyclone.sse 31 import cyclone.sse
31
32 from cycloneerr import PrettyErrorHandler 32 from cycloneerr import PrettyErrorHandler
33 from rdfdb.patch import Patch 33 from rdfdb.patch import Patch
34 from rdfdb.rdflibpatch import patchQuads, inGraph 34 from rdfdb.rdflibpatch import patchQuads, inGraph
35 35
36 log = logging.getLogger('patchablegraph') 36 log = logging.getLogger('patchablegraph')
37
38 def _writeGraphForBrowser(req, graph):
39 # We think this is a browser, so respond with a live graph view
40 # (todo)
41 req.set_header('Content-type', 'text/plain')
42 lines = graph.serialize(None, format='nquads').splitlines()
43 lines.sort()
44 req.write(b'\n'.join(lines))
45
46
47 def _writeGraphResponse(req, graph, acceptHeader: str):
48 if acceptHeader == 'application/nquads':
49 req.set_header('Content-type', 'application/nquads')
50 graph.serialize(req, format='nquads')
51 elif acceptHeader == 'application/ld+json':
52 req.set_header('Content-type', 'application/ld+json')
53 graph.serialize(req, format='json-ld', indent=2)
54 else:
55 if acceptHeader.startswith('text/html'):
56 _writeGraphForBrowser(req, graph)
57 return
58 req.set_header('Content-type', 'application/x-trig')
59 graph.serialize(req, format='trig')
60 37
61 # forked from /my/proj/light9/light9/rdfdb/rdflibpatch.py 38 # forked from /my/proj/light9/light9/rdfdb/rdflibpatch.py
62 def _graphFromQuads2(q): 39 def _graphFromQuads2(q):
63 g = ConjunctiveGraph() 40 g = ConjunctiveGraph()
64 #g.addN(q) # no effect on nquad output 41 #g.addN(q) # no effect on nquad output
144 121
145 _sendSimpleGraph = scales.PmfStat('serve/simpleGraph') 122 _sendSimpleGraph = scales.PmfStat('serve/simpleGraph')
146 _sendFullGraph = scales.PmfStat('serve/events/sendFull') 123 _sendFullGraph = scales.PmfStat('serve/events/sendFull')
147 _sendPatch = scales.PmfStat('serve/events/sendPatch') 124 _sendPatch = scales.PmfStat('serve/events/sendPatch')
148 125
126
149 class CycloneGraphHandler(PrettyErrorHandler, cyclone.web.RequestHandler): 127 class CycloneGraphHandler(PrettyErrorHandler, cyclone.web.RequestHandler):
150 def initialize(self, masterGraph): 128 def initialize(self, masterGraph: PatchableGraph):
151 self.masterGraph = masterGraph 129 self.masterGraph = masterGraph
152 130
153 def get(self): 131 def get(self):
154 with self.masterGraph._sendSimpleGraph.time(): 132 with self.masterGraph._sendSimpleGraph.time():
155 _writeGraphResponse(self, self.masterGraph, 133 self._writeGraphResponse()
156 self.request.headers.get('accept', '')) 134
135 def _writeGraphResponse(self):
136 acceptHeader = self.request.headers.get(
137 'Accept',
138 # see https://github.com/fiorix/cyclone/issues/20
139 self.request.headers.get('accept', ''))
140
141 if acceptHeader == 'application/nquads':
142 self.set_header('Content-type', 'application/nquads')
143 self.masterGraph.serialize(self, format='nquads')
144 elif acceptHeader == 'application/ld+json':
145 self.set_header('Content-type', 'application/ld+json')
146 self.masterGraph.serialize(self, format='json-ld', indent=2)
147 else:
148 if acceptHeader.startswith('text/html'):
149 self._writeGraphForBrowser()
150 return
151 self.set_header('Content-type', 'application/x-trig')
152 self.masterGraph.serialize(self, format='trig')
153
154 def _writeGraphForBrowser(self):
155 # We think this is a browser, so respond with a live graph view
156 # (todo)
157 self.set_header('Content-type', 'text/html')
158
159 self.write(b'''
160 <html><body><pre>''')
161
162 ns = NamespaceManager(self.masterGraph._graph)
163 # maybe these could be on the PatchableGraph instance
164 ns.bind('ex', 'http://example.com/')
165 ns.bind('', 'http://projects.bigasterisk.com/room/')
166 ns.bind("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
167 ns.bind("xsd", "http://www.w3.org/2001/XMLSchema#")
168
169 for s, p, o, g in sorted(self.masterGraph._graph.quads()):
170 g = g.identifier
171 nquadLine = f'{s.n3(ns)} {p.n3(ns)} {o.n3(ns)} {g.n3(ns)} .\n'
172 self.write(html.escape(nquadLine).encode('utf8'))
173
174 self.write(b'''
175 </pre>
176 <p>
177 <a href="#">[refresh]</a>
178 <label><input type="checkbox"> Auto-refresh</label>
179 </p>
180 <script>
181
182 if (new URL(window.location).searchParams.get('autorefresh') == 'on') {
183 document.querySelector("input").checked = true;
184 setTimeout(() => {
185 requestAnimationFrame(() => {
186 window.location.replace(window.location.href);
187 });
188 }, 2000);
189 }
190
191 document.querySelector("a").addEventListener("click", (ev) => {
192 ev.preventDefault();
193 window.location.replace(window.location.href);
194
195 });
196 document.querySelector("input").addEventListener("change", (ev) => {
197 if (document.querySelector("input").checked) {
198 const u = new URL(window.location);
199 u.searchParams.set('autorefresh', 'on');
200 window.location.replace(u.href);
201 } else {
202 const u = new URL(window.location);
203 u.searchParams.delete('autorefresh');
204 window.location.replace(u.href);
205 }
206 });
207
208 </script>
209 </body></html>
210 ''')
157 211
158 212
159 class CycloneGraphEventsHandler(cyclone.sse.SSEHandler): 213 class CycloneGraphEventsHandler(cyclone.sse.SSEHandler):
160 """ 214 """
161 One session with one client. 215 One session with one client.