Open Source im professionellen Einsatz
Linux-Magazin 05/2012
© Dan Kuta ,photocase.com

© Dan Kuta ,photocase.com

Amazons Kindle am Linux-Rechner

Datensauger

Streicht ein Besitzer von Amazons E-Book-Reader Textstellen auf dem Gerät an, legt sein Kindle diese persönlichen Markierungen in einer Datei ab. Steckt der Kindle im USB-Port des Linux-Rechners, saugt ein Perl-Skript die Daten ab, speichert sie in einer Datenbank und erlaubt später Volltext-Suchabfragen.

1307

E-Books bilden immer mehr Eigenschaften von Büchern aus Papier nach: Der Käufer darf elektronische Bücher auf Amazons Kindle heutzutage nicht nur zeitweise an Freunde ausleihen, sondern auch Bookmarks im Text setzen (siehe Abbildung 1) und Passagen im digitalen Lesefutter anstreichen (Highlights oder Clippings). Praktischerweise schickt der Kindle diese Markierungen auch an einen zentralen Server, sodass sie dem User später auch auf anderen Lesegeräten zur Verfügung stehen.

Abbildung 1: Streicht der Leser Stellen im Text an, speichert der Kindle-Reader diese Markierungen in einer Textdatei und gibt sie auch an den zentralen Server weiter.

Eine kompakte Sammlung angestrichener Stellen aus verschiedensten Büchern hilft zudem nach Begriffen zu fahnden, von denen der Leser zwar weiß, dass er sie irgendwo markiert hat, sich aber nicht an den zugehörigen Buchtitel erinnern kann. In [2] schlägt ein Produktivitätsguru deswegen vor, die Highlights aus der personalisierten Kindle-Webseite auf Amazon.com mittels Cut&Paste zu extrahieren. Dieses Ansinnen lässt sich aber auch eleganter lösen: Der Kindle legt diese vom Nutzer generierten Informationen in einer Klartextdatei auf dem Dateisystem des Geräts ab, und die heute vorgestellten Perl-Skripte greifen sie einfach von dort ab, sobald der User seinen Kindle an den USB-Anschluss eines Linux-Rechners einstöpselt.

Aktion beim Einstöpseln

Nach dem Anschluss des Kindle an die USB-Buchse eines Ubuntu-Systems kommt zunächst der Dialog von Abbildung 2 hoch, der vorschlägt, den Kindle mittels des MP3-Spielers Rhythmbox oder anderer Applikationen anzusteuern. Eine weitere Auswahlmöglichkeit in der angezeigten Liste weist Ubuntu dazu an, nichts dergleichen zu tun, und ein Klick auf »Always perform this action« hält Ubuntu auch in Zukunft von derartigem Unsinn ab.

Abbildung 2: Dieses nervige Popup lässt sich durch die Auswahl von Do Nothing und Always perform this action zum Schweigen bringen.

Ubuntu mountet das Dateisystem des Kindle anschließend unter »/media/Kindle« . Abbildung 3 zeigt, dass die Datei mit den gespeicherten Highlight-Informationen im Ordner »documents« unter dem Kindle-Rootverzeichnis liegt und »My Clippings.txt« (mit Leerzeichen) heißt. Ein Blick hinein offenbart, dass es sich um Klartext handelt. Die einzelnen Einträge notieren zeilenorientiert und grenzen sich vom folgenden Eintrag durch eine Kette von »=« -Zeichen ab (Abbildung 4).

Abbildung 3: Die Datei My Clippings.txt im Ordner documents auf dem Kindle speichert die angestrichenen Stellen im Textformat.

In der Textdatei finden sich nicht nur markierte Textpassagen, sondern auch Lesezeichen und Randnotizen, die der Besitzer über die Kindle-Tastatur selbst eingegeben hat, beispielsweise um sich über entdeckte Druckfehler lustig zu machen oder den Buchinhalt besserwisserisch zu kommentieren.

Abbildung 4: Textstellen, die der Leser angestrichen hat, erscheinen in der Datei My Clippings.txt im Dateisystem des Kindle-Readers.

Formatpfriemelei

Zur maschinellen Umwandlung des Kindle-Klartextformats in Datenbankeinträge, die später auch Suchabfragen zulassen, dient das Modul »ClippingsParser.pm« in Listing 1. Es nimmt einen Filedeskriptor der Clippings-Datei auf dem Kindle entgegen, durchforstet dann das undokumentierte Format und gibt die Einzelheiten wie Buchtitel, Autor, Seite, Highlight-Datum und Highlight-Text an den Aufrufer zurück.

