virtual-pdf-printer

Es gibt Situationen, da braucht man Dinge, die man eigentlich schon lange hinter sich gewähnt hat. So heute, als ich ein PDF-Formular in in PDF drucken wollte. Auf dem Mac gibt es dazu im Druck-Dialog die Möglichkeit, das Dokument als PDF zu speichern. Funktioniert prima, so lange man nicht versucht, ein Formular meiner Zentralverwaltung zu drucken. Denn dieses Formular wird ausgefüllt und anhängig von den Eingaben werden daraus zwei bis fünf neue Dokumente generiert und diese dann an den Drucker gesendet. Dazu gibt es im Formular extra den Nur diesen Button zum Drucken verwenden-Button. Problem ist nur, dass der so geöffnete Druck-Dialog es irgendwie nicht schafft, die Ausgabe in ein PDF umzuleiten. Also begab ich mich auf die Suche nach einem virtuellen PDF-Drucker für macOS.

Nach gefühlt zweihundert Suchtreffern der Kategorie "Das geht doch direkt aus dem Druck-Dialog heraus" bin ich dann auf CUPS-PDF und in dessen Dunstkreis auf VipRiser gestoßen. Da CUPS-PDF wohl den Umweg über PS zu PDF macht, VipRiser aber native PDFs erstellt, habe ich letzerem den Vorzug gegeben. Die Installation ist allerdings etwas hakelig:

  1. Das zip-Archiv entpackt und die VipRiser-App nach Applications verschoben
  2. VipRiser gestartet
  3. Nun wird per Dialog hingewiesen, dass der Druckertreiber veraltet sei. Veraltet ist er eigentlich nicht, eher nicht vorhanden. Aber sei's drum, Treiber wird installiert.
  4. Nachdem der Treiber installiert wurde, öffnet sich ein Dialog, wie man nun den Drucker einrichten soll. Dort wird empfohlen, den bei der Treiberinstallation bereits angelegten Drucker in den Systemeinstellungen wieder zu löschen und statt dessen einen neuen Drucker anzulegen. Von diesem Schritt rate ich ab, denn der von mir angelegte Drucker funktionierte nicht. Nach einer Neuinstallation des Treibers funktionierte aber der während der Installation erstellte Drucker problemlos.

Nun bin ich also in der Lage, das durch seltsame PDF-Formulare deaktivierte Save as PDF durch die Verwendung eines PDF-Druckers zu umgehen. Nicht schön, aber es funktioniert.

Thunderbirds Lightning mit Microsofts Exchange verbinden

Mein dienstliches MacBook Pro geht langsam dem Ende seines Nutzungszeitraums entgegen. Den Lüfter habe ich schon einmal getauscht, nun ist es der Akku, der immer kurzlebiger wird. Hielt der Laptop 2013 noch gute zehn Stunden mit einer Akkuladung durch, sind es mittlerweile nur mehr knapp zwei. Das wird auf Dienstreisen schon mal knapp, wenn nicht gerade eine Steckdose in Reichweite ist. Das Gerät ist eigentlich noch gut in Schuss, daher wird es demnächst wohl als Ersatzgerät in meinem Teileschrank landen. Es kommt immer mal vor, das ein Laptop eines Kollegen eingeschickt werden muss, und mit einem Ersatzgerät mit zwar eingeschränkter Mobilität lässt sich dennoch fast jede Situation meistern.

Nun bin ich also auf der Suche nach einem neuen Laptop. Ein neues MacBook Pro könnte es werden, aber angesichts der in der Vergangenheit immer wieder aufgetretenen Hardware-Problemen und auch der Kosten, die für ein neues Gerät und damit auch einem neuen Satz Adapter und Docks anfällt, bin ich motiviert, nach Alternativen zu schauen.

