summaryrefslogtreecommitdiffstats
path: root/scripts/ingen.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/ingen.py')
-rw-r--r--scripts/ingen.py218
1 files changed, 218 insertions, 0 deletions
diff --git a/scripts/ingen.py b/scripts/ingen.py
new file mode 100644
index 00000000..594e369d
--- /dev/null
+++ b/scripts/ingen.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+# Ingen Python Interface
+# Copyright 2012 David Robillard <http://drobilla.net>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import StringIO
+import os
+import rdflib
+import socket
+import sys
+
+class NS:
+ ingen = rdflib.Namespace('http://drobilla.net/ns/ingen#')
+ lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#')
+ patch = rdflib.Namespace('http://lv2plug.in/ns/ext/patch#')
+ rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+ xsd = rdflib.Namespace('http://www.w3.org/2001/XMLSchema#')
+
+class Interface:
+ 'The core Ingen interface'
+ def put(self, path, body):
+ pass
+
+ def set(self, path, body):
+ pass
+
+ def connect(self, tail, head):
+ pass
+
+ def disconnect(self, tail, head):
+ pass
+
+ def delete(self, path):
+ pass
+
+class Error(Exception):
+ def __init__(self, code, cause):
+ Exception.__init__(self, 'error %d, cause: %s' % (code, cause))
+
+class Remote(Interface):
+ def __init__(self, uri='unix:///tmp/ingen.sock'):
+ self.msg_id = 1
+ self.server_base = uri + '/'
+ self.model = rdflib.Graph()
+ self.ns_manager = rdflib.syntax.NamespaceManager.NamespaceManager(self.model)
+ self.ns_manager.bind('server', self.server_base)
+ for (k, v) in NS.__dict__.items():
+ self.ns_manager.bind(k, v)
+ if uri.startswith('unix://'):
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(uri[len('unix://'):])
+ elif uri.startswith('tcp://'):
+ self.sock = Socket(socket.AF_INET, socket.SOCK_STREAM)
+ parsed = re.split('[:/]', uri[len('tcp://'):])
+ addr = (parsed[0], int(parsed[1]))
+ self.sock.connect(addr)
+ else:
+ raise Exception('Unsupported server URI `%s' % uri)
+
+ def __del__(self):
+ self.sock.close()
+
+ def msgencode(self, msg):
+ if sys.version_info[0] == 3:
+ return bytes(msg, 'utf-8')
+ else:
+ return msg
+
+ def update_model(self, update):
+ for i in update.triples([None, NS.rdf.type, NS.patch.Put]):
+ put = i[0]
+ subject = update.value(put, NS.patch.subject, None)
+ body = update.value(put, NS.patch.body, None)
+ desc = {}
+ for i in update.triples([body, None, None]):
+ self.model.add([subject, i[1], i[2]])
+ return update
+
+ def uri_to_path(self, uri):
+ if uri.startswith(self.server_base):
+ return uri[len(self.server_base):]
+ return uri
+
+ def recv(self):
+ 'Read from socket until a NULL terminator is received'
+ msg = ''
+ while True:
+ c = self.sock.recv(1, 0)
+ if not c or ord(c[0]) == 0: # End of transmission
+ break
+ else:
+ msg += c[0]
+ return msg
+
+ def blank_closure(self, graph, node):
+ def blank_walk(node, g):
+ for i in g.triples([node, None, None]):
+ if type(i[2]) == rdflib.BNode and i[2] != node:
+ yield i[2]
+ blank_walk(i[2], g)
+
+ closure = [node]
+ for b in graph.transitiveClosure(blank_walk, node):
+ closure += [b]
+
+ return closure
+
+ def send(self, msg):
+ # Send message to server
+ payload = msg
+ if sys.version_info[0] == 3:
+ payload = bytes(msg, 'utf-8')
+ self.sock.send(self.msgencode(msg))
+
+ # Receive response and parse into a model
+ response_str = self.recv()
+ response_model = rdflib.Graph()
+ response_model.namespace_manager = self.ns_manager
+ response_model.parse(StringIO.StringIO(response_str), self.server_base, format='n3')
+
+ # Handle response (though there should be only one)
+ blanks = []
+ response_desc = []
+ for i in response_model.triples([None, NS.rdf.type, NS.patch.Response]):
+ response = i[0]
+ subject = response_model.value(response, NS.patch.subject, None)
+ body = response_model.value(response, NS.patch.body, None)
+
+ response_desc += [i]
+ blanks += [response]
+ if body != 0:
+ raise Error(int(body), msg) # Raise exception on server error
+
+ # Find the blank node closure of all responses
+ blank_closure = []
+ for b in blanks:
+ blank_closure += self.blank_closure(response_model, b)
+
+ # Remove response descriptions from model
+ for b in blank_closure:
+ for t in response_model.triples([b, None, None]):
+ response_model.remove(t)
+
+ # Remove triples describing responses from response model
+ for i in response_desc:
+ response_model.remove(i)
+
+ # Update model with remaining information, e.g. patch:Put updates
+ return self.update_model(response_model)
+
+ def get(self, path):
+ return self.send('''
+[]
+ a patch:Get ;
+ patch:subject <ingen:root%s> .
+''' % path)
+
+ def put(self, path, body):
+ return self.send('''
+[]
+ a patch:Put ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+%s
+] .
+''' % (path, body))
+
+ def set(self, path, body):
+ return self.send('''
+[]
+ a patch:Set ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+%s
+ ] .
+''' % (path, body))
+
+ def connect(self, tail, head):
+ return self.send('''
+[]
+ a patch:Put ;
+ patch:subject <ingen:root%s> ;
+ patch:body [
+ a ingen:Edge ;
+ ingen:tail <%s> ;
+ ingen:head <%s> ;
+ ] .
+''' % (os.path.commonprefix([tail, head]), tail, head))
+
+ def disconnect(self, tail, head):
+ return self.send('''
+[]
+ a patch:Delete ;
+ patch:body [
+ a ingen:Edge ;
+ ingen:tail <%s> ;
+ ingen:head <%s> ;
+ ] .
+''' % (tail, head))
+
+ def delete(self, path):
+ return self.send('''
+[]
+ a patch:Delete ;
+ patch:subject <ingen:root%s> .
+''' % path)
+