blob: 017ef0af25e46ef7c4263789d69e40f4a3c22526 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* BloglinesClient.groovy - an example of the Bloglines Web Services
*
* Written by Marc Hedlund <marc@precipice.org>, September 2004.
*
* Mangled by John Wilson September 2004
*
* Small adaptions to JSR Version by Dierk Koenig, June 2005
*
* Used in Marc's article at:
* http://www.oreillynet.com/pub/a/network/2004/09/28/bloglines.html
*
* Requirements:
* - install Groovy as detailed at <http://groovy.codehaus.org/>.
* - put commons-httpclient-3.0-rc3.jar into GROOVY_HOME/lib
* see <http://jakarta.apache.org/commons/httpclient/>.
* note: this is currently designed for HttpClient2.x and not HttpClient3.x
*
* To Launch:
* groovy BloglinesClient.groovy
*
* This work is licensed under the Creative Commons Attribution
* License. To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/2.0/> or send a letter to
* Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
*/
package swing
import groovy.swing.SwingBuilder
import java.awt.BorderLayout
import javax.swing.JOptionPane
import javax.swing.JSplitPane
import javax.swing.JTree
import javax.swing.ListSelectionModel
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'
class Feed {
def name;
def id;
def unread;
String toString() { (unread == "0" ? name : "${name} (${unread})") }
}
class Item {
def title;
def description;
String toString() { 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.state.setCredentials("Bloglines RPC", server, credentials)
abstractCallBloglines = { method, parameters ->
url = "http://${server}/${method}${parameters}"
try {
get = new GetMethod(url)
get.doAuthentication = true
client.executeMethod(get)
return get.responseBodyAsStream
} catch (Exception e) {
println "Error retrieving <${url}>: ${e}"
}
}
callBloglinesListsub = abstractCallBloglines.curry('listsubs', '')
callBloglinesGetItems = abstractCallBloglines.curry('getitems')
//Get the list of subscriptions and parse it into a GPath structure
opml = new XmlSlurper().parse(callBloglinesListsub())
//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.selectionModel
model.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
itemList.selectionMode = ListSelectionModel.SINGLE_SELECTION
//Set up the action closures that will react to user selections
listItems = { feed ->
rssStream = callBloglinesGetItems("?s=${feed.id}&n=0")
if (rssStream != null) {
try {
rss = new XmlSlurper().parse(rssStream)
itemList.listData = rss.channel.item.collect(new Vector()) {
new Item(title: it.title, description: it.description)
}
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.text = "" // clear any old item text
node = (DefaultMutableTreeNode) feedTree.getLastSelectedPathComponent()
if (node != null) {
feed = node.userObject
if (feed instanceof Feed && feed.unread != "0") {
listItems(feed)
}
}
}
itemList.valueChanged = { event ->
item = event.source.selectedValue
if (item instanceof Item && item?.description != null) {
itemText.text = "<html><body>${item.description}</body></html>"
}
}
//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()