Wordle Delicious

Tuesday, June 24, 2008

I just visualized my delicious tags using wordle, and they look good! Thought I’ll share! Note: they use Java instead of Flash for these effects, interesting!

Comment! | Permalink

India-STD-Identifier and a Thrift Interface Between Ruby and PHP

Friday, June 13, 2008

So there was interesting and possibly useful problem that I wanted to tackle. Get a landline STD BSNL number and find out the location and state. As you can see, it’s a simple mapping problem with the map being available from here (just leave everything blank and hit search). The idea was to parse that html file, extract the good bits and return an array of hashes. Since the easiest language to do this is Ruby I chose to use that.

The implementation itself is just bits of activesupport and hpricot magic (nothing to write home about), but after I did the simple ruby client, I thought about writing up a webserver frontend for it. In the ruby world, deployments a bitch (maybe phusion passenger - now that it has support for rack - will change this). The ideal way for a simple script like this to be deployed is just to write a single page PHP file. How to call the ruby function though?

Enter thrift [2]. I’ve always wanted to play around with it and this seemed a perfect opportunity. The magic with thrift is that it works with a service definition that defines the functions to be used and auto-generates most of the interface code. You just have to write it’s implementation.


First you have to make a service definition like this:


service STDService {
   map<string, string> lookup(1:string lookup)  
}

Then run the thrift generator:


thrift --gen rb -php std.thrift

This will create two folders in the source tree: gen-rb and gen-php which contains stubs for client and server implementations of the thrift interface. A lot of the boilerplate code is written for you so you don’t have to bother, but you do have to write the implementation at the client and server end.

This is how the client bit looks like (in PHP):


function lookup_number($number) {
  $transport = new TBufferedTransport(new TSocket('localhost', 9090));
  $protocol = new TBinaryProtocol($transport);
  $client = new STDServiceClient($protocol);

  $transport->open();
  $info = $client->lookup($number);
  $transport->close();
  
  return $info;
}

The relevant server bit is this:


class STDServiceHandler 
  def initialize
    @std_parser = STDParser.instance
  end
  
  def lookup(number)
    @std_parser.query(number)
  end
end
  
handler = STDServiceHandler.new()
processor = STDService::Processor.new(handler)
transport = TServerSocket.new(9090)
transportFactory = TBufferedTransportFactory.new()
server = TSimpleServer.new(processor, transport, transportFactory)
 
puts "Starting the STDService server..."
server.serve()
puts "Done"

The original files are here and here.

It’s nice to see how it works: I fire up Apache, point to my PHP file and once I submit the form, all the interaction happens behind the scenes automatically. Thrift makes it easy to call one language from the other.

India STD identifier is up at googlecode.

A quick addendum on how you get started with Thrift:

  1. You need boost C++ to compile thrift so get that installed.
  2. Download the thrift source from Facebook and do a ./configure make and make install
  3. Be sure to install the necessary language bindings from lib/. For PHP these are simple PHP files (although there seems to be a native C extension to speed things up), for Ruby and Python they employ the native setuptools interface.

Comment! | Permalink

Five Awesome and Small Unix Tools

Sunday, June 8, 2008

The UNIX philosophy has always been to tie in small tools and make a bigger whole. During the course of my programming and sysadmin carer, I’ve found these tools to be invaluable, and I’ve often noticed that many people don’t know about them:

Rlwrap

This binds functionality of the readline library to applications which doesn’t have native readline bindings. Ever wanted the bash-shell Ctrl+R reverse history search in another REPL? Just start the program with Rlwrap like so (this is for Jan’s really nice php-shell):

rlwrap php-shell

This sort of a thing makes working with the otherwise clumsy php-shell a breeze.

Daemonize

Often you want to run programs as a daemon but they themselves don’t have any sort of functionality like that. Daemonize comes to the rescue. (For ruby programs that I write I use the similarly named but much more capable daemons gem). Use this like so:

daemonize tail -f log/apache>log > /home/vishnu/.log

Redir

Redir is another gem. It redirects transparently TCP/IP traffic from one port to another IP and port. Use this for IP masking and redirect:

redir --lport=81 --cport=80

Dtach

Dtach is again great at what it does. It’s like daemonize, but it allows you to detach and attach from a running process. I’ve written about this before.

Comment! | Permalink

Most. Irritating. CAPTCHA. Ever.

Monday, May 12, 2008

Picture 2

Please enter all letters having a cat below. Enough said.

Comments (2) | Permalink

Hpricot/LibXML Benchmark

Tuesday, April 29, 2008

I’m off to release an internal XML conversion script. I began using Hpricot, but after hearing good things about LibXML, decided to switch to that. Here’s a benchmark:


real	0m11.692s
user	0m11.497s
sys	0m0.188s

… for Hpricot versus…


real	0m6.441s
user	0m6.258s
sys	0m0.177s

… for LibXML

So that’s almost twice as fast. Nice to know.

Comment! | Permalink

Symfony 0.6.2 Sandbox Script

Thursday, April 10, 2008

This is for people who still use the old Symfony 0.6.2.

Get the script from here and execute it so:


sh ./symfony_sandbox.sh

This is largely plagiarized from the create quickstart script from the Symfony SVN but changed to make the pake install work.

Comment! | Permalink

kup.in: URL redirection off Google’s App Engine

Wednesday, April 9, 2008

First the URL: Kup.in is a fast URL redirection service run off Google’s new App Engine.

This was mostly a test project than anything else, just to gauge how easy App Engine is to start up and get going (very easy!). I’ll post in detail later, but here are bulleted observations:

  1. I’ve been sitting on this project for ~6 months. The dilemma was that if I did this in PHP, I’d be slow coding it (and also ugly) but fast to deploy. If I did this in Python/Ruby, it’d be much easier, but deployment’s a bitch. With the App Engine, deployment is a simple appcfg.py update . which is uber cool.
  2. You’ve got to have py25-openssl installed if you’re on Macports.
  3. I couldn’t get Django templates to work (some import _md5 error which I suspect again is my Macports). I didn’t need templates here.
  4. The code is opensource on Github. Fork it and do what you will! I’ll decide on a license after I wake up!

