IOException.de

Icon

Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm

XMPP Multiuser Chat Bot

Multiuser Chats stellen eine gute Möglichkeit dar mit vielen Leuten gleichzeitig zu kommunizieren. Ich selbst nehme regelmäßig über MUCs an Entwicklermeetings teil oder bespreche mit Freunden, was es so zu besprechen gibt. Und natürlich macht chatten auch viel Spaß :-)

Neulich hatten wir während eines solchen Chats die Idee, dass wir doch unseren WG-Server am Chat teilnehmen lassen könnten, damit dieser uns z.B. mit Zitaten aus Fernsehsendungen unterhält oder wir über ihn die Musik in der Wohnung steuern können. Seit wir begonnen haben den ersten MUC-Bot zu implementieren, kommen immer wieder neue Ideen auf, was ein Bot so alles tun könnte. Zum Beispiel als Wörterbuch fungieren oder Kochrezepte fürs Abendessen vorschlagen.

Damit grundlegende Funktionalität nicht für jeden Bot neu geschrieben werden muss, habe ich begonnen ein kleines Framework zu entwickeln, dass sich um den XMPP-Teil des Bots kümmert und häufig benötigte Funktionen realisiert. Das Framework ist in python geschrieben.

Im Folgenden beschreibe ich die Mucbot-Klasse, die einen einfachen Bot darstellt. Um den Eintrag übersichtlich zu halten lasse ich einige Dinge weg, die nicht wesentlich sind. Daher wird der hier gepostete Code nicht lauffähig sein. Für die komplette Version schaut einfach auf github vorbei.

# Der Bot verwendet xmpppy (<http://xmpppy.sourceforge.net/>) um mit einem
# Jabber-Server zu kommunizieren.
import xmpp

# Wir brauchen Regular Expressions um in empfangenen Nachrichten nach Keywords
# zu suchen, auf die reagiert werden soll.
import re

# Das time Modul wird verwendet um Verzögerungen einzubauen. Z.B. falls der Bot
# nach einer bestimmten Zeit etwas sagen soll.
import time

# Der Bot erbt von Thread. Er kann also im Hintergrund gestartet werden, während
# das Programm noch etwas anderes tut.
from threading import Thread

class Mucbot(Thread):

# Es ist einiges an Initialisierung notwendig. Der Bot braucht eine Jabber-ID und
# ein dazu gehörendes Passwort, um sich damit am Server anmelden zu können. Da
# es ja ein MUC-Bot ist, braucht er außerdem einen Raum, dem er sich anschließen
# wird. Optional kann man ihm noch einen Namen geben, eine Liste von Zitaten,
# die er gelegentlich zitiert und ein Dictionary mit Keywords auf die er
# reagieren soll mit dazugehörenden Reaktionen. Die Keywords sind dabei als
# Reguläre Ausdrücke gegeben.
    def __init__(self, jid, pwd, room, botname='', roompwd='', quotes=[],
            minwait=-1, maxwait=-1, reactions={}, delay=3):

        # Kein expliziter Botname? -> Verwende die Node der Jabber-ID
        if botname == '':
            botname = jid.getNode()

        # […]
        # hier werden alle übergebenen Parameter in der Mucbot-Instanz
        # gespeichert, um diese später noch verwenden zu können

        # Um Nachrichten schnell nach Schlüsselwörtern durchsuchen zu können
        # werden alle Regulären Ausdrücke compiliert.
        for key in self.reactions.keys():
            self.reactions[re.compile(key)] = self.reactions.pop(key)

        # Jetzt kommt die Verbindung zum Server. Zuerst erstellen wir ein
        # xmpp.Client Objekt und verbinden uns mit dem angegebenen Server.
        self.client = xmpp.Client(jid.getDomain(), debug=[])
        self.client.connect()

        # Um auf ankommende Nachrichten reagieren zu können, müssen wir
        # entsprechende Handler registrieren.
        self.client.RegisterHandler('message', self.msg_rcv)
        self.client.RegisterHandler('presence', self.pres_rcv)

        # Die Authentifizierung am Server:
        self.client.auth(jid.getNode(), pwd, resource=jid.getResource())

        # Und schlussendlich das Betreten des Raums:
        p = xmpp.Presence(to='%s/%s' % (room, botname))
        p.setTag('x', namespace=xmpp.NS_MUC).setTagData('password', roompwd)
        p.getTag('x').addChild('history', {'maxchars':'0', 'maxstanzas':'0'})
        self.client.send(p)

