Open Source im professionellen Einsatz
Linux-Magazin 02/2013
© Gennadiy Poznyakov, 123RF.com

© Gennadiy Poznyakov, 123RF.com

Onlinebücherei

Bequem zum richtigen Buch

Um Onlinedokumente auf Google Drive mit Tags zu versehen, legt ein Skript mit Hilfe zweier APIs Metadaten auf Evernote ab. Damit kann der Benutzer beispielsweise seine in der Cloud gelagerten E-Books nach Kategorien durchsuchen.

1700

Wie vor zwei Monaten an gleicher Stelle [2] erörtert, liegen meine ehemaligen Papierbücher jetzt als PDFs auf Google Drive. Die Liste meiner digitalisierten Folianten am PC-Bildschirm zu durchstöbern ist kein Problem. Sitze ich aber vor meinem mobilen Lesegerät und möchte in der Onlinebibliothek schmökern, ist die lange Textliste in Abbildung 1 unpraktisch. Was fehlt, ist ein Mechanismus, mit dem ich zum Beispiel gelesene Bücher als solche markieren kann.

Abbildung 1: Michael Schillis Bibliothek auf Google Drive erscheint bislang nur als graue PDF-Liste gescannter Papierbücher.

Helfen würde es auch, großformatige Bücher – Informatik-Literatur mit langen Programmlistings beispielsweise – extra zu kennzeichnen, da ihre Darstellung auf Mobiltelefonen mies ist. Wenn ich an der Supermarktkasse Schlange stehe, will ich auf dem Smartphone am liebsten nur kleinformatige Werke angeboten bekommen, mit denen das Minidisplay am besten klarkommt.

Auf Google Drive fehlen also Tags, die Dateien mit Eigenschaften versehen. Ein »finished« -Tag für ganz gelesene Werke, ein »active« -Tag für halb gelesene und ein »pocket« -Tag für Taschenbücher, die auch auf einem Smartphone gut zu lesen sind. Dann könnte jeder Benutzer nach seinen Vorlieben Bücher mit bestimmten Tags auflisten und zügig auswählen.

Bis die Google-Entwickler in die Gänge kommen, fällt meine Wahl auf die Applikation Evernote. Sie ist auf allen Geräten verfügbar, auf denen auch Google Drive läuft, und speichert, wie vor einiger Zeit hier erörtert ([3], [4]), beliebige Daten in "Note" genannten Einträgen, die wiederum in "Notebooks" genannten Ordnern liegen. Außerdem bietet der Evernote-Dienst ein API auf den Datenspeicher an, sodass Programmierer ohne Weiteres Hunderte oder Tausende Einträge maschinengesteuert anlegen können.

Die gewünschten Metadaten legt Evernote als Tags ab, die es Notes zuweist. Der Anwender vergibt Tags entweder mit der Maus im Webbrowser auf Evernote.com (Abbildung 3) oder mit einem der vielen Clients auf Windows-, Mac- oder Mobilgeräten. Auch das API weist Notes API-generierte Tags zu.

Bleibt zu klären, wie die Einträge für die PDF-Dateien in die Evernote-Datenbank kommen. Das vorgestellte Skript (Listing 1) legt von allen lokal gefundenen PDF-Dateien ein Jpg-Bild der Titelseite an und lädt es in einen Note-Eintrag im Folder »50-Ebooks« meines Evernote-Accounts hoch (Abbildung 2). Nachdem ich mit der Maus Tags auf die Einträge gezogen habe, kann ich mit Evernotes Suchfunktion Einträge auswählen (Abbildung 4).

Abbildung 2: Mit Hilfe eines API-Skripts zeigt Evernote die Bücher der Bibliothek nun mit Titelbild und Tagging-Funktion.

Abbildung 3: Im Browser hat der User dem Buch "Der App-Entwickler Crashkurs" die Tags computer-book und finished-book zugewiesen.

Abbildung 4: Sind die Tags fun-book und pocket-book gesetzt, zeigt die Suche nur unterhaltsame Taschenbücher.

Einfacher als Oauth