Speaking of which, it’s 3 in the morn here and I’ve got office tomorrow!

Comment! | Permalink

file_get_contents_with_timeout

Sunday, April 6, 2008

So here’s a piece of code which I think you guys will like. It’s a URL call with timeout function in PHP.


/* This works only for simple URIs
  Returns:

  array(headers, body): if everything's fine.
  TIMED OUT if it times out.
  UNABLE TO OPEN if we can't connect to host.
  */
private function file_get_contents_with_timeout($url, $read_timeout = 5, $connection_timeout = 5) {
  $url_parts = parse_url($url);

  $host = $url_parts['host'];
  $get = $url_parts['path'] . '?' . $url_parts['query'];

  $fp = fsockopen($host, 80, $errno, $errstr, $connection_timeout);
  if (!$fp) {
    return "UNABLE TO OPEN";
  } else {
    $out = "GET $get HTTP/1.1\r\n";
    $out .= "Host: $host\r\n";
    $out .= "Connection: Close\r\n\r\n";

    fwrite($fp, $out);
    stream_set_timeout($fp, $read_timeout);

    $result = stream_get_contents($fp);
    $divider = strpos($result, "\r\n\r\n");
    $headers = substr($result, 0, $divider);

    $body = substr($result, $divider, strlen($result));

    $info = stream_get_meta_data($fp);

    fclose($fp);

    if ($info['timed_out']) {
      return 'TIMED OUT';
    } else {
      return array('headers' => $headers, 'body' => $body);
    }
  }
}

Yup, a bit crude, but hey it works for me! (Oh, and it probably needs PHP5). Feel free to take the core logic out and wrap it up in exceptions and what have you.

Why did I have to write this contraption? Ah, the joys of the Indian SMS scene. Let me take you through a tour: Fastalerts is a bulk SMS solution for end users and resellers. The web frontend is written in PHP using Symfony (an older version, 0.6). The API is written in plain PHP and it connects to a SOAP-based (using nuSOAP) SMS sending solution.

This is a comment I have on top of the new backend code which sums up all the complexity:


/*
  A note on message sending.

  These actions have to happen as transactions:
  * Calling the GATEWAY.
  * Entering data into LOGS.
  * Reducing CREDITS.

  ONLY if:
  * User has "enough" credits.
  * Input is valid (numbers and mesage)
  * User has valid credentials

  AND we have to handle:
  * Gateway timeouts
  * Gateway errors
  * CDMA senderid correction.

  ALSO:
  If the user doesn't have enough credits to send the entirety of numbers,
  messages are sent until his credits are exhausted.

  AND:
  A separate status message is returned for each of these conditions.

  BUT WE ENSURE:
  That everything is logged appropriately:
  
  * All successfully sent messages are logged and credits reduced.
  * In every other case, credits are NOT reduced (we are customer friendly).
  * When gateway times out or errors out, the messages are logged.

*/

Interested people should note that there is a lot more you can add on to this: dynamic gateway switches (automatic failover), regular gateway tests, more backend support etc. but this is the bare minimum that’s needed for the backend to work. If you spend some time thinking about the problem, you’ll come to the realization that this bit:


 AND we have to handle:
  * Gateway timeouts
  * Gateway errors

means that we have to handle gateways that time out and never return a response. Most of the SMPP providers in India use something called NowSMS to manage their backend connectivity to the operator. In short: we connect to a vendor via his common gateway, he routes it (depending on destination) to multiple operators. NowSMS exposes a simple HTTP service (a URL call in other words) to add SMS to the vendor queue and it’s this service that’s preferred by a majority of the good vendors. However, HTTP traffic being what it is, a monolithic backend in PHP will stall if the request doesn’t return a response. Hence that file_get_contents_with_timeout.

Note: this is hardly an ideal solution (that would be a separate daemon). But without adding moving parts, this simple function should increase the reliability of our backend.

Comments (2) | Permalink

Mobshare.in Broadcast Widget

Tuesday, March 25, 2008

Mobshare.in Broadcast Widget

Here’s a sneak peek of the Mobshare broadcast widget I’ve been working on. If you look to the right and below, you can see that in action live. You’ll see the last ten pics I’ve uploaded and direct from the widget you can subscribe to my feed or send me an SMS.

The widget is the first released code which uses our internal API. While the API itself is undocumented [which hopefully will soon change], it’s public and it’s JSON (which makes implementing Javascript widgets incredibly easy).

How do you get your own widget? You can’t do that easily right now (the pleasures of cutting-edge code eh?), but paste this code somewhere:


<iframe width="150" height="150" src="http://mobshare.in/user/:mobshare_id:/broadcast/widget"></iframe>

…and change :mobshare_id: to your Mobshare ID.

Try it out and let me know if you like it!

Tags:

Comment! | Permalink

Loving Git

Tuesday, March 18, 2008

I suppose this must be the zillionth post about how Git is so cool, but I’ll tell you what I love about it:

  1. Ultra fast commits. Coming from subversion, typing commit and getting a prompt back instantly is so surprising that you double check the first few times.
  2. Branching! God, you don’t realize how much you miss it until you have fast branching and merging. Without the pain of creating cps, just a simple git checkout -b branch_name and git merge branch_name and everything just works.
  3. The above was the reason I tried git in the first place. mobME’s an SVN shop and we do the usual trunk, tags, branches dance. When the trunk shapes up to be stable though I can’t seem to do anything on it. I can fork off a new branch in SVN but that’s too painful to even think about. What do I do now? git clone it, create a branch, and do regular git svn rebases and dcommits.
  4. Oh did I mention bidirectional SVN support? Without which I wouldn’t/couldn’t have switched no matter how much I wanted to try this cool new thing.
But it’s great and it really changes the way you think about VCS. Notice something? I didn’t even talk about distributed source control, and that’s coz even when I use git like I use SVN - committing to a central repo at the end and pulling changes from it, it’s brilliant. I’d definitely want to explore cool stuff like github soon for personal use (some gracious soul gave me an invite some time back).

Tags:

Comments (3) | Permalink

An Orkut Application via a JSON API