# Möchte der Bot etwas sagen, wird vom xmpp-Modul die xmpp Stanza zusammengebaut
# und an den Chatraum gesendet.
    def say(self, msg):
        m = xmpp.Message(to=self.room, body=msg, typ='groupchat')
        self.client.send(m)

# Wenn der Bot eine Nachricht empfängt, wird folgende Methode aufgerufen:
    def msg_rcv(self, sess, msg):

        # Wir ignorieren Nachrichten, die wir selbst verschickt haben.
        sender = str(msg.getFrom())
        if len(sender.split('/')) > 1:
            sender = sender.split('/')[1]
        if sender.lower().find(self.botname) >= 0:
            return

        self.react(msg.getBody())

# In der folgenden Methode wird für jedes Keyword, dass wir gespeichert haben
# geschaut, ob dieses in der empfangenen Nachricht gefunden werden kann. Falls
# ja, antwortet der Bot mit einer zufällig ausgewählten Antwort aus der Menge
# aller passenden Antworten.
    def react(self, msg):
        time.sleep(self.delay)
        for pattern in self.reactions.keys():
            print "trying to find %s in %s" % (pattern, msg)
            if pattern.search(msg):
                self.say(self.reactions[pattern]
                        [randint(0,len(self.reactions[pattern])-1)])
                return

# Die folgende Endlosschleife wird aufgerufen, wenn der Bot im Chat bleiben und
# auf ankommende Nachrichten warten soll.
    def processing(self):
        while True:
            self.client.Process(1)

# Wenn der Bot gestartet wird, beginnt er mit dem Warten auf Nachrichten. Falls
# er bei der Initialisierung eine Liste von Zitaten übergeben bekommen hat,
# zitiert er aus dieser Liste in zufälligen Abständen zufällige Zitate.
    def run(self):
        processor = Thread()
        processor.run = self.processing
        processor.start()
        if self.minwait==-1 or self.maxwait==-1:
            return
        while True:
            r = randint(self.minwait, self.maxwait)
            time.sleep(r)
            self.say(self.quotes[randint(0,len(self.quotes)-1)])

Das mucbot-Projekt ist frei auf github verfügbar. Es steht unter einer GNU General Public License. Eigentlich bin ich eher ein Fan von BSD und ähnlichen Lizenzen, aber die Verwendung von xmpppy macht dies notwendig.

Vielleicht kann ja der eine oder andere Teile des Mucbots verwenden oder findet hier Anregungen für neue Projekte. Ich grüße mit diesem meinem ersten Blogeintrag alle Leser von IOException.de und freue mich schon darauf bald weitere spannende Dinge mit der Welt zu teilen.

Streiflicht 2011

Am Mittwoch, den 16. Februar, stellen Studierende des Studiengangs Medieninformatik die Highlights ihrer Anwendungsfächer um 16 Uhr im H20 vor. Erfreulicherweise sind mit MAMPF und diretto auch zwei Projekte von uns mit dabei.

Projekte:

  • Interaktive Musikbibliothek (Interaktive Systeme)
  • Interaktives Skript MMS (Interaktive Systeme)
  • Campus Radio (Medienpädagogik)
  • MAMPF (Ubiquitous Computing)
  • diretto (Ubiquitous Computing/Interaktive Systeme)
  • Parkhaus (Interaktives Video)
  • Biologische Barcodes in Gesichtern (Computer Vision)
  • Mehrbildanalyse zur Bestimmung von Kopfposen (Computer Vision)
  • Realtime Rendering (Computer Graphics)

Weitere Informationen gibt es unter: http://www.uni-ulm.de/in/mi/highlights.html

TCP Echo-Server in einer Zeile node.js

Der folgende Einzeiler erzeugt einen TCP-Server, der alle eingehenden Daten direkt zurücksendet:

require('net').createServer(function(s){require('util').pump(s,s);}).listen(8000);

