June 29, 2011

Jasmine Guard for CoffeeScript rocks on Rails 3

CoffeeScript sure is interesting and I had a very positive experience trying it with Jasmine BDD testing kit. With Guard autocompiling the .js files, it becomes a charm on Rails 3.

Jasmine BDD in CoffeeScript on Vimeo.

This setup guide from pivotallabs.com coupled with this updated gist you will have the infrastructure set up in no time. Any change to the CoffeeScript source or spec files should trigger the Guard to recompile the JavaScript.

To compile "bare" js see this tip to add :bare => true to your Guardfile.

Start guard and jasmine servers and browse to localhost:8888 to run the test suite.
$ guard
$ rake jasmine

Here is a good example of writing a CoffeeScript spec. More spec examples will eventually sprout up, undeniably. And naturally, the Jasmine wiki is a good source of information.

Here's one example: timer.coffee
class Timer
  constructor: (@timer_json) ->
    @start_date = new Date(@timer_json.start_date)
    @end_date = new Date(@timer_json.end_date)
    [@start_hours, @start_minutes] = @timer_json.start_at.split(":")
    [@end_hours, @end_minutes] = @timer_json.end_at.split(":")
And the corresponding test: timer_spec.coffee
describe 'timer-parser', ->

  timer_data = {
    start_date: "2011/06/20", end_date: "2011/06/25",
    start_at: "12:00", end_at: "04:04"
  }

  it 'should parse properly', ->
    timer_json = JSON.parse JSON.stringify timer_data
    timer = new Timer(timer_json)
    expect(timer.start_date).toEqual new Date "2011/06/20"
    expect(timer.end_date).toEqual new Date "2011/06/25"
    expect(timer.start_hours).toEqual "12"
    expect(timer.start_minutes).toEqual "00"
    expect(timer.end_hours).toEqual "04"
    expect(timer.end_minutes).toEqual "04"

June 1, 2011

Writing JavaScript in 2011 - catching up with the latest trends

JavaScript has definitely bloomed during the last few years. I do remember the first time I heard of JavaScript in the late 90s - I wrote a small script that showed a random image (out of a static array) on a static HTML page that I tested in Netscape and IE5-or-so. Today people run JavaScript in top- and mid-range mobile devices, on their web browsers, on servers, on the Gnome3 desktop.

JavaScript is the only language for building an application for all major "smart" mobile devices from within a singular codebase. That, already, interests me enough to give a closer look on some modern JavaScript tools - and write my observations about them.

Foremost, testing JavaScript was something I had completely neglected before. If I was to write serious applications using JavaScript, I'd absolutely needed a way to write unit tests. Secondly, I wanted to have a look at CoffeeScript, which has had a lot of positive attention lately.

I started out by digging into the recent archives of JavaScriptWeekly newsletter. Out of a good number of testing tools, I selected to try out JsTestDriver. It is compact, requiring only a single jar file and a configuration file. The jar contains a small web server that is ran on the source code directory, and exposes a specific url that should be opened in the browsers that will run the JavaScript code to be tested. Cool! If you consider to try out JsTestDriver yourself, I recommend you checkout their SVN repository and compile the jar yourself - the jar file given in the example is outdated and does not contain all the features that the guides may refer to - for example, mocking a piece of HTML that the JavaScript modifies.

Mocking AJAX was out of my scope at this stage, for that you may find this article to be helpful.

For learning to use JsTestDriver, I chose to write a simple slideshow application that uses CSS3 transformations to change the pictures. The required JavaScript for this seemed simple enough, and I wanted to learn some new CSS tricks as well. ;) So, this is "bébé", written in a few days using jQuery-powered CoffeeScript. I started out by getting the basics of CoffeeScript, having it to load the images onto the page, and then wrote tests to see the pictures were actually loaded. Testing the user interface seems to be very difficult, due to differences in how different browsers behave under JsTestDriver. I set the picture position to absolute, since elsehow I could not get the CSS transition to appear smooth (and have the image to disappear without hiding it with JavaScript). CoffeeScript is pretty amazing, and works really well with jQuery. :)

The code for this, along with the JsTestDriver tests, can be found on Github. This seems to work reasonably well in Firefox 4, Chrome, Opera 11, Safari (and Mobile Safari on iOS) and Android browsers. Firefox 3 behaves correctly, it just doesn't show the transition effect. IE fails miserably, never showing anything except the first image. :/

Here are some nice pointers to related articles:

..and now for something different ..
PC emulator in the browser running Linux on JavaScript

March 7, 2011

HTML5 native audio support comparison

Here are the results of a quick test how various browsers on OS X 10.6 (and iPhone 3GS) play HTML5 <audio>.

UPDATE 25.5.2011: I updated the data and found that the audio support has become better in many browsers. I left in old data for comparison.

Browser / OS mp3 wav ogg
Android 2.2 X X X
Chrome 9.0.597 ok X1 ok
Chrome 11 ok X1 ok
Firefox 3.6.13 X ok X2
Firefox 4.0.1 X ok ok
iOS 4.2 ok X X
Opera 11.1 X ok3 ok
Safari 5.0.3 ok X X
Safari 5.0.5 ok ok3 X
  • 1    plays, but is buggy
  • 2    hangs on load
  • 3    is able to seek!

Likewise with <video>, there is a need to encode to many formats to support all browsers. I have no data of IE9. Unfortunately Android 2.2 cannot play any audio type at all! This is supposed to be fixed in Android 2.3. See this link for more information on HTML5 audio on Android. Also, Opera 11.1 could play ogg files, but I noticed that when they were encoded in higher bitrates, the playback stopped in the middle of the track, systematically. This seems to be fixed in Opera 11.11. Chrome buggyness on wav files appears upon seeking- the play stops and cannot be started again without reloading the page.

The tests were conducted by creating a very simple HTML5 document with this content:

      <!DOCTYPE html>
      <html>
      mp3: <audio src="test.mp3" controls></audio>
      wav: <audio src="test.wav" controls></audio>
      ogg: <audio src="02.ogg" controls></audio>
      </html>
    
The files were hosted (locally) from the same directory as the html document by ad-hoc Python web server:
      python -m SimpleHTTPServer
    

February 27, 2011

Audio CD mastering with open source tools

You want to burn an audio CD from a bunch of audio files from various sources, not direct CD rips? You can use open source tools to check the properties of the tracks and prepare them properly to avoid potential pitfalls and ensure a better listening experience.

The tools used in this memo:

Some of these do have overlapping functionality. With the exception of wavbreaker (needs ALSA/OSS), all of them are available on both OS X and Linux.

Installation on Ubuntu:

  $ sudo apt-get install sndfile-programs sox normalize-audio wavbreaker shntool cuetools
Please note that the following changes to wav files are destructive so be sure to operate on copies of the originals.

Normalize

