/* * BloglinesClient.groovy - an example of the Bloglines Web Services * * Written by Marc Hedlund , September 2004. * Requires Groovy 1.0-beta-6; see . * * This work is licensed under the Creative Commons Attribution * License. To view a copy of this license, visit * or send a letter to * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. */ import groovy.swing.SwingBuilder; import java.awt.BorderLayout; import java.net.URL; import javax.swing.BorderFactory; import javax.swing.JOptionPane; import javax.swing.JSplitPane; import javax.swing.JTree; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeSelectionModel; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.methods.GetMethod; // Set up global variables and data types server = 'rpc.bloglines.com'; apiUrl = { method | "http://${server}/${method}" }; class Feed { name; id; unread; String toString() { return (unread == "0" ? name : "${name} (${unread})"); } } class Item { title; contents; String toString() { return title; } } // Ask the user for account information (using simple dialogs) email = JOptionPane.showInputDialog(null, "Email address:", "Log in to Bloglines", JOptionPane.QUESTION_MESSAGE); password = JOptionPane.showInputDialog(null, "Password:", "Log in to Bloglines", JOptionPane.QUESTION_MESSAGE); // Use HTTPClient for web requests since the server requires authentication client = new HttpClient(); credentials = new UsernamePasswordCredentials(email, password); client.getState().setCredentials("Bloglines RPC", server, credentials); // Get the list of subscriptions and parse it into a GPath structure opml = new XmlParser().parseText(callBloglines(apiUrl('listsubs'))); def callBloglines(url) { try { get = new GetMethod(url); get.setDoAuthentication(true); client.executeMethod(get); return get.getResponseBodyAsString(); } catch (Exception e) { println "Error retrieving <${url}>: ${e}"; return ""; } } // Descend into the subscription outline, adding to the feed tree as we go treeTop = new DefaultMutableTreeNode("My Feeds"); parseOutline(opml.body.outline.outline, treeTop); def parseOutline(parsedXml, treeLevel) { parsedXml.each() { outline | if (outline['@xmlUrl'] != null) { // this is an individual feed feed = new Feed(name:outline['@title'], id:outline['@BloglinesSubId'], unread:outline['@BloglinesUnread']); treeLevel.add(new DefaultMutableTreeNode(feed)); } else { // this is a folder of feeds folder = new DefaultMutableTreeNode(outline['@title']); parseOutline(outline.outline, folder); treeLevel.add(folder); } } } // Build the base user interface objects and configure them swing = new SwingBuilder(); feedTree = new JTree(treeTop); itemList = swing.list(); itemText = swing.textPane(contentType:'text/html', editable:false); model = feedTree.getSelectionModel(); model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); itemList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // Set up the action closures that will react to user selections listItems = { feed | rssText = callBloglines(apiUrl('getitems') + "?s=${feed.id}&n=0"); if (rssText != null) { try { rss = new XmlParser().parseText(rssText); items = new Vector(); rss.channel.item.each() { item = new Item(title:it.title[0].text(), contents:it.description[0].text()); items.add(item); } itemList.setListData(items); feed.unread = "0"; // update the unread item count in the feed list } catch (Exception e) { println "Error during <${feed.name}> RSS parse: ${e}"; } } } feedTree.valueChanged = { event | itemText.setText(""); // clear any old item text node = (DefaultMutableTreeNode) feedTree.getLastSelectedPathComponent(); if (node != null) { feed = node.getUserObject(); if (feed instanceof Feed && feed.unread != "0") { listItems(feed); } } } itemList.valueChanged = { event | item = event.getSource().getSelectedValue(); if (item != null && item instanceof Item) { itemText.setText("${item.contents}"); } } // Put the user interface together and display it gui = swing.frame(title:'Bloglines Client', location:[100,100], size:[800,600], defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) { panel(layout:new BorderLayout()) { splitPane(orientation:JSplitPane.HORIZONTAL_SPLIT, dividerLocation:200) { scrollPane() { widget(feedTree); } splitPane(orientation:JSplitPane.VERTICAL_SPLIT, dividerLocation:150) { scrollPane(constraints:BorderLayout.CENTER) { widget(itemList); } scrollPane(constraints:BorderLayout.CENTER) { widget(itemText); } } } } } gui.show();