Sobald der D-Bus einen eingesteckten USB-Stick meldet, startet ein Perl-Daemon automatisch ein Backup mit Progressanzeige auf dem Desktop.
Ich gebe nur ungern zu, dass mein Ubuntu-Laptop zusehends verstaubt, seit mir mein Arbeitgeber vor einem halben Jahr ein Macbook aufgedrängt hat. Dafür gibt es zwei Gründe: Ein schlafen gelegtes Macbook wacht in 99 von 100 Fällen wieder korrekt auf und ist in 5 Sekunden samt Wireless betriebsbereit. Und um ein Backup zu ziehen, stöpselt der User einfach die vorkonfigurierte Backup-USB-Platte ein und braucht keinen Finger zu rühren und keinen Gedanken an den weiteren Ablauf zu verschwenden (Abbildung 1).

Abbildung 1: Auf dem Macbook springt sofort die Backup-Utility Time Machine an, sobald der User das USB-Backup-Drive einstöpselt.
Süchtig nach Schnickschnack
Man könnte das alles als Schnickschnack abtun, aber derartige Zuckerl versüßen den Alltag. Werden sie entzogen, wehrt sich der Körper sofort mit allergischen Reaktionen. Spontane, langgezogene “Waruuum?”-Rufe sind häufig die Folge, sobald es statt Torte wieder nur Diätgerichte gibt. Dabei können auch moderne Linux-Desktops solche Aktionen auslösen. Der von Gnome und mittlerweile auch von KDE verwendete D-Bus stellt einen praktischen Kommunikationskanal zwischen verschiedenen Applikationen her, die dabei nicht direkt voneinander wissen müssen.
Bekommt etwa der Hardware Abstraction Layer (HAL) mit, dass der User einen USB-Stick einstöpselt, verbreitet er diese Nachricht auf dem D-Bus. Andere Applikationen schnappen sie dort auf. Der Gnome-Desktop zum Beispiel mountet den Stick – unter Ubuntu im Verzeichnis »/media« – und zeigt ein Fenster des File-Browsers auf dem Desktop.
Fahr mit im Systembus
Das Einklinken in den D-Bus ist selbst in einer Skriptsprache wie Perl dank des CPAN-Moduls Net::DBus sehr einfach möglich. Das Skript in Listing 1 wählt in Zeile 5 den Systembus aus, der unabhängig von der gerade laufenden Desktopsession systemweite Meldungen sammelt und weitergibt. Alternativ betreibt D-Bus den Session-Bus, der die Daten der aktuellen Usersession bereithält. Die Methode »get_service()« befragt das Busobjekt nach dem Service »org.freedesktop.Hal«, der die HAL-Daten in der Hierarchie »freedesktop.org«, dem D-Bus-Mutterschiff, beherbergt. Die verdrehte Notation dient der hierarchischen Ordnung und ist aus der Java-Welt bekannt.
|
Listing 1: |
|---|
01 #!/usr/local/bin/perl -w
02 use strict;
03 use Net::DBus;
04
05 my $bus = Net::DBus->system();
06 my $hal = $bus->get_service( "org.freedesktop.Hal" );
07
08 my $manager = $hal->get_object(
09 "/org/freedesktop/Hal/Manager",
10 "org.freedesktop.Hal.Manager" );
11
12 my $devices = $manager->GetAllDevices();
13
14 for my $device ( @$devices ) {
15 print "$devicen";
16 }
|
Über diesen Service versucht nun Zeile 8 mit »get_object()« ein Objekt der Klasse des HAL-Managers einzuholen. In Hochsprachen bietet D-Bus seine Dienste oft als Objekte an, deren Methoden die Busdaten schicken oder empfangen. Der HAL-Manager verfügt über die Methode »GetAllDevices()«, die für jedes angeschlossene und von HAL erkannte Gerät einen beschreibenden String zurückgibt. Abbildung 2 zeigt eine von der For-Schleife ausgegebene Auswahl.