Nach einiger Recherche hat sich herausgestellt, dass bei einem Umstieg auf ein Linux-Gerät die Nutzung des Exchange-Kalenders einer der größtem Stolpersteine werden kann. Daher habe ich als Test Thunderbird auf dem Mac mit Lightning erweitert. Das ist ein Kalender-Add-On für das Mailprogramm. Damit lässt sich problemlos mein privater CalDAV-Kalender einbinden. Soweit, so gut.

ExchangeCalendar

Dienstlich nutzen wir jedoch den Uni-weiten Exchange Server, und der lässt sich nicht ohne Weiteres in Lightning einbinden. Für die Nutzung des Exchange-Kalenders ist daher ein weiteres Add-On nötig, welches den bestechenden Namen ExchangeCalendar trägt. Dieses ist nicht über den Add-On-Browser verfügbar, sondern muss direkt von Github geladen werden. Einmal installiert und aktiviert, benötigt Thunderbird noch einen Neustart, um die durch das Add-On hinzugefügten Einstellungen auch anzuzeigen.

Konfiguration von Unterkalendern

Wie man einen Kalender einrichtet, ist sehr schön in der Dokumentation des Vorgängers von ExchangeCalendar beschrieben, daher will ich auf diesen Punkt gar nicht weiter eingehen. Viel wichtiger für mich ist aber die Einbindung von Unterkalendern. Dazu dupliziert man den Exchange Calender in Lightning (via Kontextmenü) und öffnet die Exchange (EWS) properties. Dort gibt es auf dem Tab Exchange/Windows (AD) die Option Path below folder base, die man mit Hilfe des Browse-Buttons ändern kann. Im so geöffnete Dialog lassen sich alle Unterkalender anzeigen und einer auswählen. Den Schritt muss man also pro Unterkalender wiederholen.

Kollegen berichteten, dass die Lösung nicht dauerhaft stabil sei, ich habe bisher aber noch keine Probleme feststellen können. Wenn ich die Entscheidung für ein neues Gerät gefällt habe und es ein Linux-Gerät wird, werde ich sicher noch den ein oder anderen Post über den Umstieg schreiben, auch über bis dahin eventuell aufgetretene Probleme mit den Kalendern.

OpenID ist tot! Oder?

Am Anfang war das Internet wüst und leer. Nach und nach kamen aber immer mehr Seite, Dienste und Anbieter hinzu. Und mit ihnen kamen auch immer mehr Anmeldedaten zusammen. Als das Problem immer schlimmer wurde, versuchte man mit unterschiedlichen Methoden ein Single Sign-On (SSO) zu etablieren. Eine dieser Methoden ist OpenID.

Das Besondere dieser Methode ist, dass man nicht auf einen zentralen Anbieter angewiesen ist. Jeder kann sich mit geringem Aufwand über seine Webseite einen OpenID-Provider bauen (der dann allerdings doch nicht ganz ohne einen anderen Anbieter auskommt), mit etwas mehr Aufwand ist auch ein eigener OpenID-Server möglich.

Einen solchen OpenID-Server betreibe ich seit geraumer Zeit. Leider musste ich feststellen, dass es über die Jahre nicht wirklich viele Einsatzmöglichkeiten für OpenID gab. Heute wurde ich dann plötzlich gewahr, dass auch die Webseiten des StackExchange-Netzwerks OpenID nicht mehr als Authentifizierungsmethode akzeptieren. Damit ist für mich der letzte Anwendungsfall für meinen OpenID-Server weggebrochen. Und auch die letzte Hoffnung, dass es Abseits von Google und Facebook so etwas wie ein dezentrales SSO geben könnte. Fortschritt ist eben doch ein Vektor. Nicht nur die Geschwindigkeit ist interessant, auch die Richtung kann entscheidend sein.

Wo ist rst2man?

Der Fortran-Parser von ctags versteht bisher das protected-Attribut nicht, mit dem der schreibende Zugriff auf eine öffentliche Modulvariable von außerhalb des Moduls unterbunden werden kann. ctags liegt auf github, also war es kein Problem, den Parser soweit anzupassen, dass er beim Auftreten des Attributs zumindest die Variable weiterhin verarbeitet.