Saturday, March 8, 2008

I talked about delegating rendering in Symfony for creating a JSON API. Now here’s a consumer: an Orkut opensocial gadget:


MobshareOrkutAPI = {
	
	api_url: 'http://api.mobshare/api.php',
	cache_time: 0, //0 to disable
	
	makeCachedRequest: function(url, callback, params, refreshInterval) {
	  var ts = new Date().getTime();
	  var sep = "?";
	  if (refreshInterval && refreshInterval > 0) {
	    ts = Math.floor(ts / (refreshInterval * 1000));
	  }
	  if (url.indexOf("?") > -1) {
	    sep = "&";
	  }
	  url = [ url, sep, "nocache=", ts ].join("");
	  gadgets.io.makeRequest(url, callback, params);
	},
	
	call: function(module, action, params, callback) {
		var options = {};
		options[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
		this.makeCachedRequest(this.api_url + '/' + module + '/' + action + '?' + params, callback, options, this.cache_time);
	}

};

makeCachedRequest has been plaigarized from the Opensocial documentation and it’s very useful to bust the cache for requests. Also, notice this line for setting the content-type to JSON:


options[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;

This is how we access that API, a code fragment:


...
authenticate: function(alias, mobile_no, password) {
	MobshareOrkutAPI.call('user', 'authenticate', 
		'alias=' + encodeURIComponent(alias) + '&mobile_no=' + encodeURIComponent(mobile_no) + 
			'&password=' + encodeURIComponent(password),
 		MyOrkutApp.login
	);
}
...
login: function(orkut_response) {
	response = orkut_response.data;
	data_success = response['success'];
	data_error = response['error'];
	
	if(data_success) {
		html = '<h2>Login Successful!</h2>';
		html += '<p>Welcome: ' + data_success.name + '!</p>';
	} else if(data_error) {
		html = '<h2>Login Unsuccessful!</h2>';
		html += '<p>' + data_error + '!</p>';
	}
	
	document.getElementById('mobshare_login').innerHTML += html;
},
...

Note that orkut_response.data is automatically set by Orkut because you passed in the JSON content type; it parses the data received and creates a javascript object. Cool, ain’t it? It’s very easy to create a proper interactive Opensocial app this way.

Tags:

Comment! | Permalink

Delegate Rendering in Symfony

Thursday, March 6, 2008

Warning: pretty advanced Symfony ahead: if you’re not familiar with the framework, this wouldn’t make sense.

I recently developed a bare-bones API for Mobshare (it’s not yet live), and to keep everything clean, I abstracted away the rendering bit from the controller to an external class. It ended up being a sweet solution, so here it is!

I wanted this to be a JSONP API (the major use case would be a JS client, and parsing XML etc. via JS is a pain. Besides JSON is much shorter over the wire). I didn’t want to rewrite a lot of code: checking for a callback parameter and wrapping the returned string around the JSON output was just begging to be refactored away. So here it is, a generic Symfony JSON API wrapper class:


<?php

class JSONPAPI {
	
	const CALLBACK_PARAMETER = 'callback';
	
	var $status;
	var $data;
	var $callback;
	
	
	public function __construct($status, $data) {
		$this->status = $status;
		$this->data = $data;
		
		$callback_parameter_value = $this->getCurrentAction()->getRequestParameter(self::CALLBACK_PARAMETER);
		if($callback_parameter_value) 
			$this->callback = $callback_parameter_value;
	}
	
	public function render() {
		$render_text = json_encode(array($this->status => $this->data));
		
		if($this->callback)
			$render_text = $this->callback . '(' . $render_text . ');';
		
		$this->setJavascriptHeaders();
		return $this->getCurrentAction()->renderText($render_text);
	}
	
	//hack to get the current action
	private function getCurrentAction() {
		return sfContext::getInstance()->getActionStack()->getLastEntry()->getActionInstance();
	}
	
	private function setJavascriptHeaders() {
		sfContext::getInstance()->getResponse()->setParameter('Content-Type', 'application/javascript', 'symfony/response/http/headers');
	}
}

The bits of magic here are the getCurrentAction() function and the render() call. It works on one very simple idea: everything in Symfony can be accessed from the sfContext::getInstance() object, you just need to dig deep enough.

Once you write this boiler-plate code, using it is very elegant. First, subclass it for your API:


<?php

require_once('JSONPAPI.class.php');

class MobshareAPI extends JSONPAPI {
	

}

And then, use it like so within your controller:


<?php
class userActions extends sfActions
{
	
	public function executeAuthenticate() {
		$valid_user = UserPeer::authenticate($this->getRequestParameter('alias'), 
			$this->getRequestParameter('mobile_no'), $this->getRequestParameter('password'));
		if($valid_user instanceOf User) {
			$success = new MobshareAPI('success', $valid_user->toArray());
			return $success->render();
		} else {
			$error = new MobshareAPI('error', 'Authentication failed: Alias, mobile number or password invalid.');
			return $error->render();
		}
	}
	
}

Note: the rendering has been delegated to the $success and $error MobshareAPI objects. This allows for a really maintainable API. Adding functionality is much simpler since you don’t have to worry about the boilerplate.

You end up calling the API like this:


http://api.mobshare/user/authenticate?alias=vish&password=xxx&callback=handler

and you get back data which looks like this:


handler({"success":{"alias":"vish","name":"Vishnu Gopal","photo_mini": ...);

Note that callback handling is done entirely by the API and the controller needn’t worry about this parameter at all!

Tags:

Comments (4) | Permalink

dtach, a brilliant daemonizer

Tuesday, March 4, 2008

Most people use GNU screen (or equivalent) to daemonize processes. There is a better alternative called dtach which should become the definitive way to run a process in the background as a daemon.

Installing dtach is easy. If you’re on gentoo, it is a simple:


$ emerge dtach

dtach --help provides the following output:


Usage: dtach -a <socket> <options>
       dtach -A <socket> <options> <command...>
       dtach -c <socket> <options> <command...>
       dtach -n <socket> <options> <command...>
Modes:
  -a		Attach to the specified socket.
  -A		Attach to the specified socket, or create it if it
		  does not exist, running the specified command.
  -c		Create a new socket and run the specified command.
  -n		Create a new socket and run the specified command detached.

This basically means that you start dtach like so:


$ dtach -c ~/rtorrent/.socket/dtach rtorrent

The command-shortcut Ctrl+\ detaches the running command and runs it in the background. To re-attach, just enter:


$ dtach -a ~/rtorrent/.socket/dtach

dtach hasn’t got much more functionality, but that’s what makes it superb. Tiny and functional. As you can see, I’m using dtach to run a Seedbox :-)

Tags:

Comments (1) | Permalink

EC2-like instances with Slicehost

Sunday, February 24, 2008

Slicehost’s backup and add new slice

Virtual hosting and infrastructure is coming up in a big way, and Amazon’s EC2 is leading the pack. However, there doesn’t exist a comprehensive & provably scalable system for deploying applications to such a stack yet: the components are in place or being developed (SimpleDB or equivalent, EC2 and S3) but the crucial connecting layer is missing. Several attempts are on to fill the gap.

Until it becomes a reality (and perhaps even then), a great intermediate tool is Slicehost’s xen-based virtual machines: “slices”, that allow for a quick snapshot from any particular slice and the creation of a new slice from that snapshot.

The way it works is a bit cumbersome now (and they really should expose this feature more), but the idea is simple:

  1. Take a snapshot of a slice, currently you have to go to the backups tab to take a snapshot backup.
  2. When you choose to create a new slice, you have the option to create from that backup snapshot (see picture).
  3. Change a few variables specific to the host (maybe add a puppet instance to reconfigure automatically), and wham! EC2-like virtual infrastructure at half the pain.

I plan to use this for factory-like instances across MobME’s infrastructure.

Tags:

Comments (1) | Permalink

Leading a team

Monday, February 11, 2008

I’ve recently switched to a new role at MobME, and here are my initial impressions and a few lessons learnt in the past few months. This is a rather free-flowing account, but I want to write this down and codify before I forget.

Rule uno: people aren’t resources. There’s a constant management-level question I hear: “How many resources can you allocate to a particular product?” or “How much time and resources do you need to solve this problem?”. I don’t answer such questions at all, or when I do, I talk about the people I have in my team and their skills and limitations. I’ve strongly felt that no organization can grow without taking care of its people, and especially for small startups, to make attrition low and happiness high, this is a concern that should be addressed from the top. Rephrase these questions this way: “Can person X solve this problem?” and “Can person X and Y be reallocated to this task?”.

Following on, every person is special: they have different skills, learning speeds and communication capabilities. Every good person can improve, and they should constantly be pushed to do that. Nobody should be idle and static.

And nobody should do make-work. It’s not necessary because there are always real problems to solve. If anybody is picking up a new language or skill, after a few tutorial-level sessions and a max of two days, he should jump into the new role and start fixing bugs and solving real problems.

Good processes make a good team. At MobME, I’m implementing a few things I learnt while working at Uzanto: Daily End-Of-Day reports for the team, SCRUM in the morning, and constant communication via a small office and approachable managers. The work atmosphere is relaxed, and people are given time to learn. There’s no fixed time for lunch or a quick smoke. There’s an attendance register and people are held accountable for their work and leave.

At the end of the day, Work is Everything™. If there’s a deadline to be met, do or die, we meet it. People remain late in the office (till 1 at night is the current MobME record) and finish work before they go.

What you do when you’re not in office is none of anybody else’s business. Although as a rule, work hard, party hard.

When you lead a technical team, if you have problems you use technology to solve them. This is obvious, but I’ve so often seen this overlooked. You can’t find a way for developers to collaborate? Find an effective source control method. Project management? Bug tracking? Time tracking? A caveat: too many tools is a mess. Find effective and simple ones. We use Beanstalk and Lighthouse along with Google Apps for documents and mail.

As a corollary to the above, not every problem is technical. Instead of inventing one, try simplifying the product or the spec or going back to the drawing board. If it’s too much of a hassle technically, it’s the product pushing back at you, resisting crappy things being done to it. Hear it out and redo things. Redoing things the right way is a good thing. Take time out, fix stuff and it won’t bite you in the ass the next time.

Another gem: keep internal mail to a minimum. When everybody is in the same office, shout out to him (or take him outside) instead of firing off a mail thread. It’ll solve problems so much more efficiently. Having the management & technical wing in nearby offices means that for us, most problems are solved in a week. No network cabling in office? A quick discussion with my operations lead and I get work done. No firing off complaint mails.

Communication is key. Let everybody know your worries. Everybody includes the people under you. A lot of the time, they’ll end up finding a better solution.

The primary role of a technical lead as I understand it is to get work done. It’s not to go off to conferences evangelizing the product (although it’s a good thing to do). Meet the deadlines your boss sets for you. Make sure your people work hard and they learn lots of stuff in the process. Keep an eye out for new technology. Document your processes. When you find problems, fix it and ensure it doesn’t happen again. Get good infrastructure. Communicate problems with the management.

Everybody in sales, marketing, etc. should know more than a little bit of tech. Increase technical awareness and make the process transparent so anybody can ask questions no matter how stupid it is. Often a completely new way of thinking about a problem brings in new technical insights too.

When there’s a problem, criticize. This is hard to do initially, but good people realize that your criticism is for the work and not the person. If they still have issues after you talk it out, ask them politely to leave or toe the line. When you find people improving and processes becoming smoother after you’ve talked it out with the team, it’s a real nice feeling.

And at the end of the day, make people happy and be happy yourself. Which of the lot, is the hardest.

Tags:

Comments (5) | Permalink

Me Right Now

Saturday, February 9, 2008

So this is me right now:

Me Right Now

Comments (1) | Permalink

Mobile Social Networking In 25 Days

Sunday, February 3, 2008

This was my talk for BarcampKerala2. Decent event, although I had to leave post-noon.

Tags:

Comments (5) | Permalink

Mouse Wheel Handling in Prototype/Javascript

Sunday, January 27, 2008

Here’s how to handle mouse wheel events in prototype, code heavily borrowed from other places on the net, but brought together and made unobtrusive:


/* Mouse Wheel */

Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120; 
			if (window.opera) delta = -delta;
		} else if (event.detail) { delta = -event.detail/3;	}
		return Math.round(delta); //Safari Round
	}
});

