Aus den Augen, aus dem Sinn: 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 kitzeln den User automatisch mit Erinnerungsmeldungen in dessen Inbox wach.
"Mind like Water", die geistige Ruhe und extreme Flexibilität eines Karatekämpfers beim Bewältigen des Alltags, das verspricht die Produktivitätsmethode "Getting-Things-Done" (GTD) von Erfolgsautor David Allen ([3]). Grundregel: Man belastet sich nicht mehr mit Aufgaben, bei denen man den nächsten Schritt momentan eh nicht erledigen kann, sondern legt sie in einem Ordnungssystem (Abbildung 1) ab.
Abbildung 1: Ein Tickler-System für Papierzettel (Flickr-Foto: [http://www.flickr.com/photos/benchristen/3902110], können wir leider nicht nehmen, hat keine kommerzielle Lizenz, vielleicht könnt ihr ja ein lizensiertes auftreiben? Außerdem habe ich mal im Kommentarteil des Fotos angefragt, vielleicht erbarmt sich der User ja.) |
Die Hängeordner tragen Etiketten für die Tage des Monats und die Monate des Jahres. Hat ein Kollege zum Beispiel für den 14. eines Monats ein Ergebnis angekündigt, schreibt der GTD-Jünger einen Zettel mit den Eckdaten und wirft ihn in den Order mit der Nummer 14. Und sollte man im Januar mit der Urlaubsplanung für den Sommer beginnen, weil es dann günstige Flüge gibt, landet ein Zettel mit dem URL des Online-Buchungssystems im Order mit dem Reiter "Januar". Am Abend des 13. des Monats oder Ende Februar fällt dann beim regelmäßigen Prüfen der Ordner auf, dass für den nächsten Tag oder den kommenden Monat bestimmte Aufgaben anstehen. Die packt man dann zum Erstaunen seiner unorganisierten Mitwelt tatsächlich pünktlich an und prüft zuverlässig nach, ob Terminversprechen tatsächlich eingehalten wurden.
Wie schon im vorletzten Snapshot ([2]) erwähnt, kommt der in der
Grundversion kostenlose Service "Evernote" ([4]) wie gerufen,
um Alltagsaufgaben gemäß GTD-Tipps zu optimieren. Der User definiert sich
ein Eingangsfach (als "00-Inbox"
, damit Evernote den Ordner
ganz nach oben sortiert), in dem alle Anfragen eingehen, für die der User dann
den nächsten Bearbeitungsschritt bestimmt und diesen entweder sofort anpackt
oder die Notiz im richtigen Projektordner ablegt.
Abbildung 2: Die Inbox des Users zeigt zunächst nur einen lesenswerten Artikel ... |
Ähnlich wie mit Papierzetteln in Hängeregistern lässt sich mit Evernote
ein Tickler-System aufsetzen:
Das Notebook "01-Tickler"
enthält Einzeleinträge, die in der Betreffzeile ihr Aktionsdatum im Format
YYYY-MM-DD führen. Mittels der Evernote-API öffnet dann ein einmal
täglich ablaufender Cronjob das Tickler-Notebook, wandert durch
alle Einträge und prüft, ob ein datierter Eintrag am kommenden Tag
fällig ist. Trifft dies zu, schiebt das Skript die Notiz in die
Inbox des Users, der erfreut zur Kenntnis nimmt, dass er nun den
nächsten Bearbeitungsschritt eines Miniprojekts in Angriff nehmen kann.
Abbildung 3: Die Tickler-Datei zeigt für morgen einen Zahnarzttermin an. |
Der Eintrag "2013-01 Sommerferien planen" erinnert so daran, bereits im Januar 2013 einen Flug für den Urlaub im August zu buchen, und der Cronjob wird ihn am 31.12.2012 aus dem Tickler-Notebook ziehen und in die Inbox stellen, damit der User ein neues Projekt "Ferienplanung" in Angriff nimmt und die nächste Aktion ("Lufthansa-Angebote studieren") startet. Und der Tickler-Eintag "2012-04-14: Müller hat Linux-Version fertig" wandert am Abend des 13.04. automatisch in die Inbox des Anwenders, der seinen aus allen Wolken fallenden Kollegen am nächsten Tag an dessen vor Wochen gegebenes Terminversprechen erinnert.
Abbildung 4: ... doch nach dem Lauf des Tickler-Cronjobs landet der morgige Zahnarzttermin in der Inbox. |
Mit Evernotes Web API ist die Implementierung des Ticklers ein Kinderspiel. Der vorletzte Snapshot ([2]) hat bereits ausführlich dokumentiert, wie das verwendete Thrift-Protokoll mit Perl funktioniert, und wie Applikationsschreiber einen Application-Key von der Evernote-Webseite holen, um auf der Evernote-Sandbox zunächst etwaige Bugs auszubügeln und dann Zugriff auf die Produktions-Server zu beantragen.
Listing 1 wechselt anfangs im BEGIN-Block in das Verzeichnis $Bin
, in
dem das Skript liegt, um sicherzustellen, dass es die später
eingeholten auto-generierten Thrift-Module im Unterverzeichnis
gen-perl
auch dann findet, falls es als Cronjob startet.
Das CPAN-Modul local::lib sorgt dafür, dass es auch im Homeverzeichnis
des Users installierte CPAN-Module findet. Zeile 26 initialisiert Log4perl,
das mit DEBUG-Anweisungen in einer Logdatei festschreibt, was das Skript
so treibt. Besonders bei einem per Cronjob gestarteten Skript ist dies
hilfreich, um die Ursachen etwaiger Fehlfunktionen aufzuspüren und
Bugs auszumerzen. Neben dem Log-Level $DEBUG
legt es die Kategorie
"main"
fest, damit nicht gleich alle verwendeten CPAN-Module mit
eingebautem Log4perl-Support zu loggen anfangen, sondern nur das
Hauptprogramm. Abbildung 4 zeigt die Logdaten eines erfolgreichen
Skriptlaufs.
Zeile 52 authentisiert den User auf dem Evernote-Webserver. Stimmt das Password und der Consumer-Key, erlaubt dieser dem Skript uneingeschränkten Lese- und Schreibzugriff. Da es sich um sensitive Daten handelt, die niemand gern verlieren möchte, ist entsprechende Vorsicht beim Programmieren angebracht. Außerdem sollte der User sicherstellen, dass das Skript nur auf einem gesicherten System hinter einer Firewall läuft, um Missbrauch, zum Beispiel auf geknackten Webservern, vorzubeugen.
Um nun die Einträge des Notebooks "01-Tickler" aufzuspüren, benötigt
das Skript dessen GUID. Zeile 79 iteriert deshalb über alle Notebooks
des Accounts und prüft, ob das gerade bearbeitete den gesuchten Namen trägt.
Das Gleiche gilt für die Inbox "00-Inbox", und die GUIDs beider
Notebooks legt evernote-tickler
in den Vairablen $tickler_guid
bzw. $inbox_guid
, sowie
in der Logdatei ab, falls es sie findet.
Falls nicht, brechen die Zeilen 91 und 95 das Programm mit einem
Fehler ab, denn eine Verarbeitung in einem Account ohne entsprechend
angelegte Ordner wäre sinnlos.
001 #!/usr/local/bin/perl -w 002 use strict; 003 004 BEGIN { 005 use FindBin qw($Bin); 006 chdir $Bin; 007 } 008 009 use local::lib; 010 use Thrift; 011 use Thrift::HttpClient; 012 use Thrift::BinaryProtocol; 013 014 use lib 'gen-perl'; 015 use EDAMUserStore::Constants; 016 use EDAMUserStore::UserStore; 017 use EDAMNoteStore::NoteStore; 018 use EDAMNoteStore::Types; 019 use EDAMErrors::Types; 020 use EDAMTypes::Types; 021 use DateTime; 022 use Log::Log4perl qw(:easy); 023 024 my( $home ) = glob "~"; 025 026 Log::Log4perl->easy_init( { 027 level => $DEBUG, category => "main", 028 file => 029 ">>$home/data/evernote-tickler.log" } ); 030 031 my $username = "my-user"; 032 my $password = "my-passwd"; 033 my $consumer_key = "perlsnapshot"; 034 my $consumer_secret = "my-consumer-secret"; 035 036 my $evernote_host = "evernote.com"; 037 my $user_store_uri = 038 "https://$evernote_host/edam/user"; 039 my $note_store_uri_base = 040 "https://$evernote_host/edam/note/"; 041 042 my $http_client = 043 Thrift::HttpClient->new($user_store_uri); 044 my $protocol = Thrift::BinaryProtocol->new( 045 $http_client); 046 047 my $client = 048 EDAMUserStore::UserStoreClient->new( 049 $protocol); 050 051 my $result = 052 $client->authenticate( $username, 053 $password, $consumer_key, 054 $consumer_secret ); 055 056 my $user = $result->user(); 057 058 my $note_store_uri = 059 $note_store_uri_base . $user->shardId(); 060 061 my $note_store_client = 062 Thrift::HttpClient->new($note_store_uri); 063 064 my $note_store_protocol = 065 Thrift::BinaryProtocol->new( 066 $note_store_client); 067 068 my $note_store = 069 EDAMNoteStore::NoteStoreClient->new( 070 $note_store_protocol); 071 072 my $notebooks = 073 $note_store->listNotebooks( 074 $result->authenticationToken() ); 075 076 my $tickler_guid; 077 my $inbox_guid; 078 079 for my $notebook (@$notebooks) { 080 if ( $notebook->name() eq "01-Tickler" ){ 081 $tickler_guid = $notebook->guid(); 082 DEBUG "Found Tickler notebook"; 083 } 084 if ( $notebook->name() eq "00-Inbox" ) { 085 $inbox_guid = $notebook->guid(); 086 DEBUG "Found Inbox notebook"; 087 } 088 } 089 090 if ( !defined $tickler_guid ) { 091 die "No Tickler notebook found"; 092 } 093 094 if ( !defined $inbox_guid ) { 095 die "No Inbox notebook found"; 096 } 097 098 my $filter = 099 EDAMNoteStore::NoteFilter->new(); 100 $filter->notebookGuid( $tickler_guid ); 101 102 my $note_list = $note_store->findNotes( 103 $result->authenticationToken(), 104 $filter, 0, 1000 ); 105 106 my $tomorrow = DateTime->today( 107 time_zone => "local" )->add( days => 1 ); 108 my $tomorrow_date_match = $tomorrow->ymd(); 109 110 for my $note ( 111 @{ $note_list->{ notes } } ) { 112 my $title = $note->title(); 113 114 my( $date_in_title ) = 115 ( $title =~ /^(\S+)/ ); 116 117 DEBUG "Check if $tomorrow_date_match ", 118 "matches '$date_in_title'"; 119 120 if( $tomorrow_date_match =~ 121 /^$date_in_title/ ) { 122 123 DEBUG "$title matches. Move to Inbox."; 124 125 my $worked = $note_store->copyNote( 126 $result->authenticationToken(), 127 $note->guid(), $inbox_guid ); 128 129 die "copy note failed ($!)" if 130 !defined $worked; 131 132 DEBUG "Deleting note in Tickler file"; 133 134 $note_store->deleteNote( 135 $result->authenticationToken(), 136 $note->guid() ); 137 } 138 }
Die Evernote-API bietet nun keine Verzeichnisfunktion eines
vorgegebenen Notebooks, sondern besteht auf einer Methode
findNotes()
die in allen Notebooks nach Notes sucht. Ein Filter
vom Typ EDAMNoteStore::NoteFilter mit dem Parameter
notebookGuid
beschränkt die Suche allerdings auf ein Notebook mit
der angegebenen GUID.
Der zweite Parameter für findNotes()
gibt einen Offset an, mit dem
sich ein Paging von gefundenen Notes einrichten lässt. Im vorliegenden
Fall wünscht das Skript allerdings die vollständige Ergebnisliste und
beschränkt diese mit dem dritten Parameter lediglich auf 1.000, was
selbst für längere Ticklerlisten ausreichen dürfte.
Zeile 106 berechnet mit dem CPAN-Modul DateTime das morgige Datum,
in dem es zum heutigen Datum (today()
) einen einzelnen Tag
hinzuaddiert. Die Methode ymd()
wandelt das resultierende DateTime-Objekt
anschließend in einen String im Format "YYYY-MM-DD" um. Der reguläre
Ausdruck in Zeile 115 schneidet aus der Betreffzeile (title()
) der
Note das Datum aus und legt es in der Variablen $date_in_title
ab.
Die if-Bedingung in Zeile 120 prüft, ob das Betreffs-Datum ganz oder
teilweise mit dem morgigen Datum übereinstimmt. Sowohl eine
Monatsangabe (YYYY-MM) als auch ein Tagesdatum (YYYY-MM-DD) fördern
so Treffer zutage. Evernotes Web-API bietet keinen move-Befehl, also
kopiert Zeile 125 die Tickler-Notiz in die Inbox des Users, falls das
Datum stimmt. Die im Tickler-Notebook verbliebene Kopie löscht die
Methode deleteNote()
anschließend in Zeile 134.
Abbildung 5: Der Cronjob hat einen Tickler-Eintrag für den nächsten Tag gefunden und schiebt ihn in die Inbox des Users. |
Ein Eintrag in der Cron-Datei im Format
00 16 * * * /pfad/evernote-tickler
sorgt dafür, dass der Tickler jeden Tag um vier Uhr nachmittags startet und der User für morgen anberaumte Aufgaben in seiner Inbox sieht. Ist die Zeit für ein Projekt trotzdem noch nicht reif, korrigiert der User schlicht das Datum und schiebt die Notiz zurück in das Tickler-Notebook. Handelt es sich um zeitkritische Angelegenheiten wie ein Meeting, sucht der User dafür nun einen Termin in einer Kalenderapplikation. Kann er sogar den nächsten Schritt zur Bewältigung der Aufgabe erledigen, tut er dies nach GTD entweder sofort, falls es weniger als 2 Minuten dauert, oder erzeugt eine neue Notiz in einem Notebook, das alle brandheißen Projekte führt, und von denen der kampfbereite Alltagskrieger je nach Stimmung, Energielevel oder Kontext eines zur sofortigen Bearbeitung auswählen kann.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2012/04/Perl
Michael Schilli, "Zettels Trauma", Linux-Magazin 01/2012, http://www.linux-magazin.de/Heft-Abo/Ausgaben/2012/01/Perl-Snapshot
David Allen, "Getting Things Done: The Art of Stress-Free Productivity", http://www.amazon.com/dp/0142000280
"Evernote", evernote.com, Web-Applikation und Apps für Mac/Windows, sowie Android/iPad/iPhone.