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
 
@@ -104,15 +104,16 @@ Our web ui:
 

	
 
    graph contents. plain rdf browser like an outliner or
 
    something. clicking any resource from the other displays takes you
 
    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
 
sys.path.append(".")
 
from light9 import networking, showconfig, prof
 
from rdflib import ConjunctiveGraph, URIRef, Graph
 
@@ -126,35 +127,56 @@ from light9.rdfdb.patchreceiver import m
 
from twisted.internet.inotify import INotify
 
from run_local import log
 
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=[]))
 
    
 

	
 
class Client(object):
 
    """
 
    one of our syncedgraph clients
 
    """
 
    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.
 

	
 
    This object watches directories. Each GraphFile watches its own file.
 
    """
 
@@ -301,14 +323,14 @@ class Db(object):
 
                # this client has self-applied the patch already
 
                continue
 
            d = c.sendPatch(p)
 
            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()
 

	
 
    def summarizeToLog(self):
 
        log.info("contexts in graph (%s total stmts):" % len(self.graph))
 
        for c in self.graph.contexts():
 
@@ -332,20 +354,20 @@ class Db(object):
 
        return g
 

	
 
    def addClient(self, newClient):
 
        [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):
 
    def get(self):
 
        accept = self.request.headers.get('accept', '')
 
        format = 'n3'
 
@@ -373,12 +395,34 @@ class GraphClients(PrettyErrorHandler, c
 
        try:
 
            self.settings.db.addClient(Client(upd, self.get_argument("label")))
 
        except:
 
            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):
 
    j = asJson or json.dumps(d)
 
    for c in liveClients:
 
        c.sendMessage(j)
 
@@ -424,12 +468,13 @@ if __name__ == "__main__":
 

	
 
    reactor.listenTCP(networking.rdfdb.port, cyclone.web.Application(handlers=[
 
        (r'/live', Live),
 
        (r'/graph', GraphResource),
 
        (r'/patches', Patches),
 
        (r'/graphClients', GraphClients),
 
        (r'/syncedGraph', WebsocketClient),
 

	
 
        (r'/(.*)', NoExts,
 
         {"path" : "light9/rdfdb/web",
 
          "default_filename" : "index.html"}),
 

	
 
        ], debug=True, db=db))
light9/rdfdb/web/syncedgraph.js
Show inline comments
 
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;
 

	
 
    
 

	
 
    self.patch = function (p) {
light9/web/graph.coffee
Show inline comments
 
@@ -37,17 +37,56 @@ class GraphWatchers
 

	
 

	
 
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)
 
        
 
  Uri: (curie) ->
 
    N3.Util.expandPrefixedName(curie, @graph._prefixes)
 

	
 
@@ -84,12 +123,13 @@ class window.SyncedGraph
 
    # 
 
    # This is the only method that writes to the graph!
 
    for quad in patch.delQuads
 
      @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) ->
 
    # send a patch which removes existing values for (s,p,*,c) and
 
    # adds (s,p,newObject,c). Values in other graphs are not affected.
 
    existing = @graph.findByIRI(s, p, null, 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",
 
    "iron-ajax": "PolymerElements/iron-ajax#~1.2.0",
 
    "jquery": "~2.2.4",
 
    "underscore": "~1.8.3",
 
@@ -11,13 +11,14 @@
 
    "knockout": "knockoutjs#^3.4.0",
 
    "sylvester": "~0.1.3",
 
    "d3": "https://github.com/d3/d3.git#e7194db33090a0afc06c77a959594361ffb949df",
 
    "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",
 
    "rdflib.js": "920e59fe37",
 
    "d3": "e7194db33090a0afc06c77a959594361ffb949df"
 
  }
light9/web/rdfdb-synced-graph.html
Show inline comments
 
@@ -10,27 +10,15 @@
 
   Polymer({
 
       is: "rdfdb-synced-graph",
 
       properties: {
 
           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>
 
</dom-module>
light9/web/timeline-elements.html
Show inline comments
 
@@ -304,11 +304,12 @@
 
   });
 
  </script>
 
</dom-module>
 

	
 
<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
 
@@ -128,13 +128,13 @@ Polymer
 
      if @songTime < zs.t1() or @songTime > zs.t2() - visSeconds * .6
 
        newCenter = @songTime + margin
 
        @animatedZoom(newCenter - visSeconds / 2,
 
                      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)
 
      adjs.push(new AdjustableFloatObject({
 
        graph: @graph
 
        subj: subj
 
@@ -222,17 +222,20 @@ Polymer
 
    graph: { type: Object, notify: true }
 
    zoomInX: { type: Object, notify: true, observer: '_onIronResize' }
 
  ready: ->
 
    @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"
 
  properties:
 
    adjs: { type: Array },
 
    dia: { type: Object }
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)