Abbildung 2: Net::DBus verbindet sich mit dem HAL-Manager-Objekt und gibt alle bisher erkannten Hardwareteile aus.
Lauscher an der Wand
Während Listing 1 das Remote-Objekt des HAL-Managers anspricht, um die bislang registrierten Geräte aufzuzählen, erfordert ein nach dem Einstöpseln automatisch startendes Backup einen aktiv lauschenden Client, den der D-Bus verzögerungslos benachrichtigt, sobald ein vorher definierter Zustand eingetreten ist.
Da die Dokumentation der Bestandteile ausgesandter D-Bus-Nachrichten oft stark zu wünschen übrig lässt, ist es einfacher, ein Tool wie »dbus-monitor« zu bemühen, das dem Paket »dbus« von Haus aus beiliegt. Es abonniert nach dem Start von der Kommandozeile kurzerhand alle D-Bus-Nachrichten und gibt jede eintreffende Message aus, sobald diese eintrudelt. So zeigt Abbildung 3, dass »dbus-monitor« unter anderem einen »MountAdded«-Event zugesteckt bekommt. Für die Nachricht verantwortlich zeichnet der Service »org.gtk.Private.GduVolumeMonitor«. Dieser Event rührt zweifelsfrei von einer Gnome-Applikation her, die den USB-Stick unter »/media« mountet und dies den Lauschern auf dem D-Bus mitteilt.
Backup auf Kommando
Um diese Events abzufangen, ohne gleich in einer Flut irrelevanten Bus-Geplappers zu ertrinken, meldet sich das Backup-Daemon-Skript »dbus-mount-watcher« aus Listing 2 auf dem D-Bus an, fängt ausschließlich »MountAdded«-Nachrichten ab und untersucht mit Hilfe der voreingestellten UUID A840-E2B3, ob der User den angemeldeten Backup-Stick tatsächlich eingesteckt hat.
|
Listing 2: |
|---|
01 #!/usr/local/bin/perl -w
02 use strict;
03 use Net::DBus;
04 use Net::DBus::Reactor;
05 use App::Daemon;
06 use FindBin qw($Bin);
07 use Log::Log4perl qw(:easy);
08
09 use App::Daemon qw( daemonize );
10 daemonize();
11
12 INFO "Starting up";
13
14 my $BACKUP_STICK =
15 "file:///media/A840-E2B3";
16 my $BACKUP_PROCESS = "$Bin/gtk2-backup";
17
18 my $notifications = Net::DBus->session
19 ->get_service(
20 "org.gtk.Private.GduVolumeMonitor" )
21 ->get_object(
22 "/org/gtk/Private/RemoteVolumeMonitor",
23 "org.gtk.Private.RemoteVolumeMonitor",
24 );
25
26 INFO "Subscribing to signal";
27
28 $notifications->connect_to_signal(
29 'MountAdded', &mount_added );
30
31 ###########################################
32 sub mount_added {
33 ###########################################
34 my( $service, $addr, $data ) = @_;
35
36 INFO "Found mount point $data->[4] ";
37
38 if( $data->[4] eq $BACKUP_STICK ) {
39 my $cmd = "DISPLAY=:0.0 " .
40 "$BACKUP_PROCESS $data->[4] &";
41 INFO "Launching $cmd";
42 system( $cmd );
43 }
44 }
45
46 my $reactor = Net::DBus::Reactor->main();
47 $reactor->run();
|
Ist das der Fall und der Event stammt nicht etwa von einem anderen soeben angeschlossenen Gerät, startet das Daemon-Skript die Backup-Applikation »gtk2-backup« in Listing 3. Ihre GTK-Oberfläche zeigt den aktuellen Status der Sicherung auf dem Desktop mit einem Progressbalken an (Abbildung 6).
|
Listing 3: |
|---|
01 #!/usr/local/bin/perl -w
02 use strict;
03 use File::Finder;
04 use Glib qw/TRUE FALSE/;
05 use Gtk2 '-init';
06 use DateTime;
07
08 my $PID;
09 my $tar = "tar";
10 my $src_dir = "/home/mschilli/test";
11 my $ymd = DateTime->now->ymd('');
12
13 my($stick_dir) = @ARGV;
14
15 if(! defined $stick_dir ) {
16 die "usage: $0 stick_dir";
17 }
18 $stick_dir =~ s#^file://##;
19
20 my $dst_tarball = "$stick_dir/$ymd.tgz";
21
22 my $NOF_FILES = scalar File::Finder
23 -> type( "f" )
24 -> in( $src_dir );
25
26 my $CMD =
27 "$tar zcfv $dst_tarball $src_dir";
28
29 my $window = Gtk2::Window->new('toplevel');
30 $window->set_border_width(10);
31 $window->set_size_request( 500, 100 );
32
33 my $vbox = Gtk2::VBox->new( TRUE, 10 );
34 $window->add( $vbox );
35
36 my $pbar = Gtk2::ProgressBar->new();
37 $pbar->set_fraction(0);
38 $pbar->set_text("Progress");
39 $vbox->pack_start( $pbar, TRUE, TRUE, 0 );
40
41 my $cancel = Gtk2::Button->new('Cancel');
42 $vbox->pack_end( $cancel,
43 FALSE, FALSE, 0 );
44 $cancel->signal_connect( clicked =>
45 sub { kill 2, $PID if defined $PID;
46 Gtk2->main_quit; } );
47
48 $window->show_all();
49
50 my $timer = Glib::Timeout->add (
51 10, &start, $pbar,
52 Glib::G_PRIORITY_LOW );
53
54 Gtk2->main;
55
56 ###########################################
57 sub start {
58 ###########################################
59 my( $pbar ) = @_;
60
61 $PID = open my $fh, "$CMD |";
62
63 my $count = 1;
64 while( <$fh> ) {
65 chomp;
66 next if m#/$#; # skip dirs
67
68 $pbar->set_text( "Backup Progress " .
69 "($count/$NOF_FILES)" );
70 $pbar->set_fraction($count/$NOF_FILES);
71
72 Gtk2->main_iteration while
73 Gtk2->events_pending;
74
75 $count++;
76 }
77
78 close $fh or die "$CMD failed ($!)";
79
80 $cancel->set_label( "Success. Hooray!" );
81 undef $PID;
82
83 return Glib::SOURCE_REMOVE;
84 }
|
Bildschirmloser Daemon
Da das Daemon-Skript mit Hilfe des CPAN-Moduls App::Daemon im Hintergrund läuft und kein Terminal oder X-Server-Display kennt, gibt das Kommando in Zeile 39 von Listing 2 den Wert der »DISPLAY«-Variablen als »:0.0« vor, also das erste Display des X-Servers. Der Daemon startet mit »dbus-mount-watcher start« und schiebt sich dank der von App::Daemon exportierten Methode »daemonize()« in den Hintergrund, sodass der User schon kurz darauf wieder den Kommandozeilenprompt sieht. In der Datei »/tmp/dbus-mount-watcher.log« loggt der Daemon seine Aktivitäten (Abbildung 4).

