| Paste number 5914: | Bloglines Demo |
| Pasted by: | richhickey |
| When: | 4 years, 4 months ago |
| Share: | Tweet this! | http://paste.lisp.org/+4KA |
| Channel: | None |
| Paste contents: |
;homegrown Lisp <-> Java/CLI bridge library
(load "/dev/foil/foil")
(use-package :foil)
;generated wrappers for java libs
(load "/foil/java-lang")
(load "/foil/java-util")
(load "/foil/swt")
(load "/foil/commons-httpclient")
;Miles Egan's XML parser for Lisp - thanks Miles!
(load "/dev/xmls-1.2/xmls")
(require "comm") ;Lispworks socket stuff
(defpackage :bloglines-demo
(:use :cl :foil "org.eclipse.swt" "org.eclipse.swt.widgets" "org.eclipse.swt.events"
"org.eclipse.swt.layout" "org.eclipse.swt.custom" "org.eclipse.swt.browser"
"org.apache.commons.httpclient" "org.apache.commons.httpclient.methods" :xmls)
(:export :*display* :init-display :run-ui :swt-bloglines))
(in-package :bloglines-demo)
;;;;;;;;;;;;;;;;; SWT Boilerplate ;;;;;;;;;;;;;;;;;;;;;;;
;included here for completeness, will be the same for any Foil/SWT app
;presumes Foil java server running on these 2 ports, first will be for ui
(defvar *ui-stream* (comm:open-tcp-stream "localhost" 13578))
(defvar *non-ui-stream* (comm:open-tcp-stream "localhost" 13579))
(defvar *display*) ;hang onto the display for use in the listener thread
;create the Foil foreign VM
(setf *fvm* (make-instance 'foreign-vm :stream *non-ui-stream*))
;a helper class that comes with Foil that runs the SWT message pump
(def-foil-class "com.richhickey.foil.SWTHelper")
(defun run-ui (fn)
"Sets up a separate thread to run the GUI, and binds to a ui-dedicated Foil socket"
(let ((mp:*process-initial-bindings*
(append '((*display* . *display*)
(*standard-output* . *standard-output*)
(*fvm* . *fvm*)
(*thread-fvm-stream* . *ui-stream*)
(*thread-fvm* . *fvm*))
mp:*process-initial-bindings*)))
(mp:process-run-function "ui-proc" '() fn )))
;;;;;;;;;;;;;;;;; end SWT Boilerplate ;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;; Bloglines Demo ;;;;;;;;;;;;;;;
#|
inspired by Marc Hedlund's Article on using Bloglines web service from Groovy
http://www.oreillynet.com/pub/a/network/2004/09/28/bloglines.html
Here is a version in Lisp, using my Foil library
A typical use of Foil, it leverages Java libs to interface with the outside world (SWT for UI,
Jakarta Commons for HTTP client), and Lisp for the data model/logic
I've mimicked the Groovy code to some extent to allow for comparison by those who know only Lisp or Groovy
|#
;I don't have a wrapper for swing, but need to use its InputDialog (why doesn't SWT have this?)
;so create one on the fly
(def-foil-class "javax.swing.JOptionPane")
(use-package "javax.swing")
(defparameter +SERVER+ "rpc.bloglines.com")
(defun find-child (node name)
(find name (node-children node) :key #'node-name :test #'equal))
(defun find-attr (node attr)
(second (find attr (node-attrs node) :key #'first :test #'equal)))
(defun api-url (method)
(format nil "http://~A/~A" +SERVER+ method))
(defun add-nodes (nodes parent node-map)
(dolist (node nodes)
(let* ((is-folder (not (find-attr node "xmlUrl")))
(title (find-attr node "title"))
(tree-item
(treeitem.new parent *SWT.NONE*
:text (if is-folder
title
(format nil "~A (~A)" title (find-attr node "BloglinesUnread"))))))
(setf (gethash tree-item node-map) node)
(when is-folder
(add-nodes (node-children node) tree-item node-map)))))
(defun list-items (client node list)
(let ((rss-text (call-bloglines client
(api-url (format nil "getitems?s=~A&n=0"
(find-attr node "BloglinesSubId")))))
(descriptions nil))
(when rss-text
(let* ((rss (parse rss-text))
(items (remove "item" (node-children (find-child rss "channel"))
:key #'first :test-not #'equal)))
(list.removeall list)
(dolist (item items)
(list.add list (third (find-child item "title")))
(push (third (find-child item "description")) descriptions))
(nreverse descriptions)))))
(defun call-bloglines (client url)
(let ((get (new getmethod. (url) :doauthentication t)))
(httpclient.executemethod client get)
(httpmethod.getresponsebodyasstring get)))
(defun swt-bloglines ()
(let* ((email (joptionpane.showinputdialog nil "Email address:" "Log in to Bloglines"
*joptionpane.question_message*))
(password (joptionpane.showinputdialog nil "Password:" "Log in to Bloglines"
*joptionpane.question_message*))
(client (httpclient.new))
(credentials (usernamepasswordcredentials.new email password))
(node-map (make-hash-table)))
(httpstate.setcredentials (httpclient.state client) "Bloglines RPC" +SERVER+ credentials)
(let* ((*display* (display.getdefault))
(shell (new shell. (*display* :text "SWT Bloglines Client" :layout (gridlayout.new 1 t ))
(.setsize 800 600)
(.setlocation 100 100)))
(base-pane (sashform.new shell *SWT.HORIZONTAL*
:layoutdata (griddata.new *GRIDDATA.FILL_BOTH*)))
(feed-tree (tree.new base-pane (logior *SWT.SINGLE* *SWT.BORDER*)
:layoutdata (griddata.new *GRIDDATA.FILL_BOTH*)))
(item-pane (sashform.new base-pane *SWT.VERTICAL*
:layoutdata (griddata.new *GRIDDATA.FILL_BOTH*)))
(item-list (list.new item-pane (logior *SWT.SINGLE* *SWT.BORDER*)
:layoutdata (griddata.new *GRIDDATA.FILL_BOTH*)))
(item-text (browser.new item-pane *SWT.BORDER*))
(opml (parse (call-bloglines client (api-url "listsubs"))))
(subs (node-children (find-child opml "body")))
(descriptions nil))
(add-nodes subs feed-tree node-map)
(tree.addselectionlistener feed-tree
(new-proxy p +MARSHALL-ID+ 0
(selectionlistener.
(widgetselected (event)
(let* ((item (selectionevent.item event))
(node (gethash item node-map)))
;if it's a subscription, update list
(when (find-attr node "xmlUrl")
(setf descriptions (list-items client node item-list)))
nil)))))
(list.addselectionlistener item-list
(new-proxy p +MARSHALL-ID+ 0
(selectionlistener.
(widgetselected (event)
(declare (ignore event))
(browser.settext item-text (nth (list.selectionindex item-list) descriptions))
nil))))
(|com.richhickey.foil|::swthelper.rundispatchloop *display* shell))))
;yes, you can treat Lisp like a scripting language
(run-ui #'swt-bloglines)
This paste has no annotations.