<?xml version="1.0"?>
<paste-with-annotations>
  <paste>
    <number>
      <integer>5914</integer>
    </number>
    <user>
      <string>richhickey</string>
    </user>
    <title>
      <string>Bloglines Demo</string>
    </title>
    <contents>
      <string>;homegrown Lisp &lt;-&gt; Java/CLI bridge library
(load &quot;/dev/foil/foil&quot;)
(use-package :foil) 
;generated wrappers for java libs
(load &quot;/foil/java-lang&quot;)
(load &quot;/foil/java-util&quot;)
(load &quot;/foil/swt&quot;)
(load &quot;/foil/commons-httpclient&quot;)
;Miles Egan's XML parser for Lisp - thanks Miles!
(load &quot;/dev/xmls-1.2/xmls&quot;)

(require &quot;comm&quot;) ;Lispworks socket stuff

(defpackage :bloglines-demo
 (:use :cl :foil &quot;org.eclipse.swt&quot; &quot;org.eclipse.swt.widgets&quot; &quot;org.eclipse.swt.events&quot;
  &quot;org.eclipse.swt.layout&quot; &quot;org.eclipse.swt.custom&quot; &quot;org.eclipse.swt.browser&quot;
  &quot;org.apache.commons.httpclient&quot; &quot;org.apache.commons.httpclient.methods&quot; :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 &quot;localhost&quot; 13578))
(defvar *non-ui-stream* (comm:open-tcp-stream &quot;localhost&quot; 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 &quot;com.richhickey.foil.SWTHelper&quot;)

(defun run-ui (fn)
  &quot;Sets up a separate thread to run the GUI, and binds to a ui-dedicated Foil socket&quot;
  (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 &quot;ui-proc&quot; '() 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 &quot;javax.swing.JOptionPane&quot;)
(use-package &quot;javax.swing&quot;)

(defparameter +SERVER+ &quot;rpc.bloglines.com&quot;)

(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 &quot;http://~A/~A&quot; +SERVER+ method))

(defun add-nodes (nodes parent node-map)
  (dolist (node nodes)
    (let* ((is-folder (not (find-attr node &quot;xmlUrl&quot;)))
           (title (find-attr node &quot;title&quot;))
           (tree-item
            (treeitem.new parent *SWT.NONE*
                          :text (if is-folder
                                    title
                                  (format nil &quot;~A (~A)&quot; title (find-attr node &quot;BloglinesUnread&quot;))))))
      (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 &quot;getitems?s=~A&amp;n=0&quot;
                                                   (find-attr node &quot;BloglinesSubId&quot;)))))
        (descriptions nil))
    (when rss-text
      (let* ((rss (parse rss-text))
             (items (remove &quot;item&quot; (node-children (find-child rss &quot;channel&quot;))
                            :key #'first :test-not #'equal)))
        (list.removeall list)
        (dolist (item items)
          (list.add list (third (find-child item &quot;title&quot;)))
          (push (third (find-child item &quot;description&quot;)) 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 &quot;Email address:&quot; &quot;Log in to Bloglines&quot;
			      *joptionpane.question_message*))
         (password (joptionpane.showinputdialog nil &quot;Password:&quot; &quot;Log in to Bloglines&quot;
			      *joptionpane.question_message*))
         (client (httpclient.new))
         (credentials (usernamepasswordcredentials.new email password))
         (node-map (make-hash-table)))
    (httpstate.setcredentials (httpclient.state client) &quot;Bloglines RPC&quot; +SERVER+ credentials)
    (let* ((*display* (display.getdefault))
           (shell (new shell. (*display* :text &quot;SWT Bloglines Client&quot; :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 &quot;listsubs&quot;))))
           (subs (node-children (find-child opml &quot;body&quot;)))
           (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 &quot;xmlUrl&quot;)
                             (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)

</string>
    </contents>
    <universal-time>
      <integer>3317774372</integer>
    </universal-time>
    <channel>
      <string>None</string>
    </channel>
    <colorization-mode>
      <string>Common Lisp</string>
    </colorization-mode>
    <maybe-spam>
      <null/>
    </maybe-spam>
    <is-unicode>
      <null/>
    </is-unicode>
  </paste>
</paste-with-annotations>