;;; marketdata-komodo.el -- Query MarketData Komodo Repository
;;
;;; Copyright (C) 2008 Dow Jones
;;
;; Author:   Kyle W T Sherman <kylewsherman at gmail dot com>
;; Created:  2008-01-23
;; Version:  1.0
;; Keywords: marketdata komodo
;;
;;; Commentary:
;;
;; Provides `marketdata-komodo-query' function to query the Komodo Repository
;; Test web site with the data found in the current XML buffer and display the
;; results in a new buffer.  Legal servers are listed in
;; `marketdata-komodo-servers' and the current one being used is stored in
;; `marketdata-komodo-server'.
;;
;; You must be using Emacs 22 or later for this module to work.  (It uses
;; timing code not found in earlier versions.)
;;
;;; Installation:
;;
;; Put `marketdata-komodo.el' where you keep your elisp files and add
;; something like the following to your .emacs file:
;;
;;   (require 'marketdata-komodo)
;;
;; Also, make sure the `wget' and `date' commands are in your path.
;;
;; Can be customized with the following command:
;;
;;   (customize-group "marketdata-komodo")
;;
;;; Usage:
;;
;; Visit one of the XML files found here:
;; `~/svn/MarketData/dj/cylon/trunk/admin-tools/gateway-monitor/post' (for
;; example, the `check-marketdatagateway-post' file).  Then run
;; `marketdata-gateway-query' and either type in a server name (resolvable IP
;; address), use "all" for all production servers, or press enter for a list
;; you can pick from.  The web request will be posted to the server and the
;; response displayed.
;;
;; You may also call `marketdata-komodo-query' directly via
;; \\[eval-expression] with the server name as a parameter.
;;
;; Example using `localhost':
;;
;;   (marketdata-komodo-query "localhost")

;;; Code:

;; cl
(require 'cl)

;; easymenu
(require 'easymenu)

;; widget
(require 'widget)

;; server list
(defcustom marketdata-komodo-servers
  '(("sbkmdrdtp07" . "sbkmdrdtp07.mdprod.dowjones.net")
    ("sbkmdrdtp08" . "sbkmdrdtp08.mdprod.dowjones.net"))
  "List of MarketData Komodo servers that may be queried.
\nEach element is a tuple containing a name/server pair."
  :type 'list
  :group 'marketdata-komodo)

;; source list
(defcustom marketdata-komodo-sources
  '((0 . "ctf")
    (1 . "utp")
    (2 . "rdf")
    (3 . "cta_r")
    (4 . "mfds")
    (501 . "idpr")
    (503 . "rfh")
    (505 . "NewStox"))
  "List of MarketData symbol sources.
\nEach element is a tuple containing a source-id/source-name pair."
  :type 'list
  :group 'MarketData)

;; komodo info
(defcustom marketdata-komodo-info
  '(("http://service.marketwatch.com/ws/2006/08/dataDictionaryGateway"
     . ((:name . "DataDictionary")
        (:port . 8080)
        (:page . "WebServiceProject/testpage.jsp")))
    ("http://service.marketwatch.com/ws/2006/07/marketDataGateway"
     . ((:name . "MarketData")
        (:port . 8080)
        (:page . "WebServiceProject/testpage.jsp")))
    ("http://service.dowjones.com/ws/2006/11/symbologyGateway"
     . ((:name . "Symbology")
        (:port . 8080)
        (:page . "WebServiceProject/testpage.jsp")))
    ("http://service.dowjones.com/ws/2007/09/quoteGateway"
     . ((:name . "Quote")
        (:port . 8080)
        (:page . "WebServiceProject/testpage.jsp")))
    ("http://service.dowjones.com/ws/2007/10/timeseriesGateway"
     . ((:name . "TimeSeries")
        (:port . 8080)
        (:page . "WebServiceProject/testpage.jsp")
        (:fields . ("ticker" . ))))
    )
  "List of MarketData Gateway server query patterns and info about them.
\nEach element is a tuple containing a query-pattern/info pair.
The info is an association list of named data items as follows:
\n  :name   - gateway name
  :port   - web page port
  :page   - web page postfix
  :fields - list of field mappings (web to regexp in form)"
  :type 'list
  :group 'marketdata-komodo)

;; request timeout
(defcustom marketdata-komodo-request-timeout
  30
  "*Number of seconds to wait for a query response."
  :type 'integer
  :group 'marketdata-komodo)

;; show output
(defcustom marketdata-komodo-show-output
  t
  "*Whether to show every response in a buffer or not (t or nil respectively)."
  :type 'boolean
  :group 'marketdata-komodo)

;; format output
(defcustom marketdata-komodo-format-output
  t
  "*Whether to reformat the output xml or not (t or nil respectively)."
  :type 'boolean
  :group 'marketdata-komodo)

;; initial server history
(defcustom marketdata-komodo-initial-query-history
  '("sbkmdrdtp07 sbkmdrdtp08")
  "*Initial list of server lists to query."
  :type 'list
  :group 'marketdata-komodo)

;; server history
(defvar marketdata-komodo-query-history
  ;; `(,@marketdata-komodo-initial-query-history
  ;;   ,(do ((servers marketdata-komodo-servers (cdr servers))
  ;;         (result ""))
  ;;        ((not servers) result)
  ;;      (if (cdr servers)
  ;;          (setq result (concat result (caar servers) " "))
  ;;        (setq result (concat result (caar servers))))))
  nil
  "History of `marketdata-komodo-query' query arguments.
\nInitialized with `marketdata-komodo-query-history' function.")

;; reset server history
(defun marketdata-komodo-query-history-reset ()
  "Reset `marketdata-komodo-query-history' to initial values."
  (setq marketdata-komodo-query-history
        (copy-list marketdata-komodo-initial-query-history)))
  ;; (setq marketdata-komodo-query-history
  ;;       (append marketdata-komodo-initial-query-history
  ;;               (do ((servers marketdata-komodo-servers (cdr servers))
  ;;                    (result ""))
  ;;                   ((not servers) (list result))
  ;;                 (if (cdr servers)
  ;;                     (setq result (concat result (caar servers) " "))
  ;;                   (setq result (concat result (caar servers))))))))