Abbildung 4: Die Logdatei des Daemon offenbart, dass 20 Sekunden nach dem Skriptstart der Backup-USB-Stick erkannt und der Backup-Prozess eingeleitet wurden.
Das Kommando »dbus-mountwatcher stop« fährt den Daemon wieder herunter, mit »-X« kann der Entwickler ihn auch im Vordergrund starten (Logdaten wandern allerdings immer noch in die Logdatei) und mit »status« lässt sich der Status des Daemon abfragen.
Die Methode »connect_to_signal()« in Zeile 28 von Listing 2 weist dem Event »MountAdded« auf dem Session-Bus den ab Zeile 32 definierten Callback »mount_added()« zu. Das Net::DBus-Framework sorgt im Falle eines aufgeschnappten Events dafür, dass der Callback alle aus dem Bus stammenden und in der Ausgabe von »dbus-monitor« in Abbildung 3 aufgelisteten Parameter erhält. Als dritter Parameter liegt demnach eine Referenz auf einen Array vor, dessen fünftes Element der Mountpoint des USB-Sticks unter dem »/media«-Verzeichnis ist (Abbildung 5).

Abbildung 5: Das Perl-Modul Net::DBus ruft den Callback für das Signal »MountAdd« mit diesen Parametern auf.
Diese URI der Form »file:///media/XXX« übergibt der »system()«-Aufruf in Zeile 42 von Listing 2 dem eigentlichen Backup-Skript »gtk2-backup« aus Listing 3, das unaufgefordert direkt nach dem Start einen dicken roten Progressbalken auf den Bildschirm zaubert und den Backup-Prozess beginnt (Abbildung 6).