Problematisch wurde es allerdings, als ich den Parser lokal gebaut habe. Denn die manpage war nur sehr übersichtlich. In der Doku findet man dazu den Satz, dass für die Erstellung der manpage das Python-Tool rst2man benötigt wird. Aber woher bekommt man es? Es gehört wohl zu den docutils, aber pip install docutils hat es nicht installiert. Auch port install py-docutils hab nicht weitergeholfen. Im Netz habe ich dann den Hinweis für Mercurial-Entwickler gefunden, dass man sich das Sandbox-Archiv des docutils-Projekts laden soll. Aber auch dort ist es nicht enthalten. Schlussendlich habe ich das Tool direkt aus dem docutils-Repository geladen. Dort liegt es unterhalb des Ordners, der via pip installiert wird. Eventuell kann man das PyPi-Package so anpassen, dass rst2man mitinstalliert wird, aber ob das gewollt ist und wie das geht, weiß ich gerade nicht. Daher habe ich den pragmatisch und schnellen Weg gewählt.

Lazy-load JavaScript

Mein kürzlich schon mal erwähntes Blogprojekt basiert, wie diese Seite hier auch, auf einem Static Site Generator zur Erzeugung des HTML. Hier läuft Nikola im Hintergrund, allerdings bin ich mit einigen Dingen nicht ganz so zufrieden. Zuvorderst ist mir das Datenvolumen, welches für das Laden einer Seite transferiert wird, viel zu groß. So werden aktuell beim Aufruf von blog.pvitt.de 488,04 kB versendet. Davon entfallen aber nur 42,3 kB auf HTML, dafür aber 136,39 kB auf CSS und sogar 311,13 kB auf JavaScript. Das Problem kann man sicher über die Erstellung eines Themes angehen, aber Nikola macht es den Nutzern durch fehlende oder schlechte Dokumentation oder komplexe Strukturen nicht gerade einfach.

Daher habe ich für das neue Projekt nach einem alternativen Static Site Generator umgesehen und habe einige ausprobiert. Nach einigen Tests bin ich dann bei Pelican gelandet, der mich bisher auch nicht enttäuscht hat. Nach der einfachen Installation und der ebenfalls einfachen Installation eines Themes für die Seite sowie ein wenig Optimierung von CSS und mit Verwendung von SVG als Image-Format komme ich mit zwei Testposts auf der Startseite aktuell auf ein Transfervolumen von von 7,13 kB, 3,8 kB HTML, 2,74 kB für CSS und 609 B für SVG. Mit Transportkomprimierung sind das sogar nur 3,85 kB, die tatsächlich über die Leitung gehen. Das ist mal eine andere Hausnummer.

Umso ernüchterter war ich, als ich dann die Kommentarfunktion via isso nachrüsten wollte. Denn dafür ist ein JavaScript-Paket nötig, welches zwar schon minimiert wurde, trotzdem aber noch 54,63 kB auf die Waage (und 20,36 kB auf die Leitung) bringt. Das ist eindeutig zu viel für eine einfache Kommentarfunktion.

Meine Idee war nun, die Kommentarfunktionalität nur dann zu laden, wenn der Nutzer diese auch nutzen möchte. Dazu musste ich zwar ein wenig HTML ergänzen, aber die paar Hundert Byte fallen im Relation zu den 54 kB dann doch nicht wirklich ins Gewicht.

Der Platzhalter im DOM, an dem das JavaScript seinen HTML-Code ablegt, blieb unverändert im Code. Aber das Script-Tag aus dem Header habe ich entfernt:

<script data-isso="//comments.example.tld/"
    src="//comments.example.tld/js/embed.min.js"></script>

Stattdessen wird dieses nun erst auf Benutzerwunsch geladen:

{% if ISSO_SITENAME %}
<input id="issoBtn" type="image" height="16" src="/theme/images/comment.svg" onclick="
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.src = '//{{ ISSO_SITENAME }}/js/embed.min.js';
    document.body.appendChild(s);
    this.style.display = 'none';
    " />
{% endif %}

Das input-Tag wird (wie auch der Platzhalter) nur eingefügt, wenn ISSO auch konfiguriert wurde. Es zeigt eine Sprechblase an, mit SVG realisiert nur 184 Byte groß. Wenn der Benutzer auf diese Sprechblase klickt, wird das JavaScript geladen und angehängt, sodass es aktiv werden kann. Im Anschluss wird noch die Sprechblase versteckt, da nun die Kommentare ja geladen sind.

Isso verfügt auch noch über die Möglichkeit, das clientseitige JavaScript zu konfigurieren. Dies geschieht an Hand von data-*-Attributen, die am Script-Element definiert werden. Da ich das Script-Element erst zur Laufzeit erzeuge, müssen natürlich auch die Attribute zur Laufzeut erzeugt werden. Dazu fügen wir diese Zeilen vor dem Aufruf von document.body.append ein:

var aia = document.createAttribute('data-isso-avatar');
aia.value = 'false';
s.setAttributeNode(aia);
var aiv = document.createAttribute('data-isso-vote');
aiv.value = 'false';
s.setAttributeNode(aiv);

Mit dieser Lösung bleibt das Datenvolumen bei einem normalen Pageload klein, und nur, wenn kommentiert werden soll, wird auch das große JavaScript nachgeladen. Was mich an dieser Lösung noch stört, sind zwei Dinge:

  1. Der Leser sieht nicht, ob es schon Kommentare gibt. Hier überlege ich aktuell noch, wie ich die Anzahl der bisher schon vorhandenen Kommentare ermitteln kann, bevor ich das JavaScript geladen habe.
  2. Die Sprechblase auf der Seite wird auch dann versteckt, wenn das Javscript nicht geladen wurde. Das ist in der Praxis sicher kein wirkliches Problem, da im Fall, dass das JavaScript nicht geladen werden kann, wahrscheinlich die Seite an sich oder der Server einen Knacks hat. Da wäre ein Reload der Seite sicher besser. Aber aus Gründen der Vollständigkeit und weil ich es einfach gern wissen würde, interessiert mich schon, wie ich den HTTP-Response für das Script ermitteln kann.

Zeitnotation

Die Notation von Zeitpunkten kann schon mal zu Verwirrung führen. Bleibt man in einem Kulturkreis, ist es noch recht einfach. Das Datum 02. April 2018 kann jeder Deutsche lesen, wahrscheinlich auch viele Menschen aus anderen Kulturkreisen, sofern sie Januar als Monat (er)kennen. Schwieriger wird es schon, wenn das Datum ohne textuelle Monatsangabe einfach als 02.04.2018 notiert wird. Personen aus dem angelsächsischen Raum werden dies wahrscheinlich als 04. Februar 2018 lesen. Denn sie sind es gewohnt, die Monatsangabe vor die Tagesangabe zu stellen.

In meiner täglichen Arbeit in einem internationalen Team haben wir gelegentlich das Problem, dass Datumsangaben nicht sauber notiert und daher nicht eindeutig erkennbar sind. Zwar haben wir uns auf eine einheitliche Verwendung verständigt, aber manchmal siegt die Gewohnheit und manchmal legen einem Tools Steine in den Weg, weil sie nicht in der Lage sind, das Datum nach unseren Wünschen zu notieren.

Aber wie sieht nun unser gemeinsamer Standard aus, der für Kulturgruppen weltweit lesbar ist? Nun, es gibt mit der ISO 8601 tatsächlich seit längerer Zeit eine ISO-Norm für Datumsangaben. Diese Norm wurde durch die Übernahme in die europäische Norm EN 28601:1992 auch für Deutschland gültig und nennt sich hierzulande ISO 8601:1988.

