James Williams
LinkedInMastodonGithub

Picasa Download with Griffon

Tags: Griffon

I wrote this application partially as a port of an existing application and partially to solve a problem. You see, I recently moved to California from Florida and drove my car with my dad. Along the way we stopped at the Alamo (which is much smaller in real life), Fort Davis and a couple other places. Having forgotten his camera, he used mine but didn't have a thumb drive to download the photos. Email was out as I didn't feel like trying to attach individual 1MB photos to emails or sending a 50MB tar of the files. It's a great chance to port another AIR app to Griffon(well technically flump is based on Flickr). So after a couple months of nagging, I finally got to work, here's the product of three hours of messing around.

**Edit: Unless otherwise noted, demos assume you are using the most recent Griffon release available at the time the entry is published, in this case 0.1-SNAPSHOT. I apologize for any inconvenience or general head-banging due to this omission.**

Webstart version of the application located here.

The layout is pretty simple. I could've done something a little more snazzy but a quick tabular layout with MigLayout served my needs.

import net.miginfocom.swing.MigLayout
application(title:'PicasaDownload', size:[480,300], locationByPlatform:true) {
    panel (layout:new MigLayout()) {
        label(text:"Enter Picasa Account Info:")
        textField(id:'userId', columns:25, constraints:'newline')
        button(id:'retrieve', text:'Retrieve albums', constraints:'wrap', actionPerformed:{controller.getAlbums()})
        label(text:'Select albums:', constraints:'wrap')
        comboBox(id:'albumsCombo')
        checkBox(id:'allAlbums', text:'All')
        label(text:'Location:', constraints:'newline, wrap')
        textField(id:'saveLocation', columns:25, text:bind{model.location}, enabled:false)
        button(text:'Browse', actionPerformed:{controller.fileChooser()})
        button(id:'download', text:'Download', constraints:'cell 1 6', actionPerformed:{controller.download()})

    }
}

The getFeed method sends a HTTP GET request, follows any redirects, waits for the XML response and parses the response. Because there can be some latency and they didn't note if it was being down already, I wrapped it in a Thread.start. The getAlbums closure retrieves the user feed and puts any public albums in a combo box. The downloadFile closure is nothing really new, just a generic file download from a URL.

def getAlbums = { evt ->
    Thread.start {
        def feedUrl = new URL("http://picasaweb.google.com/data/feed/api/user/"
+view.userId.text+"?kind=album");
        model.userFeed = model.picasaService.getFeed(feedUrl, UserFeed.class)
        view.albumsCombo.removeAllItems()
        for (album in model.userFeed.getAlbumEntries()) {
            view.albumsCombo.addItem(album.getTitle().getPlainText())
        }
    }
}

def downloadFile = {url, title, fileName ->
    def is = new URL(url).openStream()
    def out = new BufferedOutputStream(new FileOutputStream(model.location+File.separator+title+File.separator+fileName))
    byte[] buff = new byte[1024];
    int bytesRead = 0;

    while((bytesRead = is.read(buff)) != -1) {
        out.write(buff, 0, bytesRead);
    }

    out.close();
    is.close();
}

You can download the source code here.