Changeset - e29315086f9f
[Not reviewed]
default
0 5 0
Drew Perttula - 6 years ago 2019-06-01 05:49:17
drewp@bigasterisk.com
minor touchups to live & graph, looking at why reconnects are broken
Ignore-this: fa0b69132da3b44593bf8eb056315d7e
5 files changed with 21 insertions and 7 deletions:
0 comments (0 inline, 0 general)
light9/web/graph.coffee
Show inline comments
 
@@ -137,25 +137,25 @@ class AutoDependencies
 
      #log('push', s,p,o,g)
 
    #else
 
    #  console.trace('read outside runHandler')
 

	
 
class window.SyncedGraph
 
  # Main graph object for a browser to use. Syncs both ways with
 
  # rdfdb. Meant to hide the choice of RDF lib, so we can change it
 
  # later.
 
  #
 
  # Note that _applyPatch is the only method to write to the graph, so
 
  # it can fire subscriptions.
 

	
 
  constructor: (@patchSenderUrl, @prefixes, @setStatus) ->
 
  constructor: (@patchSenderUrl, @prefixes, @setStatus, @clearCb) ->
 
    # patchSenderUrl is the /syncedGraph path of an rdfdb server.
 
    # prefixes can be used in Uri(curie) calls.
 
    @_autoDeps = new AutoDependencies() # replaces GraphWatchers
 
    @clearGraph()
 

	
 
    if @patchSenderUrl
 
      @_client = new RdfDbClient(@patchSenderUrl,
 
                                 @_clearGraphOnNewConnection.bind(@),
 
                                 @_applyPatch.bind(@),
 
                                 @setStatus)
 
    
 
  clearGraph: ->
 
@@ -164,24 +164,25 @@ class window.SyncedGraph
 
      @_applyPatch({addQuads: [], delQuads: @graph.getQuads()})
 

	
 
    # if we had a Store already, this lets N3.Store free all its indices/etc
 
    @graph = N3.Store()
 
    @_addPrefixes(@prefixes)
 
    @cachedFloatValues = new Map() # s + '|' + p -> number
 
    @cachedUriValues = new Map() # s + '|' + p -> Uri
 

	
 
  _clearGraphOnNewConnection: -> # must not send a patch to the server!
 
    log('graph: clearGraphOnNewConnection')
 
    @clearGraph()
 
    log('graph: clearGraphOnNewConnection done')
 
    @clearCb() if @clearCb?
 
      
 
  _addPrefixes: (prefixes) ->
 
    for k in (prefixes or {})
 
      @prefixes[k] = prefixes[k]
 
    @prefixFuncs = N3.Util.prefixes(@prefixes)
 
        
 
  Uri: (curie) ->
 
    if not curie?
 
      throw new Error("no uri")
 
    if curie.match(/^http/)
 
      return N3.DataFactory.namedNode(curie)
 
    part = curie.split(':')
 
@@ -209,24 +210,27 @@ class window.SyncedGraph
 
      if (quad)
 
        patch.addQuads.push(quad)
 
      else
 
        @_applyPatch(patch)
 
        @_addPrefixes(prefixes)
 
        cb() if cb
 
                    
 
  quads: () -> # for debugging
 
    [q.subject, q.predicate, q.object, q.graph] for q in @graph.getQuads()
 

	
 
  applyAndSendPatch: (patch) ->
 
    console.time('applyAndSendPatch')
 
    if not @_client
 
      log('not connected-- dropping patch')
 
      return
 
    if !Array.isArray(patch.addQuads) || !Array.isArray(patch.delQuads)
 
      console.timeEnd('applyAndSendPatch')
 
      log('corrupt patch')
 
      throw new Error("corrupt patch: #{JSON.stringify(patch)}")
 

	
 
    @_validatePatch(patch)
 

	
 
    @_applyPatch(patch)
 
    @_client.sendPatch(patch) if @_client
 
    console.timeEnd('applyAndSendPatch')
 

	
 
  _validatePatch: (patch) ->
 
