Friday, October 23, 2009

Windows 7 editions

Did you know there's an entire article on Wikipedia about the various Windows 7 editions? Talk about confusing.

Sunday, October 11, 2009

RhythmToWeb moved to Google Code

I have moved the RhythmToWeb project to Google code. Apart from a small bugfix (it used to send empty requests every x seconds when Rhythmbox was first started), I have also added a couple of server-side examples.

Saturday, October 10, 2009

Apache nice URLs without mod_rewrite

I recently noticed a peculiar behavior in Apache. It seems that if I have, for example, a file /var/www/hello.php, the following URLs will load the same file:
  • http://localhost/hello.php
  • http://localhost/hello
  • http://localhost/hello/goodbye/
After a bit of researching, I found that this behavior is dictated by MultiViews, so if you have that option turned on on your server you can practically forget about rewriting URLs and use this instead.

The only situation I can imagine where you would absolutely need mod_rewrite is if you need to have URLs like http://example.com/1234, you can't handle that with MultiViews (but you can handle http://example.com/article/1234).

Monday, October 5, 2009

RhythmToWeb Updated

I've recently received an e-mail from Aaron Hill, about some modifications he's done to RhythmToWeb. I took his idea of storing information about more than one song, and adapted it in my own way. You can now use the buttons under the song information on the right to browse the last 5 songs I've played. It will also automatically refresh, so if you wait around long enough you'll see it switch to a new song when I start playing one (I really don't know who I'm kidding, no one will ever wait on my blog to see when my song changes :P). Anyway, on to the code: PHP, this gets called by Rhythmbox: <?php define('CALLBACK', 'rtw_callback'); define('JS_FILE', 'nowplaying.js'); define('MAX_ENTRIES', 5); define('SERIALIZE_FILE', './data'); function test_value($var) { if ( strlen($var) && mb_strtolower($var) != 'unknown' && $var != '0' ) { return true; } return false; } $song_info = array(); foreach ($_GET as $key => $value) { if (test_value($value)) { $song_info[$key] = $value; } } $last_songs = unserialize(file_get_contents(SERIALIZE_FILE)); if ($last_songs === false) $last_songs = array(); elseif (count($last_songs) >= MAX_ENTRIES) { while (count($last_songs) >= MAX_ENTRIES) { array_shift($last_songs); } } $last_songs[] = $song_info; file_put_contents(SERIALIZE_FILE, serialize($last_songs)); file_put_contents(JS_FILE, CALLBACK . '(' . json_encode($last_songs) . ')'); ?>
Syntax Highlighting by Pygmentool
HTML, this is in the HTML widget on my blog: <div id="rtw_info">Loading...</div> <button style="padding: 2px 3px; font-size: 0.6em; background: #454545; border: solid 1px #7f7f7f; color: #fff; font-weight: bold; float: right" onclick="rtw_newer()" title="Show more recent songs">&gt;</button> <button style="padding: 2px 3px; font-size: 0.6em; background: #454545; border: solid 1px #7f7f7f; color: #fff; font-weight: bold" onclick="rtw_older()" title="Show older songs">&lt;</button> <script type="text/javascript"> rtw_songs = null; rtw_curIndex = 0; rtw_script_url = "http://znupi.no-ip.org/felix/nowplayingv2/nowplaying.js"; function rtw_callback(aSongs) { // store the received data and show the last song played if (rtw_songs == null || rtw_songs[0].title != aSongs[0].title) { rtw_songs = aSongs; rtw_curIndex = aSongs.length - 1; rtw_update(); } } function rtw_refresh() { var script = document.createElement('script'); script.src = rtw_script_url + "?" + Math.random(); document.body.appendChild(script); setTimeout(rtw_refresh, 5000); } function rtw_older() { if (rtw_songs === null) return; if (rtw_curIndex > 0) { rtw_curIndex --; rtw_update(); } } function rtw_newer() { if (rtw_songs === null) return; if (rtw_curIndex < rtw_songs.length - 1) { rtw_curIndex ++; rtw_update(); } } function rtw_update() { // update the DOM var toFill = document.getElementById('rtw_info'); // first, clear everything in the div while (toFill.childNodes.length) { toFill.removeChild(toFill.childNodes[0]); } // now fill it according to what data we have var curSong = rtw_songs[rtw_curIndex]; // no data: if (curSong.length == 0) { toFill.appendChild(document.createTextNode('Nothing currently playing.')); } // some data: else { var b; if (curSong.title) { b = document.createElement("b"); b.appendChild(document.createTextNode("Song: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.title)); toFill.appendChild(document.createElement("br")); } if (curSong.artist) { b = document.createElement("b"); b.appendChild(document.createTextNode("By: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.artist)); toFill.appendChild(document.createElement("br")); } if (curSong.album) { b = document.createElement("b"); b.appendChild(document.createTextNode("From: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.album)); toFill.appendChild(document.createElement("br")); } if (curSong.genre) { b = document.createElement("b"); b.appendChild(document.createTextNode("Genre: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.genre)); toFill.appendChild(document.createElement("br")); } if (curSong.year) { b = document.createElement("b"); b.appendChild(document.createTextNode("Year: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.year)); toFill.appendChild(document.createElement("br")); } if (curSong.duration) { b = document.createElement("b"); b.appendChild(document.createTextNode("Length: ")); toFill.appendChild(b); var len = parseInt(curSong.duration); var mins = Math.floor(len / 60); var secs = len % 60; toFill.appendChild(document.createTextNode(mins + ":" + secs)); toFill.appendChild(document.createElement("br")); } } } rtw_refresh(); </script>
Syntax Highlighting by Pygmentool
Pretty kewl, eh?

Thursday, September 24, 2009

Android Market Share

@wbm Agreed that android needs more market share before you should care.
Do you think that's true? I beg to differ. Getting your foot in early can earn you recognition on a new platform pretty easily. Let me explain: if you were to develop some awesome app for the iPhone right now, you would find it at the bottom of an over saturated App Store, amongst mostly mediocre products which no one bothers to browse. Most iPhone users just install Facebook, some Twitter client and a couple of silly games. Instead, the Android Market is hungry for new apps (not only because it's new, but also because of the emphasis that Google puts on third-party development and the general openness of the platform), and getting a head start can really make a difference. Your application gets a lot more exposure on the Android Market and the chances of it becoming a hit later are much more increased. Sure, it's a bit more risk, but I think it's worth it.