Und nein, das ist kein Perl ;-)

GoogleMap Vaadin Widget (Google Maps JavaScript API V3)

Wer gerade dabei ist, eine Anwendung mit dem Web Application Framework Vaadin zu realisieren und darüber hinaus beabsichtigt, Google Maps in sein User Interface zu integrieren, hat mehrere Möglichkeiten.

Am naheliegendsten ist es zweifellos, einfach das fertige Vaadin Add-on GoogleMapWidget von Henri Muurimaa dafür zu nutzen, welches die Google Maps JavaScript API V2 einsetzt. Wenn man nun allerdings nicht nur die grundlegenden Funktionalitäten benötigt, kommt die Frage auf, ob man Anpassungen an diesem Widget vornehmen oder ein komplett neues Widget implementieren will.

Für den Fall, dass man sich dazu entschieden hat, ein eigenes Widget zu entwickeln, wäre es natürlich unsinnig, dafür noch die alte API Version und nicht die aktuelle Version, nämlich die Google Maps JavaScript API V3, zu verwenden.

Projektstruktur des Beispielprojekts IOException

Die meisten Entwickler möchten an dieser Stelle jedoch vermutlich kein JavaScript benutzen, da es ja einer der großen Vorteile von Vaadin ist, dass man die gesamte Anwendung in Java implementieren kann.

Also begibt man sich auf die Suche nach einer passenden GWT Library, welche Java Wrapper für die JavaScript API bereitstellt. Auf einer der offiziellen Seiten über die GWT APIs wird man dann aber schnell durch folgende Aussage enttäuscht: “At the present time, the gwt-maps API only supports Google Maps API version 2.

Jetzt hat man zwei Möglichkeiten: Entweder man wartet bis Google offiziell eine Library für die Version 3 zur Verfügung stellt, wozu es allerdings keine genauen Angaben gibt, wann das geschehen soll, oder aber man verwendet einfach die Alpha-Version der besagten GWT Library, die auf einer inoffiziellen Seite von Google zu finden ist.

Wer genug Zeit zum Warten und sich aus diesem Grund für die erste Möglichkeit ent- schlossen hat, braucht nun eigentlich nicht mehr weiterzulesen ;-) Allen Anderen soll nachstehend am Beispielprojekt IOException gezeigt werden, wie man das Grundgerüst für sein eigenes GoogleMap Vaadin Widget mit Hilfe der GWT Library gwt-google-maps-v3 (Alpha-Version) erstellt.

Anmerkung: Im Folgenden werden lediglich die essenziellen Schritte aufgeführt. Für die Grundlagen zur Erstellung eines Vaadin Projekts wird auf das Book of Vaadin verwiesen, das vor allem für Anfänger sehr empfehlenswert ist.

Zuerst erzeugt man ein gewöhnliches Vaadin Projekt und legt daraufhin die im Screenshot dargestellte Projektstruktur an. Die JAR-Datei gwt-maps3-0.2b.jar (GWT Library) kann in der Rubrik Downloads der bereits erwähnten inoffiziellen Seite von Google heruntergeladen werden.

MANIFEST.MF


Manifest-Version: 1.0
Vaadin-Widgetsets: de.ioexception.widgets.MainWidgetset.gwt.xml

In der Manifest-Datei muss durch das Attribut Vaadin-Widgetsets angegeben werden, wo sich der oberste GWT Modul Descriptor befindet, welcher das zentrale Widget Set definiert und auch als Einstiegspunkt zum Kompilieren dient.

web.xml (Ausschnitt)


<init-param>
    <param-name>widgetset</param-name>
    <param-value>de.ioexception.widgets.MainWidgetset</param-value>
</init-param>

Dies muss zusätzlich im Deployment Descriptor als <init-param> innerhalb der <servlet>-Tags aufgeführt werden.

MainWidgetset.gwt.xml (Ausschnitt)


<module>
    <inherits name="com.vaadin.terminal.gwt.DefaultWidgetSet" />
    <inherits name="de.ioexception.widgets.googlemap.GoogleMapWidgetset" />
</module>