Mir als Softwerker ist dieses Format schon länger ein Begriff, aber eine Verwendung im Alltag habe ich nie bewusst bemerkt. Daher war ich umso überraschter, als ich in einer sehr guten Zusammenfassung des Formats von Markus Kuhn las, dass dieses Format das Standardformat in Deutschland sei.

In der Wikipedia liest man nun dazu:

Darüber hinaus ist das Datumsformat nach DIN EN 28601 in Deutschland am 1. Mai 1996 zum einzigen normgerechten numerischen Datumsformat (z. B. 1996-05-01) erhoben worden und löste damit das traditionelle Format nach DIN 1355-1 (1.5.1996) ab. Alle Einrichtungen, die im Einflussbereich der DIN-Normen stehen (so auch alle Bildungseinrichtungen und öffentliche Einrichtungen), sind zur Benutzung des neuen Formats angehalten. Allerdings verwendeten große Teile der Bevölkerung im Alltag weiterhin das alte Format, was durch die Neuregelung der DIN 5008 im Jahr 2001 zur Wiederzulassung des üblichen Formats führte, wenn damit keine Missverständnisse entstehen.

Um es meinem in vielen Bereich durch internationale Kontakte geprägten Umfeld zu erleichtern, werde ich daher nur noch das Datum nach ISO8601 verwenden.

Alternierende Navigationselemente mit CSS

Bei einem anderen Blogprojekte verwende ich ein Theme, das auf einem zweispaltiegen Layout aufbaut. Das Theme ist responsiv, es nimmt also abhängig von der Größe des Darstellungsfensters eine andere Struktur an, in meinem Fall bei schmalem Screen ein einspaltiges Layout statt eines zweispaltigen. Nun ist es so, dass im zweispaltigen Layout im Menübereich jeweils eine Liste mit Kategorien und Tags anzeigt werden, die ich im einspaltigen Layout wegen ihrer Position über dem Inhalt nicht mehr anzeigen möchte. Andernfalls müsste der User zu lange scrollen, um beim eigentlichen Inhalt anzukommen.

Nach etwas ratlosem Rumprobieren und CSS-Doku lesen bin ich über das display-Property gestolpert. Damit konnte ich dann etwas umständlich, da ich die Klasse sowohl an der Überschrift als auch an der Liste anhängen musste, die längeren Teile verstecken:

@media (max-width: 800px) {
  .horizontal { display: none; }
}
{% if DISPLAY_CATEGORIES_ON_MENU and categories %}
    <h2 class="horizontal">Kategorien</h2>
    <ul class="navbar horizontal">
        {% for cat, null in categories %}
            <li{% if cat == category %} class="active"{% endif %}>
                <a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a>
            </li>
        {% endfor %}
    </ul>
{% endif %}

display: none ist in diesem Fall visibility: hidden vorzuziehen, da so vermieden wird, dass die versteckten Elemente Platz im Layout belegen. Siehe dazu auch den Abschnitt Hide an Element - display:none or visibility:hidden auf der Seite der W3school.

Als das Verstecken funktioniert hat, kam auch schnell die Idee auf, im einspaltigen Layout etwas Anderes anzeigen zu lassen. Daher habe ich ein inverses media-Tag erstellt und damit die Menüeinträge dekoriert:

@media (min-width: 801px) {
  .vertical { display: none; }
}
<nav class="nav">
    <ul class="list-bare">

        {% for title, link in MENUITEMS %}
            <li><a class="nav__link" href="{{ link }}">{{ title }}</a></li>
        {% endfor %}

        {% if DISPLAY_PAGES_ON_MENU and pages %}{% for p in pages %}
            <li><a class="nav__link" href="{{ SITEURL }}/{{ p.url }}">{{ p.title }}</a></li>
        {% endfor %}{% endif %}

        {% if DISPLAY_CATEGORIES_ON_MENU and categories %}
            <li class="vertical"><a class="nav__link" href="/categories.html">Kategorien</a></li>
        {% endif %}

        {% if DISPLAY_TAGS_ON_MENU and tags %}
            <li class="vertical"><a class="nav__link" href="/tags.html">Tags</a></li>
        {% endif %}
    </ul>
