Nur halb so schlimm: Eigene Short-URLs

05 09 2009

Vor einiger Zeit habe ich gefordert, dass größere und oft bei Twitter verlinkte Seiten eigene kurze URLs für ihren Content anbieten. Neulich erst habe ich mir dann Gedanken gemacht, wie man Short-URLs uns so verüberflüssigen kann. Auf diese große Lösung mag ich nicht warten, aber zumindest bei der kleinen Lösung kann ich mit gutem Beispiel voran gehen. Also habe ich gerade schnell ein kurzes Script geschrieben, das kurze URLs für mein Blog in die vollen URLs umsetzt. Zum Beispiel lautet für diesen Eintrag die kurze URL http://spackblog.de/668, was mit 23 Zeichen deutlich kürzer ist als die verdammt lange volle URL und vor allem kürzer als 30 Zeichen, ab denen Twitter ungefragt mit bit.ly verkürzt.

Ich hatte eine Weile überlegt, diese Funktionalität als Serendipity-PlugIn zu veröffentlichen, aber das habe ich aus verschiedenen Gründen nicht gemacht. Ein Grund ist, dass mich ein undankbarer Vollspacko im S9Y-Forum angesaugt und als Schnösel bezeichnet hat, als ich mein letztes PlugIn dort vorgestellt habe. Ich will da gar nicht genauer drauf eingehen, Folge ist jedenfalls, dass meine Lust auf die extra Arbeit für ein S9Y-PlugIn dadurch doch sehr gesunken ist. Ein weiterer Grund ist, dass der generierte Link irgendwo im Template und noch mal im Header positioniert werden muss, damit es Sinn macht. Das kann ein PlugIn nicht flexibel alleine regeln. Man könnte den Link allenfalls dort hinpacken, wo momentan auch die Tags sind, aber das gefällt sicher nicht jedem. An die .htaccess des Blogs, oder wenn dieses in einem Unterverzeichnis steckt auch an die .htaccess der Domain muss man auch noch ran. Spricht alles gegen ein PlugIn. Sowas gehört in meinen Augen sowieso in den S9Y-Core.

Das Script funktioniert super simpel. Alles fängt mit einer Anweisung in der .htaccess an, die alles, was nach kurz-URL aussieht an das Script weiterreicht:

RewriteEngine On
RewriteBase /
RewriteRule ^(e|a|c)?([0-9]+)/?$ s9y_shorturl.php?type=$1&id=$2 [NC,L]

Man kann schon sehen, dass das Script auch kurze URLs für Kategorien und Autoren unterstützt. Der spannende Teil im Script selber ist die Datenbankabfrage, die die kurze URL aus der Datenbank ausliest:


// open a database-connection
$dbh = new PDO('mysql:host=' . $db_server . ';dbname=' . $db_database, $db_user, $db_pass);