Wie bereits erwähnt, wird in der Datei MainWidgetset.gwt.xml das zentrale Widget Set definiert. Dazu wird erst einmal vom Default Widget Set und anschließend von den GWT Modul Descriptoren der einzelnen Widget Sets geerbt. Da
das gezeigte Beispielprojekt jedoch nur das GoogleMap Widget beinhaltet, ist dies hier auch die einzige Angabe.

GoogleMapWidgetset.gwt.xml (Ausschnitt)


<module>
    <inherits name="com.vaadin.terminal.gwt.DefaultWidgetSet" />
    <inherits name="com.google.gwt.maps.Maps" />
    <script src="http://maps.google.com/maps/api/js?sensor=false" />
    <source path="client" />
</module>

Um die JAR-Datei gwt-maps3-0.2b.jar (GWT Library) einzubeziehen, muss von deren GWT Modul Descriptor geerbt werden. Dies geschieht durch <inherits name="com.google.gwt.maps.Maps" />. Durch den <script>-Tag erhält man Zugriff auf die Google Maps JavaScript API. Anschließend wird noch der Pfad, an dem sich die clientseitigen Dateien befinden, definiert.

GoogleMap.java


package de.ioexception.widgets.googlemap.server;

import java.util.Map;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.ClientWidget;
import de.ioexception.widgets.googlemap.client.VGoogleMap;

@ClientWidget(VGoogleMap.class)
public class GoogleMap extends AbstractComponent
{
    @Override
    public void paintContent(PaintTarget target) throws PaintException
    {
        super.paintContent(target);
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables)
    {
        super.changeVariables(source, variables);
    }
}

Die Klasse GoogleMap.java ist an dieser Stelle nur der Vollständigkeit halber aufgeführt und bedarf eigentlich keiner weiteren Erklärung, da dies der gewöhnliche Aufbau einer serverseitigen Widget-Klasse ist und erst einmal nicht erweitert werden muss.

VGoogleMap.java


package de.ioexception.widgets.googlemap.client;

import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapTypeId;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;

public class VGoogleMap extends Composite implements Paintable
{
    private SimplePanel wrapperPanel = null;
    private MapWidget mapWidget = null;
    private MapOptions mapOptions = null;

    public VGoogleMap()
    {
        wrapperPanel = new SimplePanel();

        initWidget(wrapperPanel);
    }

    @Override
    public void updateFromUIDL(UIDL uidl, ApplicationConnection client)
    {
        if(mapWidget == null)
        {
            initMap();
        }
    }

    private void initMap()
    {
        mapOptions = new MapOptions();

        mapOptions.setZoom(17);
        mapOptions.setCenter(new LatLng(48.42285529218286, 9.957287907600403));
        mapOptions.setMapTypeId(new MapTypeId().getSatellite());
        mapOptions.setScrollwheel(true);
        mapOptions.setDraggable(true);
        mapOptions.setNavigationControl(true);
        mapOptions.setMapTypeControl(true);

        mapWidget = new MapWidget(mapOptions);

        mapWidget.setWidth("800px");
        mapWidget.setHeight("500px");

        wrapperPanel.add(mapWidget);
    }
}

In der clientseitigen Widget-Klasse ist zu sehen, dass ein MapWidget nur mit MapOptions als Parameter initialisiert werden kann. Verpflichtend sind Werte zu den Eigenschaften center, mapTypeId und zoom. Im Absatz MapOptions der Google Maps JavaScript API V3 kann dies nachgelesen werden.

IOException.java


package de.ioexception;

import com.vaadin.Application;
import com.vaadin.ui.Window;
import de.ioexception.widgets.googlemap.server.GoogleMap;

public class IOException extends Application
{
    @Override
    public void init()
    {
        Window mainWindow = new Window("IOException");
        GoogleMap googleMap = new GoogleMap();
        mainWindow.addComponent(googleMap);
        setMainWindow(mainWindow);
    }
}

Wie hier zu sehen ist, kann das GoogleMap Widget nun völlig unkompliziert verwendet werden. Als Beispiel dient dafür die Anwendungsklasse IOException, in der das initialisierte Widget einfach zum Main Window hinzugefügt wird.

AF Ubiquitous Computing – Präsentationsvideos

