changeset 1347:5c54a1f94050

browser SyncedGraph client connects and receives patches Ignore-this: 7927b3d0605e34067ab2394a7a5ad385
author Drew Perttula <drewp@bigasterisk.com>
date Sun, 05 Jun 2016 02:25:31 +0000
parents 2809a8b732f6
children 9ea0dbe3c8b4
files bin/rdfdb light9/rdfdb/web/syncedgraph.js light9/web/graph.coffee light9/web/lib/bower.json light9/web/rdfdb-synced-graph.html light9/web/timeline-elements.html light9/web/timeline.coffee show/dance2016/song1.n3
diffstat 8 files changed, 123 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/bin/rdfdb	Sun Jun 05 02:04:12 2016 +0000
+++ b/bin/rdfdb	Sun Jun 05 02:25:31 2016 +0000
@@ -107,9 +107,10 @@
     to this, focused on that resource
 
 """
-from twisted.internet import reactor
+from twisted.internet import reactor, defer
 import twisted.internet.error
 from twisted.python.filepath import FilePath
+from twisted.python.failure import Failure
 from twisted.internet.inotify import humanReadableMask, IN_CREATE
 import sys, optparse, logging, json, os
 import cyclone.web, cyclone.httpclient, cyclone.websocket
@@ -129,10 +130,12 @@
 
 from lib.cycloneerr import PrettyErrorHandler
 
+class WebsocketDisconnect(ValueError):
+    pass
+
 def sendGraphToClient(graph, client):
     """send the client the whole graph contents"""
-    log.info("sending all graphs to %s at %s" %
-             (client.label, client.updateUri))
+    log.info("sending all graphs to %r" % client)
     client.sendPatch(Patch(
         addQuads=graph.quads(ALLSTMTS),
         delQuads=[]))
@@ -144,14 +147,33 @@
     """
     def __init__(self, updateUri, label):
         self.label = label
+        # todo: updateUri is used publicly to compare clients. Replace
+        # it with Client.__eq__ so WsClient doesn't have to fake an
+        # updateUri.
         self.updateUri = updateUri
 
     def __repr__(self):
         return "<%s client at %s>" % (self.label, self.updateUri)
 
     def sendPatch(self, p):
+        """
+        returns deferred. error will be interpreted as the client being
+        broken.
+        """
         return sendPatch(self.updateUri, p)
+        
+class WsClient(object):
+    def __init__(self, connectionId, sendMessage):
+        self.updateUri = connectionId
+        self.sendMessage = sendMessage
 