Check if the tracks need normalization.

  $ sndfile-info *.wav | egrep 'File|Signal Max'
    File : 01.wav
    Signal Max  : 16462 (-5.98 dB)
    File : 02.wav
    Signal Max  : 19946 (-4.31 dB)
    File : 03.wav
    Signal Max  : 24188 (-2.64 dB)
    File : 04.wav
    Signal Max  : 12092 (-8.66 dB)
    File : 05-06.wav
    Signal Max  : 16781 (-5.81 dB)
    File : 07.wav
    Signal Max  : 32768 (0.00 dB)
    File : 08-09.wav
    Signal Max  : 27442 (-1.54 dB)

So there is one with a really loud peak (07.wav) and a few bit quieter tracks. This is what normalize-audio suggests to do (gain column):

  $ normalize-audio -n *.wav
      level        peak         gain
    -12,6922dBFS -5,9791dBFS  0,6922dB   01.wav
    -14,3614dBFS -4,3119dBFS  2,3614dB   02.wav
    -15,2635dBFS -2,6370dBFS  3,2635dB   03.wav
    -22,4749dBFS -8,6588dBFS  10,4749dB  04.wav
    -15,9599dBFS -5,8126dBFS  3,9599dB   05-06.wav
    -13,3062dBFS 0,0000dBFS   1,3062dB   07.wav
    -13,3358dBFS -1,5407dBFS  1,3358dB   08-09.wav

To go with automatic settings:

  $ normalize-audio *.wav
    Applying adjustment of 0,69dB to 01.wav...
    Applying adjustment of 2,36dB to 02.wav...
    Applying adjustment of 3,26dB to 03.wav...
    Applying adjustment of 10,47dB to 04.wav...
    Applying adjustment of 3,96dB to 05-06.wav...
    Applying adjustment of 1,31dB to 07.wav...
    Applying adjustment of 1,34dB to 08-09.wav...

I really didn't like what this did to track 04.wav, so I reverted it back from the backups and adjusted a smaller gain manually:

  $ normalize-audio -g 2,5dB 04.wav 
    Applying adjustment of 2,500000dB...
     04.wav            100% done, ETA 00:00:00 (batch 100% done, ETA 00:00:00)

See normalize-audio --help for more controls. Normalization does not adjust signal-to-noise ratio, but trust your ears and speakers when adjusting volume. The desired peak level is within range -1,0 .. -6,0 dB. See Wikipedia.

Resample to Red Book CD standard

Convert the tracks to CD audio format (stereo, 16 bit, 44100 Hz). First check their current sample rate:

  $ sndfile-info *.wav | egrep 'File| Channels| Sample Rate| Bit Width'
    File : 01.wav
      Channels      : 2
      Sample Rate   : 48000
      Bit Width     : 16
    File : 02.wav
      Channels      : 2
      Sample Rate   : 48000
      Bit Width     : 16
    File : 03.wav
      Channels      : 2
      Sample Rate   : 48000
      Bit Width     : 16
    File : 04.wav
      Channels      : 2
      Sample Rate   : 44100
      Bit Width     : 16
    File : 05-06.wav
      Channels      : 2
      Sample Rate   : 48000
      Bit Width     : 16
    File : 07.wav
      Channels      : 2
      Sample Rate   : 44100
      Bit Width     : 16
    File : 08-09.wav
      Channels      : 2
      Sample Rate   : 44100
      Bit Width     : 16

Here I have a bunch of files in incompatible sampling rate. SoX can do many things to audio files, including resampling.

  $ sox inputfile.wav -r 44100 -b 16 -c 2 outputfile.wav

I converted the 48 kHz files with this ad-hoc loop:

  $ IFS=$'\n' ;\
    for f in $(\
      sndfile-info *.wav |\
      egrep 'File|^Sample Rate' |\
      grep -B 1 48000 |\
      awk -F' : ' /File/{'print $2'}); do \
        sox "${f}" -r 44100 -b 16 -c 2 "${f}-44k1.wav" && rm -f "${f}" ; done

Eliminate sector boundary errors (SBE)

To make sure there won't be any clicks between tracks, when the audio is continuous over a track change segue, you need to ensure the proper length of each track. On disc, tracks begin at full sectors, and a single sector length is 1/75 sec. If the wav of previous track is just slightly too short, the cd burner will insert some silence where audio is missing, to fill out the sector. This is enough to cause an annoying click in some cd players.

shntool is a great tool for extracting information and manipulating audio files. I will not give an example here, but see the output from shntool info *.wav to examine the properties of your wave files.

Examine the output of the shntool len command to discover problems:

  $ shntool len *.wav
     length     expanded size    cdr  WAVE problems  fmt   ratio  filename
      3:43.65       39490508 B   -b-   --   -----    wav  1.0000  01.wav-44k1.wav
      5:40.02       59979756 B   -b-   --   -----    wav  1.0000  02.wav-44k1.wav
      4:26.66       47076708 B   -b-   --   -----    wav  1.0000  03.wav-44k1.wav
      2:22.08       25067660 B   ---   --   -----    wav  1.0000  04.wav
      5:53.48       62382468 B   -b-   --   -----    wav  1.0000  05-06.wav-44k1.wav
      4:36.65       48838628 B   -b-   --   -----    wav  1.0000  07.wav
      7:39.68       81128396 B   -b-   --   -----    wav  1.0000  08-09.wav
     34:23.22      363964124 B                            1.0000  (7 files)
The b in the cdr column indicates a SBE. The hyphen indicates that everything is OK.

I wrote a small script called sbeok to check wav for SBEs. You can either use the script or shntool.

  $ sbeok *.wav
    01.wav-44k1.wav: FAILED, missing 1968 bytes
    02.wav-44k1.wav: FAILED, missing 992 bytes
    03.wav-44k1.wav: FAILED, missing 968 bytes
    04.wav: OK
    05-06.wav-44k1.wav: FAILED, missing 2024 bytes
    07.wav: FAILED, missing 696 bytes
    08-09.wav: FAILED, missing 1536 bytes

Seems all but track 04.wav has improper length and a small amount of silence would be added to the end of these tracks while burning the cd. Additionally, there are two files: 05-06.wav and 08-09.wav that need to be cut and they have music during the segue.

The brute force approach to recalculate sector boundaries is to merge all wavs into one and manually insert the track markers. This has the benefit that you don't need to cut tracks in previous steps of your workflow, but to leave it until last.

Merge all tracks together with shnjoin.

  $ shnjoin *.wav
    Joining [01.wav-44k1.wav] (3:43.65) --> [joined.wav] (34:23.22) : 100% OK
    Joining [02.wav-44k1.wav] (5:40.02) --> [joined.wav] (34:23.22) : 100% OK
    Joining [03.wav-44k1.wav] (4:26.66) --> [joined.wav] (34:23.22) : 100% OK
    Joining [04.wav] (2:22.08) --> [joined.wav] (34:23.22) : 100% OK
    Joining [05-06.wav-44k1.wav] (5:53.48) --> [joined.wav] (34:23.22) : 100% OK
    Joining [07.wav] (4:36.65) --> [joined.wav] (34:23.22) : 100% OK
    Joining [08-09.wav] (7:39.68) --> [joined.wav] (34:23.22) : 100% OK
    Post-padded output file with 1128 zero-bytes.