MAMPF [Fabian Groh, Oliver Stamm, Steffen Scherle, Sven Reichel, Timo Müller]




diretto ))) [Benjamin Erb, Christian Koch, Stefan Kaufmann]

Effiziente Bereichs-Queries mit CouchDB/GeoCouch

Im Vergleich zu klassischen SQL-Datenbank erfordern NoSQL-Datenbank vor allem bei der Datenabfrage ein Umdenken. Im Falle von CouchDB lässt sich zwar mit View Collation schon einiges erreichen, allerdings bei weitem nicht alles. Auf eine solche Grenze bin ich gestoßen, als ich Zeiträume speichern und abfragen wollte, also Einträge die ein Start- und Enddatum besitzen. Anfragen auf diese Daten könnten nun zu einem fixen Zeitpunkt alle darin ablaufenden Einträge erfragen, oder ausgeweitet auf einen Zeitraum auflisten, welche Einträge innerhalb eines Zeitfensters liegen. All dies ist mit CouchDB nicht wirklich lösbar.

Einen kleinen Workaround bietet die Idee, die Zeitleiste zu segmentieren und immer dann für einen Eintrag einen Key zu emitten, wenn der Zeitraum des Eintrages innerhalb dieses Bereichs liegt. Eine solche Map-Funktion könnte wie folgt aussehen. Hierbei wird für einen Eintrag jeweils ab dem Beginn für alle 5 Minuten ein Schlüssel in den Index emittiert.

Dokumentaufbau:

{
   "_id": "s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de",
   "begin": "2010-08-05T09:11:52.156Z",
   "end": "2010-08-05T09:23:13.457Z"
}

Map-Funktion:

//length of time segment (here 5 min)
var periodLength = (60*5);

function(doc)
{
        if(doc.begin && doc.end)
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                //calculate first matching segment of period
                var p = (begin - (begin%periodLength));

                //emit key for each matching period
                while(p<=end)
                {
                        emit([p, doc._id], null);
                        p = p + periodLength;
                }
        }
}

Der resultierende View sieht dann in etwa so aus:

Oder als Abfrage:
http://localhost:5984/entries/_design/entries/_view/docsByPeriodList?startkey=[1280998800]&endkey=[1281000193,{}]

{"total_rows":3,"update_seq":2,"offset":0,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999400,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"]15,"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999700,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1281000000,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null}
]}

Eine viel bessere Lösung bietet jedoch die CouchDB-Erweiterung GeoCouch. Dank des R-Trees lassen sich Bereichtsabfragen effizient durchführen. Da GeoCouch eigentlich für zweidimensionale Geokoordinaten gedacht, muss sie und die GeoJSON-Syntax etwas missbraucht werden. Anstatt einer WGS84-Koordinate emittieren wir einfach einen UNIX-Zeitstempel und lassen den anderen Grad leer:

function(doc)
{
        if(doc.begin && doc.end)
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                emit(
                        {
                                type: "Point",
                                bbox : [0,start,0,end]
                        }, null
                );
        }
}

So lassen sich nun effiziente Bereichsabfragen durchführen:
http://localhost:5984/entries/_design/entries/_spatial/entriesByPeriod?bbox=0,1280998800,0,1281000600

{"update_seq":16,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","bbox":[0,1280999512,0,1281000193],"value":null}
]}

Ein PubSubHubbub-Subscriber-Client für Java

Das Web ist mit HTTP fest an ein Client/Server-Modell gebunden und eine dadurch implizierte Asynchronität der Kommunikation. Requests können ausschließlich von Clients initiiert werden und immer von Servern in Form von Responses beantwortet. Ein solches Modell ist ausreichend für den Abruf von Informationen, setzt allerdings Schranken bezüglich anderer Interaktionsformen. Andere Protokolle wie XMPP, SIP oder auch Technologien wie nachrichtenbasierte Middlewaresysteme besitzen oft keine so deutliche Trennung zwischen Client/Server und erlauben weniger eingeschränkt die Kommunikation zwischen Knoten. Dadurch enstehen neben dem Request/Reply Muster weitere typischen Muster für den Austausch von Nachrichten. Ein Muster für die Benachrichtigung über Ereignisse ist das Publish/Subscribe Muster. Interessierte Knoten subskribieren sich für bestimmte Ereignisse und ereigniserzeugende Knoten publizieren diese.

