Monday, January 18, 2010

Using Java to Download Links to Picasa Web Albums

I mentioned a couple of weeks ago that I moved all of my digital photos to Picasa Web Albums. It turns out that my wife would like access to these photos. I uploaded the albums as "Unlisted", so the web interface provides no way to get links for sharing in bulk. I have somewhere around 150 albums, so going into each one to grab a sharing URL would be a really tedious way to spend the afternoon on my vacation day. I would rather write a program. Picasa Web Albums Data API to the rescue.

Since this was my first program using Google Data APIs, it took me longer to set up a working environment than it did to actually write the Java code. The getting-started guide is here. For my case, I needed as external dependencies 1) JavaMail and 2) the JavaBeansActivation Framework, both of which most open-source-Java developers already have sitting around somewhere convenient. Then I grabbed the latest Google Data Java Client libraries, unzipped, and put all of the jars in gdata/java/lib and gdata/java/deps on the classpath of my project.

From there, the code was very straightforward. Note the placeholders for Google username and password.

import java.net.URL;
import java.util.List;

import com.google.gdata.client.photos.PicasawebService;
import com.google.gdata.data.photos.GphotoEntry;
import com.google.gdata.data.photos.UserFeed;

/**
 * Short script to download links to all of my Picasa
 * Web Albums.  This includes private/public/protected
 * access levels.
 * 
 * @author Scott McMaster (smcmaster@acm.org)
 *
 */
public class DownloadPicasaWebAlbumLinks {
 private static final String USERNAME = "NOTMYREALUSERNAME";
 private static final String PASSWORD = "NOTMYREALPASSWORD";

 @SuppressWarnings("unchecked")
 public static void main(String[] args) throws Exception {
  PicasawebService myService = new PicasawebService(
    "Download Album Links");
  myService.setUserCredentials(USERNAME, PASSWORD);

  URL albumFeedUrl = new URL(
    "http://picasaweb.google.com/data/feed/api/user/" + USERNAME
      + "?kind=album");
  UserFeed albumFeed = myService.getFeed(albumFeedUrl, UserFeed.class);
  List albums = albumFeed.getEntries();
  for (GphotoEntry album : albums) {
   System.out.println(album.getHtmlLink().getHref());
  }
 }
}
For "unlisted" albums, this will print out, for each album, the same link you get if you click "Link to this album" in the UI.

Thursday, December 31, 2009

Moving to the Cloud Part 2: Email

In my last post, I mentioned my goal of moving all of my important data out of local storage and into the cloud. First, I dealt with my photos. My next order of business was email.

Traditionally, I used Outlook to organize my email and contacts. So I had years' worth of messages organized into personal folders on a local hard drive. Whenever I wanted to move to a new machine, I would export all of these as .pst files and re-import them in a new location. That was annoying, risky, and error-prone.

Now that I am paying for all of this Google storage (as discussed in my last post), it seemed obvious that Gmail was the place to move all of my old email. I wasn't sure off the top of my head exactly how to do that, but a quick search turned up many variations on a simple trick -- enable IMAP in Gmail settings, bring the Gmail account into Outlook via IMAP, and drag-and-drop folders and messages. Perfect!

As for my contacts, I imported them into the Google space (via Google Voice) a long time ago. And I never did a lot of personal calendaring with Outlook. (Online solutions are far better for that now anyway.)

Overall, I was very pleased at how easy it was to migrate away from Outlook. There are two more things that I care about enough to want to move to the cloud -- my music and my schoolwork/teaching/research files. I will cover those in future posts.

Wednesday, December 30, 2009

Moving to the Cloud Part 1: Photos

Well, it's been a long time since I've posted. Without going into a lot of details, I've had a number of life changes in the last few months. Hopefully I can get back into blogging a bit.

One goal I've made for 2010 is to care a lot less about what, exactly, is on the fleet of machines and disk drives sitting around my house. That means moving content into the cloud. I decided to start with what is most important to me, namely, my family photos.

I was a pretty early adopter of digital photography, getting my first camera in 1997. So I have a lot of pictures. I've used Picasa for a long time (well before Google bought it), so it seemed like a natural fit to upload everything into Picasa Web Albums. And of course, there is a web API, so in the future, I can manage my photos with custom scripts and applications.

The Picasa fat client application makes the upload process incredibly easy. (I used the beta version for Linux, which now runs via the WINE emulator.) One thing I had to make sure to do was force it to upload the full-size images for each album, lest I lose some JPEG quality for more recent years' photos.

Another thing I had to do was upgrade my Google Account storage, because the free space wasn't going to be nearly enough. Conveniently, there is currently a deal to get a free Eye-Fi card with a year's worth of 200G of storage. I went for that. I don't really need anywhere near 200G just for my photos, but I will use some more of that space in future blog posts. And I can't wait for my new Eye-Fi card to arrive in the mail.

Saturday, October 24, 2009

Testing Exception Messages, JUnit 4.7 Edition

Historically, JUnit has not elegantly supported verification of expected-exception messages in unit tests. Some time ago, I wrote a post about techniques for handling this situation. Brian Brooks (clearly a diligent web searcher) found that post and was kind enough to send me a message pointing out that the latest version of JUnit, 4.7, finally includes support for this incredibly important and common testing scenario.

In honor of this exciting occasion, I wanted to post an example. Here is the class I'm going to test:
public class PositiveInteger {
 private int num;

 public PositiveInteger(int num) {
  if(num <= 0) {
   throw new IllegalArgumentException("Number must be positive");
  }
  this.num = num;
 }
}
Let's test -- hmmm -- the constructor, I guess. I created two test cases -- one which passes, and one which intentionally fails (just to show the resulting output):
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;


public class TestPositiveNumber {

 @Rule
 public ExpectedException thrown = ExpectedException.none();

 @Test
 public void testConstructorInvalid_PASS() {
  thrown.expect(IllegalArgumentException.class);
  thrown.expectMessage("Number must be positive");
  new PositiveInteger(-5);
 }

 /**
  * Intentionally-failing test to demonstrate the results
  * when checking an exception message.
  */
 @Test
 public void testConstructorInvalid_FAIL() {
  thrown.expect(IllegalArgumentException.class);
  thrown.expectMessage("Not gonna pass");
  new PositiveInteger(-5);
 }
}
JUnit now supports a style embodied in "rules", which get marked with the @Rule annotation and provide a means by which the test execution behavior can be modified. You can see that here with the "thrown" field, of type "ExpectedException". It would be great if a) that field didn't have to be public, and b) the functionality could be implemented using a local variable, but unfortunately the Java language won't currently support such an implementation. So a public field it is. In a test, you can configure the rule. Here I do that with calls to the "expect" and "expectMessage" methods. When JUnit executes the test case, it checks the result against the rule and fails if the rule is not matched. In the test case that does not pass, the failure takes this form:
java.lang.AssertionError: 
Expected: (exception with message a string containing "Not gonna pass" and an instance of java.lang.IllegalArgumentException)
     got: 

 at org.junit.Assert.assertThat(Assert.java:778)
 at org.junit.Assert.assertThat(Assert.java:736)
Very useful. Thanks to Kent Beck for adding this support.

Saturday, September 26, 2009

URLEncoder Demonstrates API Design Hazards

I had to use java.net.URLEncoder yesterday. This experience was a great reminder of how annoying this API is. It demonstrates a couple of issues, so I thought I would comment on it here.

The first thing you can see here is how important it is to get the API right the first time, lest things go downhill from there. In its original form, URLEncoder provided only one static method which encoded a string as "application/x-www-form-urlencoded" (suitable for issuing an HTTP request). This method has the signature encode(String), and it used the platform's default encoding. Unfortunately, this is technically hazardous, because URL encodings are supposed to always be UTF-8. Therefore, wiser heads built and used replacement classes (such as URLCodec from Apache Commons).

Java 1.4 finally "fixed" this deficiency in the platform with a new encode(String,String) overload, the second parameter being the name of a character encoding to use. The old encode(String) method is marked as deprecated. The documentation itself states that: Note: The World Wide Web Consortium Recommendation states that UTF-8 should be used. Not doing so may introduce incompatibilites.

I don't know, but I'm guessing that the powers-that-be would have liked to make encode(String) do UTF-8 encoding. But they felt constrained by the legacy decision to use the platform default, in the name of not breaking any existing code. Never mind the substantial odds that any such existing code is already broken in this case wherever the default encoding is not UTF-8.

But wait, it gets worse. The encode(String,String) method also throws UnsupportedEncodingException whenever (surprise!) the second parameter does not refer to a valid encoding. I'm no rocket scientist, but it seems to me that if you're following the W3C recommendation and always passing UTF-8, you don't have to worry about this exception being thrown. And yet because the exception is checked, you have no choice but to handle it and clutter up your code, perhaps with a catch block that just does assert(false).

This all begs the question, if the second parameter should always be "UTF-8" for some definition of "always", why should the API require a second parameter at all? I submit that a better approach would have been to create, instead of or in addition to encode(String,String), an encodeUTF8(String) method that always did UTF-8 encoding without the added hassle of passing a (constant) parameter and dealing with a checked exception...