Technik

Perl, Unicode und Umlaute in Dateinamen

09. Juni 2022 · Programmierung · andreas · Kein Kommentar

Das Projekt klingt einfach: eine Liste von Dateinamen aus einem Verzeichnis auslesen, in einer Datenbank speichern und zu einem späteren Zeitpunkt anhand der Liste in der Datenbank wieder öffnen. Leider konnte das Skript einige Dateien nicht mehr finden, obwohl diese im Verzeichnis weiterhin vorhanden waren.

Heruntergebrochen auf ein Beispielskript sieht das Szenario wie folgt aus:

test.pl
#!/usr/bin/perl use strict; use warnings; use utf8; use DBI; # remove database unlink("files.sqlite"); # create database from scratch my $dbh = DBI->connect("dbi:SQLite:dbname=files.sqlite", "", "", { sqlite_unicode => 1 }); $dbh->do("CREATE TABLE files (name TEXT);"); # the file to be processed my $filename = "Elternschreiben Änderungen der Regelungen zum Infektionsschutz.pdf"; # create the file in the current directory open(my $fh, ">$filename"); print $fh "TEXT"; close($fh); # read files in current directory opendir(my $dh, "."); while (my $file = readdir($dh)) { next if $file !~ /pdf$/; $file = $dbh->quote($file); $dbh->do("INSERT INTO files (name) VALUES ($file);"); } closedir($dh); # get number of files with name "filename" $filename = $dbh->quote($filename); my $sth = $dbh->prepare("SELECT COUNT(*) AS cnt FROM files WHERE name = $filename;"); $sth->execute; my $row = $sth->fetchrow_hashref; print "found $row->{cnt} file(s).\n"; $sth->finish; $dbh->disconnect();

Statt des erwarteten Ergebnisses “1” ist das Skript jedoch der Meinung, keine passende Datei eingelesen zu haben:

found 0 file(s).

Um Probleme mit der Datenbank auszuschließen, kann das Szenario im Dateisystem nachgebildet werden:

test.pl
#!/usr/bin/perl use strict; use warnings; use utf8; use DBI; # the file to be processed my $filename = "Elternschreiben Änderungen der Regelungen zum Infektionsschutz.pdf"; my $found = 0; # create the file in the current directory open(my $fh, ">$filename"); print $fh "TEXT"; close($fh); # read files in current directory opendir(my $dh, "."); while (my $file = readdir($dh)) { next if $file !~ /pdf$/; if ($file eq $filename) { $found++; } } closedir($dh); print "found $found file(s).\n";

Auch hier wird statt der erwarteten “1” eine “0” für die Menge der gefundenen Dateien ausgegeben.

found 0 file(s).

Einen ersten Hinweis auf die mögliche Ursache bringt die Ausgabe der zu vergleichenden Werte mittels “Data::Dumper”. Während für “$filename” der Wert

$VAR1 = "Elternschreiben \x{c4}nderungen der Regelungen zum Infektionsschutz.pdf";

ausgegeben wird, wird für “$file” der Wert

$VAR1 = 'Elternschreiben Änderungen der Regelungen zum Infektionsschutz.pdf';

ausgegeben, d.h. die Werte der beiden Variablen sind in der internen Repräsentation in Perl tatsächlich unterschiedlich. Mit den richtigen Anhaltspunkten führt eine Suche im Internet zum Stackoverflow-Beitrag “In what encoding does readdir return a filename?” und der dort verlinkten ausführlichen Erklärung.

Des Rätsels Lösung ist die Verwendung von “Encode::decode_utf8”

test.pl
#!/usr/bin/perl use strict; use warnings; use utf8; use DBI; use Encode; # the file to be processed my $filename = "Elternschreiben Änderungen der Regelungen zum Infektionsschutz.pdf"; my $found = 0; # create the file in the current directory open(my $fh, ">$filename"); print $fh "TEXT"; close($fh); # read files in current directory opendir(my $dh, "."); while (my $file = readdir($dh)) { next if $file !~ /pdf$/; if (Encode::decode_utf8($file) eq $filename) { $found++; } } closedir($dh); print "found $found file(s).\n";

dann stimmt auch die Anzahl der gefundenen Dateien mit der erwarteten Anzahl überein:

found 1 file(s).

Debian 11, GNOME und RDP: Oh no! Something has gone wrong

27. Mai 2022 · Betriebssysteme · andreas · Kein Kommentar

Während eine RDP-Verbindung zum GNOME-Desktop mit Debian 10 problemlos funktionierte, wurde nach einem Upgrade auf Debian 11 jeder Verbindungsversuch mit “Leider ist ein Problem aufgetreten.” beendet:

Die Problematik ist sowohl auf Github unter “On Debian 11 upgraded install I get “Oh no! Something has gone wrong.” when connecting.” als auch in dem Debian Bug Report “xRDP connection results in Oh No ! something has gone wrong..Logout when Gnome Desktop used” dokumentiert, die Ursache liegt wohl in einem Fehler in der von Debian 11 ausgelieferten “xorgxrdp-0.2.12”-Version.

Da es aktuell kein fehlerbereinigtes Paket in den Standard-Paketquellen gibt, kann behelfsweise ein Paket von https://snapshot.debian.org/ verwendet werden. Die im oben genannten Github-Thread vorgeschlagenen Paketversionen “xorgxrdp_0.2.15-1_amd64.deb” sowie “xrdp_0.9.15-1_amd64.deb” laufen nach manueller Installation ohne weitere Abhängigkeiten in der Debian 11-Umgebung und ermöglichen wieder einen Zugriff auf den GNOME-Desktop mit RDP:

$ wget https://snapshot.debian.org/archive/debian/20210302T032219Z/pool/main/x/xorgxrdp/xorgxrdp_0.2.15-1_amd64.deb ... $ wget https://snapshot.debian.org/archive/debian/20210302T032219Z/pool/main/x/xrdp/xrdp_0.9.15-1_amd64.deb ... $ sudo apt install ./xorgxrdp_0.2.15-1_amd64.deb ... Die folgenden Pakete werden aktualisiert (Upgrade): xorgxrdp 1 aktualisiert, 0 neu installiert, 0 zu entfernen und 5 nicht aktualisiert. ... $ sudo apt install ./xrdp_0.9.15-1_amd64.deb ... Die folgenden Pakete werden aktualisiert (Upgrade): xrdp 1 aktualisiert, 0 neu installiert, 0 zu entfernen und 5 nicht aktualisiert. ...

Exiled Kingdoms-Spielstände bearbeiten

17. Mai 2022 · Spiele · andreas · 5 Kommentare

Auch wenn es meist der erste Schritt ist, jeglichen Spielspaß zu runinieren, so gibt es ab und an Situationen, in denen man eine kleine Korrektur an einem Spielstand vornehmen möchte. Nachdem die Suche nach “Exiled Kingdoms savegame editor” zu keinerlei Treffern geführt hat, hieß es selbst tätig werden.

Exiled Kingdoms

Zuerst müssen die Spielstände aus dem geschützen Speicher des Android-Geräts in einen zugänglichen Bereich exportiert werden, dies geschieht im Spiel mittels der Funktion “Continue Game” / “Export & Cloud Save” / “Export your saves to a file”. Im Datenverzeichnis des Android-Geräts (i.d.R. “/storage/emulated/0/”) wird eine Datei “EK.bak” angelegt, welche die Spielstände enthält.

Das Bearbeiten erledigt man am einfachsten am PC, wohin die Datei als nächstes übertragen wird. Bei der “EK.bak” handelt es sich um eine normale ZIP-Datei:

$ file EK.bak EK.bak: Zip archive data, at least v2.0 to extract

Umbenennen ist nicht notwendig, die Datei kann direkt ausgepackt werden:

$ unzip EK.bak Archive: EK.bak inflating: saves/0/auto.sav inflating: saves/0/auto.sav.nfo ... inflating: saves/global.sav

Nach dem Entpacken liegen die einzelnen Spielstände im Verzeichnis “./saves/0”, eine Beschreibung des Spielstands kann der jeweiligen “nfo”-Datei entnommen werden:

$ cat auto.sav.nfo Frozen Depths [GREEN]Level 9.[] Day 36, 4 AM

Die Spielstände selbst sind allerdings (noch) nicht direkt bearbeitbar, sondern wurden im “base64”-Format kodiert. Die Umwandlung ist allerdings schnell erledigt:

$ base64 -d auto.sav > auto.txt

Jetzt kann die Datei “auto.txt” mit einem Texteditor geöffnet und bearbeitet werden. Hierbei sollte nach Möglichkeit ein Texteditor verwendet werden, der weder die Zeichenkodierung noch die Zeilenenden verändert.

... stats: { cached_maxhp: 99 characterRace: HUMAN cached_level: 9 missingHP: 126 XP: 41911 characterClass: WARRIOR cache_valid: true } ...

Sobald die gewünschten Änderungen durchgeführt wurden, wird die Datei wieder zurückkodiert und die Textdatei (sofern gewünscht) wieder gelöscht:

$ base64 -w 0 auto.txt > auto.sav $ unlink auto.txt

Dann wird das ZIP aktualisiert

$ zip -f -r EK.bak saves/ freshening: saves/0/auto.sav (deflated 86%)

Anschließend wird die “EK.bak” zurückkopiert und in Exiled Kingdoms mittels der Schritte “Continue Game” / “Export & Cloud Save” / “Restore your saves from a file” wieder importiert.

Diese Beschreibung bezieht sich auf die Spielstände der Exiled Kingdoms Android-Version 1.3.1182, bei anderen Versionen kann die Vorgehensweise unterschiedlich sein.

Bessere Fehlerbehandlung mit Try::Tiny

14. April 2022 · Programmierung · andreas · Kein Kommentar

Die im Beitrag “MIME::Lite Fehlerbehandlung” implementierte Fehlerbehandlung mit “eval” funktioniert zwar in den meisten Fällen, kann aber in bestimmten Situationen doch nicht zum gewünschten Ergebnis führen.

Eine bessere Alternative ist die Verwendung von z.B. Try::Tiny, welches sich mit den aus anderen Sprachen bekannten “try”, “catch” und “finally”-Anweisungen um die Fehlerbehandlung kümmert:

test.pl
use strict; use warnings; use MIME::Lite; use Try::Tiny; MIME::Lite->send('smtp', 'mailserver', Debug => 0); my $msg = MIME::Lite->new( From => 'me@whereever.net', To => 'you@somewhereelse.net', Data => 'Hello World!', Subject => 'testmail', ); try { $msg->send; } catch { warn "you DON'T have mail!"; }; print "... still running ...";

Nach der Warnung “you DON’T have mail!” wird das Programm mit “… still running …” fortgesetzt.


MIME::Lite Fehlerbehandlung

13. April 2022 · Programmierung · andreas · Kein Kommentar

Ein Skript, welches MIME::Lite zum Versenden von Mails verwendet, hat sich im Fehlerfall lieber direkt mit einem “SMTP Failed to connect to mail server: Bad file descriptor” beendet, statt gemäß Dokumentation auf den Fehler zu reagieren.

test.pl
use strict; use warnings; use MIME::Lite; MIME::Lite->send('smtp', 'mailserver', Debug => 0); my $msg = MIME::Lite->new( From => 'me@whereever.net', To => 'you@somewhereelse.net', Data => 'Hello World!', Subject => 'testmail', ); $msg->send || die "you DON'T have mail!"; print "... still running ...";

Weder “you DON’T have mail!” noch “… still running …” werden ausgegeben, da diese Stellen im Code auf Grund des Programmabbruchs nicht mehr erreicht werden.

Leider ist die Erläuterung zum Verhalten der Funktion “$msg->send” nicht wirklich hilfreich:

Returns whatever the mail-handling routine returns: this should be true on success, false/exception on error.

Eine Suche im Internet führte zum Blogbeitrag “Perl exception handling”, welcher genau das aufgetretene Verhalten beschreibt und eine Lösungsmöglichkeit mittels “eval” aufzeigt:

test.pl
use strict; use warnings; use MIME::Lite; MIME::Lite->send('smtp', 'mailserver', Debug => 0); my $msg = MIME::Lite->new( From => 'me@whereever.net', To => 'you@somewhereelse.net', Data => 'Hello World!', Subject => 'testmail', ); eval { $msg->send; }; if ($@) { print "you DON'T have mail!"; } print "... still running ...";

So gekapselt läuft das Skript auch nach einem gescheiterten “$msg->send” weiter und auf einen eventuellen Fehler kann reagiert werden.