// prepare the statement
$stmt = $dbh->prepare('SELECT permalink
  FROM s9y_permalinks
  WHERE type = :type
    AND entry_id = :entry_id
  LIMIT 1');

// execute the statement
$stmt->execute(array(':type' => $type, ':entry_id' => $entry_id));
  
// read the returned urlpart
$urlpart = $stmt->fetchColumn();

// close the database-connection
$dbh = null;

// redirect the user with a 301 status code (moved permanently)
header('Location: ' $blog_base_url . $urlpart, true, 301);
exit;

Der Rest vom Script besteht aus Fehlerbehandlung und Variablen-Vorbereitung, die ich hier mal weg lasse. Wer den ganzen Code haben will, kann sich gerne bei mir melden. Damit ist der erste Teil schon fertig, der die kurzen URLs auflöst. Für die kurzen URLs werden einfach die von Serendipity vergebenen IDs genutzt, der Parameter type kann übrigens 'entry', 'category' oder 'author' sein. S9Y führt eine eigene Tabelle für die Zuordnung von IDs und URLs, die man nur abfragen muss.

Der zweite Teil ist leider Handarbeit im Template. Man muss dazu an der Stelle, wo der Link auftauchen soll folgenden Code in der entries.tpl platzieren: <a href="http://SHORTURL_DOMAIN/{$entry.id}">http://SHORTURL_DOMAIN/{$entry.id}</a>. SHORTURL_DOMAIN muss natürlich durch die jeweilige URL ersetzt werden, in meinem Fall ist das tatsächlich eine andere Domain als die, auf der das Blog läuft. Ich habe zusätzlich noch in der index.tpl im Headerbereich im {if $entry.id} Block einen Short-URl Autodiscovery Link eingefügt, wie er hier beschrieben ist.

Das ganze arbeitet extrem simpel und macht genau das, was es soll: Wenn jemand meinen Blog bei Twitter und Co. verlinken will, kann jetzt meine 23 stellige Kurz-URL hernehmen und sich so den Scheiß mit den verkürzten URLs sparen, bei denen man nicht weiß, wohin sie eigentlich linken. Alle Seiten sollten solche eigenen Kurz-URLs haben, dann wäre das mit Twitter nur noch halb so schlimm.


jQuery Stolperstein: hover() und slideUp()/slideDown()/animate()

21 06 2009

Gelegentlich möchte ein CSS Dowpdown-Menü um eine kleine Animation erweitert werden, die bei der Gelegenheit auch den Suckerfish für den IE6 übernimmt. Natürlich macht man sowas mit jQuery, wenn man das sowieso eingebunden hat, in etwa so (natürlich alles innerhalb von $(document).ready()):

$('#mainMenu>li').hover(
  function(){
    $(this).find('>ul').slideDown(150);
  },
  function(){
    $(this).find('>ul').slideUp(50);
  }
);

Nun kommt es dabei immer wieder zu den selben Problemen:

Fährt man mit der Maus schnell mehrmals über einen Punkt, werden alle nötigen Animationen in eine Warteschlange gepackt und nach und nach gemütlich abgearbeitet. Das lässt sich noch leicht und logisch beheben, indem man jeweils mit einem stop() vor slideDown() und SlideUp() die Animation erst einmal stoppt, bevor man das Gegenstück ausführt. Das gleiche passiert auch bei animate() und vergleichbaren Sachen und lässt sich da auf die gleiche Weise beheben. Soweit kein Problem und nach ein paar Sekunden Google-Recherche gefunden.

Ein weiteres Problem ist das Handling der Animation bei der ersten Berührung mit der Maus: Scheinbar wird das jQuery hover Event erst bei der zweiten Berührung genutzt, bei der ersten klappt das Menü ganz normal mit der im CSS definierten Methode aus. Auch hier hilft eine Google-Recherche schnell weiter und bringt die Lösung frisch auf den Tisch: Vor der ersten Berührung (also zweckmäßiger Weise in $(document).ready()) müssen die UL-Elemente der Untermenüs mit einem beherzten Aufruf von hide() versteckt werden, auch wenn sie durch das CSS eigentlich schon versteckt sind. OK, das muss man wissen und kommt nicht von alleine drauf.

Perfide ist das dritte Problem bei Animationen mit den Ausmaßen der Untermenüs, also slideDown()/slideUp() und animate() in Kombination mit height() oder width(): Nutzt man diese innerhalb der hover() Funktion und verlässt das Element noch während die Animation läuft, speichert jQuery den zu diesem Zeitpunkt aktuellen Wert der animierten Abmessungen zwischen und animiert fortan nur noch bis zu diesem Maximalwert. Verlässt man den Menüpunkt also sofort wieder, wird das Menü bis zum Seitenreload nie wieder erscheinen, denn es ist ja nur noch etwa einen Pixel hoch und wird entsprechend auch nur bis zu einem Pixel animiert. Hier habe ich keine Lösung bei Google gefunden, weil ich nicht wusste, wonach ich suchen sollte. Also habe ich mich mit dem Firebug auf die Lauer gelegt und das Problem in der beschriebenen Form analysieren können. Ich weiß nicht, ob das ein Bug ist oder volle Absicht, aber ich kann mir nicht wirklich eine Situation vorstellen, wo dieses Verhalten gewünscht wäre. Die Lösung dafür liegt aber auf der Hand: Ein unscheinbares height('auto') vor der ausklappenden Animation behebt das Problem sehr zuverlässig.

So sieht nun also das komplette Menüscript aus, das sich endlich so verhält, wie man es auch erwartet:

$('#mainMenu>li>ul').hide(); // needed to prevent CSS-only behaviour on first contact
$('#mainMenu>li').hover(
  function() {
    $(this).find('>ul').stop().height('auto').slideDown(150); // the height('auto') prevents the menu from memorizing an incorrect height-value forever when leaving the menu while the animation is running
  },
  function() {
    $(this).find('>ul').stop().slideUp(50);
  }
);

An anderer Stelle half mir height('auto') aber nicht weiter, was also tun? Die Lösung lag zunächst auch hier auf der Hand: Beim Laden der Seite müssen die initialen Werte des Elements in einer Variablen gespeichert werden, zu denen die Animation dann jeweils zurückkehren kann. Das schien mir immens unelegant, weil ich nicht den globalen Scope mit solcherlei Variablen vollmüllen wollte. Mein Bruder brachte dann den entscheidenden Tipp: Man kann die Werte prima als Attribute des Elements im DOM ablegen. Beliebige Attribute sind in HTML zwar nicht erlaubt, aber wenn das Dokument erst mal ins DOM eingelesen wurde, sind die Limitierungen von HTML völlig gleichgültig. Kurz gesagt: Ist das Ding im DOM, ist es kein HTML mehr. Die Denke muss man sich erst mal klar machen. Gut, also schreibe ich die Werte in $(document).ready() fix als Attribute ins DOM und alles wird gut. Doch da hatte ich nicht mit der strengen, aber korrekten Auslegung des ready()-Events in Opera gerechnet: Dort stehen zu diesem Zeitpunkt die gewünschten Werte nicht zur Verfügung, weil es sofort gefeuert wird, sobald das DOM fertig eingelesen wurde, aber eben noch bevor die Engine irgendetwas rendern konnte. Abmessungen sind also nicht bekannt. Die Lösung lautet hier $(window).load(). Dieses Event wird gefeuert, sobald die Seite gerendert wurde, mithin also auch alle Abmessungen bereit stehen. So sind die Events spezifiziert, aber Opera scheint der einzige Browser zu sein, der sie auch so handhabt. Die komplette Lösung für dieses Problem lautet also in etwa so:

$(window).load(function(){
  $("#elementId").attr('origHeight', $("#elementId").height());
});

Ausgelesen wird auf die gleiche Weise, also $("#elementId").attr('origHeight').

Nachtrag 05.07.2010: Wo ich den alten Beitrag gerade noch mal lese, fällt mir auf, dass Doku lesen oft hilft. Der letzte Punkt wird natürlich viel eleganter gelöst, wenn man einfach die data()-Methode von jQuery benutzt. Also wie immer: Augen auf und Hirn an.


SSL und wenigstens halbwegs gute Passwörter

04 05 2009

Jemand hatte für 24 Stunden den Twitter-Account cdu_news gekapert und dort eine politische Kehrtwende lanciert. Dem Hörensagen nach geschah das entweder durch ein in einem offenen WLAN gesnifftes Passwort oder durch schlichtes Raten, was bei Identität von Benutzername und Kennwort schnell gegangen sein mag. So oder so, die CDU hat sich das aus verschiedenen Gründen redlich verdient, in erster Linie aber zeigt es recht schön, wie es um die Medienkompetenz dieser Partei bestellt ist. Darauf wollte ich aber gar nicht hinaus, die Häme ist schon woanders ausgeschüttet worden.

An diesem Beispiel sieht man wieder, dass viel zu wenige Leute sich zumindest im Ansatz für die Sicherheit ihrer Online-Identität interessieren. Da werden trivialste und leicht zu erratende Passwörter genutzt und wahrscheinlich für alle Dienste das gleiche, weil man sich das ja auch merken muss. Und es wird ohne Hirneinsatz in offenen WLANs fröhlich alles ohne Verschlüsselung übertragen. Dass da jeder Anwesende mit ein wenig technischem Verständnis und einem Notebook oder PDA in Reichweite alles, mit der Betonung auf alles, mitlesen kann, hat man auch schon mal gehört irgendwann. Punkt. Ich habe keinerlei Mitleid mit Menschen, denen aus diesen beiden Gründen unschöne Dinge passieren, denn sie haben es verdient. Sie haben mutwillig alle Regeln der Umsicht missachtet und müssen die Konsequenzen tragen. Jemand, der aus Bequemlichkeit vor dem Überqueren einer großen Straße und mit MP3-Player auf den Ohren weder rechts noch links guckt, wird in der Regel früher oder später in Kontakt mit fahrenden Fahrzeugen kommen. Same here.

Also noch mal ein paar simple Regeln für den sicheren Umgang mit Passwörtern:

  1. Wenigstens halbwegs sichere Passwörter wählen. Admin und Admin sind kein gutes Paar, idiot@gmx.de und idiot auch nicht. idiot12345 ist schon besser, aber immer noch leicht zu erraten. Im Prinzip führt kein Weg an sowas wie 2i5j28dQ vorbei. Wer sich sein Passwort merken muss kann auch die ersten Buchstaben jedes Wortes in einem bescheuerten Merksatz nehmen. Nach ein paar Mal eintippen braucht man den Satz eh nicht mehr. Wie auch immer, ein Passwort darf nicht nach ein paar Tausend Versuchen erraten werden, also mit geeignetem Werkzeug nach ein paar Millisekunden bis Minuten. Der Name von Kindern und Haustieren oder der Hochzeitstag fallen also auch flach, ebenso wie Begriffe, die im Wörterbuch stehen.
  2. Nicht überall das gleiche Passwort nehmen. Klingt besonders lästig, aber eine Anekdote beschreibt das Problem ganz gut: Ich hatte mal meinen Benutzernamen vom FH-WLAN vergessen und habe diesen in der DVZ (unser Rechenzentrum) per E-Mail nachgefragt. Der freundliche Mitarbeiter nannte mir ohne weitere Überprüfung meiner Identität meinen Benutzernamen und das erste Zeichen meines Passworts (nach dem ich nicht gefragt hatte, denn das wusste ich noch). Fuck, dieses Passwort hatte ich zu dem Zeitpunkt bei etlichen Diensten genutzt und jeder Honk konnte offenbar durch geschicktes Nachfragen bei der DVZ dieses Passwort herausfinden, das dort im Klartext abgelegt war. Fuck, fuck, fuck! Merke: Man kann nie wissen, was mit den Passwörtern bei den Betreibern passiert, also führt tatsächlich kein Weg daran vorbei, für jeden Dienst ein gesondertes Passwort zu benutzen oder zumindest für die wichtigen Dienste.
  3. Kein Mensch kann sich alle Passwörter merken, wenn man für jeden Dienst ein eigenes definiert. Also führt kein Weg an einem Passwort-Manager vorbei. Ich benutze Keepass, das ein kostenloses (Open-Source) Programm ist, das auf etlichen Plattformen läuft: Neben Windows (auch vom USB Stick), Linux, Mac OSX auch auf den meisten besseren Handys und demnächst irgendwann auch mal auf dem iPhone, wenn Apple es mal im AppStore freischaltet. Aber Vorsicht: Das Master-Passwort muss besonders stark sein, denn wer eine schlecht geschützte Schlüsseldatei in die Finger bekommt, hat den Generalschlüssel.
  4. Fast einen Generalschlüssel hat auch jeder, der das Passwort zum E-Mail Postfach kennt oder sonstigen Zugriff darauf hat: Eigentlich jeder Webdienst bietet die Möglichkeit, sich ein neues Passwort per E-Mail zusenden zu lassen. Und schwupps ist alles geritzt. Immerhin bemerkt man hier den Angriff meistens im Nachhinein, wenn das alte Passwort nicht mehr funktioniert. Merksatz: E-Mail und Passwort-Manager sind die wichtigsten Passwörter von allen. Hüte sie wie Deinen Augapfel.
  5. Das E-Mail Postfach kommt aber gerne in falsche Hände, wenn man unverschlüsselt auf seine E-Mails zugreift. Also immer und ganz besonders in offenen WLANs Passwörter nur über verschlüsselte Verbindungen übertragen. Im E-Mail Programm muss man meist nur zwei Häkchen setzen (SSL oder TLS), je nach Provider muss man aber auch mehr Umstellen: Ein freundlicher Helfer oder die Online-Hilfe des Providers hilft auch Anfängern zuverlässig bei der Einrichtung. Auch der Webmailer sollte nur mit einem https:// vor der Adresse genutzt werden. Nochmal: In offenen WLANs, also allen öffentlichen und hoffentlich nicht dem eigenen, kann jeder in Reichweite des Netzes alle Daten mitlesen, wenn sie nicht verschlüsselt werden.
  6. Und zuletzt der simple Tipp: Nicht jedem das Passwort in die Hand drücken. Ein Passwort geht niemanden etwas an. Ganz besonders gilt das für die vielen Dienste, die eine direkte Twitter-Anbindung anbieten, für die Benutzername und Kennwort eingegeben werden müssen. Das ist scheiße, liebe Betreiber. Bitte erzieht die Nutzer nicht zu solch sorglosem Umgang mit ihren Zugangsdaten.

Ja, Passwörter sind ein unbequemes Thema. Aber spätestens, wenn es um handfesten Identitätsdiebstahl oder finanziellen Schaden geht (PayPal, eBay etc. schicken einem gerne ein neues Passwort per E-Mail zu), sollte etwas Umsicht walten.


Datev, immer wieder Datev

31 03 2009

Neulich hab ich ja schon dreieinhalb Stunden damit verbracht, einem nicht von sich aus funktionierenden Datev-Update auf die Beine zu helfen. Soweit so gut.

Seit gestern versuche ich einen Windows Server 2003 mit einer Reperaturinstallation auf eine neue Hardware zu migrieren und auch das scheitert beharrlich bei verbleibenden 39 Minuten im Setup. Fuck. Die Microsoft Knowledge Base ist bemüht, das Problem zu ergründen und redet etwas von nicht unterstützter Hardware. Also habe ich alle Onboard-Geräte und Power-Management-Einstellungen bis auf den Plattencontroller ausgeschaltet, aber auch das half nicht nicht. Allerdings lief eine testhalber durchgeführte normale Installation durch. Hmm.

Aber was hat das Ganze nun mit Datev zu tun? Dieser Foreneintrag half mir schließlich weiter. Kurz gefasst, soll man mit Shift+F10 eine Konsole öffnen, regedit starten und den Schlüssel "HKEY_LOCAL_MACHINE\Software\Microsoft\MS Setup (ACME)" beherzt löschen. Das habe ich gemacht und das Setup lief sofort weiter. Fein! Nun ratet mal, was in dem Schlüssel als einziger Eintrag zu finden war… Natürlich ein Überbleibsel von irgendeiner Datev-Scheiße. Zorn.


Eine kleine Fuck IE6 PHP Klasse

24 02 2009

Aktuell soll ja wieder mal der IE6 endlich sterben. In Norwegen sind einige große Nachrichtenseiten mit einer IE6-Warnung unterwegs und der IE6 Death March ist auch unterwegs. Ich verkaufe schon seit einiger Zeit IE6-Anpassungen nur noch als extra zu bezahlende Zusatzoption, wobei es bei mir dank semantisch sinnvollem Code hier nur um optische Feinheiten geht, die ich für entbehrlich halte. Wie auch immer: Bisher hat kein Kunde Aufpreis für den IE6 zahlen wollen. Geht doch.

Nun baue ich gerade meine Lifestream-Seite auf und befreie mich bei der Gelegenheit von jedem IE6-Mist. Meine Contentboxen hat der IE6 schlicht gar nicht angezeigt (wohl aber konnte man die unsichtbaren Links anklicken), so dass ich mich entschieden habe, dem IE6 und älteren Versionen einfach alle Styles vollständig wegzunehmen und stattdessen eine rote Alarm-Box einzublenden. Die Inhalte bleiben vollständig nutzbar, es sieht nur total scheiße aus. Sehr befriedigend, kann ich so den IE6-Nutzern doch etwas von der Hässlichkeit vermitteln, die dem IE6 innewohnt.

Zu diesem Zweck habe ich eine kleine statische PHP Klasse geschrieben, die ich hiermit gerne zur Verfügung stellen möchte. Wer Lust hat, kann die einfach für sich anpassen und in seine Projekte einbinden. Ich habe mich gegen eine clientseitige Lösung mit CSS und/oder JavaScript entschieden, weil ich "gute" Besucher nicht mit dem für sie sowieso unsichtbaren IE6-Code belasten wollte. Ich denke, das ist eine gute Idee.

Die Klasse ist recht simpel gestaltet und schreit geradezu danach, bei Bedarf verfeinert zu werden (mir reicht das erst mal so). Also in Kürze:

  1. Einbinden der Klasse, etwa indem man den Code direkt in sein Projekt kopiert oder als Datei abspeichert und mit require_once('PFAD/fuck_ie6.class.php'); einbindet.
  2. Da die Klasse statisch ist, muss sie nicht instanziiert werden. Man ruft also die drei statischen Methoden wo man sie braucht.
  3. Folgende Methoden und Variablen stehen zur Verfügung:
    • fuck_ie6::is_ie6() gibt true zurück, wenn der Besucher mit einem IE4, 5 oder 6 unterwegs ist, sonst false. Praktisch, wenn man bestimmte Inhalte wie Stylesheets ohne Conditional Comments für diese Besucher aus- oder einblenden möchte. Im Prinzip ist das nur eine sehr simple RegEx.
    • fuck_ie6::print_style() und fuck_ie6::print_alert_box() printen den Inhalt der Variablen $alert_style und $alert_content in Abhängigkeit vom Browser des Besuchers. Die beiden Methoden ersparen einem also nur die if-Abfrage mit fuck_ie6::is_ie6() im Template ein.
    • Die beiden Variablen $alert_style und $alert_content enthalten den Style und den Code, die für die Alarm-Box genutzt werden sollen. Hier finden Anpassungen an die eigenen Wünsche statt.

<?php
/**
* a very simple static class for sniffing Internet Explorer 6 and below
*
* can detect whether the user comes with an annoying IE (version 4,5 or 6)
* additionally holds content and styles for a red alert box
*
* @author	Gregor Nathanael Meyer <Gregor [at] der-meyer.de>
* @license  http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons cc-by-sa
* @version  0.1  first release
*/
class fuck_ie6
{
  /**
    * the style block used for the alert box
    * @static
    */
  public static $alert_style = '  <style>
  .errorBox {
    background: #fbe3e4;
    color: #8a1f11;
    border: 2px solid #fbc2c4;
    width: 80%;
    padding: 25px;
    margin: 0 auto;
    font-size: 1em;
    line-height: 1.3em;
  }
  </style>';
  
  /**
    * the HTML of the alert box
    * @static
    */
  public static $alert_content = '<p class="errorBox"><strong>Alarm:</strong> Offensichtlich bist Du mit einem alten Internet Explorer (6.0 oder älter) unterwegs. Da dieser Browser aus dem Jahr 2001 die auf dieser Seite genutzten modernen Webstandards nicht hinreichend unterstützt, habe ich das visuelle Beiwerk für Dich deaktiviert. Die Inhalte sind weiterhin erreichbar.</p>';
  
  
  /**
    * the IE6 detector function
    * uses just a simple RegEx to read the UA string
    * @static
    */
  public static function is_ie6()
  {
    return preg_match('#^Mozilla/4.0 \(compatible; MSIE [456]#i', $_SERVER['HTTP_USER_AGENT']) ? true : false;
  }
  
  /**
    * prints the style block in case of IE<=6
    * @static
    */
  public static function print_style()
  {
    if ( self::is_ie6() )
    {
      echo self::$alert_style;
    }
  }
  
  /**
    * prints the alert box content in case of IE<=6
    * @static
    */
  public static function print_alert_box()
  {
    if ( self::is_ie6() )
    {
      echo self::$alert_content;
    }
  }
}

Ein Beispiel könnt ihr bei meinem Lifestream sehen.


Dumm gucken: Phenom II auf Gigabyte GA-MA790GP-DS4H

29 01 2009

Vor ein paar Tagen habe ich mal wieder einen Rechner gebaut, spannender Weise einen AMD Phenom II 920 auf einem Gigabyte GA-MA790GP-DS4H mit AMD 790GX Chipsatz. Dies ist ein oft empfohlenes Board für diesen Prozessor, also erwartete ich keine Schwierigkeiten. Doch weit gefehlt: Beim Druck auf den Einschalter passierte exakt nichts. Kein Piepsen, kein Lüfterzucken, nichts. Als würde das Netzteil keinen Strom liefern, was ich mit einem anderen Netzteil schnell ausschließen konnte. Was ist hier los? Schalter am Gehäuse defekt? Aber auch das direkte Kurzschließen der Power-Kontakte auf dem Board brachte nichts. Ratlosigkeit… Board defekt?

Gelegentlich brauchen Boards ja ein BIOS-Update, um einen neuen Prozessor erkennen zu können. Aber dass sich gar nichts tut, wenn man das passende BIOS nicht hat, hatte ich noch nicht gehört. Trotzdem habe ich mal versuchsweise einen älteren AM2-Prozessor auf das Board gesteckt – was übrigens wegen der unfassbar strammen Halteklammer des Aplenföhn Groß Clockner eine ernsthafte Aufgabe ist. Und siehe da: Das Board startet sofort, lässt sich ein aktuelles BIOS geben (F3H) und erkennt danach auch den Phenom II. Sowas nervt total. Was soll man denn machen, wenn man keinen anderen AM2-Prozessor zur Hand hat?

P.S. Der Kühler ist großartig: Leise, bombastisch, hervorragende Kühlleistung und mit 30 Euro nicht zu teuer.


Milkdrop in AIMP2

22 12 2008

Seit einiger Zeit bin ich begeisterter Nutzer des russischen Audioplayers AIMP2, der genau das macht, was ich von einem Audioplayer erwarte. Die Medienbibliothek taugt zwar wahrscheinlich nichts, aber die installiere ich sowieso nicht: Ich kann Medienbibliotheken nicht ausstehen und verlasse mich lieber auf eine gut sortierte Ordnerstruktur.

Was ich an AIMP2 aber immer sehr vermisst habe, sind gute Visualisierungen wie Milkdrop. Die eingebauten Visualisierer sind ein schlechter Scherz und das Programm kann leider keine Winamp-Plugins laden. ProjectM, die Open-Source OpenGL Implementierung von Milkdrop gibt es nur für Linux und MaxOSX. ProjectM klinkt sich in den Audiopfad ein und kann daher Musik aus beliebigen Quellen visualisieren. Schade, dass es das nicht für Windows gibt.

Jetzt habe ich aber endlich eine Möglichkeit gefunden, zumindest das alte Milkdrop 1.04 in AIMP2 zum laufen zu bringen: aimp_vis_winamp ist ein AIMP2 Plugin, das Winamp-Visualisierungen lädt und ganz brauchbar mit Milkdrop 1.04 zusammen arbeitet. Folgende Schritte brachten mich zu einer erfolgreichen Installation:

  1. Download des aimp_vis_winamp Plugins, momentan in Version 0.3; neuere Versionen finden sich ggf. in diesem Thread.
  2. Entpacken der aimp_vis_winamp.dll in das Plugin-Verzeichnis von AIMP2. Wenn nötig (etwa, wenn der Player nicht neu gestartet wurde), das Plugin über den Plugin-Manager laden.
  3. Milkdrop 1.04 herunterladen und irgendwo entpacken. Die *.milk-Presets aus den Unterordnern musste ich ich auf eine Ebene mit der vis_milk.dll bringen, damit sie geladen wurden.
  4. Das aimp_vis_winamp Plugin als Visualisierungs-Plugin in AIMP2 laden:
    Screenshot AIMP2, Laden von aimp_vis_winamp.dll
  5. Entgegen der Beschreibung im Forum, bringt ein einzelner Linksklick in das Visualisierungsfenster jetzt ein Kontextmenü auf den Schirm, in dem ich eine Winamp vis_*.dll laden, dessen Konfigurationsmenü aufrufen und es starten und stoppen kann. Hier lade ich das zuvor entpackte Milkdrop 1.04, das allerdings nur im Vollbildmodus läuft. Milkdrop2 aus dem aktuellen Winamp bringt AIMP2 leider zum Absturz. Andere Winamp-Plugins laufen übrigens auch, wer also die AVS mag, bekommt auch die zum Laufen.

Fazit: Milkdrop 1.04 ist zwar nicht Milkdrop 2, aber immerhin habe ich endlich eine schöne Visualisierung in AIMP2. Ein Windows-Port von ProjectM wäre freilich besser.

Nachtrag 23.12.2008: Es gibt einen Windows-Port von ProjectM, der allerdings recht alt ist und nicht leicht zu finden. Ich habe die Winamp-Plugin-Version davon (von 2004) mit dem Winamp-Loader geladen und das Teil funktioniert auch leidlich: Es scheint irgendwie zu schnell oder zu hektisch zu laufen, jedenfalls nicht schön. Außerdem bekomme ich das Ding nicht in einen Vollbildmodus geschaltet. Naja. Eine aktuelle Version von ProjectM für Windows, vielleicht sogar als Standalone Programm wär ne feine Sache.

Inzwischen habe ich aber Milkdrop2 zum Laufen gebracht und bin fast glücklich. Die Version aus dem aktuellen Winamp wollte zwar nicht, aber es gibt eine Version 2.04D, die im AIMP2 Mega Pack enthalten ist. Dort habe ich die vis_milk2.dll samt der zugehörigen Dateien und der nscrt.dll herauskopiert und jetzt läuft auch Milkdrop2. Juhu! Die Frage ist nur, wie legal das Ganze jetzt ist? Das AIMP2 Mega Pack kann man jedenfalls nur fragwürdig legal und etwas verschämt von russischen Servern ziehen. Eine etwas lästige Eigenschaft der Winamp Plugin Loaders für AIMP2 ist, dass er das zuletzt geladene Plugin beim Playerstart wieder lädt und startet, auch wenn es zuletzt gar nicht gestartet war.


array_merge_recursive_overwrite()

19 06 2008

Update 30.70.2009: Seit PHP 5.3.0 gibt es endlich die Funktion array_replace_recursive(), die genau das kann, was meine array_merge_recursive_overwrite() macht. Ich habe meine Funktion erweitert, so dass sie lediglich array_replace_recursive() aufruft, wenn diese verfügbar ist. Dies dient nur der Abwärtskompatibilität in bestehenden Projekten und macht keinen Sinn, wenn man neu anfängt. Für einen solchen Fall (hoffentlich der Normalfall) habe ich unter obigem Link einen fertigen Code als Kommentar hinterlassen.

Gestern musste ich mit PHP zwei verschachtelte assoziative Arrays zusammenführen, wobei allerdings bestehende Werte im einen Array durch Werte des anderen überschrieben werden sollten. Leider kommt die PHP-Funktion array_merge_recursive() dafür nicht in Frage, weil sie im Konfliktfall die Werte nicht überschreibt, sondern ein Unterarray baut, in dem beide Werte enthalten sind. Einen Schalter zum Überschreiben gibt es nicht, also musste ich eine eigene Funktion für diesen Zweck bauen. Et voilà, hier ist sie:

/**
 * merges two arrays recursively, overwrite existing values in $base with values from $merge
 * array_merge_recursive does not overwrite values, it creates a new sub array with both values in it, so we need this function
 * since PHP 5.3.0 the built in array_replace_recursive() does the same, so it will be called, if available
 * 
 * @author     Gregor Nathanael Meyer <Gregor at der-meyer.de>
 * @param      array $base base array
 * @param      array $merge array to be merged into base array
 * @param      array $merge,... more merge arrays
 * @return     array merged inputs
 */
function array_merge_recursive_overwrite($base, $merge)
{
  // as of PHP 5.3.0 array_replace_recursive() does the work for us
  if (function_exists('array_replace_recursive'))
  {
    return call_user_func_array('array_replace_recursive', func_get_args());
  }
  
  function recurse($base, $merge)
  {
    foreach ($merge as $key => $value)
    {
      // create new key in $base, if it is empty or not an array
      if (!isset($base[$key]) || (isset($base[$key]) && !is_array($base[$key])))
      {
        $base[$key] = array();
      }
      
      // overwrite the value in the base array
      if (is_array($value))
      {
        $value = recurse($base[$key], $value);
      }
      $base[$key] = $value;
    }
    return $base;
  }
  
  // handle the arguments, merge one by one
  $args = func_get_args();
  $base = $args[0];
  if (!is_array($base))
  {
    return $base;
  }
  for ($i = 1; $i < count($args); $i++)
  {
    if (is_array($args[$i]))
    {
      $base = recurse($base, $args[$i]);
    }
  }
  return $base;
}

Genau wie ihre Schwester array_merge_recursive() nimmt sie zwei oder mehr Arrays entgegen und gibt diese verbunden zurück. Viel Spaß damit, falls es wer gebrauchen kann.

Nachtrag: Ich hätte auch einfach mal bei Google suchen können, so ziemlich die gleiche Funktion gibt es auch im Horde Framework, allerdings nur für zwei Arrays (also so wie meine innere Funktion). Mist, da hätte ich mir die verhasste Rekursion gar nicht selber ausdenken müssen.

Nachtrag: Die Kommentare in der PHP-Doku bringen auch etliche Varianten dieses Themas. Man sollte sich von unleserlicher Schreibweise nicht abschrecken lassen, auch wenn man schon müde ist. Naja, wenigstens ist meine Lösung nicht total doof.


Spaßige Seiteneffekte Wut

05 06 2008

Gerade arbeite ich an einem mehr oder weniger zeitkritischen Projekt, das in Symfony entwickelt wird. Ich habe zwar das Symfony-Buch vor etwa einem Jahr gelesen (statt meine Thesis anzufangen), aber gearbeitet habe ich damit noch nie. Das ändert sich jetzt unter gewissem Druck. Morgen habe ich mittags einen Termin mit meinem Auftraggeber und bin noch nicht wirklich weit gediehen. Warum? Zwei lustige Probleme mit dem halbfertig angelieferten Datenmodell haben mich heute beinahe den ganzen Tag gekostet:

1. VARCHAR-Feldtypen brauchen (zumindest in MySQL) eine definierte Länge. Ich hatte die im Modell vorgegebenen Feldtypen einfach übernommen. Zwar hatte ich mich gewundert über die fehlende Längenangabe, aber hab mir nichts weiter dabei gedacht. Ahnungslos laufe ich also mit Propel los und schaffe es nicht, mein Modell damit umzusetzen, die Fehlermeldungen waren leider auch wenig hilfreich. Irgendwann bin ich dann drauf gekommen. Naja, hätte ich wissen müssen.

2. Man sollte Tabellen nicht order nennen! Um diese Erkenntnis zu erlangen, habe ich quasi den restlichen Tag gebraucht. Propel baut problemlos sein Modell damit auf und MySQL legt eine solche Tabelle auch ohne Murren an, nur scheitert jede Anfrage mit dem Tabellennamen irgendwo im SELECT mit einem SQL-Syntaxfehler. Eigentlich klar, ist ORDER doch ein SQL-Schlüsselwort, weswegen auch die Syntax-Hervorhebung von Notepad++ an solchem SQL-Code scheitert. Dieser Effekt hat mich letztlich auch auf die Lösung gebracht, als ich mir die von Propel generierte lib.model.schema.sql doch noch mal genauer angeguckt habe. Also flugs die Tabelle umbenannt, das Schema neu generiert und zack, das Adminmodul funktioniert endlich ohne Probleme.

Zu meiner Verteidigung muss ich anführen, dass beide Problemursachen nicht auf meinem Mist gewachsen sind, sondern schon mit meiner Modell-Vorlage ins Haus kamen, die allerdings ins Blaue hinein geschrieben waren und nicht validiert. Das habe ich leider gemerkt…

P.S. Symfony ist wirklich genial. Zwar habe ich heute nur mit dem Model-Layer gearbeitet, aber auch hier merkt man schon, wie ausgefeilt das ganze Framework ist. Es wird mir eine Freude sein, tiefer in die Entwicklung einzusteigen.

P.P.S. Sehr hilfreich für häufige Model-Rebuilds ist übrigend dieses Script, das einen Dump der Datenbank macht, das Modell neu aufbaut und die Daten danach wieder einspielt. Das Symfony-Standardverhalten bei einem model-build ist nämlich, alle Tabellen zu löschen und neu anzulegen, was leider die darin enthaltenen Daten entsorgt.


RSS-Feed als E-Mail lesen

04 04 2008

Extra für den Jan, der mich schon zum hundertsten Mal darauf angesprochen hat, habe ich mal kurz recherchiert: RSSFWD aboniert RSS-Feeds von Blogs und anderen Website und schickt einem bei Änderungen die neuen Artikel per E-Mail zu. Find ich etwas irre, denn der Witz an RSS ist ja gerade, dass man sie direkt wie E-Mails abonnieren kann. Aber Outlook vor 2007 kommt damit offenbar nicht ohne weiteres klar und einen Feedreader will nicht jeder extra für einen einzigen Feed benutzen (ich empfehle für den Einstieg die RSS-Funktionen in allen modernen Browsern, also das orangefarbene Icon rechts neben der Adresse).

Für die, die nicht wissen, was ein RSS-Feed ist: Damit kann man Websites, die sowas anbieten, Abonnieren und bekommt eine irgendwie geartete (je nach Programm) Meldung, wenn es neue Beiträge gibt. Bei mir kann man konkret alle Beiträge in einem Feedreader (so heißt das Leseprogramm) komplett lesen, bei anderen Blogs bekommt man mitunter nur einen Hinweis auf neue Nachrichten. Eine Liste von Feedreadern und mehr Infos dazu findet sich auch in der Wikipedia. Ich selbst benutze Opera und Thunderbird für meine umfangreiche Feedsammlung.