Abbildung 6: Das automatische Backup startet auf dem Ubuntu-Desktop sofort, nachdem jemand den USB-Stick eingesteckt hat.
Damit der Daemon nach dem Registrieren mit dem D-Bus nicht abrupt abbricht, sondern ewig weiterläuft, definiert Zeile 46 einen so genannten Reactor. Dieses Objekt verfügt über eine Methode »run()«, die den Daemon auf alle Zeit mit dem D-Bus verschweißt.
Boxen als Baumaterial
Listing 3 nimmt den Mountpoint des erkannten USB-Sticks entgegen, entfernt dessen »file://«-Vorspann in Zeile 18 und definiert mit dem Kommando in Zeile 26 das simple Backup-Verfahren: Das »tar«-Kommando sammelt alle unter dem Verzeichnis »$src_dir« (Zeile 10) liegenden Dateien ein und schreibt das daraus erzeugte Tar-Archiv auf den USB-Stick. Um Überschreiben zu verhindern, erzeugt das Skript eine nach dem aktuellen Datum benannte Datei im Format »YYYYMMDD.tgz«. Wer den Stick mehrmals am Tag einlegt, muss noch Stunden und Minuten einbeziehen.
Das Layout des GUI besteht aus einem Oberteil mit dem Progressbalken und einem Unterteil mit einem Button, der während des Backup-Laufs als »Cancel« erscheint und nach dessen Beendigung eine Erfolgsmeldung anzeigt. Da Widgets wie Progressbalken oder Buttons nicht direkt in einem Fenster der Klasse Gtk2::Window liegen können, muss ein Gtk2::VBox-Container herhalten, der die in ihm liegenden Elemente (Progressbalken und Button) mit »pack_start()« untereinander darstellt.
Fortschritt mit Trick
Damit der Fortschrittsbalken auch einigermaßen die Wirklichkeit widerspiegelt, sammelt das CPAN-Modul File::Finder ab Zeile 22 alle Dateien (type »f«) unter dem Verzeichnis »$src_dir« und allen Unterverzeichnissen ein und ermittelt ihre Anzahl mit dem »scalar«-Operator. Während der »tar«-Prozess im Verbose-Modus abläuft, schnappt sich die »while«-Schleife ab Zeile 64 jeweils neu ausgegebene Zeilen und ist damit bestens darüber informiert, wie viele Dateien »tar« bereits bearbeitet hat.
Das Verhältnis von bereits erledigten Dateien zur Gesamtzahl meldet Zeile 70 an den Progressbalken, Zeile 68 schreibt den entsprechenden Text »Backup Progress (XX/YY)« dazu. Das »Gtk2«-Konstrukt mit der Methode »main_iteration« in Zeile 72 frischt die Oberfläche bei jedem Balkenruckler auf.
Nach Abschluss des »tar«-Kommandos schreibt Zeile 80 eine Erfolgsmeldung in den Button unterhalb des Balkens (Abbildung 7) und ein Mausklick darauf (oder die [Enter]-Taste) bricht das Programm ab. Damit die von »tar« geschriebenen Daten auch auf dem USB-Stick landen und das Betriebssystem sie nicht etwa nur zwischenspeichert, empfiehlt sich vor dem Entfernen des USB-Sticks ein »umount«, entweder von der Kommandozeile oder aus dem Dateimanager.

Abbildung 7: Das Backup lief erfolgreich und der Tarball liegt auf dem USB-Stick. Der vormalige »Cancel«-Button meldet jetzt Erfolg.
Nach dem Eintritt in die Haupteventschleife mit »Gtk2->main« in Zeile 54 nimmt das GUI seinen Lauf und wartet auf User-Eingaben. Da das Backup-Programm aber selbstständig zu laufen beginnen soll – sobald die Oberfläche steht und ohne dass ein Mausklick des Users dies einleitet -, setzt Zeile 50 einen Timer. Dieser ruft die ab Zeile 57 definierte Funktion »start()« als Task mit der niedrigsten Priorität Glib::G_PRIORITY_LOW auf, die der Glib-Kern erst dann startet, wenn keine GUI-Aufbau-Tasks mehr anliegen. Als einzigen Parameter übergibt der Timer »start()« das Widget des Progressbalkens »$pbar«. Wichtig ist dann noch, dass »start()« nach getaner Arbeit den Wert »Glib::SOURCE_REMOVE« zurückliefert, sonst ruft der Timer den Callback nach dem Timeout erneut auf und der Backup begänne von vorne.
Installieren
Das Paket »dbus« enthalten alle gängigen Linux-Distributionen. Die benötigten Perl-Module liegen als »libdatetime-perl«, »libfile-finder-perl«, »libgtk2-perl«, »libglib -perl«, »libapp-daemon-perl«, »liblog-log4perl-perl« und »libnet-dbus-perl« in den Ubuntu-Repositories vor.
Der Befehl »dbus-mount-watcher start« fährt anschließend den Daemon hoch. Wer möchte, dass dies bei einem Reboot des Rechners automatisch geschieht, sollte den Daemon unter »/etc/init.d/« einhängen und mit »update-rc.d« registrieren. Das grafische Backup-Skript sollte im selben Verzeichnis wie der Daemon landen oder sich über eine absolute Pfadangabe aufrufen lassen.
Die Möglichkeiten mit D-Bus gehen noch weit über die hier vorgestellten Tricks hinaus. Applikationen wie der Instant-Messenger-Client Pidgin oder der Musikspieler Rhythmbox sind eng mit D-Bus integriert und lassen sich damit regelrecht fernsteuern [4]. (jcb)
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2011/04/Perl] [2] “Introduction To DBus”, Sprach-agnostische Einführung in D-Bus: [http://www.freedesktop.org/wiki/IntroductionToDBus] [3] Emmanuel Rodriquez, “D-Bus with Perl”: [http://bratislava.pm.org/presentation/dbus/] [4] Pidgin-Integration mit D-Bus: [http://developer.pidgin.im/wiki/DbusHowto] |







