Changeset - 5c54a1f94050
[Not reviewed]
default
0 7 1
Drew Perttula - 9 years ago 2016-06-05 02:25:31
drewp@bigasterisk.com
browser SyncedGraph client connects and receives patches
Ignore-this: 7927b3d0605e34067ab2394a7a5ad385
8 files changed with 123 insertions and 32 deletions:
0 comments (0 inline, 0 general)
bin/rdfdb
Show inline comments
 
@@ -107,9 +107,10 @@ Our web ui:
 
    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 @@ log.setLevel(logging.DEBUG)
 

	
 
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 @@ class Client(object):
 
    """
 
    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 @@ class Db(object):
 
            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 @@ class Db(object):
 
        [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 @@ class GraphClients(PrettyErrorHandler, c
 
            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 @@ if __name__ == "__main__":
 
        (r'/graph', GraphResource),
 
        (r'/patches', Patches),
 
        (r'/graphClients', GraphClients),
 
        (r'/syncedGraph', WebsocketClient),
 

	
 
        (r'/(.*)', NoExts,
 
         {"path" : "light9/rdfdb/web",
light9/rdfdb/web/syncedgraph.js
Show inline comments
 
@@ -3,6 +3,8 @@ function SyncedGraph(label) {
 
      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;
 

	
light9/web/graph.coffee
Show inline comments
 
@@ -40,11 +40,50 @@ class window.SyncedGraph
 
  # 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 @@ class window.SyncedGraph
 
      @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) ->
light9/web/lib/bower.json
Show inline comments
 
{
 
  "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",
light9/web/rdfdb-synced-graph.html
Show inline comments
 
@@ -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>
light9/web/timeline-elements.html
Show inline comments
 
@@ -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>
light9/web/timeline.coffee
Show inline comments
 
@@ -131,7 +131,7 @@ Polymer
 
                      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 @@ Polymer
 
    @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"
show/dance2016/song1.n3
Show inline comments
 
new file 100644
 
@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 .
0 comments (0 inline, 0 general)