Open Source im professionellen Einsatz

Der Aktienwecker in Aktion

Noch ein weiterer Vorteil der lockeren Bindung durch Any Event: Das Modul »WatchQuotes.pm« lässt sich unabhängig von Pidgin testen, etwa mit dem Skript in Listing 2. Es erzeugt eine Instanz der Klasse »WatchQuotes« und ruft die Methode »init()« auf, mit der »WatchQuotes« die vom User definierte Konfigurationsdatei »~/.pidgin-stockwatch.yml« einliest und intern in eine Perl-Datenstruktur umformt (Abbildung 3).

Abbildung 3: Perl formt aus der Yaml-Datei in Abbildung 2 eine für sich geeignete Datenstruktur.

Abbildung 3: Perl formt aus der Yaml-Datei in Abbildung 2 eine für sich geeignete Datenstruktur.

Listing 2:
»watch-quotes«

01 #!/usr/bin/perl -w
02 use strict;
03 use lib local::lib;
04 use AnyEvent;
05 use WatchQuotes;
06
07 my $watcher = WatchQuotes->new();
08 $watcher->init();
09 $watcher->watch( &callback );
10
11 my $quit_program = AnyEvent->condvar;
12 $quit_program->recv;
13
14 ###########################################
15 sub callback {
16 ###########################################
17     print "$_[0]n";
18 }

Als Beispiel einer Eventloop bringt Any Event eine in Perl implementierte Schleife mit, die das Testskript benutzt. Listing 2 definiert mit »condvar()« eine Bedingungsvariable, welcher der Any-Event-Kernel Nachrichten schicken kann. Zeile 12 wartet mit »recv()« auf eine solche Nachricht - die aber nie kommt - und lässt derweil den Any-Event-Kernel Events von Modulen wie »WatchQuotes« bearbeiten.

Das aufgerufene Testskript holt also in regelmäßigen Zeitabständen Börsenkurse ein und ruft den ab Zeile 15 definierten Callback mit einer Tabelle formatierter Stockquotes auf, falls der Kurs einer der überwachten Aktien die in der Konfigurationsdatei festgelegten Grenzwerte überschreitet. Die Funktion »print()« in Zeile 17 gibt die Nachricht jeweils auf Stdout aus. Das Skript läuft weiter, bis der User es mit [Ctrl]+[C] abbricht.

Das Testskript hilft etwaige Fehler in Ruhe zu lokalisieren, bevor das Skript in die feindliche Pidgin-Umgebung gerät, wo das Debuggen schwer ist, ganz besonders wenn Pidgin das Plugin aus irgendwelchen Gründen nicht korrekt lädt (siehe Kasten "RTFM, falls existent"). Das Modul »WatchQuotes.pm« selbst definiert in Listing 3 zunächst Perl-typisch einen Konstruktor »new()«, der lediglich das Homedirectory des Aktienspezies herausfindet und im Objekthash einige Standardwerte wie den Namen der Konfigurationsdatei festlegt.

RTFM, falls existent

Dokumentation über das Schreiben von Pidgin-Plugins ist rar. Zwar hat Pidgin-Chefentwicker Sean Egan ein Buch zum Thema "Building and Extending Gaim" [5] geschrieben, doch nicht nur der Name des Projekts hat sich seither geändert (Pidgin statt Gaim), sondern auch so ziemlich jeder Funktionsaufruf und jede Datenstruktur. Das an sich sehr gut geschriebene Werk taugt daher allenfalls zum Studium der Pidgin-Architektur.

Auch die Aktualität der Online-Dokumentation ([6], [7]) lässt schwer zu wünschen übrig. Die automatisch generierte Doxygen-Dokumentation ist, wie so oft, lieblos zusammengeschustert, unvollständig und damit für die Katz. Die effektivste Methode, um in der aktuellen Pidgin-Version einen Parameter zu einem Funktionsaufruf zu finden, ist das Studium real existierenden Plugins neuerer Bauart, zum Beispiel [8].

Listing 3:
»WatchQuotes.pm«

