diff options
Diffstat (limited to 'scripts/ingen.py')
-rw-r--r-- | scripts/ingen.py | 218 |
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) + |