Saturday, January 30, 2010

A Python client for bit.ly

I recently found myself in need of a command-line URL shortening tool, primarily for use from inside Vim, and a quick search on Freshmeat didn't turn up any likely candidates. In particular I was looking for something that worked with bit.ly, since they provide a variety of tracking tools.

The bit.ly API looked pretty simple, so I spent a little time wrapping it in a simple Python module. The result is available on GitHub for general consumption.

The heart of the module is clever use of Python's __getattr__ call. From the Python 2.6 documentation:

object.__getattr__(self, name)

Called when an attribute lookup has not found the attribute in the
usual places (i.e. it is not an instance attribute nor is it found
in the class tree for self). name is the attribute name. This
method should return the (computed) attribute value or raise an
AttributeError exception.

The design of the bit.ly REST API makes it very easy to use __getattr__ to generate method accessors. A bit.ly API call is generally a GET request to a URL like this:

http://api.bit.ly/shorten

Arguments to the call are provided as URL parameters, e.g:

http://api.bit.ly/shorten?version=2.0.1&longUrl=http://cnn.com

Since (a) the method calls are all approximately identical and (b) they all take named arguments, this maps nicely to a Python method call using the **kwargs syntax. The general framework of the __getattr__ function looks like this:

def __getattr__ (self, func_name):

  def _ (**kwargs):
      url = '/'.join([self.api_url, func_name])
      query_string = self._build_query_string(kwargs)
      fd = urllib.urlopen(url, query_string)
      res = json.loads(fd.read())

      return res['results']

  return _

That is, when client code attempts to access a method attribute, like bitly.shorten(...), we generate an anonymous function that appends the method name to a base url, then appends all the query paramters, and lastly returns the results (decoded from JSON into Python data structures).