;; push to server history
(defun marketdata-komodo-query-history-push (servers)
  "Push SERVERS onto `marketdata-komodo-query-history' list
after removing any existing entries."
  ;; convert string server to list, if needed
  (when (stringp servers)
    (setq servers (split-string servers)))
  ;; continue if there are servers
  (when servers
    ;; flatten list to string
    (setq servers (join-strings-delimiter servers " "))
    ;; remove existing entry if needed
    (when (member servers marketdata-komodo-query-history)
      (setq marketdata-komodo-query-history (remove servers marketdata-komodo-query-history)))
    ;; add to history
    (push servers marketdata-komodo-query-history)))

;; initialize history
(marketdata-komodo-query-history-reset)

;; select server buffer name
(defvar marketdata-komodo-select-server-buffer-name
  "*MarketData-Komodo-Select-Server*"
  "Buffer name to use for select server menu.")

;; process status buffer name
(defvar marketdata-komodo-process-status-buffer-name
  "*MarketData-Komodo-Process-Status*"
  "Buffer name to use for showing process status.")

;; temp directory to store request files
(defvar marketdata-komodo-tmp-dir-request
  (expand-file-name "~/tmp/marketdata-komodo-request")
  "Temp directory to store request post files.")

;; temp directory to store response files
(defvar marketdata-komodo-tmp-dir-response
  (expand-file-name "~/tmp/marketdata-komodo-response")
  "Temp directory to store komodo responses.")

;; output buffer name
(defun marketdata-komodo-output-buffer-name (server)
  "Generate output buffer name."
  ;;(concat "*" server ":" (buffer-name) "*")
  (concat server ":" (buffer-name)))

;; output file name
(defun marketdata-komodo-output-file-name (buffer)
  "Generate an output file name from a BUFFER name."
  (concat marketdata-komodo-tmp-dir-response "/" buffer))