</nav>

Funktioniert prima. Ich bin mir zwar nicht sicher, ob das korrektes und sinnvolles CSS ist und ob man dies eigentlich anders macht, daher bin ich über Hinweise immer dankbar. Was mich noch ein wenig ärgert ist die doppelte Verwendung der horizontal-Klasse für Überschrift und Liste. Ich muss noch mal tiefer in die CSS-Doku eintauchen um zu sehen, ob man dies nicht auch eleganter lösen kann.

Grundig disqualifiziert sich selbst

Vor einiger Zeit habe ich ein Gerät von Grundig gekauft, welches mit einem Akku ausgestattet ist. Nun hat der Akku nach erstaunlich wenigen Ladezyklen die Flügel gestreckt. Das ist zwar ärgerlich, aber sicherlich verschmerzbar, denn man bekommt heutzutage ja fast an jeder Straßenecke die exotischsten Akkus angeboten. Leider lassen sich von den sechs Schrauben im Gehäuse nur fünf entfernen, da die sechste einen Innendreikant aufweist, für den wohl die wenigsten ein passendes Werkzeug vorrätig halten.

Liebe Firma Grundig: Warum? Ich kann es ja verstehen, dass man verschiedene Sollbruchstellen einbaut, um z.B. Sicherheitsbestimmungen zu erfüllen. Aber was überhaupt nicht geht, ist es, zu verhindern, dass Verschleißteile, zu denen der Akku offensichtlich gehört, getauscht werden können. Statt eines neuen Akkus muss ich mir nun ein neues Gerät kaufen. Aber, liebe Firma Grundig, seien sie versichert, dass das neue Gerät nicht aus ihrem Hause stammen wird.

Kein Web-Interface nach Netgear NAS Update

Aus Gründen habe ich mich in das Web-Interface meines Netgear ReadyNAS eingeloggt. Direkt nach dem Login zeigte es mir eine Benachrichtigung über eine neue Version des Betriebssystems, welche ich direkt installieren ließ. Leider war nach dem Update das Web-Interface nicht mehr verfügbar. Er zeigt zwar den Netgear-Splashscreen, und auch die Authentifizierung erfolgte, aber anschließend wurde die Admin-Oberfläche nicht gelanden. Stattdessen wurde eine Fehlerseite angezeigt, die auf eine nicht hilfreiche Supportseite verlinkte.

Da die Kiste prinzipiell ja erreichbar war und sich auch Shares mounten ließen, konnte es eigentlich keine große Sache sein. Also per ssh verbinden und mal im Log schauen. Stellt sich raus, dass der apache über das php-Modul stolpert:

apache2[2555]: apache2: Syntax error on line 244 of /etc/apache2/apache2.conf: Syntax error on line 1 of /etc/apache2/mods-enabled/php5.load: Cannot load /usr/lib/apache2/modules/libphp5.so into server: /usr/lib/apache2/modules/libphp5.so: cannot open shared object file: No such file or directory

systemd[1]: apache2.service: Control process exited, code=exited statAus=1

Netterweise reicht es, /etc/apache2/apache2.conf und /etc/apache2/apache2.load zu löschen, um dem apache wieder auf die Füße zu helfen. Nun halt ohne PHP, aber das scheint nicht benötigt zu werden.

Python iterator in nested types

Will man in Python einen Iterator für eine Container-Klasse erstellen, deren Elemente wiederum Instanzen der Container-Klasse sind, so kann dieser Iterator einfach so erstellt werden:

class Container:

    def __init__(self):
        self.list = []

    def __iter__(self):
        for element in self.list:
            if isinstance(element, Container):
                for subelement in element:
                    yield subelement
            else:
                yield element

As simple as that!