Tuesday, September 22, 2009

Java: parse XML from the web smartly

Up until recently, I used to fetch the entire XML document before parsing it. I've found that that can be extremely unhealthy for your application, especially if you're developing on Android, where resources are scarce and the GC is very unforgiving. The optimal way to parse XML is to parse it while it's loading, so that resources are freed much more efficient. Here's a snippet of code that demonstrates how to do this: URL oUrl = new URL(sUrl); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); MyXMLHandler handler = new MyXMLHandler(); xr.setContentHandler(handler); xr.parse(new InputSource(oUrl.openStream()));
Syntax Highlighting by Pygmentool
This way, XML is parsed as it comes in, and the application is much faster and smoother. Also, if you don't parse the whole XML this is an even greater improvement, because you can stop parsing when you have enough data (by throwing a SAXException) and it will even stop loading data, which results in faster load times and less bandwidth usage.

Sunday, September 20, 2009

Java Package Mayhem

Here's how a 350-some line Android Activity looks like, in terms of imports: import java.io.StringReader; import java.net.URLEncoder; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.TextView; import com.tastekid.TasteKid.TasteKidUtils.Resource; import com.tastekid.TasteKid.TasteKidUtils.ResourceList; Thank God for Eclipse's Ctrl+Shift+O (yes, I know I bashed Eclipse before, but it seems to behave much more nicely under Ubuntu than under Windows -- it doesn't go above 300MB RAM usage).