;; komodo info
(defun marketdata-komodo-info (&optional item)
  "Return requested komodo info for XML request based on the
SOAP header in the current buffer."
  (save-excursion
    (goto-char (point-min))
    (let (info)
      ;; loop through all komodos checking buffer for a match
      (dolist (komodo marketdata-komodo-info)
        (unless info
          (when (re-search-forward (car komodo) nil t)
            (setq info (cdr komodo)))))
      ;; either return all info or requested item
      (if item
          (cdr (assq item info))
        info))))

;; date offset
(defun marketdata-komodo-date-offset (&optional offset)
  "Return current UTC date plus OFFSET seconds."
  (unless offset
    (setq offset 0))
  (substring
   (shell-command-to-string
    (concat
     "TZ=UTC date -d \"" (shell-quote-argument (number-to-string offset))
     " sec\" \"+%Y-%m-%dT%H:%M:%SZ\""
     )) 0 -1))

;; http://10.243.1.13:8080/WebServiceProject/testpage.jsp?ticker=&instrId=457050&source=ctf&startdate=&enddate=&method=snap&B1=Submit
;; http://10.243.1.12:8080/WebServiceProject/testpage.jsp?ticker=IBM&instrId=&source=ctf&startdate=06%2F12%2F2008+14%3A00&enddate=06%2F12%2F2008+16%3A00&method=minutebars&B1=Submit

(defun marketdata-komodo-web-service-args ()
  "Return arguments for Web Service Tester from current buffer XML.
\nNote: This only returns arguments for the first request found
in the buffer XML."
  (save-excursion
    (goto-char (point-min))
    (let (port)
      ;; loop through all komodos checking buffer for a match
      (dolist (komodo marketdata-komodo-ports port)
        (when (re-search-forward (car komodo) nil t)
          (setq port (cdr komodo))))
      )))


  )

;; prepare data
(defun marketdata-komodo-prepare-data (server port)
  "Prepare post data in current buffer for request."
  (let ((created (marketdata-komodo-date-offset 0))
        (expires (marketdata-komodo-date-offset 5)))
    (save-excursion
      ;; replace double quotes with single quotes in data
      ;; (goto-char (point-min))
      ;; (while (re-search-forward "\"" nil t)
      ;;   (replace-match "'"))
      ;; replace wsa:To fields with server:port
      (goto-char (point-min))
      (while (re-search-forward "<wsa:To>.*</wsa:To>" nil t)
        (replace-match (concat "<wsa:To>http://" server ":" port "</wsa:To>")))
      ;; replace wsu:Created fields with current timestamp
      (goto-char (point-min))
      (while (re-search-forward "#{created}" nil t)
        (replace-match created))
      ;; replace wsu:Expires fields with current timestamp
      (goto-char (point-min))
      (while (re-search-forward "#{expires}" nil t)
        (replace-match expires))
      )))