Use wavbreaker to set track split markers.

  $ wavbreaker joined.wav

This opens up a friendly GUI to insert your track marks. Seek to proper position in the topmost waveform view, and adjust the exact cut point from the second-to-top view. Click "Add" to enter a marker .. the interface is very easy to use. You could split the tracks from within wavbreaker, but once you have the marker file, it is possible to script the whole process. "Export to TOC" into file master.toc and exit the program.

Use shnsplit with cuebreakpoints to split the files based on the TOC. Write the master CD cast to directory "master":

  $ mkdir master
  $ cuebreakpoints -i toc master.toc | shnsplit -t %n -d master joined.wav
    Splitting [joined.wav] (34:23.22) --> [master/01.wav] (3:44.44) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/02.wav] (5:40.54) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/03.wav] (4:25.48) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/04.wav] (2:23.67) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/05.wav] (3:23.38) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/06.wav] (2:28.05) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/07.wav] (4:36.48) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/08.wav] (1:18.24) : 100% OK
    Splitting [joined.wav] (34:23.22) --> [master/09.wav] (6:21.69) : 100% OK

How about those sector sizes now?

  $ sbeok master/*.wav
    master/01.wav: OK
    master/02.wav: OK
    master/03.wav: OK
    master/04.wav: OK
    master/05.wav: OK
    master/06.wav: OK
    master/07.wav: OK
    master/08.wav: OK
    master/09.wav: OK
  $ shntool len master/*.wav
    length     expanded size    cdr  WAVE problems  fmt   ratio  filename
     3:44.44       39617132 B   ---   --   -----    wav  1.0000  master/01.wav
     5:40.22       60027788 B   ---   --   -----    wav  1.0000  master/02.wav
     4:26.05       46934204 B   ---   --   -----    wav  1.0000  master/03.wav
     2:23.67       25382828 B   ---   --   -----    wav  1.0000  master/04.wav
     3:21.15       35491724 B   ---   --   -----    wav  1.0000  master/05.wav
     2:30.28       26525900 B   ---   --   -----    wav  1.0000  master/06.wav
     4:36.48       48799340 B   ---   --   -----    wav  1.0000  master/07.wav
     1:18.24       13815692 B   ---   --   -----    wav  1.0000  master/08.wav
     6:21.69       67370732 B   ---   --   -----    wav  1.0000  master/09.wav
    34:23.22      363965340 B                            1.0000  (9 files)

Looks good. How are the volume levels?

    File : master/01.wav
    Signal Max  : 22620 (-3.22 dB)
    File : master/02.wav
    Signal Max  : 28166 (-1.31 dB)
    File : master/03.wav
    Signal Max  : 29384 (-0.95 dB)
    File : master/04.wav
    Signal Max  : 16501 (-5.96 dB)
    File : master/05.wav
    Signal Max  : 26729 (-1.77 dB)
    File : master/06.wav
    Signal Max  : 20980 (-3.87 dB)
    File : master/07.wav
    Signal Max  : 29205 (-1.00 dB)
    File : master/08.wav
    Signal Max  : 17139 (-5.63 dB)
    File : master/09.wav
    Signal Max  : 27442 (-1.54 dB)
All of them are within acceptable range, and the volume is perceived to be sensible acroll the tracks during playback. The perceived volume depends on the dynamic range of the audio - along with the equipment and the listener self as well.

Burn to CD

Since you're already on the command line, on Linux you can use cdrecord to burn the CD.

  $ cdrecord -v speed=1 dev=0,0,0 -dao -audio master/*.wav

January 29, 2011

Recipe of request-response over AMQP with Python and Java

This is a recipe to setup synchronized messaging over AMQP. The recipe source zip file contains an implementation of a Python backend broker. Along comes example Python and Java clients that send messages over AMQP to the running broker(s). The broker load can be balanced over to several processes. RabbitMQ can automagically balance the incoming requests to multiple consumers. This setup has been deployed successfully, achieving decent performance (we have benchmarked only the private implementation, our throughput is approx. 30 msgs/sec. with three backend processes).

Good presentations on concurrent messaging and AMQP are available from:

AMQP can be used as a message bus between various system components in different languages maintained by different teams.

Here are the bare bones of this recipe:

  • create a unique identifier (qid) for the request
  • create a new consumer and bind it to a temporary response queue
  • publish the message into the durable request queue
  • wait for the response on the response queue
  • close response channel
Topic exchange is the only exchange type that honors routing keys, which are essential for pairing request and response together, using the unique identifier in routing key.

Unzip the recipe (or clone the recipe repository from GitHub). Install RabbitMQ (default settings are ok), and install Python modules 'amqplib' and 'carrot' (plus 'jsonrpclib' for the tests).

Launch the RabbitMQ server.

sudo rabbitmq-server

Start the example backend broker:

cd py-src
python example_broker.py
This will launch a daemon that listens for messages on a specific AMQP channel. A "request" is a thin JSON wrapper. If the request includes a request id (qid), the daemon will return the current time over the AMQP. If the request is not identified, the time will be printed instead.

Run the example requests in Python and Java, while the example_broker is running:

cd py-src
python example_request.py
Output:
Response from AMQP: 
{u'msg': u'Sat Jan 29 20:43:36 2011'}
Run the same requests from Java:
cd java-src
java -cp .:build:lib/json.jar:lib/rabbitmq-client-1.8.1.jar:lib/commons-logging.jar:lib/commons-io.jar \
  example.ExampleRequest
Output:
29.1.2011 21:11:01 recipe.amqpbus.AMQPRequestResponseImpl newConnection
INFO: ExampleBroker connected to RabbitMQ @ 127.0.0.1:5672/
29.1.2011 21:11:01 recipe.amqpbus.AMQPPublisher send
INFO: publishing query to ExampleBroker_req with binding key: example.request
{"q":{"q":"hello AMQP backend, I am Java"}}
-----------------------------------------
29.1.2011 21:11:01 recipe.amqpbus.AMQPPublisher send
INFO: publishing query to ExampleBroker_req with binding key: example.request.q538
{"q":{"q":"time can you get me please?"},"qid":"q538"}
29.1.2011 21:11:01 recipe.amqpbus.AMQPConsumer receive
INFO: amq.gen-dyK/PZQn/brNl9jTR/tlAg== received message: 
{"msg": "Sat Jan 29 21:11:01 2011"}
Response from AMQP: {"msg":"Sat Jan 29 21:11:01 2011"}

The example_broker output should print the events when it receives the requests: Output:

  consumer received message: 
  {'exchange': u'TestExchange', 
    'consumer_tag': u'__main__.ExampleBroker-e7175ec0-cd1f-4d2e-973b-1eeb42d4071d', 
    'routing_key': u'example.request.*', 
    'redelivered': False, 
    'delivery_tag': 2, 
    'channel': <amqplib.client_0_8.channel.Channel object at 0x1007bbad0>}
  -------------------
  received request 355:
  what time is it?
  -------------------
  response to TestExchange with routing_key: example.response.355, message: 
  {"msg": "Sat Jan 29 20:47:59 2011"}
  using channel_id: 3
  Channel open
  Closed channel #3

You should be able to pick up the ingredients and integrate them to your codebase as you see fit.

December 5, 2010

Hybrid Qt applications with PySide and Django

This article describes some thoughts and experiments on developing applications targeted for mobile systems, especially MeeGo. I am trying to imagine a secure and agile way to write applications that are robust yet elegant.

About the author – I have worked with Ruby on Rails, Django and Qt. I think that web run-time technologies (HTML5, JavaScript and CSS3 – called WRT in this article) can provide a flexible way to build user interfaces. Many developers are familiar with these technologies, and maximizing WRT percentage in the codebase has potential to better portability for devices from different manufacturers. Personally to me MeeGo is the most interesting mobile operating system target. I am trying not to completely forget about business models, but then again, I have a software libre bias.

I have experimented of "hybridizing" Qt with Django. PySide provides Qt bindings for Python in LGPL licensing. Django is a CMS-oriented web framework written also in Python. Using Python instead of C++ has a drawback in runtime efficiency, but in some cases developer productivity and codebase maintainability is more important. Python has become one of my favourite languages.

This approach interests me because an application from the same codebase can be deployed both on desktop and on (a Nokia) mobile device. Alternatively a desktop/server Django application could serve plain WRT components also for non-Qt clients (but I can't comment how Apple would respond to this sort of application for the App Store). Android Market - well - they probably would not have a problem with it. That's all I have to say about marketing and business politics.

Nokia N900 is a fabulous platform for prototyping, on which I wrote an experimental application for controlling a TV set within the local area network (LAN). Figure 1 displays an event cycle in which the whole application is self-contained on the mobile device.

Fig 1. The software components on N900

Not depicted in figure 1 for clarity, the Python process contains an implementation of a finite-state machine. User actions are sent as events to the backend process handling logical states, while the WRT defines slots that update the UI.

This approach is reminescent of cloud computing, but with an important difference in that the data that moves between the server an the mobile device would not be stored on servers the user does not have control over. The data would never pass to the ISP either. Consider a Windows PC with a media repository was running PySide/Django in the LAN, and an iPhone owner could control the media stream onto a MeeGo TV set? Switch Windows for Linux and iPhone for a MeeGo device and it still would work without a heavy rewrite of the codebase. The PC would serve an ad-hoc "cloud" that could be operated from the hand-held device, acting as a remote controller. That is the core of this vision.

It makes sense to browse local data (from the PC) and "stream" it onto the mobile device. You wouldn't carry the precious data around in your phone, but it still would be reachable from home over a secure network connection. This is a data privacy issue. Photos are a good example. A major concern in any networked application is security and I've foolishly ignored many gaping secury holes in this prototype. As for my defense I can only say I am experimenting the feasibility and hardware performance so far.

 
Fig 2. Cloud computing within the LAN with MeeGo

The prototype application I've written (called MxManager) is a remote controller for Maximum T-8000 personal video recorder (PVR) that runs on embedded Linux. Its prorietary firmware provides a simple HTTP interface that mimics the physical remote controller. This prototype uses that API to set recordings from the EPG and play recordings on the TV. Here is a screenshot of the application (on MacBook desktop) and a blurry video of it running on N900.

Fig 3. MxManager UI


Qt hybrid on N900 on Vimeo. Sorry about the quality. It was shot up close, I guess I would have needed to strap on a macro lens.


The screen and remote control UI are individual QWebView elements and their content is composed by Django views. Events from the UI are triggered in JavaScript and events back from PySide are injected as JavaScript function calls into the page. PySide is running a finite-state machine (using QStateMachine) that holds the internal state of the application. Changes in UI are triggered by state change events. I wrote about this in more detail in a past blog post. The source code is released under the BSD, so you are welcome to have a look and give an opinion.

Initial experiments with QML appeared subjectively much faster, nearing smooth animation along the button press, but in the past I had a problem in sending signals into QML. This seems to have been fixed in PySide 1.0.0. This is a solid direction to experiment - perhaps Django could be used to compose QML for the QDeclarativeView..

Another video of the application in action with the TV set!

While building the web stack on well-proven frameworks, security issues still need to be considered carefully. Web sockets would be better designed for such eventing purpose than Ajax, with a better security model. I do repeat, this is not production-ready code.
If the mobile phone supported QtMobility, it could be possible to use mobile accelerometer and orientation to control playback. Turn the phone upside down to pause, for example. The next video (by perlinet, not me) portrays using QtMobility API in PySide with QML.


PySide is great technology and it's inclusion to MeeGo opens up many ways for developers to bring their applications to the platform. There are definite points to focus on this basis:
  • security implications
  • web sockets
  • QML
  • QtMobility experimentation
Especially intriguing for me would be to hook up digiKam database into a desktop-based Django app for browsing photos on a mobile device / tablet computer.

What's unique here is the merging of the Qt and web frameworks to operate within the same OS process, which makes it easier to separate the presentation and business logic. Personally I feel this technology seems feasible, and perhaps I will continue to investigate it further in 2011. There are web services that operate on a similar business model, and maybe open source software is following the trend.

November 19, 2010

Recipe: handling database communication breakdown in Rails and Django

This recipe is for users who manually handle persistent (shared) database connection, with Oracle, although it's probably easy to adapt for other database backends with small adjustments. I needed to implement this both in Ruby (Rails' ActiveRecord) and Python (django.db) daemons, both working in the same setting. I thought the solution was elegant enough to be cleaned up into a recipe.

Automated database connection pooling for Python (nor Ruby) is not supported with Oracle 10g. Only 11g introduced Database Resident Connection Pooling (DRCP). It probably makes this recipe redundant for Python users with 11g. Oracle has official support for using Python with Oracle Database 11g, using the cx_Oracle driver. I'm not sure but I think Oracle doesn't have any form of official support for Ruby, but the de-facto ruby-oci8 driver works well with ActiveRecord.

Without automated connection pooling you have two decent options. Either open and close a new connection for each request, or save open connections into shared memory. The latter has obvious performance benefits if the expected request rate is high, but exposes a number of other problems this recipe aims to address. A connection can abruptly terminate because of e.g. a network glitch or maintenance downtime, and the sysadmins deploying your software will be happier having to deal with less services to reboot.

The handler should catch exceptions only related to network or database resource failures, whereas in other cases (SQL typo?) the original exception should not be reacted upon. Figuring out the proper ORA error codes to use was an exciting task, I hope I got them right ;). Those guys at Oracle sure love to make things easy. Since I use a regular expression to match the error message string, a multitude of errors can be matched with a single rule. Fortunately, all errors dealing with Transparent Network Substrate are marked by prefix "TNS". In my tests most errors were indeed caught with the single "ORA-.....: TNS" regexp.

Python and Ruby have much in common, both could let me write this very expressively with about the same LOC. There is one small detail I'd like to bring up. These two expressions both compare the codes listed with the exception (error message in variable "message"), and return true if the message matches to one of the given ORA error codes.

Python

# Example message="ORA-03113: end-of-file on communication channel"
any(filter(lambda oracode: re.search('ORA-'+oracode,message), re.split(' *\n *| +(?=\d)',"""
    .....: TNS
    01000 01001 01014 01033 01034 01037 01089 01090
    011.. 015.. 016..
    031..
    28547
    30678
    """)[1:-1]))
Ruby
# Example message="ORA-01034: ORACLE not available"
%w{ .....:\ TNS
    01000 01001 01014 01033 01034 01037 01089 01090
    011.. 015.. 016..
    031..
    28547
    30678
}.select {|oracode| message[/ORA-#{oracode}/]}.any?
Especially take notice of Ruby's %w{multiline string} versus Python's re.split(' *\n *| +(?=\d)',"""multiline string""")[1:-1]. This just for the sake of the visual appearance of input data on the screen. Ruby makes it easier to write aesthetic code. Perhaps Python has an idiom for this, though, it just didn't pass my mind.

example_oci_exception_handler.py

# -*- coding: utf-8 -*-
"""
You may use this file under the terms of the BSD license as follows:

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of copyright holder.
"""
import re
import sys
from time import sleep
from myapp.settings import settings # your django settings module
from django.core.management import setup_environ
setup_environ(settings)
import cx_Oracle
from django.db import connection
from django.db.utils import DatabaseError
from logging import getLogger
log = getLogger(__name__)

class ExampleOCIExceptionHandler():
    """Recipe for handling lost Oracle database connection.
    
    Depends on Django ORM, and that proper settings.py exist and is initialized properly.
    
    Selects the network and connection errors, waits until the server is reachable again,
    and calls the method again during upon which the exception happened.
    
    This is very useful if the connection, for some reason, is shared between requests.
    Oracle 10g does not support connection pooling with cx_Oracle.
    """

    @staticmethod
    def dispatch(message_data):
        """Processes request.

        In case of database connection failure, the waitForOCIConnection() loop is called
        and connection should be re-established. In case of other Oracle errors,
        the error is raised again.
        """
        try:
            # do some database operations...

        except (cx_Oracle.Error, DatabaseError):
            """
            Catch lost database connection.

            Handles all TNS errors:
              ORA-xxxxx: TNS errors

            And all errors from these ranges:
              ORA-011xx: Database file errors
              ORA-015xx: Execution errors
              ORA-016xx: Execution errors
              ORA-031xx: communication errors

            Along with these specific errors:
              ORA-01000: maximum open cursors exceeded
              ORA-01001: invalid cursor
              ORA-01014: ORACLE shutdown in progress
              ORA-01033: ORACLE initialization or shutdown in progress
              ORA-01034: ORACLE not available
              ORA-01037: cannot allocate sort work area cursor; too many cursors
              ORA-01089: immediate shutdown in progress - no operations are permitted
              ORA-01090: shutdown in progress - connection is not permitted
              ORA-28547: connection to server failed, probable Oracle Net admin error
              ORA-30678: too many open connections

            """
            msg = str(sys.exc_info()[1]).rstrip()

            # is the error about broken connection?
            if any(filter(lambda oracode: re.search('ORA-'+oracode,msg), re.split(' *\n *| +(?=\d)',"""
                .....: TNS
                01000 01001 01014 01033 01034 01037 01089 01090
                011.. 015.. 016..
                031..
                28547
                30678
                """)[1:-1]) # ignore outermost items as they are empty strings
            ):
                log.error(msg)
                ExampleOCIExceptionHandler.waitForOCIConnection()
                # enter recursion and call this method again..
                sleep(2)
                return ExampleOCIExceptionHandler.dispatch(message_data)

            else:
                log.debug(msg)
                log.debug("Error seems to be not about lost connection, raising again ..")
                raise

    @staticmethod
    def waitForOCIConnection(time_to_wait=60):
        """Creates a new database connection, loops until one is established."""
        # nullify the connection first, since it can't discover that the socket is gone
        connection.connection = None
        while not connection._valid_connection():
            try:
                log.info("Attempt to establish OCI connection to %s ..." % [
                    settings.DATABASES['default']['NAME']])
                # in django parlance, calling _cursor() opens the database connection
                cursor = connection._cursor()
                sleep(2)
                return connection._valid_connection()

            except (cx_Oracle.Error, DatabaseError):
                exctype, message = sys.exc_info()[:2]
                log.error(str(message).rstrip())
                log.info("Retrying OCI connection in %i seconds" % time_to_wait)
                sleep(time_to_wait)
                return ExampleOCIExceptionHandler.waitForOCIConnection(time_to_wait)


example_oci_exception_handler.rb
# encoding: utf-8
=begin
You may use this file under the terms of the BSD license as follows:

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of copyright holder.
=end
require 'rubygems'
require 'active_record'

# Recipe for handling lost Oracle database connection with ActiveRecord.
#
# Depends on RAILS_ROOT and RAILS_ENV variables to be set and RAILS_ROOT/config/database.yml file to exist.
#
# Selects the network and connection errors, waits until the server is reachable again,
# and calls the method again during upon which the exception happened.
#
# This is very useful if the connection, for some reason, is shared between requests.
class ExampleOCIExceptionHandler

  # Processes request.
  #
  # In case of database connection failure, the waitForOCIConnection() loop is called
  # and connection should be re-established. In case of other Oracle errors,
  # the error is raised again.
  def self.dispatch(message_data)
    begin
      # do some database operations...

    # Catch lost database connection.
    #
    # Handles all TNS errors:
    #   ORA-xxxxx: TNS errors
    #
    # And all errors from these ranges:
    #   ORA-011xx: Database file errors
    #   ORA-015xx: Execution errors
    #   ORA-016xx: Execution errors
    #   ORA-031xx: communication errors
    #
    # Along with these specific errors:
    #   ORA-01000: maximum open cursors exceeded
    #   ORA-01001: invalid cursor
    #   ORA-01014: ORACLE shutdown in progress
    #   ORA-01033: ORACLE initialization or shutdown in progress
    #   ORA-01034: ORACLE not available
    #   ORA-01037: cannot allocate sort work area cursor; too many cursors
    #   ORA-01089: immediate shutdown in progress - no operations are permitted
    #   ORA-01090: shutdown in progress - connection is not permitted
    #   ORA-28547: connection to server failed, probable Oracle Net admin error
    #   ORA-30678: too many open connections
    #
    rescue OCIError, ActiveRecord::StatementInvalid
      msg = $!.message
      
      # is the error about broken connection?
      if %w{
        .....: TNS
        01000 01001 01014 01033 01034 01037 01089 01090
        011.. 015.. 016..
        031..
        28547
        30678
      }.select{|oracode| msg[/ORA-#{oracode}/]}.any?
      
        logger.error msg
        waitForOCIConnection()
        # enter recursion and call this method again..
        return dispatch(message_data)

      else
        logger.debug msg
        logger.debug("Error seems to be not about lost connection, raising again ..")
        raise $!
      end
    end
  end


  # Attempts to establish database connection.
  # Loops until ActiveRecord is connected.
  #
  # Set time_to_wait in seconds.
  def self.waitForOCIConnection(time_to_wait=60)
    begin
      database_configuration = YAML.load_file(File.join(RAILS_ROOT,"config","database.yml"))
      ActiveRecord::Base.configurations = database_configuration
      logger.info(
        "Attempt to establish OCI connection to %s ..." % database_configuration[RAILS_ENV]['database'])
      ActiveRecord::Base.establish_connection(database_configuration[RAILS_ENV])
      ActiveRecord::Base.connection # essential to open connection
      return ActiveRecord::Base.connected?

    rescue OCIError, ActiveRecord::StatementInvalid
      logger.error($!.message)
      logger.info("Retrying OCI connection in %i seconds" % time_to_wait)
      sleep time_to_wait
      return waitForOCIConnection(time_to_wait)
    end
  end
end

October 15, 2010

Interacting with legacy Oracle database in Python

I needed to use an Oracle database in Python. It all went rather smoothly with the guides, hints and examples, but I was missing a walkthrough. This memo is about setting up Oracle with Python and how to use legacy database with Django. Better yet, use Django Models without any of the HTTP functionality.

Oracle more or less has official support for cx_Oracle, and it was fairly easy to set up. cx_Oracle needs Oracle headers to compile. From the Oracle download site get, according to your OS and arch, instantclient-basic, instantclient-sqlplus and instantclient-sdk. The Oracle download site requires registration.

Select a working path and extract the Instant Client packages.

$ cd /opt
$ unzip ~/Downloads/instantclient-basic-10.2.0.4.0-macosx-x64.zip
$ unzip ~/Downloads/instantclient-sdk-10.2.0.4.0-macosx-x64.zip
$ unzip ~/Downloads/instantclient-sqlplus-10.2.0.4.0-macosx-x64.zip

It is necessary to set the environment variables ORACLE_HOME, LD_LIBRARY_PATH and DYLD_LIBRARY_PATH. Put these into a convenient file and update the environment.

~/.oraenv

export ORACLE_HOME=/opt/instantclient_10_2
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME
export DYLD_LIBRARY_PATH=$ORACLE_HOME

On OS X, you need to create one symlink for the build to succeed:

$ cd /opt/instantclient_10_2/
$ ln -s libclntsh.dylib.10.1 libclntsh.dylib

Now you are ready to build and install the cx_Oracle driver.

$ cd /opt
$ tar xzf ~/Downloads/cx_Oracle-5.0.4.tar.gz
$ cd cx_Oracle-5.0.4
$ source ~/.oraenv # load environment
$ python setup.py build
$ python setup.py install

Assuming the database is already running somewhere, enter the connection parameters to this python script and test the connection:

test_connection.py

# -*- coding: utf-8 -*-
import cx_Oracle
connection = cx_Oracle.connect(
    "user",
    "pass",
    "127.0.0.1:1524/some.oraservice"
    )
cursor = connection.cursor()
cursor.execute("select  * from v$version")
for column_1 in cursor:
    print "Values:", column_1
You should see something like this:
$ python test_connection.py
Values: ('Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi',)
Values: ('PL/SQL Release 10.2.0.4.0 - Production',)
Values: ('CORE\t10.2.0.4.0\tProduction',)
Values: ('TNS for Linux: Version 10.2.0.4.0 - Production',)
Values: ('NLSRTL Version 10.2.0.4.0 - Production',)
BUT if you get:
cx_Oracle.DatabaseError: ORA-12737: Instant Client Light: unsupported server character set WE8ISO8859P15
You took the BASICLITE package!!! Go back to download the proper package and start from the beginning.

Python can connect to the database with cx_Oracle, and Django models offer a clean way to make use of that connection. If you happen to have schema read privileges to the database, Django can generate models.py from it. Now, this is pretty nice feature when it comes to interacting with legacy database, as we shall see.

myapp/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': '127.0.0.1:1524/some.oraservice',
        'USER': 'user',
        'PASSWORD': 'pass',
    }
}

myapp/manage.py

from django.core.management import execute_manager
import settings
if __name__ == "__main__":
    execute_manager(settings)

The database SQL shell is useful to have and to test the connection. You can get greeted with the friendly SQL*Plus console by command dbshell:

$ source ~/.oraenv # remember environment!
$ python myapp/manage.py dbshell
SQL*Plus: Release 10.2.0.4.0 - Production on Fri Oct 15 12:56:05 2010

Copyright (c) 1982, 2007, Oracle. All Rights Reserved.


Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>

Then you are ready to inspect the schema and generate the Python classes:

$ python myapp/manage.py inspectdb > myapp/models.py
Check the new file and assign "primary_key=True" to a column you choose, for each table you need.

Say your legacy db has a table called OraAbbOy1001 you need to manipulate. You may have a start script bin/start.py (as you don't run this with django-manage) in which you setup Django and do whatever you do to init your program. In myapp/logic.py you import the models and operate with the database.

bin/start.py

# -*- coding: utf-8 -*-
import myapp.settings
from django.core.management import setup_environ
setup_environ(myapp.settings)
# before do something else,
# the three lines above here is your first operation.
You can then operate on the models by finding or creating instances (selecting and inserting rows) through the ORM. It is also possible to mix in raw SQL, like in this example. I had trouble figuring out how to set a custom sequence name (legacy value) so I opted to select it manually.

myapp/logic.py

# -*- coding: utf-8 -*-
import time
from django.db import models, connection
from myapp.models import OraAbbOy1001

id = # comes somewhere
try:
    user = OraAbbOy1001.objects.get(id=id)

except OraAbbOy1001.DoesNotExist:

    # select nextval from ID sequence
    seq_name = 'q1001user'
    cursor = connection.cursor()
    cursor.execute("SELECT %s.NEXTVAL FROM DUAL" % seq_name)
    userid = cursor.fetchone()[0]

    # create new user
    user = OraAbbOy1001.objects.create(
        id=userid,
        name='Vic Video',
        create_date=time.strftime('%Y-%m-%d',time.localtime()),
    )

    log.info("OraAbbOy1001 user id %i (%s) created" % (
        user.id,
        user.name
        )
    )

SQLAlchemy looks very interesting. If you need to have more control over the queries, look up for it.

BTW, I'm not affiliated with O'Reilly, I just like the illustrations. They do have many good books.

September 11, 2010

QML signals on PySide

I studied QML with signal-slot communication across to Python a bit.
If you are looking for examples in source code, see

The latter one use PySide but not QML, and has a preview video elsewhere in this blog. Here are my earlier experiments.

This video shows a very small application, consisting of two files - webview.qml and webview-qml.py.
The first three signals originate from QML and Python opens up a QMessageBox to display them, along by printing to stdout. The third signal will emit a fourth signal from Python to QML, with an url as a parameter, and the url will open inside a WebView.

This app evolves the idea a bit further. The HTML5 canvas demos seen in the video are not by me, they are from the web. There is a state machine working behind the scenes.

September 10, 2010

Compiling PySide on OS X

Binary packages of PySide are available from developer.qt.nokia.com/wiki/PySideBinariesMacOSX. You only need to compile it yourself if you have explicit reason to.

However, if for any reason you would like to compile it yourself, read on. First of all, download Qt SDK. After it has been installed you can use this Makefile script to download, compile and package PySide from git master.

$ git clone git://gitorious.org/~lamikae/pyside/lamikae-pyside-packaging.git
$ cd lamikae-pyside-packaging/osx/
$ make package

Maybe after an hour or more you should have a .pkg file, which has PySide dynamically linked to Qt installed from official dmg.
Install it with the command:

$ sudo installer -pkg pyside-<version>.pkg -target "/"
See if it works (if there is no error, it does =) and try out some of the official PySide examples.
$ python -c "from PySide import QtCore"
You can inspect the package contents by
$ xar -xf pyside-<version>.pkg Bom && lsbom package.pkg/Bom

May 12, 2010

Gource animation of Rails-portlet development

Gource is a fabulous program that can visualize common version control repository history. This is the development of Rails-portlet for the last few years, in a few seconds.

I had to write a small Python script that parses logs from Git submodules. The script is at Gist and may be freely used.

April 15, 2010

Testing JavaScript & XHR with Python

I am working on a library for Django - which I'll describe later - and for building this project I need to test the behavior of XHR on the HTML page. That is, I have a page, inject some JavaScript (that may do a request on the server), and see how it changes the document. The Django application the library is designed for creates some parts of the original page and processes some parts of the server-side XHR.

Python is great for this. I've mentioned already in this blog how I like Qt4, and it proves to be very useful again. I could not get PySide running on OS X just yet, so I opted for PyQt4.

Four tests; four evenings working with them – each having an intricate problem to solve ;) Two tests work on the local document, the other two connect to the Portlet test bench, running on localhost. These tests trigger onclick submit actions from the XHR test page, simulating a user clicking a button.

This way it is possible to test the effect of JavaScript injection onto any web page in the wild. At least the WebKit response.. Firefox could be tested by using python-gtkmozembed. QWebKit also has methods for binding Python variables to the page (it is possible for JavaScript to trigger a Python callback function) and render an image snapshot of the page.

If you wish to run the tests, download the file, start up the Rails server with the test bench and run

$ python xhr_tests.py

April 5, 2010

Comparison of Web Proxy Portlet, Portletbridge and html2jsr286

There are at least three different JSR 168 / 286 portlet projects that have a similar target: to allow custom web apps and arbitrary web content be accessible in portlets.

I am the author of "Rails-portlet" and it's alive - I just updated the setup guide. It all started when I needed to develop a webapp for Liferay, and I wanted to do it with Rails. html2jsr286 is not an indication of the NIH syndrome, I just now learned of their existance. I inspected their source code, and this is a quick run-down of their features and pondering of future direction.

These are the common features in all three projects:

  • Getting content from a downstream site
  • Proxying of remote resources (e.g. images, Flash etc.)
  • Regular expression defining which URL's are in the portlet and which should be regular links
  • Rewriting CSS urls
  • Moving Javascript and CSS links out of the head
  • Session cookie support
All three have the same shortcoming concerning Ajax: the urls are not being rewritten so that XHR from the browser is not catched by the portlet.

WebProxyPortlet is being developed mainly for uPortal, and

..it provides mechanisms for connecting to and rendering HTML with options for clipping, maintaining session information, handling cookies. Proxied content is rendered within the portlet window. Web Proxy Portlet is often used to incorporate web content or applications that are built and run in non-Java environments allowing a site flexibility for integrating with many different technologies.

Sources of portletbridge

$ mkdir portletbridge
$ cd portletbridge/
$ cvs -z3 -d:pserver:anonymous@portletbridge.cvs.sourceforge.net:/cvsroot/portletbridge co -P .
Sources of WebProxyPortlet
$ svn co https://www.ja-sig.org/svn/portlets/WebproxyPortlet/trunk/ WebproxyPortlet
Sources of html2jsr286
$ git clone git@github.com:lamikae/html2jsr286.git

My impression after a while looking through the sources is that WebProxyPortlet is the most advanced, and has a number of nice features, especially in ways of user authentication and portlet configuration through the user interface.

Considering code cohesion and the number of Java classes: WebProxyPortlet has 58, Portletbridge 51 and html2jsr286 only 10 Java source files. Here are links to their main view methods:

void renderContent(final RenderRequest request, final RenderResponse response) in WebProxyPortlet.java
void doView(final RenderRequest request, final RenderResponse response) in BridgeViewPortlet.java
void render(RenderRequest request, RenderResponse response) in Rails286Portlet.java

WebProxyPortlet works in JSR 168 containers and supports a variety of authentication techniques, handles caching, has pluggable Java classes for custom request filtering and page processing. On top of this is uses a whole lot of JavaBeans and JSPs that make the portlet configurable from the GUI in the portlet config mode. I am not sure how much runtime overhead this accouts to, and how useful it is to let users select the HTML parser the portlet is going to use. Page processing is implemented by chaining up SAX filters.

Some criticism must be given; there are virtually no unit tests and there seems to be some code repetition and lots of "boilerplate" code (which is quite difficult to neutralize in Java) and somehow it just hurts me that the renderContent() method is 374 lines long. It could use some refactoring.

Portletbridge seems to have stalled active development some years ago, but there seems to be some recent code cleanup in progress, according to CVS logs.. It probably is doing what it's supposed to, feature-wise it seems to be close on par with html2jsr286, and it runs on JSR 168 portals. Page processing is done by XSLT, a feature that I really like.

html2jsr286 and Rails-portlet are strong in that they offer a toolchain for a Rails developer to get quickly into deployment with as much automation as possible. The other two projects look like they are more tuned to serve the portal administrator instead of the development team. The Rails-portlet project tools can also work without Rails, but then the deployment gets more encumbered, requiring more dirty work with Java and XML. Rails-portlet is targeting for web app developers who want to keep a healthy distance to the J2EE stack and "get things done" quickly.

html2jsr286 passes the Liferay UID in request headers, with a unique authentication mechanism. It can also handle Liferay GID – feature that is missing from the other two portlets. It is, however, restricted for JSR 286 portals, as it makes use of portlet filters, which were introduced only in JSR 286. The rationale behind this is to improve code coherence (there are no technical reasons that would demand using filters). JSR 286 container will, however, be needed for the XMLPortletRequest feature. The portlet is being used on at least two production sites, and in practise is doing fairly well. Page processing is done by HTML Parser, which does a decent job but has shortcomings that I would like to address with XSLT, and this is what Portletbridges is doing.

Nonetheless, I am considering a rational move to leverage WebProxyPortlet in Rails-portlet, and it seems that the first step is to get it running on Liferay and uPortal, hosting the portlet test bench.

Next big step would be to work on Ajax support, by carrying out ideas from these posts to implement XMLPortletRequest.

April 4, 2010

Flash problems after upgrading from Hardy to Lucid

I upgraded Ubuntu 8.04 (Hardy Heron) to 10.04 (Lucid Lynx) a few days ago. While it is still in "RC" stage, the graphical update-manager already offered me the upgrade. Which I did. I had to resolve to an ugly fix after the upgrade to get the proprietary Adobe Flash working. No matter what apt-get command I entered, there was this error message:
Package is in a very bad inconsistent state - you should reinstall it before attempting a removal.
There were a few others too who had similar problems on Ubuntu forums and this solution finally worked for me: remove all occurances of "flashplugin-installer" and "flashplugin-nonfree" from /var/lib/dpkg/status.

March 30, 2010

Mobile SDKs

I met a nice guy who has a web page that presents an award-winning grill he's designed. He briefly mentioned the site that runs entirely on Flash and I offered to upgrade it for HTML, CSS and Javascript. I wanted to learn some CSS3 and MooTools on a real-world problem. The design consists of a menu, a large image and a caption. Images are switched either by timing or by user clicking next/back buttons, complete with fade out and fade in. As it came together I tested it on Firefox, Chrome, Opera, Arora, Safari, Konqueror and IEs 6-8. I decided to try out the popular mobile SDKs as well then. Namely, Nokia Maemo 5, Google Android and Apple iPhone. This is an overview and a quick, superficial evaluation of the mobile developer toolkits.

My aim was not to work on any platform-specific code, but to test out how these devices would display the page and the image transitions. I very much appreciate the ease of installation, good documentation and in general getting to the point very quickly with a minimum amount of hassle. Happy developers make better software. Even better when they needn't to fight with the toolchain. :)

Maemo 5 was the first one I picked since being Debian-based it seems to be the most open platform. I remember briefly using a Nokia N770 in 2006 and apparently Nokia thinks it's a product line worth upgrading by N810 and now N900 released in 2009. Unfortunately even with the backing of Nokia and several years of development, the documentation is not quite as organized as I'd like it to be, with all respect. From the main Maemo.org developer page, it takes five clicks through a Nokia site on another host, back to Maemo wiki, and to the cryptic installation notes that remind me of Gentoo manuals. Choosing those five clicks wasn't quite trivial either.

Thankfully some fellow Maemo developers kindly guided me to a hidden Nokia page hosting a prepared Ubuntu virtual image with the SDK and the rest of the tools. Apparently even hosted at nokia.com this is an unofficial image. I let the 1.7 GB image to download overnight and launched it in VirtualBox the next morning without a hitch. It brought up the Ubuntu desktop, with no assistance or an emulator icon around. Here be dragons indeed.. Spells and incantations will apparently be needed. Now, somewhere in the middle of the installation spellbook it says "Xephyr" needs to be started:

$ Xephyr :2 -host-cursor -screen 800x480x16 -dpi 96 -ac -kb &

Mm-hm. This launches up an empty, gray X window. Now, the commands to start the "scratchbox environment" and inside it, the Maemo startup script:

$ setxkbmap fi
$ /scratchbox/login
> export DISPLAY=:2
> af-sb-init.sh start

Success! I still think I shouldn't need to know any of this, but it is running with a fair amount of work. Unfortunately none of the apps were installed and the virtual device seemed quite empty – all there was this Application Manager:

Some extra packages need to be manually installed into the scratchbox environment:

> apt-get install nokia-binaries nokia-apps

Then I had the Maemo browser, which displayed the page correctly. Happiness!

I wonder what Intel does to the SDK now after merging Moblin and Maemo to MeeGo...

Next, testing Google Android. The SDK is a ~20 MB zip file that is a Java GUI which downloads the actual SDK. The size after downloading platforms v1.6 and v2.1 is 343 MB.

Creating an Android virtual device was very easy with the GUI.

And it renders correctly on version 2.1!

Finally, since I work on OS X, I can also try out the iPhone SDK. It required a registration and another overnight download of the whopping 2.8 GB package that comes with bundled Xcode. After downloading, QuickSilver finds the simulator quite nicely and it looks sleek.

I have only a few times used a real iTouch device, but I see the appeal in their design. iPhone with its Safari browser can display my test page correctly.

Overall, considering these mobile SDKs, Apple provides the most elegant solution, Google being in the middle ground and (now-depracated) Maemo the worst experience. While these being developer tools, the user is expected to have some technical knowledge. This is only a very superficial evaluation, just from the perspective of a web developer who wanted to test how his page renders on these platforms. Speaking of the devices, personally I cannot justify their price to their capabilities just yet, but it is nice to evaluate them by their SDKs. Maemo/MeeGo has the Debian sex appeal for the better and the worse. I feel that Nexus One won't be the first really big model even being technologically superior to the iPhone..

March 18, 2010

Python Qt4 soup - quick way to cook up a cross-platform GUI

A friend of mine asked me to do a very quick and dirty throwaway application that parses data off a standard HTML file into an Excel spreadsheet. I chose to do it in Python and spent about an hour to decide which backend libraries to use, and opted for BeautifulSoup and xlwt to do the hard work. The core app – one function to parse input and another function to create the Excel workbook – was written in under two hours. It was only running from the command line – so I cooked him up a GUI:


This friend is a Windows user who dislikes Linux and Mac, and he needs a clean GUI that is dead simple to run and operate. After one sleepless night of furious hacking, he got one. In a Windows exe. Developed on OSX, also running under Linux. This is the power of Python combined with Qt4. The sources are online for those interested to see the details. To emphasize the throwoff nature of the app, all labels are hard-coded to Finnish.

I already had some experience on PyQt4. If you're reading this and decide to look it up, do not pass PySide – it is a newer Python Qt4 bindings library endorsed by Nokia et al. Either one you choose, you can't pass QtDesigner, which is a nice tool to design the UI and comes bundled in the Qt developer packages. In this case all I did was to add a QTableView element with some margin for the tool and status bars.



QtDesigner creates an .ui file (XML) that is translated to Python via pyuic4. With a text editor (XCode) I wrote a subclass of the UI model that extends it with a toolbar with two functions and a status bar for messages. Using file dialogs is easy. The most tricky bit is to make the data from the input file visible in the table view. PyQt4 handles that by a custom subclass of QAbstractTableModel with specific methods the Qt4 API will call to update the table contents.

Quite simple until you get to signals, slots and threading, and it doesn't hurt too much to write and maintain. Overall, about 120 lines of code for the UI, 50 lines for the core.

Now all is left is to test and package it for Windows for my friend to run. I've gotten over my worst fear and loathing of Windows, but I still have a very uneasy feeling every time I boot the OS, even in VirtualBox. Text editor and the console are my friends, and cmd.exe does not quite meet my standards, even on Windows 7. Perhaps I'll look up TCC, but honestly I'd expect a better act on behalf of Microsoft. I had a better time with 4DOS 20 years ago running on MS-DOS 5. Oh, don't even get me started on "edit.com"..



    Si si, I'm a terminal snob.

Seems that py2exe is just what I need. It installed fine via easy_install into XP. Their wiki houses the information how to construct the setup.py for py2exe to build it. Mainly, name of the main script for the executable and which libraries to bundle. It creates a whole number of files, totalling 26 MB. I was unable to generate a single executable file, but this does the trick.