Ein solches Kommunikationsmuster ist mit HTTP direkt nicht möglich, auch wenn es insbesondere für Feeds interessant wäre. Zwar bestehen mit Server Pushes / Long Polling oder dem aufkommenden WebSocket Standard vereinzelte Lösung für das prinzipielle Problem, dass HTTP keine serverinitiierte Kommunikation erlaubt, jedoch sind diese Insellösungen bisher kaum in der Breite verwendbar.

Google hat mit dem PubSubHubbub-Protokoll ein einfaches offenes Protokoll erschaffen, dass auf reinem HTTP basiert und ein solches Publish/Subscribe Muster unterstützt. Der Trick hierbei ist die Tatsache, dass alle beteiligten Knoten selbst sowohl Server wie auch Client sind und somit sowohl Requests empfangen wir auch versenden können.

Im Rahmen des diretto Projekts habe ich für unseren Client eine java-basierte Subscriber-Implementierung entwickelt. Als Feed kann jeder PubSubHubbub-fähige Atom-Feed benutzt werden. Im Falle einer Änderung des Feeds, zum Beispiel der Veröffentlichung eines neuen Eintrags, wird das “Delta” des Feeds, also der neue Teil an die Callback-Methode übergeben.

Subscriber subscriber = new SubscriberImpl("subscriber-host",8888);
Subscription subscription = subscriber.subscribe(URI.create("http://feed-host/my-push-enabled-feed.xml"));

subscription.setNotificationCallback(new NotificationCallback()
{

    @Override
    public void handle(SyndFeed feed)
    {
        //TODO: Do something more useful with the new entries
    	WireFeed inFeed = (WireFeed) feed.originalWireFeed();
    	if(inFeed instanceof Feed)
    	{
    		List<?> entries = ((Feed) inFeed).getEntries();
    		for (Object o : entries)
    		{
    			if(o instanceof Entry)
    			{
    				final Entry entry = (Entry) o;
    				System.out.println("New entry: "+entry.getId());
    			}
    		}
    	}
    }

} );

Der Client benutzt intern Rome für die Auswertung der Atom-Feeds und Jetty als leichtgewichtigen, internen Webserver. Der Subscriber muss übrigens für den Hub erreichbar sein, insofern sollte er an eine öffentliche IP und den angegebenen Port gebunden werden.

Projekt auf github: java-sub-pubsubhubbub

Barrier points in Node.js

Node.js ist ein serverseitiges, hochskalierbares Framework für die Entwicklung von asynchronen Netzwerkanwendungen in JavaScript. Aufgrund der Asynchronität ist das Framework in der Lage, tausende Verbindung gleichzeitig offen zu halten und zu verarbeiten. Ein wesentliches Merkmal hier von ist, dass ausschließlich mit Callbacks gearbeitet wird, um auf Beendigungen von Operationen zu reagieren.

Im Normalfall führt das zu einem Chaining von Callback. Im folgenden Beispiel sollen zwei Dateien geöffnet werden und ihr kompletter Inhalt zurückgegeben werden:

var size = 0;

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (!err)
	{
		// first file read
		size += data.length;
		fs.readFile('/home/benjamin/file2', function(err, data)
		{
			if (!err)
			{
				// second file read
				size += data.length;
				fs.writeFile('/home/benjamin/size', size, function(err)
				{
					if (!err)
					{
							// both file read and content written to file
							sys.log('done');
						}
					});
			}
		});
	}
});

Das Problem hierbei ist, dass zwei Aktionen, die eigentlich parallel ablaufen könnten, nämlich das lesen beider Dateien, nacheinander ablaufen müssen. Für einen parallelen Ablauf ist eine zusätzliche Koordination notwendig, da die Resultate beider Aufrufe in Verbindung stehen. Hierfür habe ich mich an den CyclicBarrier Klassen von Java orientiert und eine einfache Klasse für die Koordination von asynchronen Callbacks geschrieben. Eine Barriere wird erzeugt unter Angabe der Anzahl von teilnehmenden Parties. Desweiteren können optional ein Callback für die Beendigung sowie ein optionaler Callback im Abbruchsfall registriert werden. Anschließend können die einzelnen Aufgaben der Parties angestoßen werden. Im Erfolgsfall müssen diese an der Barriere mit submit() anzeigen, dass sie beendet sind. Durch abort() kann auch ebenfalls ein Abbruch signalisiert werden. Eine Propagation der Ergebnisdaten lässt am besten durch eine externe Variable realisieren, auf den die Funktionen ebenfalls Zugriff haben.

