Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
6.6 KiB

  1. ;;; ein-query.el --- jQuery like interface on to of url-retrieve -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2012- Takafumi Arakaki
  3. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; This file is NOT part of GNU Emacs.
  5. ;; ein-query.el is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; ein-query.el is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with ein-query.el. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;;; Code:
  18. (require 'request)
  19. (require 'url)
  20. (require 'ein-core)
  21. (require 'ein-log)
  22. ;;; Utils
  23. (defun ein:safe-funcall-packed (packed &rest args)
  24. (when packed
  25. (ein:log-ignore-errors (apply #'ein:funcall-packed packed args))))
  26. ;;; Variables
  27. (defcustom ein:query-timeout
  28. (if (eq request-backend 'url-retrieve) 1000 nil)
  29. "Default query timeout for HTTP access in millisecond.
  30. Setting this to `nil' means no timeout.
  31. If you have ``curl`` command line program, it is automatically set to
  32. `nil' as ``curl`` is reliable than `url-retrieve' therefore no need for
  33. a workaround (see below).
  34. If you do the same operation before the timeout, old operation
  35. will NO LONGER be canceled (as it the cookie jar gets clobbered when curl
  36. aborts). Instead you will see Race! in debug messages.
  37. .. note:: This value exists because it looks like `url-retrieve'
  38. occasionally fails to finish \(start?) querying. Timeout is
  39. used to let user notice that their operation is not finished.
  40. It also prevent opening a lot of useless process buffers.
  41. You will see them when closing Emacs if there is no timeout.
  42. If you know how to fix the problem with `url-retrieve', please
  43. let me know or send pull request at github!
  44. \(Related bug report in Emacs bug tracker:
  45. http://debbugs.gnu.org/cgi/bugreport.cgi?bug=11469)"
  46. :type '(choice (integer :tag "Timeout [ms]" 5000)
  47. (const :tag "No timeout" nil))
  48. :group 'ein)
  49. ;;; Functions
  50. (defvar ein:query-running-process-table (make-hash-table :test 'equal))
  51. (defvar ein:query-xsrf-cache (make-hash-table :test 'equal)
  52. "Hack: remember the last xsrf token by host in case we catch cookie jar in transition. The proper fix is to sempahore between competing curl processes.")
  53. (defun ein:query-prepare-header (url settings &optional securep)
  54. "Ensure that REST calls to the jupyter server have the correct _xsrf argument."
  55. (let* ((host (url-host (url-generic-parse-url url)))
  56. (cookies (request-cookie-alist host "/" securep))
  57. (xsrf (or (cdr (assoc-string "_xsrf" cookies))
  58. (gethash host ein:query-xsrf-cache))))
  59. (ein:log 'debug "EIN:QUERY-PREPARE-HEADER: Found xsrf: %s" xsrf)
  60. (setq settings (plist-put settings :headers
  61. (append (plist-get settings :headers)
  62. (list (cons "User-Agent" "Mozilla/5.0")))))
  63. (when xsrf
  64. (setq settings (plist-put settings :headers
  65. (append (plist-get settings :headers)
  66. (list (cons "X-XSRFTOKEN" xsrf)))))
  67. (setf (gethash host ein:query-xsrf-cache) xsrf))
  68. (setq settings (plist-put settings :encoding 'binary))
  69. settings))
  70. (defcustom ein:max-simultaneous-queries 100
  71. "Limit number of simultaneous queries to Jupyter server.
  72. If too many calls to `request' are made at once Emacs may
  73. complaint and raise a 'Too Many Files' exception. By setting this
  74. variable to a reasonable value you can avoid this situation."
  75. :group 'ein
  76. :type 'integer)
  77. (let ((checked-curl-version nil))
  78. (defun ein:warn-on-curl-version ()
  79. (let ((curl (executable-find request-curl)))
  80. (unless checked-curl-version
  81. (setq checked-curl-version t)
  82. (with-temp-buffer
  83. (call-process curl nil t nil "--version")
  84. (goto-char (point-min))
  85. (when (search-forward "mingw32" nil t)
  86. (warn "The current version of curl (%s) may not work with ein. We recommend you install the latest, official version from the curl website: https://curl.haxx.se" (buffer-string))))))))
  87. (defsubst ein:query-enforce-curl ()
  88. (ein:warn-on-curl-version)
  89. (unless (eq request-backend 'curl)
  90. (ein:display-warning
  91. (format "request-backend: %s unsupported" request-backend))
  92. (if (executable-find request-curl)
  93. (setq request-backend 'curl)
  94. (ein:display-warning
  95. (format "The %s program was not found" request-curl) :error))))
  96. (cl-defun ein:query-singleton-ajax (key url &rest settings
  97. &key (timeout ein:query-timeout) &allow-other-keys)
  98. "Do not cancel the old process if there is a process associated with
  99. KEY, then call `request' with URL and SETTINGS. KEY is compared by
  100. `equal'."
  101. (ein:query-enforce-curl)
  102. (with-local-quit
  103. (when timeout
  104. (setq settings (plist-put settings :timeout (/ timeout 1000.0))))
  105. (cl-loop do (ein:query-running-process-table)
  106. for running = (hash-table-count ein:query-running-process-table)
  107. until (< running ein:max-simultaneous-queries)
  108. do (ein:log 'warn "ein:query-singleton-ajax: %d running processes"
  109. running)
  110. do (sleep-for 3))
  111. (ein:aif (gethash key ein:query-running-process-table)
  112. (unless (request-response-done-p it)
  113. (ein:log 'debug "Race! %s %s" key (request-response-data it))))
  114. (let ((response (apply #'request (url-encode-url url)
  115. (ein:query-prepare-header url settings))))
  116. (puthash key response ein:query-running-process-table)
  117. response)))
  118. (defun ein:query-running-process-table ()
  119. "Keep track of unfinished curl requests."
  120. (maphash
  121. (lambda (key buffer)
  122. (when (request-response-done-p buffer)
  123. (remhash key ein:query-running-process-table)))
  124. ein:query-running-process-table))
  125. (defun ein:get-response-redirect (response)
  126. "Determine if the query has been redirected, and if so return then URL the request was redirected to."
  127. (let ((url (url-generic-parse-url (format "%s" (request-response-url response)))))
  128. (format "%s://%s:%s"
  129. (url-type url)
  130. (url-host url)
  131. (url-port url))))
  132. ;;; Cookie
  133. (defalias 'ein:query-get-cookie 'request-cookie-string)
  134. (provide 'ein-query)
  135. ;;; ein-query.el ends here