@@ -359,25 +363,25 @@ class window.SyncedGraph
 
          "list node #{current} has #{firsts.length} rdf:first edges")
 
      out.push(firsts[0].object)
 

	
 
      if rests.length != 1
 
        throw new Error(
 
          "list node #{current} has #{rests.length} rdf:rest edges")
 
      current = rests[0].object
 
    
 
    return out
 

	
 
  contains: (s, p, o) ->
 
    @_autoDeps.askedFor(s, p, o, null)
 
    log('contains calling getQuads when graph has ', 
 
    log('contains calling getQuads when graph has ', @graph.size)
 
    return @graph.getQuads(s, p, o).length > 0
 

	
 
  nextNumberedResources: (base, howMany) ->
 
    # base is NamedNode or string
 
    # Note this is unsafe before we're synced with the graph. It'll
 
    # always return 'name0'.
 
    base = base.id if base.id
 
    results = []
 

	
 
    # @contains is really slow.
 
    @_nextNumber = new Map() unless @_nextNumber?
 
    start = @_nextNumber.get(base)
light9/web/light9-music.coffee
Show inline comments
 
log = debug('music')
 
debug.enable('music')
 

	
 
# port of light9/curvecalc/musicaccess.py
 
coffeeElementSetup(class Music extends Polymer.Element
 
  @is: "light9-music",
 
  @getter_properties:
 
    status: { type: String, notify: true }
 
    statusTitle: { type: String, notify: true }
 
    turboSign: { type: String, notify: true }
 
    
 
    duration: { type: Number, notify: true }
 
    song: { type: String, notify: true }
 
    # It does not yet work to write back to the playing/t
light9/web/live/live.coffee
Show inline comments
 
log = debug('live')
 
debug.enable('live')
 

	
 
# Like element.set(path, newArray), but minimizes splices.
 
# Dotted paths don't work yet.
 
syncArray = (element, path, newArray, isElementEqual) ->
 
  pos = 0
 
  newPos = 0
 

	
 
  while newPos < newArray.length
 
    if pos < element[path].length
 
      if isElementEqual(element[path][pos], newArray[newPos])
 
        pos += 1
 
        newPos += 1
 
@@ -462,24 +461,25 @@ coffeeElementSetup(class Light9LiveContr
 
  clearAll: ->
 
    # clears the effect!
 
    @graphToControls.emptyEffect()
 
    
 
  update: ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    newDevs = []
 
    for dc in @graph.sortedUris(@graph.subjects(U('rdf:type'), U(':DeviceClass')))
 
      for dev in @graph.sortedUris(@graph.subjects(U('rdf:type'), dc))
 
        newDevs.push({uri: dev})
 

	
 
    log("controls update now has #{newDevs.length} devices")
 
    syncArray(@, 'devices', newDevs, (a, b) -> a.uri.value == b.uri.value)
 

	
 
    return
 

	
 
    # Tried css columns- big slowdown from relayout as I'm scrolling.
 
    # Tried isotope- seems to only scroll to the right.
 
    # Tried columnize- fails in jquery maybe from weird elements.
 
    
 
    # not sure how to get this run after the children are created
 
    setTimeout((() -> $('#deviceControls').isotope({
 
      # fitColumns would be nice, but it doesn't scroll vertically
 
      layoutMode: 'masonry',
light9/web/rdfdb-synced-graph.html
Show inline comments
 
@@ -20,31 +20,36 @@
 
  <script src="graph.js"></script>
 
  <script>
 
   class RdfdbSyncedGraph extends Polymer.Element {
 
       static get is() { return "rdfdb-synced-graph"; }
 
       
 
       static get properties() {
 
           return {
 
               graph: {type: Object, notify: true},
 
               status: {type: String, notify: true},
 
               testGraph: {type: Boolean},
 
           }
 
       }
 
       
 

	
 
       onClear() {
 
           console.log('reset')
 
       }
 
     
 
       connectedCallback() {
 
           super.connectedCallback();
 
           this.graph = new SyncedGraph(
 
               this.testGraph ? null : '/rdfdb/syncedGraph',
 
               {
 
                   '': 'http://light9.bigasterisk.com/',
 
                   'dev': 'http://light9.bigasterisk.com/device/',
 
                   'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 
                   'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 
                   'xsd': 'http://www.w3.org/2001/XMLSchema#',
 
               },
 
               function(s) { this.status = s; }.bind(this));
 
             function(s) { this.status = s; }.bind(this),
 
             this.onClear.bind(this));
 
           window.graph = this.graph;
 
       }
 
   }
 
   customElements.define(RdfdbSyncedGraph.is, RdfdbSyncedGraph);
 
  </script>
 
</dom-module>
light9/web/rdfdbclient.coffee
Show inline comments
 
@@ -42,24 +42,30 @@ parseJsonPatch = (input, cb) ->
 
  parseDels = (cb) =>
 
    parser = N3.Parser()
 
    parser.parse input.patch.deletes, (error, quad, prefixes) =>
 
      if (quad)
 
        patch.delQuads.push(quad)
 
      else
 
        cb()
 

	
 
  async.parallel([parseAdds, parseDels], ((err) => cb(patch)))
 

	
 
class window.RdfDbClient
 
  # Send and receive patches from rdfdb
 
  #
 
  # What this should do, and does not yet, is keep the graph
 
  # 'coasting' over a reconnect, applying only the diffs from the old
 
  # contents to the new ones once they're in. Then, remove all the
 
  # clearGraph stuff in graph.coffee that doesn't even work right.
 
  # 
 
  constructor: (@patchSenderUrl, @clearGraphOnNewConnection, @applyPatch,
 
                @setStatus) ->
 
    @_patchesToSend = []
 
    @_lastPingMs = -1
 
    @_patchesReceived = 0
 
    @_patchesSent = 0
 
    @_connectionId = '??'
 
    @_reconnectionTimeout = null
 
    @_newConnection()
 

	
 
  _updateStatus: ->
 
    ws = (if not @ws? then 'no' else switch @ws.readyState
 
@@ -80,25 +86,25 @@ class window.RdfDbClient
 
    log('rdfdbclient: queue patch to server ', patchSizeSummary(patch))
 
    @_patchesToSend.push(patch)
 
    @_updateStatus()
 
    @_continueSending()
 

	
 
  _newConnection: ->
 
    wsOrWss = window.location.protocol.replace('http', 'ws')
 
    fullUrl = wsOrWss + '//' + window.location.host + @patchSenderUrl
 
    @ws.close() if @ws?
 
    @ws = new WebSocket(fullUrl)
 

	
 
    @ws.onopen = =>
 
      log('rdfdbclient: connected to', fullUrl)
 
      log('rdfdbclient: new connection to', fullUrl)
 
      @_updateStatus()
 
      @clearGraphOnNewConnection()
 
      @_pingLoop()
 

	
 
    @ws.onerror = (e) =>
 
      log('rdfdbclient: ws error ' + e)
 
      @ws.onclose()
 

	
 
    @ws.onclose = =>
 
      log('rdfdbclient: ws close')
 
      @_updateStatus()
 
      clearTimeout(@_reconnectionTimeout) if @_reconnectionTimeout?
0 comments (0 inline, 0 general)