Nach dem Erscheinen der Perl-Snapshots ([3], [4) hat Evernote leider von Passwort-basierter Authentisierung auf Oauth 2 umgestellt. Wegen des geänderten API funktionieren die Skripte von damals nicht mehr und neue Applikationen zu entwickeln gestaltet sich etwas aufwändiger als zuvor.

Doch haben die Evernote-Entwickler ein Einsehen mit Hobbybastlern, die statt Daten von wildfremden App-Kunden nur ihre eigenen Accounts manipulieren wollen. Freizeitschrauber dürfen sich nämlich unter [5] einen so genannten Developer-Token abholen. Der Textstring lässt sich lokal speichern und gewährt ähnlich wie ein Passwort Zugang zu genau einem Evernote-Datenspeicher.

Listing 1 zieht als wichtigstes CPAN-Modul Net::Evernote::Simple herein, das der Einfachheit halber das offizielle Evernote-Thrift-API schon enthält. Eine Yaml-Datei namens »~/.evernote.yml« im Homeverzeichnis des Users enthält unter dem Eintrag »dev_token« den Developer-Token, den sich der auf Evernote eingeloggte User unter [5] abgeholt hat. In Zeile 18 liefert die Methode »note_store()« ein Objekt, mit dem der User direkt Evernote-API-Funktionen aufrufen darf, die der Evernote-Server intern als Webrequests geschickt bekommt.

Zeile 20 ermittelt über »en_folder_id()« die GUID des Notebooks »50-Ebooks« , das der User auf Evernote schon angelegt hat. Ab Zeile 115 ruft sie zunächst die API-Funktion »listNotebooks()« auf, übergibt den Developer-Token und erhält eine Liste aller Notebooks des Accounts. Die If-Bedingung in Zeile 123 filtert das Gesuchte heraus. Die Methode »guid()« extrahiert daraus die GUID und gibt sie ans Hauptprogramm zurück.

Listing 1

github-push-receiver.cgi

001 #!/usr/local/bin/perl -w
002 use strict;
003 use Net::Evernote::Simple;
004 use Log::Log4perl qw(:easy);
005 use File::Basename;
006 use File::Temp qw( tempfile );
007 use Digest::MD5 qw( md5_hex );
008 use Sysadm::Install qw( :all );
009
010 Log::Log4perl->easy_init( $INFO );
011
012 my( $HOME ) = glob "~";
013
014 my $EN_FOLDER = "50-Ebooks";
015 my $BOOKS_DIR = "$HOME/books";
016
017 my $enote   = Net::Evernote::Simple->new();
018 my $enstore = $enote->note_store();
019
020 my $en_folder_id = en_folder_id(
021   $enote, $enstore );
022
023 my %en_books = map { $_ => 1 }
024    en_folder_notes( $enote, $enstore,
025                     $en_folder_id );
026
027 for my $pdf ( <$BOOKS_DIR/*.pdf> ) {
028   my $file = basename $pdf;
029   (my $title = $file ) =~ s/\.pdf$//;
030
031   if( exists $en_books{ $title } ) {
032     DEBUG "$title already in Evernote";
033     next;
034   }
035
036   en_add( $enote, $enstore, $title,
037         title_pic( $pdf ), $en_folder_id );
038 }
039
040 ###########################################
041 sub en_add {
042 ###########################################
043   my( $enote, $enstore, $title,
044       $title_pic, $en_folder_id ) = @_;
045
046   my $PRFX = "Net::Evernote::Simple::" .
047     "EDAMTypes::";
048   my $data_class     = $PRFX . "Data";
049   my $resource_class = $PRFX . "Resource";
050   my $note_class     = $PRFX . "Note";
051
052   eval "require $data_class";
053   eval "require $resource_class";
054   eval "require $note_class";
055
056   INFO "Adding $title to Evernote";
057
058   my $data = $data_class->new();
059
060   my $content = slurp $title_pic;
061   $data->body( $content );
062   my $hash = md5_hex( $content );
063   $data->bodyHash( $hash );
064   $data->size( length $content );
065
066   my $r = $resource_class->new();
067   $r->data( $data );
068   $r->mime( "image/jpeg" );
069   $r->noteGuid( "" );
070
071   my $note = $note_class->new();
072   $note->title( $title );
073   $note->resources( [$r] );
074   $note->notebookGuid( $en_folder_id );
075
076   my $enml = <<EOT;
077 <?xml version="1.0" encoding="UTF-8"?>
078 <!DOCTYPE en-note SYSTEM
079 "http://xml.evernote.com/pub/enml2.dtd">
080 <en-note>
081    <en-media type="image/jpeg"
082              hash="$hash"/>
083 </en-note>
084 EOT
085
086   $note->content( $enml );
087
088   $enstore->createNote(
089       $enote->dev_token(), $note );
090 }
091
092 ###########################################
093 sub title_pic {
094 ###########################################
095   my( $in_pdf ) = @_;
096
097   my ($fh1, $pdf_file) = tempfile(
098       SUFFIX => '.pdf', UNLINK => 1 );
099
100   my ($fh2, $jpg_file) = tempfile(
101       SUFFIX => '.jpg', UNLINK => 1 );
102
103   tap { raise_error => 1 },
104     "pdftk", "A=$in_pdf", "cat", "A1",
105     "output", $pdf_file;
106
107   tap { raise_error => 1 },
108     "convert", "-resize", "100x",
109     $pdf_file, $jpg_file;
110
111   return $jpg_file;
112 }
113
114 ###########################################
115 sub en_folder_id {
116 ###########################################
117   my( $enote, $store ) = @_;
118
119   my $notebooks = $enstore->listNotebooks(
120     $enote->dev_token() );
121
122   for my $notebook (@$notebooks) {
123     if ( $notebook->name() eq $EN_FOLDER ){
124       return $notebook->guid();
125     }
126   }
127
128   die "$EN_FOLDER not found";
129 }
130
131 ###########################################
132 sub en_folder_notes {
133 ###########################################
134   my( $enote, $store, $en_folder_id ) = @_;
135
136   my $filter_class = "Net::Evernote::" .
137    "Simple::EDAMNoteStore::NoteFilter";
138   eval "require $filter_class";
139
140   my $filter = $filter_class->new();
141   $filter->notebookGuid( $en_folder_id );
142
143   my @titles = ();
144
145   my $max_per_call = 50;
146
147   for( my $offset = 0; ;
148        $offset += $max_per_call ) {
149
150     my $note_list = $store->findNotes(
151         $enote->dev_token(),
152         $filter, $offset, $max_per_call );
153
154     my $notes_found = 0;
155
156     for my $note (
157         @{ $note_list->{ notes } } ) {
158       $notes_found++;
159
160       push @titles, $note->title();
161     }
162
163     last if $notes_found != $max_per_call;
164   }
165
166   return @titles;
167 }

Schrittweise abpumpen

Bei jedem Aufruf holt das Skript mit »en_folder_notes()« alle im Notebook »50-E-books« auf Evernote gespeicherten Notes ab und iteriert über die lokal gespeicherten E-Book-PDFs. Das Gleiche ließe sich auch mit Cloud-PDFs auf Google Drive erreichen. Wie auch immer, das Programm findet raus, welche lokalen PDFs noch nicht in Evernote liegen.

Für jedes gefundene Dokument legt die in Zeile 36 aufgerufene Funktion »en_add()« eine neue Evernote-Note an. Die Funktion »en_folder_notes()« erzeugt zum Finden der Einträge in einem Notebook einen Filter, der die Notebook-GUID vorgibt, und holt dann jeweils 50 Einträge auf einmal ab. Kommen tatsächlich 50 zurück, erhöht sie den Zähler »$offset« und fragt abermals nach.

Da Net::Evernote::Simple das Evernote-API enthält, muss die Library im Modulraum das gleichnamige Präfix vorsehen. Modulnamen wie EDAMTypes::Data geraten wegen des führenden Präfix etwas länglich. Da die Spaltenbreite beim gedruckten Listing 1 begrenzt ist, stellen die Zeilen 46 bis 54 die erforderlichen Modulnamen des API zeilenweise zusammen und ziehen dann die Module mit »require« herein. Normale Skripte würden einfach »use Net::... ::Data« benutzen.

Das neben dem Bücher-Dateinamen ebenfalls benötigte Jpg-Bild der Titelseite fieselt die Funktion »title_pic()« unter tätiger Mithilfe des Utility »pdftk« aus den PDF-Dateien heraus. »convert« aus dem Imagemagick-Fundus wandelt die PDF-Titelseite in Zeile 107 in ein Jpg-Foto um. Beide Utilities sind mit einem Paketmanager wie »apt-get« schnell installiert.

Das Anlegen einer neuen Note in Evernote mit einer eingebetteten Jpg-Datei erfordert etwas XML, wie »en_add()« ab Zeile 41 nahelegt. Zunächst schlürft die Funktion »slurp« aus dem Fundus des CPAN-Moduls Sysadm::Install die Jpg-Daten in die Variable »$content« . Diese Rohdaten nimmt die Methode »body()« der Klasse EDAMTypes::Data entgegen und verlangt außerdem einen MD5-Hash des Inhalts, den die Funktion »md5_hash()« aus dem CPAN-Modul Digest::MD5 herzustellen in der Lage ist. Auch die Dateigröße in Bytes ist einzutüten.

Aus der Datenklasse formen die Zeilen 66 bis 69 ein Objekt der Klasse EDAMTypes::Resource. Diese wiederum packt die Methode »resources()« in ein Objekt der Klasse EDAMTypes::Note. Jede Evernote-Notiz besteht aus einer Anzahl dieser Ressourcen, auf welche die Notes verlinken, damit Browser und Evernote-Applikationen sie als Teil der Note anzeigen können. Am Ende stopft die Zeile 86 das weiter oben angegebene XML in den Contentbereich der Note, damit die Inhalte ein Format bekommen.

Ein abschließender Aufruf der API-Funktion »createNote()« mit dem Developer-Token spielt die neue Note auf dem Evernote-Server ein.

Online PLUS

In einem Screencast demonstriert Michael Schilli das Beispiel: http://www.linux-magazin.de/plus/2013/02

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 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

    Ein kräftiger Windstoß genügt, um das System der Notizzettel vom Schreibtisch zu fegen. Evernote, eine Art hochstrukturierter digitaler Notizblock, zeigt dem User seine Aufzeichnungen unabhängig von Ort und Endgerät. Ein API erlaubt programmatischen Zugriff mit Facebooks Thrift-Library.

  • Perl-Snapshot

    Um vorläufig eingefrorene Projekte regelmäßig zu reanimieren, sortieren Anhänger der "Getting Things Done"-Methode Zettel in Hängeregistern nach Datum und sehen diese Tickler-Files regelmäßig durch. Perl und Evernote hingegen wecken den User automatisch mit Erinnerungsmeldungen in der Inbox.

  • Cebit 2014: Smartphones und Tablets im Sysadmin-Einsatz

    Auf der Special Conference Open Source der Cebit hat Holger Gantikow von Science + Computing erörtert, wie Systemadministratoren Smartphones und Tablets als Arbeitsmittel nutzen können.

comments powered by Disqus

Ausgabe 09/2016

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

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