The actual code is only slightly more involved (I've tried to provide some reasonable exceptions, for example).

Posting to Blogger from Git

GitBlogger is a small tool, written in Python, that allows you to write blog posts in reStructuredText using an editor of your choice, commit your posts to a git repository, and then have them automatically converted to Blogger blog posts when you push your repository to a "remote" repository ("remote" in quotes because "remote" may simply mean "another copy of the repository on your local system").

I put this together to scratch a few of my own itches, which may not be the same as yours:

  • I don't want to write blog posts in HTML. Our life on this planet is short and we shouldn't waste it with all those angle brackets.
  • I want to use a real editor, rather than editing text in browser text fields.
  • I want to use some form of version control on all of documents, not just my source code. Git has proven itself to be extremely flexible and easy to work with.
  • I want things to be handled as automatically as possible.

GitBlogger handles the task of synchronizing changes in your git repository to your Blogger blog. It specifically does not make any attempt to take changes you've made to your blog via other mechanisms and somehow propogate them back to the repository -- the model here is that your blog is primarily a display mechanism.

GitBlogger handles adds, updates, renames, and deletes.

How it works

  1. You compose your post using your editor of choice and commit your changes to your local repository.
  2. When you're ready, to use git push to transfer the changes to a remote repository that has been configured to use GitBlogger's post-receive hook services.
  3. GitBlogger maintains a local database that allows it to associate files in your repository with specific post id's on your Blogger blog.
  4. GitBlogger then uses git diff-tree to generate a list of files that have been created/renamed/updated/deleted as part of the commits since you last pushed the repository.
  5. For each file, GitBlogger will either create a new post in your Blogger blog, delete a Blogger post, or update the contents of an existing Blogger post.
  6. GitBlogger handles the conversion from reStructuredText to HTML, and extracts certain metadata (e.g., tags or draft status) from the documents docinfo section.

Caveats

I'm using GitBlogger "in production" on two sites right now, and the only mishaps I've had have been in the positive direction (duplicate posts) rather than the negative direction (which would be erroneously deleted posts). That said, it has not seen extensive testing nor use outside of my own environment.

Documentation is pretty light at the moment. The included README file shows a sample configuration, but the docs could use some fleshing out.

I'd be happy to hear about your success stories, suggestions for improvements, or problems that you have run into using this software.

Friday, January 29, 2010

Retrieving Blogger posts by post id

I spent some time recently trying to figure out, using Google's gdata API, how to retrieve a post from a Blogger blog if I know corresponding post id. As far as I can tell there is no obvious way of doing this, at least not using the gdata.blogger.client api, but after much nashing of teeth I came up with the following solution.

Given client, a gdata.blogger.client instance, and blog, a gdata.blogger.data.Blog instance, the following code will return a gdata.blogger.data.BlogPost instance:

post = client.get_feed(blog.get_post_link().href
          + '/%s' % post_id,
        auth_token=client.auth_token,
        desired_class=gdata.blogger.data.BlogPost)

I'm not sure if this is the canonical solution or not, but it appears to work for me.

Linux UPnP Gateway

Like many other folks out there, I have several computers in my house connected to the outside world via a Linux box acting as a NAT gateway. I often want to use application such as BitTorrent and Freenet, which require that a number of ports be forwarded from my external connection to the particular computer on which I happen to be working. It turns out there's a protocol for this, called UPnP. From Wikipedia:

Universal Plug and Play (UPnP) is a set of networking protocols promulgated by the UPnP Forum. The goals of UPnP are to allow devices to connect seamlessly and to simplify the implementation of networks in the home (data sharing, communications, and entertainment) and in corporate environments for simplified installation of computer components.

The practical use of UPnP, from my perspective, is that it allows a device or application inside the network to request specific ports to be forwarded on the gateway. This means that what used to be a manual process -- adding the necessary forwarding rules to my iptables configuration -- is now performed automatically, and only when necessary.

The Linux UPnP Internet Gateway Device project implements a Linux UPnP service. You can download the source from the project web page, or you can download an RPM from here and install it on any recent vintage RedHat-ish system.

Using the gateway service is really simple:

  1. Start up upnpd:

    # /etc/init.d/upnpd
    
  2. Start your application. You will see messages like the following in syslog (if you are logging DEBUG level messages):

    Aug  6 20:10:12 arcadia upnpd[19816]: Failure in
      GateDeviceDeletePortMapping: DeletePortMap: Proto:UDP Port:57875
    Aug  6 20:10:12 arcadia upnpd[19816]: AddPortMap: DevUDN:
      uuid:75802409-bccb-40e7-8e6c-fa095ecce13e ServiceID: urn:upnp-org:serviceId:WANIPConn1
      RemoteHost: (null) Prot: UDP ExtPort: 57875 Int: 192.168.1.118.57875
    Aug  6 20:10:12 arcadia upnpd[19816]: Failure in
      GateDeviceDeletePortMapping: DeletePortMap: Proto:UDP Port:11657
    Aug  6 20:10:12 arcadia upnpd[19816]: AddPortMap: DevUDN:
      uuid:75802409-bccb-40e7-8e6c-fa095ecce13e ServiceID: urn:upnp-org:serviceId:WANIPConn1
      RemoteHost: (null) Prot: UDP ExtPort: 11657 Int: 192.168.1.118.11657
    

    For each forwarding requested by the client, upnpd first attempts to remove the mapping and then creates a new rule. Exactly how upnp implements these rules on your system is controlled by /etc/upnpd.conf -- if you want to use something other than iptables, or use custom chains, this is where you would make your changes.

  3. Look at your firewall rules. Upnpd modifies the FORWARD chain in the filter table and the PREROUTING chain in the nat table. You can change this behavior by editing /etc/upnpd.conf.

    To see forwarding rules:

    # iptables -nL FORWARD
    

    The rules might look something like this:

    Chain FORWARD (policy DROP)
    target     prot opt source               destination
    ACCEPT     udp  --  0.0.0.0/0            192.168.1.118       udp dpt:57875
    ACCEPT     udp  --  0.0.0.0/0            192.168.1.118       udp dpt:11657
    

    To see prerouting rules:

    # iptables -t nat -vnL PREROUTING
    

    The rules might look something like this:

    Chain PREROUTING (policy ACCEPT)
    target     prot opt source               destination
    DNAT       udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:11657 to:192.168.1.118:11657
    DNAT       udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:57875 to:192.168.1.118:57875
    
  4. Upnpd will delete the mappings when they expire. The expiration time may be set by the client, or, if the client specifies no expiration, than by the "duration" configuration item in /etc/upnpd.conf.

Configuration file

The upnpd configuration file (/etc/upnpd.conf) allows you to change various aspects of upnpd's behavior. Of particular interest:

  • insert_forward_rules

    Default: yes

    Whether or not upnpd needs to create entries in the FORWARD chain of the filter table. If your FORWARD chain has a policy of DROP you need set to yes.

  • forward_chain_name

    Default: FORWARD

    Normally, upnpd creates entries in the FORWARD chain. If you have a more advanced firewall setup this may not be the appropriate place to make changes. If you enter a custom name here, you will need to create the corresponding chain:

    iptables -N my-forward-chain
    

    You will also need to call this chain from the FORWARD chain:

    iptables -A FORWARD -j my-forward-chain
    
  • prerouting_chain_name

    Default: PREROUTING

    Like forward_chain_name, but for entries in the nat table.

Security considerations

Consider the following, from the Linux IGD documentation:

UPnP version 1.0, on which this program is based, is inherently flawed...what appears to have happened is that in Microsoft's first UPnP implementation they weren't concerned with security .... Simply all they wanted was connectivity.... The UPnP server, by itself, does no security checking. If it recieves a UPnP request to add a portmapping for some ip address inside the firewall, it just does it. This program will attempt to verify the source ip contained in the UPnP request against the source ip of the actual packet, but as always, these can be forged. The UPnP server makes no attempt to verify this connection with the caller, and therefore it just assumes that whoever asked is the person really wanting it.

In other words, in the battle between security and convenience, UPnP is weighs in heavily on the convenience side. You will have to decide whether this meets your particular requirements.

Cleaning up Subversion with Git

Overview

At my office, we have a crufty Subversion repository (dating back to early 2006) that contains a jumble of unrelated projects. We would like to split this single repository up into a number of smaller repositories, each following the recommended trunk/tags/branches repository organization.

What we want to do is move a project from a path that looks like this:

.../projects/some-project-name

To a new repository using the recommended Subversion repository layout, like this:

.../some-project-name/trunk

Our lives are complicated by the fact that there has been a lot of mobility (renames/moves) of projects within the repository.

Setup

We'll set up a test environment that will demonstrate the problem and our solution.

  1. Create the empty repositories:

    set -x
    rm -rf work && mkdir work
    cd work
    WORKDIR=$(pwd)
    mkdir repos
    
    # create source repository
    svnadmin create repos/src
    
    # create destination reposiory
    svnadmin create repos/dst
    
  2. Create our desired repository structure in the destination repository:

    svn mkdir -m 'create trunk' file://$WORKDIR/repos/dst/trunk
    svn mkdir -m 'create branches' file://$WORKDIR/repos/dst/branches
    svn mkdir -m 'create tags' file://$WORKDIR/repos/dst/tags
    
  3. Create a simple revision history:

    svn co file://$WORKDIR/repos/src src
    (
    cd src
    
    # Create our initial set of projects.
    mkdir projects
    mkdir projects/{project1,project2}
    touch projects/project1/{file11,file12}
    touch projects/project2/{file21,file22}
    svn add *
    svn ci -m 'initial commit'
    
    # Relocate a file between projects.
    svn mv projects/project1/file11 projects/project2/
    svn ci -m 'moved file11'
    
    # Rename a project.
    svn mv projects/project2 projects/project3
    svn update
    svn ci -m 'renamed project2 to project3'
    )
    
  4. We can see the structure of the source repository like this:

    echo "Contents of source reposiory:"
    svn ls -R file://$WORKDIR/repos/src
    

    Your output should look something like this:

    projects/
    projects/project1/
    projects/project1/file12
    projects/project3/
    projects/project3/file11
    projects/project3/file21
    projects/project3/file22
    

In this example, we'll try to import project3 into a new repository.

Using Subversion

With Subversion, it's easy to extract a single project from the repository:

svn co file://$WORKDIR/repos/src/projects/project3

This gives us a directory called project3 containing the contents of the project. Unfortunately, there are no tools that will allow us to take this working copy and move it into another repository.

Subversion includes a tool called svnadmin that allows on to perform a number of operations on a Subversion repository, but it requires access to the filesystem instance of the repository (it will not work over the network). This is a substantial limitation if you are working with a repository that is maintained by someone else, but we have the necessary access to our repository.

The svnadmin command includes a dump operation that serializes a repository -- and its entire revision history -- into a text stream that can be loaded into another repository with a corresponding load operation. We don't want the entire repository, so we'll make use of the svndumpfilter command which, as you might expect, can apply certain filters to the output of svnadmin dump.

We might try something like this:

svnadmin dump repos/src |
  svndumpfilter include projects/project3/ |
  svnadmin load repos/dst

Unforunately, this will fail with an error along the lines of:

svndumpfilter: Invalid copy source path '/projects/project2'
svnadmin: Can't write to stream: Broken pipe

And if you were to look at the destination repository, you would find projec3 entirely absent:

echo "Contents of destination repository (after dump/filter/load):"
svn ls -R file://$WORKDIR/repos/dst

And even if it worked we would still have to muck about in the destination repository to create our desired repository layout.

Using Git

Git is another version control system, similar in some ways to Subversion but designed for distributed operation. If you're not familiar with git there is lots of documentation available online.

We'll start by checking out project3 from the Subversion repository:

rm -rf project3
git svn clone file://$WORKDIR/repos/src/projects/project3
cd project3

Because we're going to import this code into a new repository we need to erase all references to the source repository:

git branch -rD git-svn
git config --remove-section svn-remote.svn
rm -rf .git/svn

And now we associate this git repository with the destination Subversion repository:

git svn init -s file://$WORKDIR/repos/dst
git svn fetch

We now apply the revision history to the trunk of the destination repository and commit the changes:

git rebase trunk
git svn dcommit

After all of this, we have exactly what we want -- our project hosted in a new repository with our desired layout. The following commands show the contents of the repository:

echo "Contents of destination repository (after git):"
svn ls -R file://$WORKDIR/repos/dst

And produce output like this:

branches/
tags/
trunk/
trunk/file11
trunk/file21
trunk/file22

And the revision history of the project is available in the destination repository:

echo "Revision history in destination repository:"
svn log file://$WORKDIR/repos/dst

The output will look something like:

Revision history in destination repository:
+ svn log file:///home/lars/projects/svn-to-svn-via-git/work/repos/dst
------------------------------------------------------------------------
r7 | lars | 2009-06-03 14:46:02 -0400 (Wed, 03 Jun 2009) | 1 line

renamed project2 to project3
------------------------------------------------------------------------
r6 | lars | 2009-06-03 14:46:02 -0400 (Wed, 03 Jun 2009) | 1 line

initial commit
------------------------------------------------------------------------
r5 | (no author) | 2009-06-03 14:45:55 -0400 (Wed, 03 Jun 2009) | 1 line

This is an empty revision for padding.
------------------------------------------------------------------------
r4 | (no author) | 2009-06-03 14:45:53 -0400 (Wed, 03 Jun 2009) | 1 line

This is an empty revision for padding.
------------------------------------------------------------------------
r3 | lars | 2009-06-03 14:45:52 -0400 (Wed, 03 Jun 2009) | 1 line

create tags
------------------------------------------------------------------------
r2 | lars | 2009-06-03 14:45:52 -0400 (Wed, 03 Jun 2009) | 1 line

create branches
------------------------------------------------------------------------
r1 | lars | 2009-06-03 14:45:52 -0400 (Wed, 03 Jun 2009) | 1 line

create trunk
------------------------------------------------------------------------

Please make ConfigParser go away

Oh, ConfigParser, how I wish you would go away. You're un-pythonic and strict to the point of senslesness. You're parsing simple key/value files yet you won't give me a dictionary -- what are you smoking? And why are you in the standard library?

Finally fed up with yet another ConfigParser debacle (hint: try reading a git config file with ConfigParser), I've written my own replacement. I've called it configdict, and you find it on GitHub. configdict has the following properties:

  • When you parse an INI style config file, you get back a dictionary of dictionaries.
  • Dictionaries can "inherit" keys from the DEFAULT section.

Also, for the record:

  • It's largely untested (other than in my little personal projects), and might eat your cat.

This means that given a config file:

[DEFAULT]

in stock = yes

[widgets]

price = 110.00
size = large

[sprockets]

price = 15.99
shape = oblong

You can do this:

>>> config = configdict.ConfigDict('myconfig.ini')
>>> print config['widgets']['price']
110.00
>>> print config['widgets']['in stock']
yes

And look, it really is a dictionary:

>>> import ppprint
>>> pprint.pprint(config)
{'DEFAULT': {'in stock': 'yes'},
 '__GLOBAL__': {},
 'widgets': {'description': '%(size)s widgets',
             'options': 'this that and the other thing',
             'price': '110.00',
             'size': 'large'}}

It supports some reasonably common features of this sort of configuration file. For example, line continuation:

options = this \
  that \
  and \
  the other thing

Which yields:

>>> c['sprockets']['options']
'this that and the other thing'

Sunday, January 24, 2010

Fring: How not to handle registration

I thought I'd give Fring a try after seeing some favorable reviews on other sites. If you haven't previously heard of Fring, the following blurb from their website might be helpful:
Using your handset′s internet connection, you can interact with friends on all your favourite social networks including Skype®, MSN Messenger®, Google Talk™, ICQ, SIP, Twitter, Yahoo!™ and AIM®. You can listen to music with your Last.fm friends, check out what each other are up to on Facebook, receive alerts of new Google Mail™ and tailor make your very own fring by adding more cool experiences from fringAdd-ons
Sounds great, right? To use any of these services, you need a Fring account. When you first launch the Fring application on your Android phone, it will prompt you to either provide your existing credentials or set up a new account.

I haven't previously used Fring so I needed to set up a new acount. I selected a username, alias, and password, provided my email, and then clicked on "Register", and Fring said:
Password contains invalid characters.
What, that's it?  How about a few hints?  Or better yet, how about you tell me what your stupid password requirements are before you complain about them?  Seriously, guys, have you hired monkeys to handle your authentication mechanisms?  Or have you simply decided to ignore decades of best practices concerning password selection?  I have some suggestions for you:
  • If you have password character class limitations, state them up front.
  • Don't have character class limitations.  Let me type whatever the heck I want.
  • Don't set weird arbitrary limits on maximum password lengths.  Storage is not that expensive.
  • Adjust complexity requirements with the length of my password.  A 20 character password is secure even if it only contains letters.
Fring is the current winner of the "shortest lifetime on my phone" award, and because it was so bad it doesn't even get a QR code.  Bad app.  Not for you.

Edit: Removed punctuation from my password.  Now Fring tells me, "Password too long."  Ha!

Friday, January 22, 2010

Review: Google Sky Map...We're livin' in the future!


We went on a family flashlight walk last night after dinner.  There were a surprising number of stars visisble, which gave me the perfect excuse to try out Google's Sky Map application for my Droid.  Google Sky Map uses both your location -- ideally computed from GPS signals -- and the phone's orientation sensors to perform some funky magic: it displays on screen the section of the sky you're at which you're looking, with stars and constellations labelled.  As you move the phone around, you see different parts of the sky.  It's a magic window!



I've never been an astronomy buff, but I remember being fascinated by H. A. Rey's "The Stars" when I was little.  This application brings back some of that fascination, and I hope I can use it to teach the kids as they get older.


Thursday, January 21, 2010

Review: 3Banana Notes, a note taking application for Android

The other day I found myself in need of a notes application for my Droid phone.  A quick search of the Market turned up several likely possibilities, but the one that stood out was the oddly named "3Banana Notes", by Snaptic.  This application turns out to have some seriously cool features.

Tagging, hot links, and multimedia


3Banana lets you tag your notes with Twitter-esque hashtags.  Just add a "#" before a word and that words becomes a tag for your note.  You can filter your notes by tag in both the Android application and the web site.

3Banana will also convert a number of common text elements into hotlinks.  Click on URLs to open them in a browser, and click on phone numbers to dial them.

3Banana makes it very easy to attach photos to your notes.  Just click the camera icon on the main screen, take a picture, and then return to 3Banana where you can add text.

Synchronization


The application can synchronize your notes to the snaptic.com website.  These folks have made account registration a smooth and easy process -- if you have a Google account (which you do, by definition, if you're using the Android Market), you can sign in using your Google credentials and end up logged into a snaptic.com account without any further work on your part.  Listen up, other websites!  This is how it should be!

Once you've established your snaptic.com account, notes are synchronized automatically in both directions.

Sharing


Notes on the snaptic.com website are generally private.  You can share them with others by generating a special URL that links directly to the note (without exposing any of your other notes).  Snaptic uses this feature to provide an interesting sharing mechanism: both the Android application and the website can generate a QR code encoding the sharing URL for a note.

You can also share notes using the standard suite of sharing services: Twitter, Facebook, SMS, Email, etc.

Snaptic suggests using the "sharing URL" feature to create small informal communities -- share information with groups of friends and have conversations via comments attached to your note.

Final thoughts


3Banana is a great note taking application.  The tagging, hyperlinks, and synchronization features make it excellent for personal use, and the sharing features make it an interesting collaborative tool.  It has definately earned a place on my home screen.

Wednesday, January 20, 2010

Importing Facebook notes into Blogger

I've tweaked the code for my Facebook Data Exporter such that the Atom export can be imported into a Blogger blog using Blogger's Import Blog feature.

This accomplishes my original goal of combining my posts about Max and Kira from Facebook with earlier posts on my blog so that the entire chronology is all in one place.

It also means that I have everything squirreled away for safekeeping so that in ten years I can show the kids what I was saying about them, even if Facebook is no longer around.

Woot!

Tuesday, January 19, 2010

Facebook exporter...now with links!

Spent some more time this evening fiddling with my Facebook exporter application. In addition to notes and status messages it will now export links, too. I've cleaned up most of the export formats a little bit. The code changes mean that it will probably explode in new and interesting ways, but it seems to work for me. If you have a moment to test it out, I'm interested in hearing success and/or failure stories.

Monday, January 18, 2010

Preserving Facebook for posterity

I've written my first Facebook application. It's horribly ugly, error messages are cryptic or nonexistent, and it's guaranteed to explode, but it will let you export both your notes and status updates as HTML, Atom, or a CSV file. Excel will happily import the CSV file and Safari will happily treat the Atom export as a feed.

Go here and authorize the application:
http://ping.fm/rwh1P

Code is here:
http://ping.fm/ZF38w

It's hosted on Google App Engine, which occasionally generates a DownloadError exception for unknown reasons; if you get this a reload usually fixes the problem.

Saturday, January 16, 2010

Exporting Notes from Facebook

I've put a number of Notes on Facebook that I'd like to preserve, but I've been unable to find any good options for exporting these from Facebook so that I can save them locally (or put them somewhere else). SocialSafe (http://ping.fm/56Za8) will save some Facebook data, but at the moment has no support for Notes...

...so, I wrote something myself. It exports all of your Facebook notes as an Atom feed. You can find the code at http://ping.fm/uq954 It's not pretty, it will probably fail in strange and inexplicable ways, and it's largely unsuitable for anyone not already a Python developer. But it works for me, and I figured it might be useful for someone else, too.

English Handwriting 1500-1700: An Online Course

Wow, this is fun. Examples and discussion of handwriting between 1500 and 1700. The section on transcription conventions is particularly interesting reading.

From: http://ping.fm/MX1Dn

What's wrong with my Internet?

ICSI Netalyzr is a java applet that performs an impressive collection of tests on your Internet connection, and reports the results back to you (and to the ICSI) in an easily readable format.
Finally out of beta test, Netalyzr is useful for debugging weird connectivity issues and finding out what things your ISP might be doing to your connection intentionally that you don't know about.

From: http://ping.fm/QYb63