001 ###########################################
002 package WatchQuotes;
003 # Mike Schilli, 2010 (m@perlmeister.com)
004 ###########################################
005 use strict;
006 use warnings;
007 use AnyEvent;
008 use AnyEvent::HTTP;
009 use YAML qw(LoadFile);
010
011 ###########################################
012 sub new {
013 ###########################################
014   my($class, %options) = @_;
015
016   my ($home) = glob "~";
017
018   my $self = {
019       watcher => undef,
020       data    => {},
021       refdata => {},
022       conf_file =>
023         "$home/.pidgin-stockwatch.yml",
024       conf    => {},
025       %options,
026   };
027
028   bless $self, $class;
029 }
030
031 ###########################################
032 sub init {
033 ###########################################
034   my($self) = @_;
035
036   my $yml = LoadFile( $self->{conf_file} );
037
038   for my $e ( @$yml ) {
039     if( ref $e eq "HASH") {
040       my($key, $val) = %$e;
041       $val =~ s/%//g;
042       $self->{conf}->{ $key } = $val;
043     } else {
044         # 2% by default
045       $self->{conf}->{ $e } = 2;
046     }
047   }
048 }
049
050 ###########################################
051 sub watch {
052 ###########################################
053   my($self, $cb) = @_;
054
055   $self->{watcher} = AnyEvent->timer (
056     after    => 10,
057     interval => 300,
058     cb    => sub {
059       $self->fetch( $cb );
060     },
061   );
062 }
063
064 ###########################################
065 sub fetch {
066 ###########################################
067   my($self, $cb) = @_;
068
069   my $url = "http://" .
070    "download.finance.yahoo.com/d/" .
071    "quotes.csvr?e=.csv" .
072    "&f=spl1p2&s=" .
073    join('+', sort keys %{ $self->{conf} });
074
075   http_get($url, sub {
076     $self->parse_csv( $_[0] );
077     $self->check( $cb );
078   });
079 }
080
081 ###########################################
082 sub parse_csv {
083 ###########################################
084   my($self, $csv) = @_;
085
086   for my $line ( split /n/, $csv ) {
087
088     my($symbol, $prev, $last, $change) =
089       map { s/[^w.-]//g; $_ }
090       split /,/, $line;
091
092     next unless defined $symbol;
093
094     $symbol = lc $symbol;
095
096     $self->{data}->{$symbol} =
097       [ $prev, $last, $change ];
098   }
099 }
100
101 ###########################################
102 sub check {
103 ###########################################
104   my($self, $cb) = @_;
105
106   if(!scalar keys %{ $self->{refdata} }) {
107     $self->{refdata} = {%{$self->{data}}};
108   }
109
110   for my $stock (keys %{ $self->{data}} ) {
111     if( $self->noteworthy( $stock ) ) {
112       $self->{refdata} = {
113           %{$self->{data}} };
114
115         # reset 'prev'
116       for my $s (
117           keys %{$self->{refdata}} ) {
118         $self->{refdata}->{$s}->[0] =
119             $self->{data}->{$s}->[1];
120       }
121
122       $cb->( $self->message );
123       last;
124     }
125   }
126 }
127
128 ###########################################
129 sub message {
130 ###########################################
131   my($self) = @_;
132
133   my $msg = "n";
134
135   for my $stock (keys %{ $self->{data} }) {
136       my($prev, $last, $change) =
137          @{ $self->{data}->{$stock} };
138       $msg .= "$stock: $last $change%n";
139   }
140
141   return $msg;
142 }
143
144 ###########################################
145 sub noteworthy {
146 ###########################################
147   my($self, $stock) = @_;
148
149   my $price_ref =
150     $self->{refdata}->{$stock}->[0];
151
152   my $price_now =
153     $self->{data}->{$stock}->[1];
154
155   my $change_percent = abs(
156       ($price_now - $price_ref))/
157       $price_ref*100;
158
159   return($change_percent >
160          $self->{conf}->{$stock});
161 }
162
163 1;

Yaml für Mensch und Maschine

Die ab Zeile 32 definierte Methode »init()« liest die Konfiguration mit der Funktion »LoadFile()« ein, die aus dem Yaml-Modul vom CPAN stammt. Das Yaml-Format hat den Vorteil, dass es sowohl für Menschen wie Maschinen gleichermaßen leicht lesbar ist (Abbildung 2).

Die Applikation erlaubt sowohl einfache Array-Einträge der Art »- amzn« genauso wie kleine Hashes nach dem Muster »- goog: 2%«, die Yaml als Referenz auf einen Hash unter dem Array-Eintrag speichert (»"goog" => "2%"«). Zeile 39 prüft, ob Perls »ref«-Funktion das Wort »HASH« zurückliefert, was auf eine Hashreferenz hindeutet.

Kommt aber der Leerstring zurück, handelt es sich um einen einfachen Skalar und Zeile 45 setzt einen Standard-Schwellenwert von 2 Prozent an. Die überwachten Aktienkürzel speichert das Modul unter dem Eintrag »conf« im Objekthash und ordnet ihnen die konfigurierten prozentualen Triggerwerte zu.

Die Methode »watch« ab Zeile 51 erzeugt einen periodischen Timer. Der Parameter »after => 10« bestimmt, dass der Timer den unter »cb« definierten Callback genau 10 Sekunden nach dem Start aufruft. Die Verzögerung ist gewollt und nötig, falls das Plugin schon startet, bevor der User im Instant-Messaging-Netzwerk ansprechbar ist. Ohne diese Denkpause würden frühe Nachrichten in die ewigen Jagdgründe gehen statt zum Stockholder. Der Parameter »interval« gibt mit dem Wert 300 an, dass der Timer den Callback nach dem ersten Aufruf alle 5 Minuten erneut anspringt, um die neuesten Börsenkurse vom Yahoo-Server einzuholen und die Werte auf Ausreißer hin zu untersuchen.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 6 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook