QR Code Business Card

noctus.net

ねぇ、今日はどこに行こうかな?

Archiv für den Tag ‘Code’

Extbase: Model-Zugriff in Validatoren

keine Kommentare

Für den Fall, dass die Standard-Validatoren von Extbase oder benutzerdefinierte Validatoren für einzelne Eigenschaften eines Models nicht genügen, gibt es noch die Option eines benutzerdefinierten Validators mit vollem Zugriff auf die neue Instanz eines Models.

Dafür kann man sich eine gänzlich undokumentierte Feinheit des Extbase-Frameworks zunutze machen: standardmäßig wird in einem Standardpfad versucht, einen passenden Validator passend zu einem Model zu finden. Wieder ein Beispiel für „Konvention über Konfiguration“, worauf in Extbase und FLOW3 Wert gelegt wird.

Der besagte Validator wird standardmäßig unter Classes/Domain/Validator/ModelNameValidator.php gesucht. Wie bei allen Extbase-Validatoren üblich ist zur Erfüllung der Schnittstelle Tx_Extbase_Validation_Validator_ValidatorInterface (OOP-Konvention wäre hier eigentlich IValidator) eine Ableitung von Tx_Extbase_Validation_Validator_AbstractValidator empfehlenswert. Dessen isValid-Methode erhält als einziges Argument die betreffende Instanz des Models, womit dann beliebige Überprüfungen vorgenommen werden können. Da hier ein uneingeschränkter Zugriff auf alle Eigenschaften des Models gegeben ist, können so auch komplexe Validierungen mit Abhängigkeiten zwischen Eigenschaften und dergleichen vorgenommen werden.

Der Validator sollte zu guter Letzt wie immer true oder false zurückgeben um über das Ergebnis der Validierung zu informieren und im Fehlerfall zuvor noch eine aussagekräftige Fehlermeldung vermerken. Dies kann entweder über die addError-Methode oder direkt per Manipulation der errors-Eigenschaft geschehen:

// 1) addError()
$this->addError('Descriptive error message', 0000000000); // Hier sollte 0000000000 eine eindeutige Nummer sein
 
// 2) errors
$this->errors[] = new Tx_Extbase_Validation_Error('Descriptive error message', 0000000000); // Siehe oben

Letztere Variante bietet die Möglichkeit, manuell Instanzen von Tx_Extbase_Validation_PropertyError einzufügen. Dadurch können bestehende, auf Model-Eigenschaften optimierte, Partials für die Ausgabe von Validierungsfehlern ohne Anpassung verwendet werden. Dazu hängt man die eigentlichen Fehlermeldungen an eine Fake-Eigenschaft:

// Container für gruppierte Fehlermeldungen über eine Fake-Eigenschaft anlegen
if (!isset($this->errors['fakeProperty']) {
 
  $this->errors['fakeProperty'] = new Tx_Extbase_Validation_PropertyError('fakeProperty');
}
 
// Eigentliche Fehlermeldungen an den Container anhängen
$this->errors['fakeProperty']->addErrors(array(
  new Tx_Extbase_Validation_Error('Descriptive error message', 0000000000); // See above
));

Da dieser Validator allgemein für das Model gilt, wird er immer dann zur Rate gezogen, wenn eine neue Instanz des Models im Zuge einer Aktion angelegt werden soll. Wahlweise kann man dies allerdings auch manuell für die gewünschten Aktionen eines Controllers über die  @validate …Validator.php DocComment-Notation erfolgen. Allerdings sollte man dann darauf achten, dass der Validator nicht der oben beschriebenen Pfad-Konvention folgt. Andernfalls wird der Validator wie gehabt immer aufgerufen, @dontvalidate einmal ausgenommen. Auf diese Weise können der Zielaktion auch beliebige weitere Validatoren hinzugefügt werden, welche jeweils den gleichen Zugriff auf das Model haben.

Geschrieben von Mathias

16. Mai 2011 um 21:31

Tags: , , ,

Mingetty und systemd

keine Kommentare

Das von systemd standardmäßig für das Öffnen virtueller Terminals genutzte agetty verfügt verglichen mit mingetty nur über einen sehr beschränkten Funktionsumfang. So ist gerade die Option --autologin von mingetty interessant, wenn man beim Start des Systems automatisch einen Nutzer ohne manuelle Eingaben anmelden möchte. Unter SysVinit ist hierfür folgender Eintrag in der /etc/inittab erforderlich:

1:2345:respawn:/sbin/mingetty --autologin USER tty1

Die Konfiguration unter systemd gestaltet sich dagegen komplett anders, ermöglicht aber eine nicht minder flexible Anpassung:

  1. Die Datei /lib/systemd/system/getty@.service nach /etc/systemd/system kopieren
  2. Die Datei /etc/systemd/system/getty@.service editieren und in der Sektion [Service] die Option ExecStart wie folgt ändern:
    ExecStart=-/sbin/mingetty --autologin USER %I
  3. Ändern des Standard-Symlinks für die Startdefinition des getty-Dienstes auf tty1 (analog für die anderen virtuellen Terminals):
    ln -sf /etc/systemd/system/getty@.service \
    /etc/systemd/system/getty.target.wants/getty@tty1.service

Hiernach kann der Dienst neu gestartet werden. Eine laufende X-Sitzung wird damit allerings auch beendet:

systemctl restart getty@tty1.service

Ab diesem Zeitpunkt wird bei jedem Systemstart der per USER spezifierte Nutzer automatisch angemeldet. Als kleiner Bonus noch eine mögliche Vorgehensweise, um dabei auch automatisch den X-Server zu starten:

if [ -z "$DISPLAY" -a $(tty) = /dev/tty1 ]; then
  startx
fi

Hier wird geprüft, ob noch kein X-Server gestartet wurde (die Umgebungsvariable $DISPLAY ist andernfalls gesetzt) und ob wir uns gerade auf tty1 befinden, von wo aus üblicherweise der erste X-Server gestartet wird. Einzutragen ist das ganze in die ~/.bash_profile, nicht in die ~/.bashrc.

Geschrieben von Mathias

13. Mai 2011 um 22:58

Tags: ,

Shell-Autovervollständigung

keine Kommentare

Um den Umgang mit der Shell zu vereinfachen, habe ich schon seit geraumer Zeit die sofortige Anzeige aller Optionen bei einer mehrdeutigen Autovervollständigung aktiviert. Hierzu genügt folgender Eintrag in der ~/.inputrc:

# If you have this in your /etc/inputrc or ~/.inputrc, you will no longer
# have to hit the <Tab> key twice to produce a list of all possible
# completions.
# A single <Tab>  will suffice.
set show-all-if-ambiguous on

Dazu gesellte sich auch immer gleich die Festlegung der Autovervollständigung auf die Tab-Taste:

TAB:menu-complete

Nun bin ich auch endlich auf einen Hinweis gestoßen, wie man das Gegenstück, das rückwärtige Durchlaufen der Optionen, mittels Shift-Tab erreichen kann:

"\e[Z":menu-complete-backward

Die geliebten Einzeiler präsentieren sich wieder einmal in ganzer Pracht.

Geschrieben von Mathias

18. April 2011 um 23:07

Tags: , ,

Mehr Liebe für Exaile

keine Kommentare

Das ständige Aufrufen meines Last.fm-Profils im Browser um ein Stück meinen Lieblingen hinzuzufügen nervte mich schon lange und eine Integration dieser Funktion in Exaile lag auf der Hand. Geplant hatte ich dies schon seit langer Zeit und vergangenes Wochenende befasste ich mich nun damit. Heraus kam das Plugin Last.fm Lieblinge:

Exaile-Wiedergabeliste mit Plugin-Integration als Spalte und im Kontextmenü

Integration in Exailes Wiedergabelisten

Durch die hervorragende Dokumentation der Last.fm-API waren mir bereits die API-Aufrufe track.love und track.unlove bekannt. Allerdings grübelte ich lange darüber nach, wie ich den derzeitigen Lieblings-Status eines Stücks abfragen könnte. Aus früheren Experimenten meinte ich mich zu erinnern, dass track.getInfo unter anderem einen Eintrag dafür lieferte. Dem ist allerdings nicht so und nach einem kurzen Besuch in #audioscrobbler wurde mir nahe gelegt, den Aufruf user.getLovedTracks zu nutzen. Eingangs scheute ich noch die Iteration über alle Stücke in dieser Liste, da die lokalen Tag-Informationen ja durchaus von denen auf Last.fm aufgrund automatischer Korrekturen abweichen können. Schnell wurde jedoch klar, dass dies der einzig gangbare Weg ist, da es 1. sowieso keine andere Möglichkeit gibt, den Status abzufragen und 2. ein einmaliges Abrufen aller Lieblings-Stücke deutlich weniger Datenverkehr verursacht als eine ständige Abfrage pro Stück.

Nachdem dies geklärt war ging es an den zweiten wichtigen Aspekt: Schreibzugriffe auf Profile. Die Aufrufe track.love und track.unlove erfordern wie zu erwarten ist Schreibzugriff auf das Profil des jeweiligen Nutzers. Ein Einbetten meines persönlichen API-Schlüssels und Geheimnisses stand außer Frage; hierdurch hätte  jeder Schreibrechte auf die Profile aller Nutzer, die diesen Zugriff auf ihr Profil erlaubt hätten. Mehr als ein Kopieren des API-Schlüssels und Geheimnisses aus dem Quellcode des Plugins wäre hierfür nicht notwendig gewesen.

Aus diesem Grund entschied ich mich für das einzig Logische: jeder Nutzer müsste dem Plugin zur Nutzung seinen ganz persönlichen  API-Schlüssel und das zugehörige Geheimnis mitteilen. Damit obliegt dem Nutzer weiterhin die Entscheidung, ob Exaile auf sein Profil zugreifen darf und die erforderlichen Authentifizierungsdaten bleiben privat. Grafisch sieht das ganze daher nun so aus:

Dialog mit Einstellungen des Plugins Last.fm Lieblinge

Einstellungsdialog

Ein Klick auf die Schaltfläche „Zugriffsgenehmigung anfragen“ öffnet die Seite zum Verbinden von Apps auf Last.fm im Browser.

Bei der Umsetzung habe ich besonders Wert auf Multithreading gelegt, denn nichts nervt mehr als eine Anwendung, die nicht mehr reagiert, weil sie irgendetwas Aufwändiges im Hintergrund macht. Die Integration in Exailes grafische Oberfläche gestaltete sich dank des bereits vorhandenen Providers-Frameworks simpel. Der zum Umschalten des Lieblings-Status verwendete CellRendererToggleImage ist gekapselt und andernorts uneingeschränkt nutzbar. Die Kommunikation mit Last.fm (und in Zukunft auch Libre.fm, sollte dieser Dienst die AudioScrobbler 2.0 API implementieren) findet durch die wunderbare pylast-Bibliothek statt. Praktisch alle API-Aufrufe werden von dieser gekapselt und komfortabel zur Verfügung gestellt. Lediglich den track.unlove-Aufruf musste ich manuell hinzufügen.

Nunmehr genügt ein einzelner Klick auf das Herz-Symbol in der durch das Plugin hinzugefügten Spalte für Wiedergabelisten oder wahlweise der passende Eintrag im Kontextmenü von Stücken um diese als Lieblinge zu markieren oder diese Markierung zu entfernen. Für Letzteres hatte ich bisher allerdings noch keinen Bedarf. ;-)

Die Integration der Lieblingsstücke als benutzerdefinierte oder dynamische Wiedergabeliste ist schon vorgesehen. Langfristig ist übrigens auch geplant, dieses und alle anderen AudioScrobbler-bezogenen Plugins zu vereinen. Bis dahin gibt es noch viele interessante Funktionen, die es zu integrieren heißt.

Geschrieben von Mathias

25. März 2011 um 00:44

Tags: , , ,

Geliebte Einzeiler

keine Kommentare

Da mich das lautstarke Knarzen meiner Logitech MX518 beim Scrollen schon lange störte und auch die Oberfläche dieser nicht mehr wirklich angenehm war, gönnte ich mir nach langer Zeit einmal etwas Neues. Nach kurzer Suche fiel meine Wahl auf die Logitech M500:

Damit kann ich nun endlich laut- und scheinbar endlos scrollen. Die Kippfunktion des Mausrads ist allerdings nicht ganz glücklich geraten; bei einem Mittelklick löst man viel zu leicht die Vor/Zurück-Funktionalität aus. Das fehlen der Tasten zum Umstellen der Auflösung im Vergleich zur MX518 kann ich verschmerzen, da ich diese sowieso nie wirklich genutzt habe. Nichtsdestotrotz wünschte ich mir bei meiner neuen Maus eine höhere Auflösung; die augenscheinlich standardmäßigen 400 CPI sind mir zu wenig.

Von früheren Versuchen war mir noch das Werkzeug Lomoco bekannt, welches ebenso die Möglichkeit bietet, die Auflösung sowie einige anderen Parameter von Logitech-Mäusen anzupassen. Der Versuch sollte allerdings erst einmal fehlschlagen:

$ lomoco --1200
002.018: 046d:c069 Unsupported Logitech device: Unknown

Da hieß es nicht verzagen sondern Quellen laden. Die schließlich notwendige Anpassung erwies sich als einer der heißgeliebten Einzeiler:

--- src/lomoco.c    2011-03-05 22:06:12.000000000 +0100
+++ src/lomoco.c    2011-03-05 22:07:23.000000000 +0100
@@ -47,6 +47,7 @@
 {0xc025, "MX500 Optical Mouse",                        "M-BP81A",     0, 1, 1, 1, 0},
 {0xc031, "iFeel Mouse (silver)",                       "M-UT58A",     0, 1, 0, 0, 0},
 {0xc041, "G5 Laser Gaming Mouse",                      "M-UAC113",    0, 1, 0, 1, 0},
+    {0xc069, "M500 Laser Mouse",                           "M-500",       0, 1, 1, 0, 0},
 {0xc501, "Mouse Receiver",                             "C-BA4-MSE",   1, 0, 0, 0, 0},
 {0xc502, "Dual Receiver",                              "C-UA3-DUAL",  1, 0, 0, 0, 1},
 {0xc503, "Receiver for MX900 Receiver",                "C-UJ16A",     1, 0, 0, 1, 0},

Der Quellcode selbst dokumentierte in einfacher Form, wie man an die nötigen Angaben gelangen kann. (In diesem Fall der Inhalt von /proc/bus/input/devices)

Und damit kann ich nun meine neue Maus per Lomoco konfigurieren. :-) Um das ganze festzuhalten erstellte ich auch gleich einen Report für das Lomoco-Debian-Paket.

Geschrieben von Mathias

5. März 2011 um 23:55

Aktueller Befehl im Terminaltitel

keine Kommentare

Ein schönes Feature worauf ich nicht mehr verzichten möchte ist die automatische Anzeige des aktuell ausgeführten Befehls im Titel des Terminals bzw. des Tabs. Dadurch wird die Arbeit mit multiplen Fenstern bzw. Tabs ungemein einfacher. Hier die Umsetzung, einzutragen in die ~/.bashrc:

function reset_prompt_and_title() {
  # Gewünschten Standard-Prompt hier angeben
  PS1='\[\033[0;32m\]\u\[\033[00m\] \[\033[0;33m\]\w\[\033[00m\] \$ '
 
  echo -ne "\e]0;${USER}@${HOSTNAME}: ${PWD}\a"
  # Variante: Die überflüssige Angabe des Home-Verzeichnisses
  #           durch die übliche Tilde ersetzen
  #echo -ne "\e]0;${USER}@${HOSTNAME}: ${PWD/#${HOME}/~}\a"
}
 
# Nur für grafische Terminals gedacht
case "$TERM" in
  xterm*|rxvt*)
    # Eventuelle weitere Kommandos für $PROMPT_COMMAND unbedingt erst
    # nach dieser Zeile anhängen
    PROMPT_COMMAND=reset_prompt_and_title
 
    # Aktuell ausgeführten Befehl in den Terminaltitel schreiben
    trap 'echo -ne "\e]0;$BASH_COMMAND\a"' DEBUG
    ;;
esac

Geschrieben von Mathias

27. August 2010 um 00:58

Tags: ,

Zugriff auf USB-Datenträger mittels udisks

keine Kommentare

Um endlich in den Genuss eines aktuellen Thunars mit Gio-Unterstützung zu kommen begab ich mich auf eine kurze Suche und wurde im Xubuntu Developers PPA fündig. Die Aktualisierung verlief reibungslos, womit ich nun auch einmal auf entfernte Quellen zuzugreifen. So habe ich bisher auf mein Heimverzeichnis meiner Hochschule per sshfs und einem Eintrag in /etc/fstab zugegriffen. Grafisch einigermaßen komfortabel habe ich das Einhängen mit Hilfe einer benutzerdefinierten Aktion in Thunar vorgenommen.

Doch mittels Gio-Unterstützung in Thunar sollte dies der Vergangenheit angehören. Also rief ich ssh://ilux150.informatik.htw-dresden.de auf und erfreute mich der Tatsache, dass dies einfach funktionierte. Auch FTP-Zugriff funktioniert einwandfrei, auch wenn das wünschenswerte FTPES noch nicht in Gvfs implementiert wurde.

Schnell zeigte sich allerdings ein Problem: beim versuchten Zugriff auf meinen USB-Stick wurde ich nun mit einer Not Authorized Fehlermeldung abgewiesen. Nach kurzer Suche zu diesem Problem erkannte ich schnell, dass hier PolicyKit seine Finger im Spiel hat. Weiteres Suchen offenbarte mir schließlich eine Lösung, welche ich unter /etc/polkit-1/localauthority/50-local.d/plugdev.plka platzierte:

[Access to removable media for the plugdev group]
Identity=unix-group:plugdev
Action=org.freedesktop.udisks.drive-eject;org.freedesktop.udisks.filesystem-mount
ResultAny=yes

Damit allein war es jedoch noch nicht getan, da mir ck-list-sessions nach wie vor bescheinigte, dass die ConsoleKit-Sitzung nicht aktiv sei. Ohne eine aktive ConsoleKit-Sitzung könnte kein Programm unter X Dienste wie Udisks in Anspruch nehmen. Lange Rede kurzer Sinn war die Lösung schließlich, meine .xinitrc folgendermaßen zu gestalten:

exec ck-launch-session startxfce4

Hierdurch wird Xfce in einer ConsoleKit-Sitzung gestartet, womit ich nun ohne weiteres auf meine USB-Datenträger zugreifen kann. (Nachtrag: mit dem 4.7.0 Release von xfce4-session sollte dies nicht mehr erforderlich sein.) Meiner Meinung nach sollten Distributionen den hier beschriebenen Zugriff auf USB-Datenträger von Haus aus für reguläre Nutzer erlauben. Einige wie Ubuntu werden das auch sicher machen, bei Debian ist die Akzeptanz angesichts des serverlastigen Einsatzgebietes allerdings ungewiss.

Geschrieben von Mathias

24. August 2010 um 12:41

Tags: , ,

Mausklicks in GtkLabel

keine Kommentare

Entgegen der landläufigen Behauptung ist es durchaus möglich, Mausklicks auf GtkLabel-Instanzen zu verarbeiten. Der Schlüssel liegt darin, GTK anzuweisen, dass das jeweilige Label ein GdkWindow haben soll. Aufmerksam gemacht wurde ich hierauf durch diesen Beitrag von Tadej Borovšak. Hier ein Beispiel:

#! /usr/bin/env python
 
import gtk
 
window = gtk.Window()
window.set_title('Label Button Press Test')
window.set_size_request(400, 300)
window.connect('destroy', lambda w: gtk.main_quit())
 
box = gtk.VBox(spacing=6)
window.add(box)
 
class ClickableLabel(gtk.Label):
    __gsignals__ = {'button-press-event': 'override'}
 
    def __init__(self, *args):
        gtk.Label.__init__(self, *args)
        # This is the important line to enable more signals
        self.set_has_window(True)
        self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
 
    def do_button_press_event(self, event):
        print 'Clickable: %s' % event.type
 
clickable_label = ClickableLabel()
clickable_label.set_markup('<span size="25000">Clickable label</span>')
box.pack_start(clickable_label, False)
 
def on_button_press_event(widget, event):
    print 'Regular: %s' % event.type
 
label = gtk.Label()
label.set_markup('<span size="25000">Regular label</span>')
# Does not work and renders the label invisible
label.set_has_window(True)
label.add_events(gtk.gdk.BUTTON_PRESS_MASK)
label.connect('button-press-event', on_button_press_event)
box.pack_start(label, False)
 
window.show_all()
gtk.main()

Wie zu sehen ist, lassen sich unter Nutzung einer einzelnen Zeile weitere Signale für ein GtkLabel „freischalten“. Jedoch nur für abgeleitete Typen, nicht für die Standard-GtkLabels, wie am zweiten Label zu erkennen ist. Warum dies so ist ist mir trotz Blick auf gtkwidget.c noch nicht ersichtlich. Erklärungen zur Methode set_has_window() erwähnen, dass diese nur in der Initialisierung eines Widgets aufgerufen werden sollte. Intern wird hier nur das GTK_NO_WINDOW-Flag gesetzt oder gelöscht und infolge dessen unter anderem das Zeichnen von Widgets anders gehandhabt. Aus diesem Grund hat ein GtkLabel bspw. auch keinen Hintergrund welchen man ändern könnte.

Geschrieben von Mathias

26. Juli 2010 um 18:43

Tags: , ,

Iteration durch gtk.TreeModel

keine Kommentare

Zur Verarbeitung von Einträgen in einem gtk.TreeModel bedient man sich in Python üblicherweise der Iteration. Hierbei bietet gtk.TreeModel einige Methoden zur Arbeit damit. Ein übliches Konstrukt sieht damit so aus:

iter = model.get_iter_first()
while True:
    value = model.get_value(iter, 0)
    # Do something with value
    iter = model.iter_next(iter)
    if iter is None:
        break

Ziemlich umständlich und daher oft auch in dieser etwas vereinfachten Version vorzufinden:

iter = model.get_iter_first()
while iter:
    value = model.get_value(iter, 0)
    # Do something with value
    iter = model.iter_next(iter)

Doch wie so oft in Python gibt es eine viel intuitivere und simplere Lösung; gtk.TreeModel implementiert __iter__ und __next__, womit sich das obige Konstrukt auf folgenden Zweizeiler reduzieren lässt.

for row in model:
    value = row[0]

Bei row handelt es sich um eine gtk.TreeModelRow, welche intuitiven Zugriff auf die Daten der jeweiligen Spalten ermöglicht.

Geschrieben von Mathias

7. Mai 2010 um 21:35

Tags: , ,

Operatoren-Verkettung in Python

keine Kommentare

Eher durch Zufall bin ich heute auf etwas gestoßen, was ich mir seit jeher in einer vernünftigen Programmiersprache gewünscht aber bis jetzt nie vorgefunden habe:

if a < b < c
    # …

Ein völlig intuitives und sehr gut lesbares Konstrukt zum einfachen Vergleich dreier oder mehr Operanden. In praktisch jeder Sprache muss man sich jedoch mit diesem Konstrukt behelfen:

if a < b and b < c
    # …

Nicht so jedoch in Python. Dort ist genau obiges möglich. Hierbei können offenbar beliebig viele Operanden verkettet werden. Eine feine Sache.

Geschrieben von Mathias

30. März 2010 um 17:44

Tags: ,