Kommentare, die die Welt nicht braucht – Spezial
Eine neue Spezialausgabe der beliebten Kategorie bietet sich an, denn: "Kategorie"? Ja! Kommentare, die die Welt nicht braucht, haben jetzt eine eigene Kategorie.
Als kleines Extra: codecrap.com (Danke, Chris!) und commitlogsfromlastnight.com (Danke, Alex!) 🙂
Java/Liferay: iCal-Datei erzeugen und ausliefern [UPDATE]
Wer mit Liferay (genauer: Mit einem Portlet) eine iCal-Datei ausliefern will, benötigt zuerst mal iCal4j. Ich brauchte die JARs backport-util-concurrent-3.1 und ical4j-1.0.3, aber vermutlich benötigt man auch noch commons-lang-2.6 und commons-logging-1.1.1, davon habe ich (ältere) bereits durch/für Liferay im Projekt. Die anderen brauchte ich nicht.
Anmerkung: Außerdem habe ich eine leere Datei namens ical4j.properties direkt in src angelegt, um eine Warnung zu unterdrücken.
Um eine Ressource abzurufen, nutze ich die ResourceURL von Liferay, in meinem Fall plus einer Event-ID:
|
1 2 3 4 |
ResourceURL calendarURL = myRenderResponse.createResourceURL(); calendarURL.setParameter("eventID", myEventID); View myView = new VelocityView("myVelocityView"); myView.put("calendarURL", calendarURL); |
Ein Aufruf der URL landet in der serveResource() meines Portlets, und mündet in der Erzeugung des Files:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// TODO day-long dummy event! java.util.Calendar date = java.util.Calendar.getInstance(); date.set(java.util.Calendar.MONTH, java.util.Calendar.DECEMBER); date.set(java.util.Calendar.DAY_OF_MONTH, 25); VEvent christmas = new VEvent(new Date(date.getTime()), "Christmas Day"); // siehe unten! //christmas.getProperties().getProperty(Property.DTSTART).getParameters().add(Value.DATE); // create ical-event from VEvent, plus necessary props net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar(); calendar.getProperties().add(new ProdId("-//Events Calendar//iCal4j 1.0//EN")); calendar.getProperties().add(Version.VERSION_2_0); //calendar.getProperties().add(CalScale.GREGORIAN); calendar.getComponents().add(christmas); // actual output: CalendarOutputter outputter = new CalendarOutputter(); <del>// TODO else: Error "Parameter [VALUE] must only be specified once":</del> <del>outputter.setValidating(false);</del> outputter.output(calendar, response.getPortletOutputStream()); // set filename to be dowloaded: response.addProperty(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=event.ics"); // otherwise file will be interpreted as HTML-document: response.setContentType("text/calendar"); |
ACHTUNG: Wer einen Termin mit Startzeit (und Endzeit) erzeugen will, muss dem VEvent ein (zwei) Objekte vom Typ DateTime statt Date übergeben!
Quellen: Basis, verschiedene iCal4j-Fehlermeldungen, Filename setzen, ICS Mime type, Unterschied Date/DateTime 😉
Wer rausfindet, warum mir beim Validieren ein "Parameter [VALUE] must only be specified once" geworfen wird, bekommt ein Bier! EDIT: Es lag an der jetzt auskommentierten Zeile! Prost!
Raspberry Pi als Spotify-Client [UPDATE]
Update Juli 2013: Besser ist die "Pi MusicBox"
Der Prozessor des Raspberry ist mit seinen 700 MHz nicht eben schnell – bisher habe ich den Desktop noch nicht ausprobiert, aber ich habe mir sagen lassen, dass schon Surfen keinen Spaß macht. Habe ich aber auch nicht vor 🙂 Denn der Prozessor ist zum Beispiel explizit in der Lage, HD-Videos zu rendern – wohlgemerkt lüfterlos. Das prädestiniert ihn als Media Center. Gut, HD-Videos kann mein Bluray-Player auch, aber ich will mal ausprobieren, wie sich der Raspberry als Spotify-Client schlägt. Das kann mein Bluray-Player (oder irgendwas anderes in meinem Heimkino) nämlich nicht.
Also, ich habe Raspbian “wheezy” installiert, und setze funktionierendes LAN voraus:
Es gibt eine Anleitung von Hexxeh, die funktioniert aber nicht. Zuerst kann ich die angegebenen Pakete nicht installieren – irgendwo ist .../libmpg123-0_1.14.2+svn20120622-1_armhf.deb eingetragen, online ist aber (Stand 5.10.12) nur libmpg123-0_1.14.4-1_armhf.deb 🙁 Für die Akten: Man lade libmpg123-dev_1.14.4-1_armhf.deb und libmpg123-0_1.14.4-1_armhf.deb runter, schiebe sie per FTP-Programm (SFTP-Verbindung wählen; User "pi"/Passwort "raspberry") auf das Gerät nach "/home/pi/", und installiere sie manuell:
|
1 2 |
sudo dpkg -i libmpg123-0_1.14.4-1_armhf.deb sudo dpkg -i libmpg123-dev_1.14.4-1_armhf.deb |
Aber das genügt nicht; ich einen bekomme fehlende vcos_platform_types.h und vcos_platform.h und ilclient.h. Nach gut zwei Stunden (denn man beachte den Lösungsansatz für das erste Problem: zweistellig viele Files editieren; sowie die Unübersichtlichkeit des zweiten Links) gebe ich auf, und versuche einen anderen Weg – der geht zum Glück ziemlich straight forward:
Zuerst installiere ich Subversion (Quelle):
|
1 |
sudo apt-get install subversion |
Dann ziehe ich mir die despotify-Sourcen (Quelle) plus einiger Libs (Quelle: README-Datei) direkt, und compiliere sie:
|
1 2 3 4 5 6 7 8 9 |
svn co https://despotify.svn.sourceforge.net/svnroot/despotify despotify sudo apt-get install libssl-dev zlib1g-dev libvorbis-dev libtool sudo apt-get install libpulse-dev sudo apt-get install libgstreamer-plugins-base0.10-0 libgstreamer0.10-dev --fix-missing sudo apt-get install libao-dev cd despotify/src/ cp Makefile.local.mk.dist Makefile.local.mk make sudo make install |
Im Wesentlichen war es das! Man sollte sich nun mittels
|
1 |
despotify-simple <userid> <password> |
einloggen können.
Achtung #1: Die userid ist nicht der Username! Sondern die Nummer, die man unter anderem hier findet (man sieht sie auch, wenn man eine Support-Anfrage stellt) – Danke, @moritzmoro. Achtung #2: Laut FAQ von despotify geht das nur mit Premium-Accounts! Sowas habe ich nicht (mehr), deshalb breche ich hier bis auf Weiteres ab. In den kommenden Tagen werde ich zwei Dinge updaten: Ich werde mein Raspberry frisch aufsetzen, und die obige Anleitung prüfen – ich habe doch so einiges installiert heute, und möchte vermeiden, dass etwas davon wichtig war, aber hier vergessen wurde. Zweitens werde ich (hoffentlich) meinen Spotify-Account, der momentan mit meinem Facebook-Konto verbunden ist, in einen "richtigen" umwandeln können, und diesen auf Premium upgraden.
UPDATE: Richtig, mit einem Premium-Accoount kann man sich einloggen. Der Login dauert recht lange, aber es geht:

Anmerkungen:
- Während ich als Nicht-Premium-User tatsächlich die User-ID benutzen musste, um wenigstens brauchbare Fehlermeldungen zu bekommen, kann ich als Premium-User ganz normal meinen Usernamen verwenden
- Wenn das Passwort Sonderzeichen enthält, können diese mit Backslash ("") escaped werden
- Das ganze rockt! Das Raspberry hat überhaupt keine Probleme mit dem Decoding von 320kb
- Man kann nur sehr unkomfortabel nach etwas anderem als Tracks suchen
- Mein erster Song war standesgemäß "Tom's Diner" von Suzanne Vega, dem ersten MP3 🙂
- Sound geht über HDMI raus, Steuerung läuft nach wie vor über SSH – mit dem passenden Client geht das dann auch vom Handy (für's Windows Phone nutze ich gerade The SSH Client Pro)
- Als nächstes brauche ich trotzdem ein Webinterface oder so, für meine Freundin.
- Außerdem steht ein Langzeit-Test an, mal sehen, wie warm das Raspberry wird
- Das Raspberry spielt weiter, wenn man bsplw das Notebook mit dem SSH-Terminal in den Ruhezustand fährt. Kehrt man "zurück", dauert es eine Weile, bis wieder der aktuelle Status angezeigt wird – aber er kommt!
- UPDATE: Inzwischen gibt es eine Version vom Raspberry mit 512MB RAM – ich habe die mit 256, und es läuft prime. Wie oben erwähnt, habe ich den Desktop aber auch nie gestartet, und für eine Konsolenanwendung genügen 256 dicke
- UPDATE Februar 2013: Offenbar gab es eine Protokolländerung, die den von Prime beschriebenen "segmentation fault" hervorruft 🙁
Raspberry Pi: Probleme mit USB-Tastatur und LAN
Wer seinen Raspberry grundsätzlich eingerichtet hat, will vermutlich bald in's Internet. Bei mir stellte sich gerade das Problem, dass meine Tastatur nicht mehr geht, wenn ich LAN einstöpsel... keine Ahnung, woran das liegt, irgendwelche Vorschläge? Vielleicht zieht sie zu viel Strom oder so.
Aber wie dem auch sei: LAN-Kabel wieder ab. Über "sudo raspi-config" öffne ich den Config-Screen und aktivere den SSH-Server. LAN-Kabel wieder dran, Tastatur ab. Über die Heimnetzwerk-Übersicht meines Alice-Routers (ok, über Umwege geht es auch über's Terminal) finde ich heraus, dass das Rasperry (Netzwerkname "raspberrypi") die IP 192.168.1.53 hat. Von meinem Macbook aus rufe ich im Terminal "ssh pi@192.168.1.53" auf (und bestätige beim ersten mal den Fingerprint), und gebe das von mir nicht geänderte Passwort "raspberry" ein. Schon bin ich auf dem System, und kann von hier aus so weitermachen, wie ich es eigentlich über die USB-Tastatur machen wollte.
Stellt sich raus: Das muss ich erst mal gar nicht 🙂 Ein "ping www.google.de" funktioniert direkt. DHCP muss im Router natürlich aktiviert sein, sonst wäre hier manuelle Konfigurationsarbeit nötig. Trotzdem ist es nett zu wissen, dass man so einfach per SSH draufkommt, denn schliesslich will man ja irgendwas machen können im LAN, und ohne Tastatur wird das knifflig. Außerdem muss mein Fernseher nicht mehr eingeschaltet sein, und ich kann auf dem Sofa sitzen^^
Raspberry Pi einrichten
Vor inzwischen auch schon wieder einigen Monaten habe ich mir einen Raspberry Pi bestellt (Danke, Tobi!) – jetzt bin ich dazu gekommen, ihn auch mal anzuschließen 🙂 Für's grundsätzliche Setup sind einige Dinge zu wissen:
Das Board kommt (bzw. kam in meinem Fall) ohne irgendeinen Zubehör. Man benötigt aber mindestens:
- Eine SD-Karte. Es funktioniert wohl nicht jede, also Obacht. Laut Quick start guide tut es eine Class 4 Karte mit mindestens 4 GB. In meinem Fall funktionierte eine 8GB SanDisk Extreme (Class 10).
- Stromversorgung über Micro USB (nicht zu verwechseln mit Mini-USB!), und wir reden bei "Stromversorgung" von "mindestens" 700 mA! Der Quick start guide sagt "Do not attempt to power your Raspberry Pi by plugging it into a computer", und das würde ich unterschreiben, falls ihr euern Computer (bzw. dessen USB-Anschluss) nicht grillen wollt. Ich benutze ein USB-Netzteil vom Kindle meiner Freundin zusammen mit einem Ladekabel meines Handys.
- eine USB-Tastatur, bei mir eine vom Mac.
- Einen Monitor/TV/... mit HDMI-Eingang! Bei mir ein Panasonic TX-P42GW30.
- ggf. eine USB-Maus.
- ggf. eine LAN-Verbindung.
- ggf. ein Gehäuse. Ich habe vorerst keines, aber wenn das alles wie gewünscht funktioniert, hole ich mir eins.
Zuerst muss das Betriebssystem auf die SD-Karte installiert werden; im Folgenden die Anleitung für OS X (Sorry, Thore). Dazu gibt es auf der offiziellen Seite verschiedene Downloads; für's erste habe ich das empfohlene Raspbian "wheezy" image in der Version vom 18.9.2012 verwendet. SD Karte eingesteckt, und im Terminal mittels
|
1 |
diskutil list |
den mount point ("Einhängepunkt") herausgefunden, der bei mir "/dev/disk2" ist. Nun muss man das Image auf diesen mount point schreiben – ich empfehle, hier Vorsicht walten zu lassen, und das Image nicht aus Versehen auf die Systempartition zu schreiben 🙂 Folgendes Kommande
|
1 |
sudo dd if=2012-09-18-wheezy-raspbian.img of=/dev/disk2 bs=1m |
sollte es tun (Quelle). Anmerkung: Eine "Resource busy"-Meldung umgeht man, indem man im "Festplatten-Dienstprogramm" die Partition auf der SD-Karte deaktiviert (Quelle). Nicht "auswerfen", sonst bekommt man ein "Operation not supported". Die Karte darf von OS X nicht verwendet werden, muss aber weiterhin unter "diskutil list" aufgeführt werden! Das Schreiben des Images wird eine Weile dauern, bei mir eine gute viertel Stunde.
Dann wird das gute Stück mit Tastatur (und Maus) bestückt, und an's TV angeschlossen, schlussendlich kommt Strom. Das sollte es im Wesentlichen tun, um den Config-Screen zu sehen zu bekommen:

Dort gibt's Folgendes zu tun (Quelle):
- info: blabla 🙂
- expand_rootfs: Per default werden nur 4 GB verwendet; dieses Tool vergrößert die Partition beim nächsten Reboot auf die volle verfügbare Größe, was durchaus einige Minuten dauern kann! [Anmerkung 1, 5]
- overscan: Soll schwarze Balken auf dem Bildschirm vermeiden – führt bei einem Flachbild-Fernseher aber nur dazu, dass man die Bash (eigentlich am unteren Bildschirmrand) nicht mehr lesen kann^^ Ich musste das Seitenverhältnis des Fernsehers ändern, um die Bash sehen zu können. Beim nächsten Reboot ggf. einfach wieder disablen [Anmerkung 1, 4]
- configure_keyboard: Achtung, das Erzeugen der Liste dauert ein paar Sekunden! Dann kann man Tastatur ("Apple Aluminium Keyboard (ANSI)"), plus -Layout ("Other"->"German"->"German"->"Right Alt"->"No compose key"->"No") auswählen. Das Speichern dauert wieder einige Sekunden [Anmerkung 1, 2, 4]
- change_pass: Wer das Passwort ändert will [default siehe Anmerkung 6]
- change_locale: Voreingestellt ist "en_GB.UTF-8 UTF-8". UTF-8 ist schon OK, aber spaßeshalber habe ich trotzdem mal "de_DE.UTF-8 UTF-8" gesetzt. Achtung, das Erzeugen der Liste und das Speichern dauern ein paar Sekunden!
- change_timezone: "Europe"->"Berlin"
- memory_split: Ändert das Verhältnis der Speicherzuweisung zwischen Hauptprozessor und Grafik. Bis auf Weiteres will ich keinen Desktop starten, deshalb wähle ich 240/16.
- overclock: Will ich bis auf Weiteres nicht
- ssh: (De-)Aktiviert SSH-Zugriff über's Netzwerk. Klingt interessant, bis auf Weiteres aber deaktiviert
- boot_behaviour: Desktop automatisch starten?
- update: Erlaubt ein Update via Netzwerk.
Anmerkungen:
- "Finish" erreicht man über Pfeil links/rechts; danach wird man gefragt, ob man rebooten will.
- Mit ESC kann man den Config-Screen verlassen, und die bash öffnen - Achtung, dann wurde nicht gespeichert! Aber in der bash kann man das neue Tastaturlayout ausprobieren 🙂
- bash: Mit "sudo shutdown -r now" rebooted man, mit "sudo shutdown -h now" fährt man das System runter (Quelle). Auf der Default-Tastaturbelegung ist "-" auf dem deutschen "ß" 🙂
- bash: "sudo raspi-config" öffnet den config-screen erneut. Achtung: Man sollte später möglichst keine Einstellungen mehr ändern!
- bash: "df -h" zeigt die Partitionsgrößen an (Quelle)
- Default-Login ist "pi"/"raspberry"
- Der HDMI-Ausgang zeigt nur Bild, wenn das Kabel schon beim Booten angeschlossen war
Und last but not least: Neue Software kann man per apt-get installieren – wenn man LAN hat^^
Neuer Trailer vom Kleinen Hobbit
via. Aber ernsthaft: Eine Trilogie? Wie schlecht.
WordPress: Posts sortieren über mehrere Custom Fields
In WordPress kann man eigene Daten zu jedem Post speichern, so genannte Custom Fields. Technisch funktioniert das so, dass pro Post und Field ein Eintrag in der Tabelle wp_postmeta angelegt wird.
Beispiel: Ein Rating-Plugin könnte pro bewertetem Post einen Eintrag <meta_key:rating> mit der Gesamtzahl an "Sternen" (1=schlecht, 5=super), und einen Eintrag <meta_key:raters> mit der Anzahl an abgegebenen Stimmen anlegen. Also zwei Zeilen in wp_postmeta pro Post, für den Stimmen abgegeben wurden. Genau so macht es "Post Rate". Anmerkung: Man speichert das schon deshalb als zwei diskrete Werte, um bei der nächsten Bewertung wieder den Durchschnitt berechnen zu können – ich musste kurz überlegen, bis mir das klar wurde 🙂
So, nun hat man in dem Beispiel bewertete Posts, und möchte diese Posts vielleicht nach ihrer Bewertung sortiert anzeigen. Kann ja nicht so schwer sein, schließlich gibt es zB $wpdb mit get_results(). Zu einem Problem wird das (für jemanden, dessen SQL-Kenntnisse etwas eingerostet sind mich) dadurch, dass es eben zwei Einträge in wp_postmeta gibt – und beide speichern ihren Wert in wp_postmeta.meta_value! Für eine Sortierung müsste man die Bewertung (stehen unter <meta_key:rating> in meta_value) durch die Anzahl an Stimmen (stehen unter <meta_key:raters> in meta_value) teilen.
Lösung: JOIN. Erst habe ich einen LEFT JOIN versucht geklaut, aber der definiert sich so, dass er alle Posts ausgegeben hätte, selbst wenn für sie gar kein Rating abgegeben wurde. Deshalb also nur JOIN. Basierend auf dem Foreneintrag mit dem LEFT JOIN ist dabei folgendes entstanden:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$querystr = "SELECT * FROM $wpdb->posts JOIN $wpdb->postmeta AS rating ON( $wpdb->posts.ID = rating.post_id AND rating.meta_key = 'rating' ) JOIN $wpdb->postmeta AS raters ON( $wpdb->posts.ID = raters.post_id AND raters.meta_key = 'raters' ) WHERE $wpdb->posts.post_status = 'publish' AND $wpdb->posts.post_type = 'post' AND $wpdb->posts.post_date < NOW() ORDER BY ((rating.meta_value)/(raters.meta_value)) DESC"; $pageposts = $wpdb->get_results($querystr, OBJECT); |
Etwas wie AS lag mir schon auf der Zunge, allerdings eher als SELECT AS... das in einem (bzw. zwei) JOIN zu verwenden, und zwar sowohl in dem JOIN ("rating.post_id"), wie auch im ORDER BY hätte ich nicht hinbekommen. In diesem Sinne: HTH 🙂
PS: Die formatierte Ausgabe ergibt sich dann aus dem bereits erwähnten Eintrag.
Liferay: Portlet Lokalisierung [UPDATE]
Laut Liferay-Wiki legt man im Classpath (dort ist es explizit ein Package .../com/my/portlets/p1/...) eine Datei "Language.properties", plus für jede "foreign language" (=Locale) ein "Language_*.properties" (mit *=locale). Das kann ich so nicht bestätigen: Meine Dateien müssen direkt in classes liegen, also ohne Package. Andernfalls bekomme ich ein
java.util.MissingResourceException: Can't find bundle for base name Language, locale de_DE
Auf Dateien direkt in classes konnte ich per
|
1 2 |
ResourceBundle myRes = ResourceBundle.getBundle("Language"); System.out.println( myRes.getString("foo") ); |
zugreifen. Groß-/Kleinschreibung oder der ClassLoader waren bei mir nicht das Problem. Vermutlich muss ich beim Laden das Package mit angeben... allerdings sagt das Wiki das so nicht, und ich bin gerade zu faul 🙂
Achtung: Prinzipiell gibt es eine Fallback-Datei, d.h. wenn ich eine Language.properties und eine Language_de.properties habe, ist Language.properties der Fallback. Aber: Der Fallback wird auf Basis des Locale-Defaults ausgewählt! Sprich: Für Language.properties und Language_de.properties ist auf deutschen Rechnern Language_de.properties der Fallback! Das zu vermeiden, bzw. das auf allen Rechnern einheitlich zu haben, schafft man mit einem
|
1 |
Locale.setDefault(Locale.ENGLISH); |
Interessanterweise muss ich die Language.properties auch nicht in der portlet.xml angeben!? Das Wiki spricht von <supported-locale> und <resource-bundle>; bei mir geht es ohne. Vielleicht mache ich hier einfach noch was falsch. Anyway, kurz zum Aufbau der portlet.xml: Wer einen
14:05:30,735 ERROR [PortletLocalServiceImpl:704] com.liferay.portal.kernel.xml.DocumentException: Error on line 12 of document : cvc-complex-type.2.4.a: Invalid content was found starting with element 'supported-locale'. One of '{...}' is expected.
bekommt, der möge auf die Reihenfolge der Knoten achten - so einfach kann's manchmal sein 🙂 Zum Beispiel muss <supported-locale> vor <resource-bundle> deklariert werden. Für mich funktioniert die Reihenfolge
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<portlet> <portlet-name>...</portlet-name> <display-name>...</display-name> <portlet-class>...</portlet-class> <init-param> <name>foo</name> <value>bar</value> </init-param> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> <portlet-mode>edit</portlet-mode> </supports> <supported-locale>de</supported-locale> <resource-bundle>...</resource-bundle> <portlet-info> <title>...</title> <short-title>...</short-title> <keywords>...</keywords> </portlet-info> <security-role-ref> <role-name>administrator</role-name> </security-role-ref> <security-role-ref> <role-name>guest</role-name> </security-role-ref> <security-role-ref> <role-name>power-user</role-name> </security-role-ref> <security-role-ref> <role-name>user</role-name> </security-role-ref> </portlet> |