Listing 1

ClippingsParser.pm

01 ###########################################
02 package ClippingsParser;
03 ###########################################
04 # Mike Schilli, 2012 (m@perlmeister.com)
05 ###########################################
06 use strict;
07 use warnings;
08
09 ###########################################
10 sub new {
11 ###########################################
12   my( $class ) = @_;
13
14   bless {}, $class;
15 }
16
17 ###########################################
18 sub parse_fh {
19 ###########################################
20   my( $self, $fh, $callback ) = @_;
21
22   my $line_sep = "==========\r\n";
23   my $entry    = "";
24   my $first    = 1;
25
26   while( my $line = <$fh> ) {
27
28     if( $first ) {
29         $first = 0;
30         $line =~ s/^\W+//;
31     }
32
33     if( $line eq $line_sep ) {
34       $self->parse_entry( $entry,
35                           $callback );
36       $entry = "";
37     } else {
38       $entry .= $line;
39     }
40   }
41 }
42
43 ###########################################
44 sub parse_entry {
45 ###########################################
46   my( $self, $entry, $callback ) = @_;
47
48   my( $head, $whence, $empty, $text ) =
49       split /\r\n/, $entry, 4;
50
51     # format error?
52   die "format error" if !defined $text;
53
54   $text =~ s/\r\n\Z//;
55
56   my( $title, $author ) =
57       ( $head =~ /^(.*) \((.*?)\)$/ );
58
59     # sometimes there's no author
60   if( !defined $author ) {
61       $author = "";
62       $title  = $head;
63   }
64
65   my @whence = split /\s*\|\s*/, $whence;
66   my $when = pop @whence;
67   my $what = join "|", @whence;
68
69   my( $type, $loc ) =
70       ( $what =~ /^- (\w+) (.*)/ );
71
72   $callback->( $type, $loc, $author,
73       $title, $when, $text );
74 }
75
76 1;

Die Methode »parse_fh()« nimmt einen Filedeskriptor auf die geöffnete Textdatei und eine Codereferenz als Callback entgegen, den sie bei jedem gefundenen Eintrag anspringt. Die erste Zeile der Kindle-Datei kann vor dem ersten Eintrag am Zeilenanfang einige unlesbare Zeichen enthalten, die das Ersetzenkommando in Zeile 30 verschwinden lässt.

Entspricht der Inhalt der aktuell untersuchten Textzeile in Skriptzeile 33 nicht dem aus »=« -Zeichen zusammengesetzten Eintragstrenner, hängt Zeile 38 den Inhalt an die Variable »$entry« an. Stößt das Skript auf eine Trennzeile zwischen Einträgen, ruft Zeile 34 die Methode »parse_entry« auf und übergibt ihr die bislang aufgesammelten Textdaten und den Callback, der nach erfolgreicher Analyse anzuspringen ist.

In »parse_entry()« teilt der »split« -Befehl in Zeile 49 einen Eintrag in Überschrift (»$head« ), Buchseite und Erfassungsdatum (»$whence« ), eine Leerzeile und den markierten Text (»$text« ) auf. Da die Kindle-Datei das Windows-Format mit »\r\n« als Zeilentrenner verwendet, nutzt »split« diese Kombination im regulären Ausdruck in Zeile 49.

Listing 2

sqlite-setup.sh

01 file=highlights.sqlite
02 rm -f $file
03
04 sqlite3 $file <<EOT
05 CREATE VIRTUAL TABLE highlights USING FTS3 ( type TEXT, loc TEXT,
06   author TEXT, title TEXT, date TEXT, text TEXT );
07
08 CREATE TABLE seen ( type TEXT, loc TEXT, title TEXT,
09   UNIQUE(type, loc, title) );
10 EOT

Manche E-Books, etwa Wörterbücher, geben keine Autoren in Klammern hinter dem Titel an, also bleibt das Autorenfeld in Zeile 61 in diesem Fall leer. Das letzte, durch den Trenner »|« abgesonderte Feld in der zweiten Zeile mit den Location- und Seitenangaben enthält das Datum, an dem die Textstelle markiert oder das Lesezeichen gesetzt wurde. In den Zeilen 72 und 73 stehen alle Felder bereit und der Callback-Aufruf übergibt ihre Werte an die vom Hauptprogramm als Referenz hereingereichte Funktion.