;; marketdata komodo select server
(defun marketdata-komodo-select-server ()
  "Create a menu of servers the user may select from."
  (interactive)
  ;; list to string function
  (defun list-to-string (lst)
    "Return space delimited version of items in LST."
    (do ((x lst (cdr x))
         result)
        ((not x) (apply 'concat (reverse result)))
      (if (cdr x)
          (push (concat (car x) " ") result)
        (push (car x) result))))
  (let (buffer                          ; menu buffer
        query-history                   ; query history
        all-servers                     ; all servers ever queried
        last-history                    ; last history item
        last-history-servers)           ; last history server list
    ;; setup buffer
    (setq buffer (generate-new-buffer marketdata-komodo-select-server-buffer-name))
    (set-buffer buffer)
    (kill-all-local-variables)
    ;; use local variable to store server list
    (make-local-variable 'widget-servers)
    ;; create unique history list and all servers list
    (dolist (servers marketdata-komodo-query-history)
      (pushnew servers query-history :test 'string=)
      (dolist (server (split-string servers))
        (pushnew server all-servers :test 'string=)))
    (setq query-history (nreverse query-history))
    (setq all-servers (sort all-servers 'string-lessp))
    ;; last history
    (setq last-history (car marketdata-komodo-query-history))
    (setq last-history-servers (split-string (car marketdata-komodo-query-history)))
    ;; use local variables to store checkbox server widgets
    (make-local-variable 'widget-checkboxes)
    (setq widget-checkboxes nil)
    (dolist (server all-servers)
      (let ((name (intern (concat "widget-checkbox-" server))))
        (make-local-variable name)
        (push name widget-checkboxes)))
    ;; add header
    (widget-insert "MarketData Komodo Query\n\n")
    ;; add recent history options
    (widget-insert "Recent Queries:\n\n")
    (do ((servers query-history (cdr servers))
         (cnt 1 (1+ cnt)))
        ((or (not servers) (> cnt 10)))
      (widget-insert "  ")
      (widget-create 'push-button
                     :value (car servers)
                     :notify (lambda (widget &rest ignore)
                               (let ((value (widget-value widget)))
                                 (kill-buffer nil)
                                 (marketdata-komodo-query value))))
      (widget-insert "\n"))
    (widget-insert "\n")
    ;; server selection
    (widget-insert "Select Servers:\n\n")
    ;; add all/none buttons
    (widget-insert "  ")
    (widget-create 'push-button
                   :notify `(lambda (&rest ignore)
                              ;; set current servers to all
                              (widget-value-set widget-servers ,(list-to-string all-servers))
                              ;; check all server checkboxes
                              (dolist (widget (list ,@widget-checkboxes))
                                (widget-value-set widget t))
                              (widget-setup))
                   "All")
    (widget-insert "  ")
    (widget-create 'push-button
                   :notify `(lambda (&rest ignore)
                              ;; set current servers to none
                              (widget-value-set widget-servers "")
                              ;; uncheck all server checkboxes
                              (dolist (widget (list ,@widget-checkboxes))
                                (widget-value-set widget nil))
                              (widget-setup))
                   "None")
    (widget-insert "\n\n")
    ;; add checkbox list
    (dolist (server all-servers)
      (widget-insert "  ")
      (let ((widget
             (widget-create
              'checkbox
              :notify (lambda (widget &rest ignore)
                        (let ((value (widget-value widget))
                              (server (widget-get widget :server))
                              (servers (split-string (widget-value widget-servers))))
                          (if value
                              (unless (member server servers)
                                (widget-value-set widget-servers (list-to-string (sort (cons server servers) 'string-lessp))))
                            (widget-value-set widget-servers (list-to-string (remove server servers))))
                          (widget-setup)))
              (member server last-history-servers))))
        (widget-put widget :server server)
        (widget-insert (concat " " server "\n"))
        (funcall `(lambda () (setq ,(intern (concat "widget-checkbox-" server)) widget)))))
    (widget-insert "\n")
    ;; free form entry
    (widget-insert "Servers: ")
    (setq widget-servers
          (widget-create 'editable-field
                         last-history))
    (widget-insert "\n")
    ;; add timeout
    (widget-insert "Timeout: ")
    (widget-create 'editable-field
                   :notify (lambda (widget &rest ignore)
                             (let ((timeout (widget-value widget)))
                               (setq marketdata-komodo-request-timeout (string-to-number timeout))))
                   (number-to-string marketdata-komodo-request-timeout))
    (widget-insert "\n")
    ;; add submit button
    (widget-create 'push-button
                   :notify (lambda (&rest ignore)
                             (let ((servers (widget-value widget-servers)))
                               (kill-buffer nil)
                               (marketdata-komodo-query servers)))
                   "Submit Query")
    (widget-insert "  ")
    (widget-create 'push-button
                   :notify (lambda (&rest ignore)
                             (kill-buffer nil)
                             (customize-group "marketdata-komodo"))
                   "Configure")
    (widget-insert "\n")
    ;; final setup
    (use-local-map widget-keymap)
    (widget-setup)
    (switch-to-buffer buffer)
    (goto-char (point-min))
    (widget-forward 1)
    ))

;; delete temp directory
(defun marketdata-komodo-delete-directory (dir)
  "Delete all files in DIR and DIR itself."
  (when (file-exists-p dir)
    (dolist (file (directory-files dir t))
      (unless (string-match "^\\." (file-name-nondirectory file))
        (delete-file file)))))

;; create and clean temp directory
(defun marketdata-komodo-create-directory (dir)
  "Delete the files in DIR, if it exists, else create it."
  ;; delete dir
  (when (file-exists-p dir)
    (marketdata-komodo-delete-directory dir))
  ;; create dir
  (unless (file-exists-p dir)
    (make-directory dir)))

;; test and rename gzip file
(defun marketdata-komodo-rename-gzip-file (file)
  "Test if FILE is a GZIP file, and if it is, rename it with a
`.gz' extension.
\nReturn the new file name."
  (let ((new-file file))
    (when (string-match
           "gzip compressed data"
           (shell-command-to-string (concat "file \"" file "\"")))
      (setq new-file (concat file ".gz"))
      (rename-file file new-file))
    new-file))

;; join strings delimiter
(defun join-strings-delimiter (lst &optional delim)
  "Convert LST of strings into a single string.
\nUse optional DELIM as a delimiter."
  (unless delim
    (setq delim ""))
  (reduce #'(lambda (x y) (concat x delim y)) lst))

;; komodo query
(defun marketdata-komodo-query (&optional server output)
  "Submit current buffer XML as a SOAP request to a MarketData
 Komodo SERVER on the appropriate port.

Requests are created in the `marketdata-komodo-tmp-dir-request'
directory.  Responses are written to files in the
`marketdata-komodo-tmp-dir-response' directory.

If SERVER is nil, the user is prompted for one or a list of them,
with history available and seeded with
`marketdata-komodo-servers'.

If OUTPUT is nil, a buffer is opened for every response file,
otherwise, a `dired' buffer opened on the
`marketdata-komodo-tmp-dir-response' directory."
  (interactive
   (let ((hist (copy-list marketdata-komodo-query-history)))
     (list (read-from-minibuffer "Server(s) to query: "
                                 "" nil nil
                                 'hist))))
  ;; check for required emacs version
  (if (>= emacs-major-version 22)
      ;; check for server parameter
      (if (and server (not (equal server "")) (not (equal server '(""))))
          (let ((servers server)
                (hist marketdata-komodo-query-history))
            ;; if servers is `all' then substitute full list
            (when (equal servers "all")
              (setq servers (mapcar #'car marketdata-komodo-servers)))
            ;; convert string server to list
            (when (stringp servers)
              (setq servers (split-string servers)))
            ;; update server history
            (when (listp servers)
                  (setq server (join-strings-delimiter servers " "))
                  (when (member server hist)
                    (setq hist (remove server hist)))
                  (push server hist)
                  (setq marketdata-komodo-query-history hist))
            ;; main process block
            (let ((xml-buffer (window-buffer)) ; xml buffer
                  status-buffer                ; status buffer
                  processes             ; list of asynchronous processes
                  times                 ; timing information
                  dired-buffer          ; response dired buffer
                  desc                  ; komodo description
                  port)                 ; komodo port
              ;; wrap main block in error trap to make sure cleanup occurs
              (condition-case err
                  (progn
                    ;; create request dir
                    (marketdata-komodo-create-directory marketdata-komodo-tmp-dir-request)
                    ;; create response dir
                    (marketdata-komodo-create-directory marketdata-komodo-tmp-dir-response)
                    ;; open dired buffer
                    (dired marketdata-komodo-tmp-dir-response)
                    (setq dired-buffer (current-buffer))
                    ;; loop through servers
                    (dolist (server servers)
                      ;; switch to xml buffer
                      (set-buffer xml-buffer)
                      (message "Querying server: %s" server)
                      (let ((data (buffer-substring-no-properties (point-min) (point-max)))
                            (name (concat "marketdata-komodo-process-" server))
                            (buffer (marketdata-komodo-output-buffer-name server))
                            (file (concat marketdata-komodo-tmp-dir-request "/emacs-marketdata-komodo-buffer-" server))
                            cmd
                            args)
                        ;; if server is in marketdata-komodo-servers resolve to full server name
                        (when (assoc server marketdata-komodo-servers)
                          (setq server (cdr (assoc server marketdata-komodo-servers))))
                        ;; create temporary buffer to modifiy data in
                        (with-temp-buffer
                          (insert data)
                          ;; get komodo description and port, if not already set
                          (unless (and desc port)
                            (let ((info (marketdata-komodo-info)))
                              (setq desc (cdr (assq :name info)))
                              (setq port (number-to-string (cdr (assq :port info))))))
                          ;; prepare data
                          (marketdata-komodo-prepare-data server port)
                          ;; save buffer to temporary file
                          (write-region nil nil file))
                        ;; build request command (output to files)
                        ;; note: wget -nv parameter must be given to prevent status characters
                        ;; from being inserted into the output
                        (setq args (concat
                                    "\"-nv\""
                                    " \"--tries=1\""
                                    " \"--timeout=" (shell-quote-argument (number-to-string marketdata-komodo-request-timeout)) "\""
                                    " \"--post-file=" (shell-quote-argument file) "\""
                                    " \"-O\" \"" (marketdata-komodo-output-file-name buffer) "\""
                                    " \"http://" (shell-quote-argument server)
                                    ":" (shell-quote-argument port) "\""))
                        ;;(message "wget %s" args)
                        ;; create request call
                        ;;(setq cmd (concat "(call-process \"wget\" nil buffer nil " args ")"))
                        (setq cmd (concat "(start-process \"" name "\" nil \"wget\" " args ")"))
                        ;; submit request and track process/buffer/file
                        ;;(message cmd)
                        (push (list (eval (read cmd)) server buffer file (current-time)) processes)
                        ))
                    ;; reverse processes order
                    (setq processes (nreverse processes))
                    ;;(message "%S" processes)
                    ;; setup buffer
                    (setq status-buffer (generate-new-buffer marketdata-komodo-process-status-buffer-name))
                    (switch-to-buffer status-buffer)
                    ;; turn off undo
                    (buffer-disable-undo status-buffer)
                    ;; initialize times
                    (dolist (lst processes)
                      (push (cons (process-name (car lst)) (list 0 0 0)) times))
                    ;; loop until all processes have finished
                    (let ((running t))
                      (while running
                        (setq running nil)
                        (setq buffer-read-only nil)
                        (erase-buffer)
                        ;; output status
                        (insert (concat "MarketData Komodo Query Process Status\n\n"))
                        (insert (concat "Komodo: " desc "  Port: " port "\n\n"))
                        (insert "Server                                  Status  Time\n")
                        (insert "--------------------------------------  ------  ---------------\n")
                        ;; loop through processes
                        (dolist (lst processes)
                          (let* ((process (first lst))
                                 (name (process-name process))
                                 (server (second lst))
                                 (status (process-status process))
                                 (time (fifth lst))
                                 (time-diff (cdr (assoc name times))))
                            ;; if processes is running
                            (when (eq status 'run)
                              ;; calculate time difference
                              (setq time-diff (time-subtract (current-time) time))
                              ;; set time difference
                              (setcdr (assoc name times) time-diff)
                              ;; set to not done
                              (setq running t))
                            ;; output process status
                            (when (> (length server) 38)
                              (setq server (substring server 0 38)))
                            (let* ((microsecs (third time-diff))
                                   (total-seconds (+ (* (first time-diff) 65536) (second time-diff)))
                                   (hours (floor (/ total-seconds 3600)))
                                   (mins (floor (/ (- total-seconds (* hours 3600)) 60)))
                                   (secs (- total-seconds (* hours 3600) (* mins 60))))
                              ;; format time-diff
                              (setq time-diff (concat
                                               (if (> hours 0)
                                                   (format "%2d:%02d:%02d.%d" hours mins secs microsecs)
                                                 (if (> mins 0)
                                                     (format "   %2d:%02d.%d" mins secs microsecs)
                                                   (format "      %2d.%d" secs microsecs)))))
                              (insert (format "%-39s %-7S %s\n" server status time-diff)))))
                        (setq buffer-read-only t)
                        ;; draw screen and wait 0.1 second
                        (sit-for 0.1)
                        ))
                    ;; format responses
                    (setq buffer-read-only nil)
                    (insert "\nFormatting responses...")
                    (setq buffer-read-only t)
                    ;; draw screen
                    (sit-for 0)
                    ;; delete temp request files and directory
                    (marketdata-komodo-delete-directory marketdata-komodo-tmp-dir-request)
                    ;; loop through all processes (in reverse order)
                    (dolist (lst (reverse processes))
                      (let* ((process (first lst))
                             (server (second lst))
                             (buffer (third lst))
                             file)
                        ;; cleanup results
                        ;; get response file name and add .gz to it if it is a gzip response
                        (setq file (marketdata-komodo-rename-gzip-file (marketdata-komodo-output-file-name buffer)))
                        ;; load response file
                        (find-file file)
                        ;; reformat if buffer is not too large
                        ;; use `font-lock-maximum-size' to determine what "too large" is
                        (when (<= (buffer-size) font-lock-maximum-size)
                          (marketdata-komodo-xml-reformat))
                        ;; save buffer
                        (save-buffer)
                        ;; close buffer if output is set
                        (when output
                          (kill-this-buffer))
                        ;; update status
                        (switch-to-buffer status-buffer)
                        (setq buffer-read-only nil)
                        (insert ".")
                        (setq buffer-read-only t)
                        ;; draw screen
                        (sit-for 0)
                        ))
                    ;; kill dired buffer
                    (kill-buffer dired-buffer)
                    ;; switch to status buffer
                    (switch-to-buffer status-buffer)
                    (setq buffer-read-only nil)
                    (insert "done\n")
                    (setq buffer-read-only t)
                    (delete-other-windows)
                    ;; open dired buffer
                    (when output
                      (dired marketdata-komodo-tmp-dir-response)
                      (switch-to-buffer status-buffer))
                    ;; kill status buffer
                    ;;(kill-buffer status-buffer)
                    )
                ;; cleanup on trapped error
                (error
                 ;; delete temp request files and dir
                 (marketdata-komodo-delete-directory marketdata-komodo-tmp-dir-request)
                 ;; open dired buffer
                 (dired marketdata-komodo-tmp-dir-response)
                 (switch-to-buffer status-buffer)
                 ;; pass thru normal error message
                 (message "%s" (error-message-string err))))))
        ;; query user for server
        (marketdata-komodo-select-server))
    ;; inform user of emacs version limitation
    (error "This function requires Emacs version 22 or higher")))

;; xml reformat
(defun marketdata-komodo-xml-reformat ()
  "Reformat XML buffer.
\nConvert poorly formatted XML into something better."
  (xml-mode)
  (let ((xml-eol "[ \t]*\n[ \t]*")
        (xml-tag-end-regexp ">")
        (xml-close-tag-regexp "</")
        (xml-block-regexp "<[^>]*>[^<]*</[^>]*>"))
    (save-excursion
      ;; preserve search data
      (save-match-data
        ;; remove all existing EOL characters
        (goto-char (point-min))
        (while (re-search-forward xml-eol nil t)
          (replace-match "" nil nil))
        ;; move down code adding newlines were needed
        (goto-char (point-min))
        (while (re-search-forward xml-tag-end-regexp nil t)
          (insert "\n")
          (when (and
                 (looking-at xml-block-regexp)
                 (not (looking-at xml-close-tag-regexp)))
            (re-search-forward xml-block-regexp nil t)
            (forward-char -1)))
        ;; indent buffer
        (indent-region (point-min) (point-max))
        ))))

(provide 'marketdata-komodo)

;;; marketdata-komodo.el ends here