Mercurial > code > home > repos > homeauto
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. |