Mit dem Parser-Modul ist es nun ein Leichtes, die Kindle-Daten umzumodeln und in ein Format zu überführen, das schnelle Abfragen gestattet. Damit der User später mit der Volltextsuche nach Begriffen in markierten Textabschnitten suchen kann, speist das Skript in Listing 3 die gefundenen Highlight-Daten in eine SQlite-Datenbank mit Volltext-Engine ein. Listing 2 zeigt, dass besondere »CREATE« -Kommandos mit dem Kommandozeilentool »sqlite3« notwendig sind, damit SQlite, das Datenbanken in flachen Binärdateien anlegt, die abgelegten Textdaten so indiziert, dass Suchabfragen nach mehreren Wörtern mit Google-Geschwindigkeit ablaufen.

Listing 3

kindle-connected

01 #!/usr/local/bin/perl -w
02 use strict;
03 use local::lib qw(/home/mschilli/perl5);
04 use Log::Log4perl qw(:easy);
05 use POSIX;
06 use DBI;
07
08 my $run_as = "mschilli";
09 my $clip_path =
10 "/media/Kindle/documents/My Clippings.txt";
11
12 BEGIN {
13     use FindBin qw($RealBin);
14     chdir $RealBin;
15 }
16 use ClippingsParser;
17
18 my $logfile = "/var/log/kindle.log";
19
20 Log::Log4perl->easy_init({
21   file  => ">>$logfile",
22   level => $DEBUG,
23 });
24
25 my ( $name, $passwd, $uid, $gid ) =
26   getpwnam( $run_as);
27
28 chown $uid, $gid, $logfile or
29   LOGDIE "Cannot chown $logfile: $!";
30 chmod 0644, $logfile or
31   LOGDIE "Cannot chmod $logfile: $!";
32
33 POSIX::setuid( $uid );
34
35 my $pid = fork();
36 die "fork failed" if !defined $pid;
37 exit 0 if $pid; # parent
38
39   # wait until kindle root dir is mounted
40 for( 1..10) {
41     if( -f $clip_path) {
42         last;
43     } else {
44         DEBUG "Waiting for $clip_path";
45         sleep 5;
46         next;
47     }
48 }
49
50 LOGDIE "$clip_path not found"
51   if !-f $clip_path;
52
53 my $dbh = DBI->connect(
54   "dbi:SQLite:highlights.sqlite", "", "",
55   { RaiseError => 1, PrintError => 0 }
56 );
57
58 open my $fh, "<", $clip_path or
59     LOGDIE "Cannot open $clip_path ($!)";
60
61 my $cp = ClippingsParser->new();
62
63 my $items_added = 0;
64
65 $cp->parse_fh( $fh, sub {
66   my( $type, $loc, $author, $title,
67       $when, $text ) = @_;
68
69   my $sth = $dbh->prepare(
70     "INSERT INTO seen VALUES (?, ?, ?)");
71
72   eval {
73       $sth->execute( $type, $loc, $title );
74   };
75
76   return if $@; # most likely a dupe
77
78   $sth = $dbh->prepare( "INSERT INTO " .
79    "highlights VALUES (?, ?, ?, ?, ?, ?)");
80   $sth->execute( $type, $loc, $author,
81                  $title, $when, $text );
82   $items_added++;
83 } );
84
85 INFO "$items_added items added";
86 close $fh;

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 6 Heftseiten

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

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Perl-Snapshot Linux-Magazin 2012/08

    Perlmeister Michael Schilli hat seinen Snapshot verfilmt.

  • Perl-Snapshot

    Fertige E-Books für den Kindle leicht gemacht: Das CPAN-Modul EBook::MOBI überführt das bewährte POD-Format für Perl-Dokumentation per Tastendruck in das digitale Mobi-Format für unterschiedliche mobile Endgeräte der Kindle-Familie und ihrer Ableger.

  • Perl-Snapshot Linux-Magazin 2012/05

    "Perlmeister" Michael Schilli hat seinen Snapshot mit dem Magazin 2012/04 als Screencast verarbeitet.

  • Perl-Snapshot

    Wer die Ethernet-Leitung oder den Wireless-Router kontrolliert, jubelt den angeschlossenen Geräten neue Inhalte unter oder gaukelt ihnen vor, sie stünden am anderen Ende der Welt. Besonders eindrucksvoll ist der Effekt, wenn diese sich erst einmal zugeknöpft geben.

  • Leserbriefe

    Haben Sie Anregungen, Statements oder Kommentare? Dann schreiben Sie an [redaktion@linux-magazin.de]. Die Redaktion behält es sich vor, die Zuschriften und Leserbriefe zu kürzen. Sie veröffentlicht alle Beiträge mit Namen, sofern der Autor nicht ausdrücklich Anonymität wünscht.

comments powered by Disqus

Ausgabe 06/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.