Archives for January 2009

Problematic Twitter API Cap

Though unfortunate, if tuned and re-architected a bit,  I don't believe the 20,000 calls per hour cap would shutter many Twitter dependent services. That is, if they don't try to continue to operate as they have. I believe the future might be a return to the concept of timeslices from the dark ages of computer science. More apps should run on the desktop. Forget supported by ads, it should additionally be supported by processed work units. For the average user, I think even netbooks have enough headroom to not be affected. I admit that each situation is unique and this isn't a panacea and is more of a braindump.

One of the benefits of the a desktop app and Java applets in particular, though the latter has be vilified by the faux pas of amateur Swing developers, is the fact that that they usually run in a sandboxed VM on the user's machine. This is a feature I took advantage of when creating FriendBackup. Because it ran on the user's machine so I never had access to their data and because they were communicating directly with the Twitter API, I didn't incur a cap. I know that the browser is the lowest common denominator but who says the meat of the application has to run only your servers. I'm not saying you should use Joe Schmo's machine to parse the data from @Scobleizer but you could easily wire up your own pseudo-SETI@HOME with some Hadoop/MapReduce magic to have the users process a couple packets of information and send it back. It's not optimal but it should work.

Though I've not tried them (but have signed up for their beta), Plura Processing allows developers to earmark CPU time to process work units. They currently support Java applications and websites. It would be a pain to engineer but it would provide some headroom to work with the caps. As long as it's unobstructive, clearly stated in your ToS, I don't think users would mind a couple packets here or there.

Why does everything have to run in the browser? We can use AIR, Griffon, Silverlight, a draggable Java6 applet, whatever. It is starting to box us in. The decision of Google devs to make the Native Client is becoming more clear.
 

Picasa Download with 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.

JSR 223 Scripting API with Griffon

So you would really like to benefit from the features of Griffon but don't exactly know Groovy? One of the previously unexplored areas is JSR 223: Scripting API. In this tutorial, we will use JSR 223 to embed scripts into our application and run non-Groovy code in response to application events.

Getting Started

If you don't have Griffon installed already, go and get it. If you are using Griffon with Java 6, you already have JSR 223 and support for Rhino(which we'll be using for our examples). Alternatively, the concepts apply to any JSR 223 language such as JRuby or Jython.

Scripting API Hello World

For our first foray into Griffon powered by JSR 223, we'll keep it simple and code a basic UI with a label, textfield, and button. Our view looks like this:

application(title:'JSR223Hello', size:[100,200], pack:true, locationByPlatform:true) {
    hbox {
        label('Name')
        textField(columns:25, text:bind(target:model, targetProperty:'name'))
        button(text:'Click', actionPerformed:{controller.hello()})
    }
}
		

We've also bind to a model property name and invoke a actionPerformed closure on button click. Before we get around to defining the actual hello closure, we have to do a little setup work to initialize the ScriptingEngineManager. I executed the following code in Startup.groovy but it could have been executed earlier if desired:

import javax.script.ScriptEngineManager

Thread.start {
    def model = app.models.JSR223Hello
    model.manager = new ScriptEngineManager()
    model.engine = model.manager.getEngineByName("js")
}
		

In the above snippet, we retrieve an instance of the model and instantiate the ScriptEngineManager and Javascript engine. Because we retain a instance of the manager, we could theorhetically use several engines concurrently. Now on to the part that actually does something. Given that I'm a relative novice in Javascript and this is a simple demo, our code just retrieves the name typed in the textfield and constructs a Hello, < name > dialog box. Here's the controller with the uninteresting bits cut out:

class JSR223HelloController {
    // these will be injected by Griffon
    def model
    def view

...

    def hello = {
	    def js = "importPackage(javax.swing);"+
		    "JOptionPane.showMessageDialog(null, 'Hello, ${model.name}');"
		
        model.engine.eval(js)
    }
}
		

If you are planning to use several functions, it would probably be best to put them in the resources directory as either a big file or mini-scripts and then evaluate and invoke them on demand.

Please note that Groovy remains the official language of Griffon until otherwise noted. No expressed efforts are planned at this time to improve integration with other languages other than is already provided by JSR 223.