+    def __repr__(self):
+        return "<WsClient %s>" % self.updateUri
+
+    def sendPatch(self, p):
+        self.sendMessage(p.makeJsonRepr())
+        return defer.succeed(None)
+        
 class WatchedFiles(object):
     """
     find files, notice new files.
@@ -304,8 +326,8 @@
             d.addErrback(self.clientErrored, c)
         
     def clientErrored(self, err, c):
-        err.trap(twisted.internet.error.ConnectError)
-        log.info("connection error- dropping client %r" % c)
+        err.trap(twisted.internet.error.ConnectError, WebsocketDisconnect)
+        log.info("%r %r - dropping client", c, err.getErrorMessage())
         self.clients.remove(c)
         self.sendClientsToAllLivePages()
 
@@ -335,14 +357,14 @@
         [self.clients.remove(c)
          for c in self.clients if c.updateUri == newClient.updateUri]
 
-        log.info("new client %s at %s" % (newClient.label, newClient.updateUri))
+        log.info("new client %r" % newClient)
         sendGraphToClient(self.graph, newClient)
         self.clients.append(newClient)
         self.sendClientsToAllLivePages()
 
     def sendClientsToAllLivePages(self):
         sendToLiveClients({"clients":[
-            dict(updateUri=c.updateUri, label=c.label)
+            dict(updateUri=c.updateUri, label=repr(c))
             for c in self.clients]})
 
 class GraphResource(PrettyErrorHandler, cyclone.web.RequestHandler):
@@ -376,6 +398,28 @@
             import traceback
             traceback.print_exc()
             raise
+            
+_wsClientSerial = 0
+class WebsocketClient(cyclone.websocket.WebSocketHandler):
+
+    def connectionMade(self, *args, **kwargs):
+        global _wsClientSerial
+        self.connectionId = 'connection-%s' % _wsClientSerial
+        log.info("new ws client %r", self.connectionId)
+        _wsClientSerial += 1
+
+        self.wsClient = WsClient(self.connectionId, self.sendMessage)
+        self.settings.db.addClient(self.wsClient)
+
+    def connectionLost(self, reason):
+        log.info("bye ws client %r", self.connectionId)
+        self.settings.db.clientErrored(
+            Failure(WebsocketDisconnect(reason)), self.wsClient)
+
+    def messageReceived(self, message):
+        log.info("got message from %s: %s", self.connectionId, message)
+        # how
+        self.sendMessage(message)
 
 liveClients = set()
 def sendToLiveClients(d=None, asJson=None):
@@ -427,6 +471,7 @@
         (r'/graph', GraphResource),
         (r'/patches', Patches),
         (r'/graphClients', GraphClients),
+        (r'/syncedGraph', WebsocketClient),
 
         (r'/(.*)', NoExts,
          {"path" : "light9/rdfdb/web",
--- a/light9/rdfdb/web/syncedgraph.js	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/rdfdb/web/syncedgraph.js	Sun Jun 05 02:25:31 2016 +0000
@@ -3,6 +3,8 @@
       like python SyncedGraph but talks over a websocket to
       rdfdb. This one has an API more conducive to reading and
       querying.
+
+      light9/web/graph.coffee is the newer attempt
     */
     var self = this;
 
--- a/light9/web/graph.coffee	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/web/graph.coffee	Sun Jun 05 02:25:31 2016 +0000
@@ -40,11 +40,50 @@
   # Note that applyPatch is the only method to write to the graph, so
   # it can fire subscriptions.
 
-  constructor: (patchSenderUrl, prefixes) ->
+  constructor: (@patchSenderUrl, prefixes) ->
     @graph = N3.Store()
     @_addPrefixes(prefixes)
     @_watchers = new GraphWatchers()
+    @newConnection()
 
+  newConnection: ->
+    fullUrl = 'ws://' + window.location.host + @patchSenderUrl
+    @ws = new WebSocket(fullUrl)
+
+    @ws.onopen = =>
+      log('connected to', fullUrl)
+
+    @ws.onerror = (e) =>
+      log('ws error ' + e)
+
+    @ws.onclose = =>
+      log('ws close')
+
+    @ws.onmessage = (evt) =>
+      @onMessage(JSON.parse(evt.data))
+
+  onMessage: (msg) ->
+    log('from rdfdb: ', msg)
+    
+    patch = {delQuads: [], addQuads: []}
+
+    parseAdds = (cb) =>
+      parser = N3.Parser()
+      parser.parse msg.patch.adds, (error, quad, prefixes) =>
+                    if (quad)
+                      patch.addQuads.push(quad)
+                    else
+                      cb()
+    parseDels = (cb) =>
+      parser = N3.Parser()
+      parser.parse msg.patch.deletes, (error, quad, prefixes) =>
+                    if (quad)
+                      patch.delQuads.push(quad)
+                    else
+                      cb()
+      
+    async.parallel([parseAdds, parseDels], ((err) => @applyPatch(patch)))
+      
   _addPrefixes: (prefixes) ->
     @graph.addPrefixes(prefixes)
         
@@ -87,6 +126,7 @@
       @graph.removeTriple(quad)
     for quad in patch.addQuads
       @graph.addTriple(quad)
+    log('applied patch -' + patch.delQuads.length + ' +' + patch.addQuads.length)
     @_watchers.graphChanged(patch)
 
   getObjectPatch: (s, p, newObject, g) ->
--- a/light9/web/lib/bower.json	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/web/lib/bower.json	Sun Jun 05 02:25:31 2016 +0000
@@ -1,5 +1,5 @@
 {
-  "name": "3rd-party polymer elements",
+  "name": "3rd-party libs",
   "dependencies": {
     "polymer": "~1.4.0",
     "paper-slider": "PolymerElements/paper-slider#~1.0.11",
@@ -14,7 +14,8 @@
     "rdflib.js": "https://github.com/linkeddata/rdflib.js.git#920e59fe37",
     "rdfstore": "https://github.com/antoniogarrote/rdfstore-js.git#b3f7c0c9c1da9b26261af0d4858722fa982411bb",
     "N3.js": "https://github.com/RubenVerborgh/N3.js.git#04f4e21f4ccb351587dc00a3f26340b28d4bb10f",
-    "shortcut": "http://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js"
+    "shortcut": "http://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js",
+    "async": "https://github.com/caolan/async.git#^1.5.2"
   },
   "resolutions": {
     "paper-styles": "^1.1.4",
--- a/light9/web/rdfdb-synced-graph.html	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/web/rdfdb-synced-graph.html	Sun Jun 05 02:25:31 2016 +0000
@@ -13,23 +13,11 @@
            graph: {type: Object, notify: true}
        },
        ready: function() {
-           this.graph = new SyncedGraph('noServerYet', {
+           this.graph = new SyncedGraph('/rdfdb/syncedGraph', {
                '': 'http://light9.bigasterisk.com/',
                'xsd': 'http://www.w3.org/2001/XMLSchema#',
            });
-           this.graph.loadTrig(
-               '          @prefix : <http://light9.bigasterisk.com/> .'+
-               '          @prefix dev: <http://light9.bigasterisk.com/device/> .'+
-               '          <http://example.com/> {'+
-               '            :demoResource0 :startTime 1; :endTime 120 .'+
-               '            :demoResource1 :startTime 13; :endTime 16 .'+
-               '            :demoResource2 :startTime 38; :endTime 60 .'+
-               '            :demoResource3 :startTime 56; :endTime 60 .'+
-               '            :demoResource4 :startTime 73; :endTime 74 .'+
-               '            :demoResource5 :startTime 91; :endTime 105 .'+
-               '            :demoResource6 :startTime 110; :endTime 120 .'+
-               '            :demoResource7 :startTime 133; :endTime 140 .'+
-               '          }');
+           window.graph = this.graph;
        }
    });
   </script>
--- a/light9/web/timeline-elements.html	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/web/timeline-elements.html	Sun Jun 05 02:25:31 2016 +0000
@@ -307,8 +307,9 @@
 
 <script src="/lib/sylvester/sylvester.js"></script>
 <script src="/lib/d3/build/d3.min.js"></script>
-<script src="/lib/N3.js/browser/n3-browser.min.js"></script>
-<script src="/lib/knockout/dist/knockout.js"></script>
+<script src="/lib/N3.js/browser/n3-browser.js"></script>
+<script src="/lib/knockout/dist/knockout.debug.js"></script>
 <script src="/lib/shortcut/index.js"></script>
+<script src="/lib/async/dist/async.js"></script>
 <script src="adjustable.js"></script>
 <script src="timeline.js"></script>
--- a/light9/web/timeline.coffee	Sun Jun 05 02:04:12 2016 +0000
+++ b/light9/web/timeline.coffee	Sun Jun 05 02:25:31 2016 +0000
@@ -131,7 +131,7 @@
                       newCenter + visSeconds / 2, zoomAnimSec)
 
   persistDemo: ->
-    ctx = @graph.Uri('http://example.com/')
+    ctx = @graph.Uri('http://light9.bigasterisk.com/show/dance2016/song1')
     adjs = []
     for n in [0..7]
       subj = @graph.Uri(':demoResource'+n)
@@ -225,11 +225,14 @@
     @graph.subscribe("http://light9.bigasterisk.com/demoResource6", null, null, @_onIronResize.bind(@))
   _onIronResize: ->
     return if !@zoomInX
-    subj = "http://light9.bigasterisk.com/demoResource6"
-    setNote(subj,
-            @zoomInX(@graph.floatValue(subj, @graph.Uri(':startTime'))),
-            @zoomInX(@graph.floatValue(subj, @graph.Uri(':endTime'))),
-            @offsetTop, @offsetTop + @offsetHeight)
+    try
+      subj = "http://light9.bigasterisk.com/demoResource6"
+      setNote(subj,
+              @zoomInX(@graph.floatValue(subj, @graph.Uri(':startTime'))),
+              @zoomInX(@graph.floatValue(subj, @graph.Uri(':endTime'))),
+              @offsetTop, @offsetTop + @offsetHeight)
+    catch e
+      log('during resize, ', e)
 
 Polymer
   is: "light9-timeline-adjusters"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/show/dance2016/song1.n3	Sun Jun 05 02:25:31 2016 +0000
@@ -0,0 +1,11 @@
+@prefix : <http://light9.bigasterisk.com/> .
+@prefix dev: <http://light9.bigasterisk.com/device/> .
+
+  :demoResource0 :startTime 1; :endTime 120.3 .
+  :demoResource1 :startTime 13; :endTime 16 .
+  :demoResource2 :startTime 38; :endTime 60 .
+  :demoResource3 :startTime 56; :endTime 60 .
+  :demoResource4 :startTime 73; :endTime 74 .
+  :demoResource5 :startTime 91.88; :endTime 105 .
+  :demoResource6 :startTime 110; :endTime 120 .
+  :demoResource7 :startTime 133; :endTime 140 .