2012-05-25 20:57:48 +02:00
|
|
|
|
;;; ein-query.el --- jQuery like interface on to of url-retrieve
|
|
|
|
|
|
|
|
|
|
;; Copyright (C) 2012- Takafumi Arakaki
|
|
|
|
|
|
2012-07-01 20:18:05 +02:00
|
|
|
|
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; ein-query.el is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; ein-query.el is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with ein-query.el. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(eval-when-compile (require 'cl))
|
2012-12-29 17:05:32 +01:00
|
|
|
|
(require 'request)
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
2012-08-28 15:26:32 +02:00
|
|
|
|
(require 'ein-core)
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(require 'ein-log)
|
|
|
|
|
|
2012-06-08 13:54:39 +02:00
|
|
|
|
|
|
|
|
|
;;; Utils
|
|
|
|
|
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(defun ein:safe-funcall-packed (packed &rest args)
|
|
|
|
|
(when packed
|
|
|
|
|
(ein:log-ignore-errors (apply #'ein:funcall-packed packed args))))
|
|
|
|
|
|
2012-06-08 13:54:39 +02:00
|
|
|
|
|
|
|
|
|
;;; Variables
|
2012-05-27 01:58:39 +02:00
|
|
|
|
|
2012-06-21 00:02:14 +02:00
|
|
|
|
(defcustom ein:query-timeout 1000
|
2012-06-21 00:38:15 +02:00
|
|
|
|
"Default query timeout for HTTP access in millisecond.
|
|
|
|
|
|
|
|
|
|
Setting this to `nil' means no timeout.
|
|
|
|
|
|
|
|
|
|
If you do the same operation before the timeout, old operation
|
|
|
|
|
will be canceled \(see also `ein:query-singleton-ajax').
|
|
|
|
|
|
|
|
|
|
.. note:: This value exists because it looks like `url-retrieve'
|
2012-06-21 15:03:41 +02:00
|
|
|
|
occasionally fails to finish \(start?) querying. Timeout is
|
|
|
|
|
used to let user notice that their operation is not finished.
|
|
|
|
|
It also prevent opening a lot of useless process buffers.
|
|
|
|
|
You will see them when closing Emacs if there is no timeout.
|
2012-06-21 00:38:15 +02:00
|
|
|
|
|
2012-06-21 15:03:41 +02:00
|
|
|
|
If you know how to fix the problem with `url-retrieve', please
|
|
|
|
|
let me know or send pull request at github!
|
2012-06-21 00:38:15 +02:00
|
|
|
|
\(Related bug report in Emacs bug tracker:
|
|
|
|
|
http://debbugs.gnu.org/cgi/bugreport.cgi?bug=11469)"
|
2012-05-27 01:58:39 +02:00
|
|
|
|
:type '(choice (integer :tag "Timeout [ms]" 5000)
|
|
|
|
|
(const :tag "No timeout" nil))
|
|
|
|
|
:group 'ein)
|
|
|
|
|
|
2012-12-17 19:26:13 +01:00
|
|
|
|
;; FIXME: Passing around timer object using buffer local variable is
|
|
|
|
|
;; not a good idea, as it looks like `url-retrieve' opens
|
|
|
|
|
;; different buffer when following redirections (probably
|
|
|
|
|
;; another bug...).
|
|
|
|
|
|
2012-08-18 22:46:45 +02:00
|
|
|
|
(ein:deflocal ein:%query-ajax-timer% nil)
|
2012-05-26 16:34:04 +02:00
|
|
|
|
|
2012-08-18 22:47:45 +02:00
|
|
|
|
(ein:deflocal ein:%query-ajax-canceled% nil
|
2012-06-20 20:29:06 +02:00
|
|
|
|
"Buffer local variable which is set to the reason for cancel (a symbol)
|
|
|
|
|
when it is cancelled.")
|
2012-06-12 18:13:08 +02:00
|
|
|
|
|
2012-06-08 13:54:39 +02:00
|
|
|
|
|
|
|
|
|
;;; Functions
|
|
|
|
|
|
2012-06-08 14:01:33 +02:00
|
|
|
|
(defun* ein:query-default-error-callback (url &key symbol-status
|
|
|
|
|
&allow-other-keys)
|
|
|
|
|
(ein:log 'error
|
|
|
|
|
"Error (%s) while connecting to %s. Please retry."
|
|
|
|
|
symbol-status url))
|
|
|
|
|
|
|
|
|
|
(defun ein:query-get-default-error-callback (url)
|
|
|
|
|
(cons #'ein:query-default-error-callback url))
|
|
|
|
|
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(defun* ein:query-ajax (url &rest settings
|
|
|
|
|
&key
|
|
|
|
|
(cache t)
|
|
|
|
|
(type "GET")
|
|
|
|
|
(data nil)
|
2012-05-26 20:23:22 +02:00
|
|
|
|
(parser nil)
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(headers nil)
|
|
|
|
|
(success nil)
|
|
|
|
|
(error nil)
|
|
|
|
|
(timeout nil)
|
|
|
|
|
(status-code nil))
|
|
|
|
|
"Mimic `$.ajax'.
|
|
|
|
|
|
|
|
|
|
:CACHE (nil/t) : append time-stamp to URL so the URL is always loaded.
|
|
|
|
|
:TYPE (string) : sets `url-request-method'
|
|
|
|
|
:DATA (string) : sets `url-request-data'
|
2012-05-26 20:23:22 +02:00
|
|
|
|
:PARSER (symbol) : a function that reads current buffer and return data
|
2012-05-25 20:57:48 +02:00
|
|
|
|
:HEADERS (alist) : sets `url-request-extra-headers'
|
|
|
|
|
:SUCCESS (cons) : called on success
|
|
|
|
|
:ERROR (cons) : called on error
|
|
|
|
|
:TIMEOUT (number) : timeout in millisecond
|
|
|
|
|
:STATUS-CODE (alist) : map status code (int) to callback (cons)
|
|
|
|
|
|
|
|
|
|
* Callback functions
|
|
|
|
|
|
|
|
|
|
All callbacks must be given as `cons' where car is a FUNCTION and
|
|
|
|
|
cdr is its first ARGUMENT. It is analogous of `$.proxy'. Call
|
|
|
|
|
signature is like this:
|
|
|
|
|
\(FUNCTION ARGUMENT [other callback specific arguments])
|
|
|
|
|
|
2012-05-26 20:00:17 +02:00
|
|
|
|
Also note that the callback FUNCTION must be defined
|
2012-05-25 20:57:48 +02:00
|
|
|
|
using `defun*' with `&key' and `&allow-other-keys' to ignore
|
|
|
|
|
missing/extra arguments as some callback (namely :ERROR) changes
|
|
|
|
|
arguments to be passed, depending on situation.
|
|
|
|
|
|
|
|
|
|
* :ERROR callback
|
|
|
|
|
|
|
|
|
|
:SYMBOL-STATUS (`error'/`timeout') : analogous of `textStatus'
|
|
|
|
|
:STATUS (list) : see `url-retrieve'
|
2012-05-26 20:00:17 +02:00
|
|
|
|
:RESPONSE-STATUS : = `url-http-response-status'
|
|
|
|
|
|
|
|
|
|
* :SUCCESS callback
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
2012-05-26 20:00:17 +02:00
|
|
|
|
This callback takes :DATA (object), which is a data object parsed
|
2012-05-26 20:23:22 +02:00
|
|
|
|
by :PARSER. If :PARSER is not specified, this is nil.
|
2012-05-26 20:00:17 +02:00
|
|
|
|
The :SUCCESS callback also takes the :STATUS and :RESPONSE-STATUS
|
|
|
|
|
argument.
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
2012-05-26 19:25:00 +02:00
|
|
|
|
* :STATUS-CODE callback
|
|
|
|
|
|
|
|
|
|
Each value of this alist is a callback which is similar to :ERROR
|
|
|
|
|
or :SUCCESS callback. However, current buffer of this callback
|
|
|
|
|
is not guaranteed to be the process buffer.
|
|
|
|
|
|
2012-05-26 20:23:22 +02:00
|
|
|
|
* :PARSER function
|
|
|
|
|
|
|
|
|
|
This is analogous to the `dataType' argument of `$.ajax'.
|
|
|
|
|
Only this function can accuses to the process buffer, which
|
|
|
|
|
is killed immediately after the execution of this function.
|
|
|
|
|
|
2012-05-25 20:57:48 +02:00
|
|
|
|
* See also: http://api.jquery.com/jQuery.ajax/"
|
2012-05-26 16:34:04 +02:00
|
|
|
|
(ein:log 'debug "EIN:QUERY-AJAX")
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(unless cache
|
|
|
|
|
(setq url (ein:url-no-cache url)))
|
2012-06-08 14:01:33 +02:00
|
|
|
|
(unless error
|
|
|
|
|
(setq error (ein:query-get-default-error-callback url))
|
2012-12-17 16:50:47 +01:00
|
|
|
|
(setq settings (plist-put settings :error error)))
|
|
|
|
|
(when (and (equal type "POST") data)
|
|
|
|
|
(push '("Content-Type" . "application/x-www-form-urlencoded") headers)
|
|
|
|
|
(setq settings (plist-put settings :headers headers)))
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(let* ((url-request-extra-headers headers)
|
|
|
|
|
(url-request-method type)
|
|
|
|
|
(url-request-data data)
|
|
|
|
|
(buffer (url-retrieve url #'ein:query-ajax-callback settings)))
|
2012-06-20 20:29:06 +02:00
|
|
|
|
(ein:log 'debug "Start querying: %s" url)
|
2012-05-27 01:58:39 +02:00
|
|
|
|
(unless timeout (setq timeout ein:query-timeout))
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(when timeout
|
2012-05-26 16:34:04 +02:00
|
|
|
|
(ein:log 'debug "Start timer: timeout=%s ms" timeout)
|
|
|
|
|
(with-current-buffer buffer
|
2012-08-18 22:46:45 +02:00
|
|
|
|
(setq ein:%query-ajax-timer%
|
2012-05-26 17:01:23 +02:00
|
|
|
|
(apply #'run-at-time
|
|
|
|
|
(/ timeout 1000.0) nil
|
|
|
|
|
#'ein:query-ajax-timeout-callback
|
|
|
|
|
(cons buffer settings)))))
|
2012-06-10 02:38:16 +02:00
|
|
|
|
(set-process-query-on-exit-flag (get-buffer-process buffer) nil)
|
2012-05-25 20:57:48 +02:00
|
|
|
|
buffer))
|
|
|
|
|
|
2012-09-18 22:21:05 +02:00
|
|
|
|
(defun ein:query-ajax--parse-data (parser status-error)
|
|
|
|
|
"Run PARSER in current buffer if STATUS-ERROR is nil,
|
|
|
|
|
then kill the current buffer."
|
2012-09-19 01:24:03 +02:00
|
|
|
|
(let ((buffer (current-buffer)) ; NOTE: `parser' could change buffer...
|
|
|
|
|
noerror)
|
2012-09-18 22:21:05 +02:00
|
|
|
|
(unwind-protect
|
2012-09-19 01:24:03 +02:00
|
|
|
|
(prog1
|
|
|
|
|
(when (and parser (not status-error))
|
|
|
|
|
(funcall parser))
|
|
|
|
|
(setq noerror t))
|
|
|
|
|
(unless noerror
|
|
|
|
|
(ein:log 'error "QUERY-AJAX--PARSE-DATA: error from parser %S"
|
|
|
|
|
parser))
|
2012-09-18 22:21:05 +02:00
|
|
|
|
(kill-buffer buffer))))
|
|
|
|
|
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(defun* ein:query-ajax-callback (status &key
|
|
|
|
|
(headers nil)
|
2012-05-26 20:23:22 +02:00
|
|
|
|
(parser nil)
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(success nil)
|
|
|
|
|
(error nil)
|
|
|
|
|
(timeout nil)
|
|
|
|
|
(status-code nil)
|
|
|
|
|
&allow-other-keys)
|
2012-12-04 11:04:46 +01:00
|
|
|
|
(declare (special url-http-method
|
|
|
|
|
url-http-response-status))
|
2012-05-26 20:00:17 +02:00
|
|
|
|
|
|
|
|
|
(ein:log 'debug "EIN:QUERY-AJAX-CALLBACK")
|
|
|
|
|
(ein:log 'debug "status = %S" status)
|
2012-12-04 11:04:46 +01:00
|
|
|
|
(ein:log 'debug "url-http-method = %s" url-http-method)
|
2012-05-26 20:00:17 +02:00
|
|
|
|
(ein:log 'debug "url-http-response-status = %s" url-http-response-status)
|
|
|
|
|
(ein:log 'debug "(buffer-string) =\n%s" (buffer-string))
|
|
|
|
|
|
|
|
|
|
(ein:query-ajax-cancel-timer)
|
2012-09-18 22:21:05 +02:00
|
|
|
|
(let* ((response-status url-http-response-status)
|
2012-05-26 20:00:17 +02:00
|
|
|
|
(status-code-callback (cdr (assq response-status status-code)))
|
|
|
|
|
(status-error (plist-get status :error))
|
2012-08-18 22:47:45 +02:00
|
|
|
|
(canceled ein:%query-ajax-canceled%)
|
2012-09-18 22:21:05 +02:00
|
|
|
|
(data (ein:query-ajax--parse-data parser status-error)))
|
2012-05-26 20:41:21 +02:00
|
|
|
|
(ein:log 'debug "data = %s" data)
|
2012-06-20 20:29:06 +02:00
|
|
|
|
(ein:log 'debug "canceled = %s" canceled)
|
2012-05-26 20:00:17 +02:00
|
|
|
|
|
|
|
|
|
(ein:log 'debug "Executing success/error callback.")
|
|
|
|
|
(apply #'ein:safe-funcall-packed
|
2012-06-20 20:29:06 +02:00
|
|
|
|
(append (if (or (plist-get status :error) canceled)
|
2012-06-12 18:13:08 +02:00
|
|
|
|
(list error :symbol-status
|
2012-06-20 20:29:06 +02:00
|
|
|
|
(or canceled 'error))
|
2012-05-26 22:55:23 +02:00
|
|
|
|
(list success))
|
|
|
|
|
(list :status status :data data
|
|
|
|
|
:response-status response-status)))
|
2012-05-26 20:00:17 +02:00
|
|
|
|
|
2012-06-20 20:29:06 +02:00
|
|
|
|
(unless canceled
|
2012-06-12 18:13:08 +02:00
|
|
|
|
(ein:log 'debug "Executing status-code callback.")
|
|
|
|
|
(ein:safe-funcall-packed status-code-callback
|
|
|
|
|
:status status :data data))))
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
|
|
|
|
(defun* ein:query-ajax-timeout-callback (buffer &key
|
2012-12-17 19:26:13 +01:00
|
|
|
|
error parser
|
2012-05-25 20:57:48 +02:00
|
|
|
|
&allow-other-keys)
|
2012-06-21 01:05:54 +02:00
|
|
|
|
(ein:log 'debug "EIN:QUERY-AJAX-TIMEOUT-CALLBACK buffer = %S" buffer)
|
2012-05-26 19:08:15 +02:00
|
|
|
|
(ein:with-live-buffer buffer
|
2012-08-18 22:47:45 +02:00
|
|
|
|
(setq ein:%query-ajax-canceled% 'timeout)
|
2012-06-08 00:37:48 +02:00
|
|
|
|
(let ((proc (get-buffer-process buffer)))
|
2012-12-17 19:26:13 +01:00
|
|
|
|
(ein:log 'debug "EIN:QUERY-AJAX-TIMEOUT-CALLBACK proc = %S" proc)
|
|
|
|
|
(if proc
|
|
|
|
|
;; This will call `ein:query-ajax-callback'.
|
|
|
|
|
(delete-process proc)
|
|
|
|
|
;; No associated process. This means that `url-retrieve' failed
|
|
|
|
|
;; to call callback function. This happens sometimes.
|
|
|
|
|
;; Let's call the error callback manually.
|
|
|
|
|
(destructuring-bind (&key code &allow-other-keys)
|
|
|
|
|
(progn
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(ein:query--parse-response-at-point))
|
|
|
|
|
(ein:log 'debug "(buffer-string) =\n%s" (buffer-string))
|
|
|
|
|
;; FIXME: error callback may be called already in
|
|
|
|
|
;; `ein:query-ajax-callback'. This happens when
|
|
|
|
|
;; `ein:query-ajax-callback' is called in
|
|
|
|
|
;; differnt buffer.
|
|
|
|
|
(ein:safe-funcall-packed
|
|
|
|
|
error
|
|
|
|
|
;; Passing data to error callback makes no sense, but it is
|
|
|
|
|
;; needed for implementing `ein:notebooklist-login--error'.
|
|
|
|
|
;; Also, this kills the buffer.
|
|
|
|
|
:data (ein:query-ajax--parse-data parser nil)
|
|
|
|
|
:symbol-status 'timeout :response-status code))))))
|
|
|
|
|
|
|
|
|
|
(defun ein:query--parse-response-at-point ()
|
|
|
|
|
(re-search-forward "\\=[ \t\n]*HTTP/\\([0-9\\.]+\\) +\\([0-9]+\\)")
|
|
|
|
|
(list :version (match-string 1)
|
|
|
|
|
:code (string-to-number (match-string 2))))
|
2012-05-25 20:57:48 +02:00
|
|
|
|
|
2012-05-26 16:34:04 +02:00
|
|
|
|
(defun ein:query-ajax-cancel-timer ()
|
|
|
|
|
(ein:log 'debug "EIN:QUERY-AJAX-CANCEL-TIMER")
|
2012-08-18 22:46:45 +02:00
|
|
|
|
(when ein:%query-ajax-timer%
|
|
|
|
|
(cancel-timer ein:%query-ajax-timer%)
|
|
|
|
|
(setq ein:%query-ajax-timer% nil)))
|
2012-05-26 16:34:04 +02:00
|
|
|
|
|
2012-06-20 20:56:04 +02:00
|
|
|
|
(defvar ein:query-running-process-table (make-hash-table :test 'equal))
|
|
|
|
|
|
|
|
|
|
(defun ein:query-singleton-ajax (key &rest args)
|
|
|
|
|
"Cancel the old process if there is a process associated with
|
2012-12-29 17:05:32 +01:00
|
|
|
|
KEY, then call `request' with ARGS. KEY is compared by
|
2012-06-20 20:56:04 +02:00
|
|
|
|
`equal'."
|
|
|
|
|
(ein:query-gc-running-process-table)
|
|
|
|
|
(ein:aif (gethash key ein:query-running-process-table)
|
2012-12-29 17:05:32 +01:00
|
|
|
|
(unless (request-response-done-p it)
|
|
|
|
|
(request-abort it))) ; This will run callbacks
|
|
|
|
|
(let ((response (apply #'request args)))
|
|
|
|
|
(puthash key response ein:query-running-process-table)
|
|
|
|
|
response))
|
2012-06-20 20:56:04 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:query-gc-running-process-table ()
|
|
|
|
|
"Garbage collect dead processes in `ein:query-running-process-table'."
|
|
|
|
|
(maphash
|
|
|
|
|
(lambda (key buffer)
|
2012-12-29 17:05:32 +01:00
|
|
|
|
(when (request-response-done-p buffer)
|
2012-06-20 20:56:04 +02:00
|
|
|
|
(remhash key ein:query-running-process-table)))
|
|
|
|
|
ein:query-running-process-table))
|
|
|
|
|
|
2012-12-17 16:48:18 +01:00
|
|
|
|
|
|
|
|
|
;;; Cookie
|
|
|
|
|
|
|
|
|
|
(defun ein:query-get-cookie (host &optional localpart secure)
|
|
|
|
|
"Return cookie string (like `document.cookie').
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
(ein:query-get-cookie \"127.0.0.1\" \"/\")
|
|
|
|
|
"
|
|
|
|
|
(let ((cookies (mapcar
|
|
|
|
|
(lambda (c) (cons (url-cookie-name c) (url-cookie-value c)))
|
|
|
|
|
(url-cookie-retrieve host localpart secure))))
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (nv) (concat (car nv) "=" (cdr nv)))
|
|
|
|
|
cookies
|
|
|
|
|
"; ")))
|
|
|
|
|
|
2012-05-25 20:57:48 +02:00
|
|
|
|
(provide 'ein-query)
|
|
|
|
|
|
|
|
|
|
;;; ein-query.el ends here
|