Der Code des Beispielszenarios ändert sich dann wie folgt:

var size = 0;

var b = new Barrier(2, function()
{
	//Success callback
	fs.writeFile('/home/benjamin/size', size, function(err)
	{
		sys.log('done');
	});
}, function()
{
	//Aborted callback
	sys.log("aborted");
});

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

fs.readFile('/home/benjamin/file2', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

Wie im Vergleich zu erkennen ist, lässt sich die Ausführung deutlich beschleunigen. Ein paar Rahmenbedingungen gibt es jedoch. Zunächst dürfen die Teilaufgaben keine Abhängigkeiten untereinander besitzen. Dann darf ein Abbruch einer Teilaufgabe keine Auswirkung auf noch laufende Teilaufgaben besitzen, dessen Ergebnis später verworfen wird. Schließlich muss bei der Programmierung darauf geachtet werden, dass in jedem Fall eine Teilaufgabe mit submit() oder abort() terminiert, da ansonsten ein Lock entsteht.

Die Klasse ist relativ einfach:

/**
 * @class
 *
 * Creates a new barrier for the given amount of parties.
 * @param parties
 * @param barrierCallback
 * @param abortCallback
 * @return
 */
var Barrier =  function(parties, barrierCallback, abortCallback)
{
	this.parties = parties;
	this.barrierCallback = barrierCallback;
	this.abortCallback = abortCallback;

	this.running = true;
	this.count = 0;
};

/**
 * Signals a completion of one of the parties.
 * @return
 */
Barrier.prototype.submit = function()
{
	if (++this.count === this.parties && this.running)
	{
		this.barrierCallback();
	}
};

/**
 * Signals an abort by one of the parties. If not callback is passed, the default abort callback will be executed.
 * @param customAbortCallback Optional callback that should be executed due to the abort.
 * @return
 */
Barrier.prototype.abort = function(customAbortCallback)
{
	if (this.running && customAbortCallback)
	{
		customAbortCallback();
	}
	else if (this.running && this.abortCallback)
	{
		this.abortCallback();
	}
	this.running = false;
};

Klasse auf github: http://gist.github.com/464179

Histogramplot in Sage/Matplotlib

Wer Matlab mag wird Sage lieben, insbesondere wenn die Ausgangsdaten sowieso in Python vorliegen. Ein einfaches Histogram für Ganzzahlen lässt sich wie folgt realisieren:


from sage.all import *
import numpy
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab

def plot_hist(subplot,name,x_data,x_var,y_var,b_width):

  #create array
  n_array = numpy.array(x_data)

  #labels
  subplot.set_xlabel('variable '+x_var+' [min = '+str(n_array.min())+', max='+str(n_array.max())+' ]')
  subplot.set_ylabel(y_var)

  #create bins
  mmin = n_array.min()
  mmax = n_array.max()
  b = numpy.arange(mmin-(1.0/2),mmax+1+(1.0/2))

  #set ticks
  subplot.set_xticks(b)

  #hist
  n, bins, patches = subplot.hist(x_data,bins=b, rwidth=b_width)

  #title
  subplot.set_title(r'Histogram of '+x_var)

Benutzt wird das Ganze dann mittels:

f = matplotlib.pyplot.figure()
a = f.add_subplot(111)

plot_utils.plot_hist(a,'Decimals', [1,1,1,2,2,3,3,3,4,4,5,5,6,6,7,7],'decimal','# of decimals',0.8)

matplotlib.pyplot.savefig('plot.png')

Atom Feeds in Java mit ROME direkt lesen

Für die Interaktion mit Feeds gibt es in Java die weit verbreitete ROME-Library. Diese Library unterstützt sowohl RSS als auch ATOM in den verschiedenen Versionen. Außerdem bietet es eine Abstraktion an, die den Umgang mit den verschiedenen Feedarten vereinfachen soll. Ihre sogenannten Syndication Feeds bieten eine einheitliche Schnittstelle an, und sind unabhängig vom darunter liegenden Format. Dies mag allgemein sehr hilfreich sein und für viele Fälle auch ausreichen. Typische Operationen sind somit entkoppelt vom Format und können wiederverwendet werden, oder das konkrete Format kann problemlos ausgetauscht werden.

Der Nachteil hierbei ist, dass bei dieser Abstraktion Besonderheiten der einzelnen Formate verborgen werden. Problematisch wird es zum Beispiel, wenn man explizit ein bestimmtes Format lesen möchte, um auf bestimmte Elemente zuzugreifen. So muss in Atom jeder Feed und Einträg ein ID Element besitzen, in RSS existiert dies jedoch nicht. Leider existiert nun auch keine Methode, ein solches Feld in einem Syndication Feed direkt abzufragen.

Nach einigem Suchen bin ich nun auf die Lösung gestoßen. Beim Einlesen des Feeds muss explizit ein Flag aktiviert werden, dass das zugrunde liegende Format ebenfalls mitgespeichert werden soll. Erst wenn dieses Flag gesetzt ist, lässt sich später der Feed im Originalformat (WireFeed) abrufen:

InputSource source = new InputSource(...);
SyndFeedInput feedInput = new SyndFeedInput();
feedInput.setPreserveWireFeed(true);
SyndFeed feed = feedInput.build(source);

Später bietet dann der Syndication Feed Zugriff auf den konkreten Feed:

WireFeed wireFeed = (WireFeed) feed.originalWireFeed();
if(wireFeed instanceof com.sun.syndication.feed.atom.Feed)
{
   String feedId = ((Feed) wireFeed).getId()
}

ioexception.de

Benjamin Erb [] studiert seit 2006 Medieninformatik und interessiert sich insbesondere für Java, Web-Technologien, Ubiquitous Computing, Cloud Computing, verteilte Systeme und Informationsdesign.


Raimar Wagner studiert seit 2005 Informatik mit Anwendungsfach Medizin und interessiert sich für C++ stl, boost & Qt Programmierung, Scientific Visualization, Computer Vision und parallele Rechenkonzepte.


David Langer studiert seit 2006 Medieninformatik und interessiert sich für Web-Entwicklung, jQuery, Business Process Management und Java.


Sebastian Schimmel studiert seit 2006 Informatik mit Anwendungsfach Medizin und interessiert sich für hardwarenahe Aspekte, Robotik, webOs, C/C++ und UNIX/Linux.


Timo Müller studiert seit 2006 Medieninformatik. Er interessiert sich allen voran für Mobile and Ubiquitous Computing, systemnahe Enwticklung und verteilte Systeme, sowie Computer Vision.


Achim Strauß studiert seit 2006 Medieninformatik. Seine Interessen liegen in Themen der Mensch-Computer Interaktion sowie Webentwicklung und UNIX/Linux.


Tobias Schlecht studiert seit 2006 Medieninformatik und interessiert sich vor allem für Software Engineering, Model Driven Architecture, Requirements Engineering, Usability Engineering, Web-Technologien, UML2 und Java.


Fabian Groh studiert seit 2006 Medieninformatik. Seine Interessengebiete sind Computer Graphics, Computer Vision, Computational Photography sowie Ubiquitos Computing.


Matthias Matousek studiert seit 2007 Medieninformatik und interessiert sich besonders für Skriptsprachen, Echtzeitsysteme und Kommunikation.


Michael Müller [] studiert seit 2009 Medieninformatik. Er interessiert sich vor allem für Web-Technologien, Ubiquitous Computing, User-Interfaces, UNIX und Creative Coding.


Falco Nogatz [] studiert seit 2010 Informatik mit Anwendungsfach Mathematik. Er interessiert sich für Web-Technologien, Programmierparadigmen und theoretische Grundlagen.

Archiv

Mai 2012
M D M D F S S
« Apr    
 123456
78910111213
14151617181920
21222324252627
28293031