SetupMouseWheel = {
	initialize: function() {
		Event.observe(document, 'dom:loaded', this.setup_mouse_wheel);
	},
	
	setup_mouse_wheel: function() {
		Event.observe($('element'), "mousewheel", function(e) { SetupMouseWheel.handleDiv(e) }, false);
		Event.observe($('element'), "DOMMouseScroll", function(e) { SetupMouseWheel.handleDiv(e) }, false); // Firefox
	},
	
	handleDiv: function(e) {
		direction = Event.wheel(e) < 0 ? leftSeek : rightSeek;
		console.log(direction); //handle scroll 
		console.log(Event.wheel(e));
		direction(); //call leftSeek or rightSeek depending on direction.
	}
};

The problem with this code though, and the reason I haven’t found a productive use for this yet is that the browser always scrolls the viewport when it extends beyond screen dimensions. So you have your scroll handler and the browser’s scrolling viewport playing hanky-panky, and it’s not a good sight to see. For applications which always stays within the browser window though, this is useful code.

Tags:

Comments (1) | Permalink

Self-labeling text entry INPUT boxes using Prototype JS

Tuesday, January 15, 2008

Like previous code snippets, this one is mostly all code and no explanation.

A brief primer: you’ve often seen self-labeling input boxes, the ones that display a label inside in gray text, and when you click replaces with an empty input element using Javascript. This is a simple class to help you achieve that effect:


SelfLabelingBox = {
	initialize: function(box_id, message) {
		this.setup_behavior(box_id, message);
	},
	
	setup_behavior: function(box_id, message) {
		box_id = $(box_id);
		if(box_id) {
			if(box_id.value == '') {
				box_id.value = message;
				box_id.addClassName('inactive');
			}
			Event.observe(box_id, 'focus', function() {
				if(box_id.value == message) {
					box_id.value = '';
					box_id.removeClassName('inactive');
				}
			});
			Event.observe(box_id, 'blur', function() {
				if(box_id.value == '') {
					box_id.addClassName('inactive');
					box_id.value = message;
				}
			});
		}
	}
};

SetupDefaultInputs = {
	initialize: function() {
		Event.observe(document, 'dom:loaded', this.setup_default_inputs);
	},
	
	setup_default_inputs: function() {
		SelfLabelingBox.initialize('search_text', 'Search');
	}
};

Until next time!

Tags:

Comment! | Permalink

Simple Javascript Validation

Saturday, January 12, 2008

I hunted around for a good Javascript class to use for validation and finally clobbered together this from various sources on the net. This should be extremely useful for anyone doing client-side validation.


Validate = {	
	forMinLength: function(whatYouTyped, length_min) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length >= length_min) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	forMaxLength: function(whatYouTyped, length_max) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length < = length_max) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	forLength: function(whatYouTyped, length) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length == length) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	asEmail: function(whatYouTyped) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	asNumber: function(whatYouTyped) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (/^\d+$/.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	asEqualTo: function(whatYouTyped, whatYouMatchWith) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		var expect = whatYouMatchWith.value;
		if (txt == expect && txt != "") {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	asNotEqualTovalue: function(whatYouTyped, whatYouMatchWith) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		var expect = whatYouMatchWith;
		if (txt != expect) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},
	
	withRegex: function(whatYouTyped, regex) {
		var fieldset = whatYouTyped.parentNode;
		txt = whatYouTyped.value;
		if (regex.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	}
	
};

How do you use it? This is how:

HTML:


<form name="signup" id="signup">
<label for="alias">Alias <sup>*</sup></label>
<fieldset>
<input type="text" name="alias" id="alias" value="" />
</fieldset>
<br />
<label for="mobile_no">Mobile Number <sup>*</sup></label>
<fieldset>
<input type="text" name="mobile_no" id="mobile_no" />
</fieldset>
<br />
[..]
</form>

CSS (different images for fieldset.valid and fieldset.invalid):


fieldset.valid {
	background:transparent url(/images/bg-fieldset-valid.gif) no-repeat 194px 0px;
}

fieldset.invalid {
	background:transparent url(/images/bg-fieldset-invalid.gif) no-repeat 194px 0px;
}

And I hate onclick and onkeyup, so here’s some unobtrusive Javascript to tie it all in:


SignupValidation = {
	initialize: function() {
		Event.observe(document, "dom:loaded", this.setup_validation)
	},
	
	setup_validation: function() {
		if($('signup_body')) {
			Event.observe($('alias'), 'keyup', function() { 
				Validate.withRegex($('alias'), /^[A-Za-z]+[A-Za-z0-9]{4,}$/);
			});
			Event.observe($('mobile_no'), 'keyup', function() { 
				Validate.withRegex($('mobile_no'), /^9[0-9]{9,9}$/)
			});
		}
	}
};

SignupValidation.initialize();

That’s it. The idea (for those who can’t bother to grok code) is that a fieldset changes its class from valid to invalid depending on the status of the field, this is then reflected in the styling.

AsktheCSSGuy was of much help.

Tags:

Comments (1) | Permalink

Multiple DB connections in Rails/ActiveRecord

Saturday, January 12, 2008

You might often want to connect to different databases using ActiveRecord. Here’s how you do it:


#DB definitions:

class DatabaseCurrent < ActiveRecord::Base
  self.abstract_class = true
  establish_connection settings['database']
end

class DatabaseOld < ActiveRecord::Base
  self.abstract_class = true
  establish_connection settings['database2']
end

#Model definitions (current):

class Video < DatabaseCurrent
  belongs_to :user
  
  set_table_name :videos
end

class Photo < DatabaseCurrent
  belongs_to :user
  
  set_table_name :photos
end

class User < DatabaseCurrent
  has_many :videos
  has_many :photos
  
  set_table_name :user
end

class Tag < DatabaseCurrent
  set_table_name :tag
end

#Model definitions (old):

class FileDB < DatabaseOld
  set_table_name :file
end

Pretty easy. The only thing to note is that you should set_table_name, otherwise, AR chokes up. Also, often you want to directly play directly with SQL. Unfortunately, ActiveRecord::Base.execute doesn’t work any longer (coz it doesn’t have a connection), but you can do it this way:


DatabaseCurrent.connection.execute("SQL")

That’s the tutorial for the day!

Tags:

Comments (2) | Permalink

TOEFL iBT Prerequisite

Tuesday, December 18, 2007

I recently registered for the TOEFL again. Anal-retentive ETS only stocks its TOEFL scores for 2 years. Why, may I ask, does my English deteriorate so much after a couple years of a perfect score? Money-loving land lubbers if you ask me… but that is a topic for another day.

Here’s what you’re presented when you seek to create a username and password at the TOEFL site to register for an exam:

Important User Name and Password Information
User names must be between 6 and 16 characters long and may contain both letters and numbers. The user name may NOT contain any special characters like !, $, #, % or +.

Passwords must be a minimum of 8 characters in length. The maximum is 16 characters. For added security, they must also contain three of the following four character categories:
English uppercase characters (A through Z)
English lowercase characters (a through z)
Numeric characters (0 through 9)
Special characters (for example, !, $, #, %)
Examples of allowable passwords are:

Urt#5489
RT@GR125
UIrty452
H!tTh3M@rK
Your password is case-sensitive. For example, "H!tTh3M@rK" is not equal to "h!TtH3m@Rk".
Only English characters are allowed and your password cannot contain your user name, first/given name, or last/family name.

Let’s see: A username must be between six and sixteen chars long. Check. Cannot contain special chars. Check. Passwords must be a min of 8 chars long. Check. Max is 16… Check. They must contain three of four character categories…. what is this my bank vault?

Or think of this another way: a reading comprehension test even before the exam starts. Can we applaud and bring the roof down already?

Tags:

Comment! | Permalink

Deploying JAR/JAD content via WAP

Wednesday, November 28, 2007

An easy way to deploy JAR/JAD content on all phones with GPRS is to create a WML site and provide a link to the JAR file that you have. [As an aside, the whole J2ME scene is so fragmented that it sucks completely. Let’s hope Android sorts it out.]

Here’s how you go about setting up that in Lighttpd:

Add these mimetypes (to lighttpd.conf or mime-types.conf):

mimetype.assign += (
  ".wml"     =>      "text/vnd.wap.wml",
  ".wmlc"   =>      "application/vnd.wap.wmlc",
  ".wmls"   =>      "text/vnd.wap.vmlscript",
  ".vmlsc"  =>      "application/vnd.wap.wmlscriptc",
  ".wbmp"   =>     "image/vnd.wap.wbmp",
  ".jar"      =>      "application/java-archive",
  ".jad"     =>      "text/vnd.sun.j2me.app-descriptor" )

Add the index.wml file as a valid DirectoryIndex:

server.indexfiles    += ("index.wml")

Make this .wml file and upload it somewhere (replace JARCLIENT with your .jar file, and JADCLIENT with your .jad file):

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>
<card title="Download">

<p>Download the Jar file:</p>
<p>
<a href="JARCLIENT.jar">Download</a>
</p>
<p>
<a href="JADCLIENT.jad">Download (JAD)</a>
</p>

</card> </wml>

Restart your server, and that should be it! If you’re using Apache, the steps are similar.

Tags:

Comment! | Permalink

Barcamp Kerala LiveBlog

Saturday, November 24, 2007

(Edit: was a liveblog, now over!)

Hey so I’m at BarcampKerala and this is a liveblog. Random impressions:

  1. The hall is really cool. It’s a big open, really lighted space and the infra is also pretty nice: power, wifi, et. al.
  2. Sreekanth did a really great job with the goodies: every camper gets a name tag, a file with a notepad, and a coke. Good stuff!
  3. I gave a presentation on Gosu-N3wton. The slides are at the end.
  4. Hari gave a pretty interesting presentation about Bluetooth automation which generated a lot of discussion.
  5. We have an Airport Base Station here (courtesy Pratap) as a WiFi router. How cool is that? :-)
  6. In general, I found all the talks well done. For a first time event, it was really awesome. The percentage of really techy people (the best kind) was also the same. There were fewer people, but that’s easily corrected the next time around.
  7. So we had more than seventy people turn up. Exact number not known because by the time we counted them, lots had left (afternoon syndrome). Including some students from the nearby UCK college who made an interesting audience. I was impressed with Dhanesh and Dev who did something with QT. Kudos!
  8. All my photos are on flickr!
  9. Amit linked to me.

Tags:

Comments (3) | Permalink

GOSU-N3WTON

Saturday, November 24, 2007

For BarcampKerala, I made a small 2D game in Ruby and Gosu, which is a clone of a popular flash-based game by the same name.

I call it Gosu-N3wton, and I had great fun developing this game. Go check it out! (The code is opensource)

Gosu-N3wton screenshot

Tags:

Comments (1) | Permalink

OpenSocial and Facebook

Wednesday, November 14, 2007

A lot has been said about OpenSocial and Facebook, this is an attempt to say a bit more. As usual, a free-flowing list of observations:

  • Google uses Javascript as its base client-side language and extends gadgets with OpenSocial features. This I consider “good enough” but perhaps more importantly has a much lower barrier to adoption. The way a FB app is delivered (request-mapping, restricted FBML, fine-grained privilege control) feels much more professional, but at the same time a bit intimidating for newbies to the field. Libraries for almost all languages (esp. rfacebook) lower the barrier, but it’s never as simple as a .xml file which is mostly html and javascript (this is what a gadget is).
  • It’s currently not clear how OpenSocial apps communicate between each other across networks and how one app maps a user profile in one network to another. Without this, it’s mostly a single API which works across multiple containers in isolation. A single API that collates multiple containers is much more interesting.
  • Is there any set of UI guidelines? Is a gadget supposed to emulate the look and feel of its container or is it supposed to look the same across networks? Facebook provides extensive CSS classes and Javascript goodies (AJAX friend selector) to keep the experience uniform. I hope OpenSocial addresses this and soon.

Tags:

Comment! | Permalink

Ache

Tuesday, November 13, 2007

Stay, says Viragor,
I want you near me.
Nay, breathes Matilda,
Tho I can’t bear to leave thee.

His hair she clasps with twists and turns,
Hers he touches softly, kissing in burns.
‘Twill be months, years yet we meet,
But fleet should be our hearts be, sweet.

And then she’s gone, an orphan left,
Between people near but dead.
Stolen moments, sweet but misery,
Slowly turned dear, restless and finicky.
Oft-shaded heart and steady work’s a blanket,
A poor salve that, uproars many a racket.
Bent, slippery, tested and hurt,
But so doth love be stronger for worth.
A true refrain then, to ease parted hearts,
His heart and hers may beat to stars:

Come, says Viragor,
Been aching to hold thee.
Love, smiles Matilda,
When have we been free?

Tags:

Comment! | Permalink

BarCamp Kerala

Monday, November 12, 2007

Barcamp Kerala

Kenney had this great idea about having a BarCampKerala over here and it’s got me really excited. I’ve been to BarCamps in Delhi and one in Hyderabad, and it’s always been an amazing place to talk to good people. I’m pretty sure I’ll find loads of techies in Thiruvananthapuram to contribute and because of the event I’ve already reconnected with some great people I know. Raj from Linuxense is chipping in, and so are many others I’ve yet to meet.

It’s going to happen Nov 24, and we haven’t decided on the venue yet it’s at the UST Zenith hall (next to Coffee Beanz) at Technopark. It should be easy enough to find. Most probably it’s going to be somewhere in or near Technopark.

In case you don’t know what a BarCamp is, it’s an unconference (yes cool word) which anybody can organize and anyone can come and speak on a first-come-first-speak basis. What I love about BarCamps are the discussions on the fringes of talks and the great people that come to them.

Gimme a call (+91.9846087017) if you’re somewhere in the vicinity and want to contribute!

Other assorted stuff:

Tags:

Comments (2) | Permalink

Nalu Pennungal

Monday, November 12, 2007

nallu pennungal

I saw Nalu Pennungal today. Before I forget a lot of the film, here’s some random impressions:

  • In the first short the way Kunzhupennu is sexy without makeup and wearing that nadan dress has to be seen to be believed. The swagger in the way she walks is uber cool. Her husband’s helpless refrain “We are husband and wife” when faced with the judge’s questions is probably one the most poignant scenes in the movie.
  • The first and last segments made the most impression on me. Nandita Das is superb and the way that story ends does tickle your brain.
  • Chinnu Amma and Kanyaka I found amusing, esp. the scenes where the husband swallows whole bucketfuls of rice. There’s also a scene in Kanyaka where she tries to make an overture and he dismisses her by saying it’s too hot. Her riposte then is beautiful. Chinnu Amma’s plight is again darkly humorous especially the very last scene (which I almost missed) where she as an old woman recites her story to younger ladies.

I took the shorts as they came to me and didn’t think about any connecting thread then. Looking back, one thing which is perhaps common is that all the stories are about women who did the ‘right thing’ and somehow is punished because of it. Kunzhupennu who found a husband but is still branded a prostitute, Kanyaka who married but didn’t find happiness, Chinnu Amma who sacrificed progeny for being faithful, and the eternal virgin who sacrificed for her sister and so couldn’t marry. And still that punishment isn’t so severe that they aren’t able to lead a semi-normal life or as in the case of Chinnu Amma, look back upon the incident with a bit of humor.

It’s a good movie. Not a must watch, but it does make you think.

Tags:

Comments (3) | Permalink

Downtime and Upgrade to WP 2.3.1

Saturday, October 27, 2007

There was a bit of a downtime for this site because my MySQL binary logs and lighty logs had temporarily filled up my 10G slice. That’s fixed now but I haven’t found a permanent solution (this has happened before). I suppose I should whip up a cron job that runs every week to do some housekeeping.

Also while I was at it, I upgraded to the latest version of Wordpress. This was the easiest upgrade ever. I got a notification from the WP console that a new version is available and I used svn to switch to that tag:


vysnu@slicey ~/public $ svn sw ↵
http://svn.automattic.com/wordpress/tags/2.3.1/
U    wp-login.php
U    wp-includes/default-filters.php
U    wp-includes/bookmark.php
U    wp-includes/query.php
A    wp-includes/wlwmanifest.xml
U    wp-includes/formatting.php
U    wp-includes/taxonomy.php
A    wp-includes/images/wlw
A    wp-includes/images/wlw/WpComments.png
A    wp-includes/images/wlw/wp-icon.png
A    wp-includes/images/wlw/WpIcon.png
A    wp-includes/images/wlw/wp-watermark.png
A    wp-includes/images/wlw/WpWatermark.png
A    wp-includes/images/wlw/wp-comments.png
U    wp-includes/post.php
U    wp-includes/version.php
U    wp-includes/general-template.php
U    wp-includes/canonical.php
U    wp-includes/pluggable.php
U    wp-includes/widgets.php
U    wp-includes/functions.php
U    wp-includes/script-loader.php
U    wp-mail.php
U    wp-admin/includes/template.php
U    wp-admin/includes/upload.php
U    wp-admin/includes/upgrade.php
U    wp-admin/edit-post-rows.php
U    wp-admin/import/wp-cat2tag.php
U    wp-admin/import/utw.php
U    wp-admin/import/mt.php
U    wp-admin/link-import.php
U    wp-admin/link.php
Updated to revision 6294.

See? That’s it. I think my complaints regarding hard WP upgrades are finally over. If you’re not maintaining your WP release via SVN, you should.

Edit: There’s scope to make a plugin to automate this process. Just an upgrade link that runs this svn command in your root folder and redirects to /wp-admin/upgrade.php after that. That’ll make it a no-shell no-brainer to update. Of course, you’ve got to install via SVN first, but an admin could do that.

Tags:

Comments (2) | Permalink

Web.py on MacOSX

Sunday, October 21, 2007

I’m loving web.py for developing tiny web apps. Here’s how to get it going on a Mac:


sudo port -v install python2.4 py-setuptools mysql5
#coz macports insists on adding a '5' suffix to all mysql5 tools.
sudo ln -s /opt/local/bin/mysql_config5 /opt/local/bin/mysql_config 
sudo easy_install web.py cheetah markdown MySQL-python DBUtils

Not the twenty odd steps that’s on the web.py page (that even includes installing and configuring postgresql - bizarre!).

What I love about web.py is that this small bit of code:


#code.py
import web

urls = (
'/', 'index')

class index:
    def GET(self):
        print "Hello, world!"
        
if __name__ == "__main__": web.run(urls, globals())

… and running python code.py in a terminal gets you a working dev environment. Even camping doesn’t feel this tiny.

Edit: the performance numbers for this simple action are also very impressive:


time httperf --client=0/1 --server=localhost --port=8080 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=10000 --rate=500  
httperf --client=0/1 --server=localhost --port=8080 --uri=/ --rate=500 --send-buffer=4096 --recv-buffer=16384 --num-conns=10000 --num-calls=1
Maximum connect burst length: 17

Total: connections 10000 requests 10000 replies 10000 test-duration 20.000 s

Connection rate: 500.0 conn/s (2.0 ms/conn, <=24 concurrent connections)
Connection time [ms]: min 0.1 avg 1.8 max 2987.4 median 0.5 stddev 42.2
Connection time [ms]: connect 0.7
Connection length [replies/conn]: 1.000

Request rate: 500.0 req/s (2.0 ms/req)
Request size [B]: 60.0

Reply rate [replies/s]: min 499.8 avg 500.0 max 500.0 stddev 0.1 (4 samples)
Reply time [ms]: response 1.0 transfer 0.0
Reply size [B]: header 108.0 content 14.0 footer 2.0 (total 124.0)
Reply status: 1xx=0 2xx=10000 3xx=0 4xx=0 5xx=0

CPU time [s]: user 3.75 system 9.17 (user 18.7% system 45.8% total 64.6%)
Net I/O: 88.9 KB/s (0.7*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
httperf --client=0/1 --server=localhost --port=8080 --uri=/ --send-buffer=409  3.75s user 9.17s system 64% cpu 20.028 total

That’s 10K connections and more than 500req/s in a 20 sec timeframe. Really good.

Tags:

Comment! | Permalink

Ubuntu Gutsy

Friday, October 19, 2007

Ubuntu Logo

I tried out the new Ubuntu Gutsy today on my mom’s Compaq Presario V3000 (didn’t install, just tried out the LiveCD) and found it to be a really good revamp from the last version. Now WiFi works out of the box, the battery indicator works perfectly, and there’s a bluetooth screen for managing bluetooth devices. The new desktop effects are also pretty cool: I switched from ‘normal’ to ‘extreme’ and found em windows wobbling all over the place. The inbuilt virtual desktop is also smooth and not distracting. While the fonts look a bit too rough, I found web pages very readable in Firefox, and the color theme is also pretty easy on the eyes (although I’d prefer a more colorful default theme). I love the Ubuntu Terminal, it’s much better than the default Mac one.

I can cautiously say that Ubuntu has gotten to the point where a web programmer (a non ASP type) can easily ditch Windows for Linux and not pay the premium for a Mac. There are things the Mac still does better and places where the Mac experience feels a lot more polished, but Ubuntu is closing the gap very fast.

Tags:

Comments (5) | Permalink

Children and Computers

Monday, October 15, 2007

Ganga on the Mac

These are a set of informal observations made while watching my little niece Ganga (she’s about 4 years old) draw a picture on the computer.

  1. Kids pick up the basics really fast. Having walked my mom and DP through many a confusing day, I noticed that teaching Ganga is not as slow, but different. For e.g. she didn’t understand that the thing you point and click is called a ‘mouse’, but the motor skills that go with it came very quickly.
  2. She liked to draw and move windows around much more than the online games I found for her. This may be because those games are largely singing and dancing stuff in english, or she might be too young (because frankly I found those games really interesting. Vaibhav and Gauri too when they came here).
  3. The paint application I used is Seashore. It’s a Mac-only minimal port of GIMP. I use it for editing only because it’s much faster than opening up Photoshop or GIMP on a Mac and it gets simple editing done quickly. However, there are three things that I realized a “kid paint” tool must have:
    1. A subset of features: a pen, brush, color selection, an eraser, easy undo.
    2. Much bigger and lifelike icons. A pen for a pen, a brush for a brush. Avoid a dotted rectangle for selection.
    3. Individual colors put up there on the screen instead of a two-step process to click the foreground color, bring up the window, select the color and then close it. She took a long time to get this step right.
    4. A quick way to clear the paper (her terminology) and to get a new one. She spent eons rubbing the eraser all over a finished work to get a clear paper and I had to intervene to click File -> New. Menus are beyond her at this point.
  4. Instant gratification. At one point where she clicked on many items in the dock and the mouse cursor turned into a spinwheel (the mac hourglass) and she couldn’t click anymore, she was really irritated.
  5. She was fascinated by the fact that you could click and drag windows everywhere on the screen. Add to it my multi-monitor set up and this made for her second-best computer adventure.
  6. This is what a 3 hour intro to a computer produces:

The whole incident made me really curious about kids & computers. How do you introduce programming for example? Squeak? _why has something called hacketyhack as well. I’ll be keeping an eye on this in the future coz watching kids use computers is really interesting because of the way they instinctively search for new features.

Tags:

Comment! | Permalink

Chak De India

Sunday, October 14, 2007

Chak De India

I watched Chak De India yesterday and found the movie disturbing. I’d rather India had lost at the end but still come home to accolades for playing well. Focusing on victory as the singular goal seems to be so wrong. I also thought the movie took a rather unsubtle dig at the media’s reaction to loss and its treatment of a muslim player. [The movie itself was mediocre with Sharukh’s overacting and tripe dialogues; however surprisingly broad: everything from cricket’s unequaled popularity to sexual undertones between the coach and players].

Tags:

Comments (7) | Permalink