summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVineet Kumar <git@vineetk.net>2026-03-07 17:52:17 -0500
committerVineet Kumar <git@vineetk.net>2026-03-07 18:07:43 -0500
commitf6e925363cd72b0cdfd3ec5d96ab2faf0e569313 (patch)
tree964d510398670649cc5b1dc8489aae10003e1bb9
parentdd583bc92c00994ef41b384f40ff8b9da11dec84 (diff)
replace url-retrieve with elfeed-curl-enqueue to eliminate blockingHEADmaster
url-retrieve blocks emacs during dns resolution and tls handshake. elfeed-curl-enqueue runs curl as a subprocess, making fetches fully non-blocking and mirrors regular elfeed operation. Also adds Accept header for GitHub API requests.
-rw-r--r--elfeed-custom-feeds-github.el124
-rw-r--r--elfeed-custom-feeds-huggingface.el114
2 files changed, 110 insertions, 128 deletions
diff --git a/elfeed-custom-feeds-github.el b/elfeed-custom-feeds-github.el
index 97a2f60..49e0d6b 100644
--- a/elfeed-custom-feeds-github.el
+++ b/elfeed-custom-feeds-github.el
@@ -11,7 +11,7 @@
11;;; Code: 11;;; Code:
12 12
13(require 'elfeed) 13(require 'elfeed)
14(require 'url) 14(require 'elfeed-curl)
15(require 'json) 15(require 'json)
16(require 'cl-lib) 16(require 'cl-lib)
17 17
@@ -44,29 +44,27 @@
44 (dn display-name)) 44 (dn display-name))
45 (run-at-time delay nil 45 (run-at-time delay nil
46 (lambda () 46 (lambda ()
47 (url-retrieve url 47 (elfeed-curl-enqueue url
48 (lambda (status) 48 (lambda (status)
49 (if (plist-get status :error) 49 (if (not status)
50 (message "Error fetching GitHub PRs %s [%s]: %s" r dn (plist-get status :error)) 50 (message "Error fetching GitHub PRs %s [%s]: %s" r dn elfeed-curl-error-message)
51 (goto-char (point-min)) 51 ;; Capture JSON text for async parsing
52 (re-search-forward "\n\n") 52 (let ((json-text (buffer-substring-no-properties (point-min) (point-max))))
53 ;; Capture JSON text for async parsing 53 ;; Parse JSON in idle timer to avoid blocking
54 (let ((json-text (buffer-substring-no-properties (point) (point-max)))) 54 (run-with-idle-timer 0 nil
55 ;; Parse JSON in idle timer to avoid blocking 55 (lambda ()
56 (run-with-idle-timer 0 nil 56 (condition-case err
57 (lambda () 57 (with-temp-buffer
58 (condition-case err 58 (insert json-text)
59 (with-temp-buffer 59 (goto-char (point-min))
60 (insert json-text) 60 (let* ((json-object-type 'alist)
61 (goto-char (point-min)) 61 (json-array-type 'list)
62 (let* ((json-object-type 'alist) 62 (response (json-read))
63 (json-array-type 'list) 63 (items (alist-get 'items response)))
64 (response (json-read)) 64 (funcall cb r dn items)))
65 (items (alist-get 'items response))) 65 (error
66 (funcall cb r dn items))) 66 (message "Error parsing JSON for GitHub %s [%s]: %s" r dn err))))))))
67 (error 67 :headers '(("Accept" . "application/vnd.github+json")))))))
68 (message "Error parsing JSON for GitHub %s [%s]: %s" r dn err)))))))
69 nil t)))))
70 68
71(defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs completion-callback) 69(defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs completion-callback)
72 "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME. 70 "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME.
@@ -126,7 +124,7 @@ Call COMPLETION-CALLBACK with (repo display-name new-entries) when done."
126 (run-with-idle-timer 0 nil #'process-next-chunk) 124 (run-with-idle-timer 0 nil #'process-next-chunk)
127 ;; All done - call completion callback 125 ;; All done - call completion callback
128 (funcall completion-callback repo display-name (nreverse new-entries))))))) 126 (funcall completion-callback repo display-name (nreverse new-entries)))))))
129 (process-next-chunk))))) 127 (process-next-chunk))))
130 128
131;;;###autoload 129;;;###autoload
132(defun elfeed-custom-feeds-github-update () 130(defun elfeed-custom-feeds-github-update ()
@@ -138,48 +136,42 @@ Batches all entries and updates UI once at the end to avoid blocking."
138 (all-new-entries '()) 136 (all-new-entries '())
139 (results '())) 137 (results '()))
140 138
141 (when (zerop total) 139 (if (zerop total)
142 (message "No GitHub PR queries configured") 140 (message "No GitHub PR queries configured")
143 (cl-return-from elfeed-custom-feeds-github-update nil)) 141 (let ((completion-callback
144 142 (lambda (repo display-name new-entries)
145 ;; Completion callback that batches everything 143 (run-with-idle-timer 0 nil
146 (let ((completion-callback 144 (lambda ()
147 (lambda (repo display-name new-entries) 145 (setq processed (1+ processed))
148 (run-with-idle-timer 0 nil 146 (when new-entries
149 (lambda () 147 (setq all-new-entries (append all-new-entries new-entries)))
150 (setq processed (1+ processed)) 148 (push (list repo display-name (length new-entries)) results)
151 (when new-entries 149 ;; When all queries are done, batch add to DB and update UI once
152 (setq all-new-entries (append all-new-entries new-entries))) 150 (when (= processed total)
153 (push (list repo display-name (length new-entries)) results) 151 (let ((total-count (length all-new-entries)))
154 152 (when (> total-count 0)
155 ;; When all queries are done, batch add to DB and update UI once 153 (run-with-idle-timer 0 nil
156 (when (= processed total) 154 (lambda ()
157 (let ((total-count (length all-new-entries))) 155 (elfeed-db-add all-new-entries))))
158 (when (> total-count 0) 156 ;; Report results for each feed
159 (run-with-idle-timer 0 nil 157 (dolist (result (nreverse results))
158 (message "GitHub feed %s [%s]: %d new entries"
159 (nth 0 result) (nth 1 result) (nth 2 result)))
160 ;; Defer UI update to avoid blocking
161 (run-with-idle-timer 0.2 nil
160 (lambda () 162 (lambda ()
161 (elfeed-db-add all-new-entries)))) 163 (when (get-buffer "*elfeed-search*")
162 ;; Report results for each feed 164 (with-current-buffer "*elfeed-search*"
163 (dolist (result (nreverse results)) 165 (elfeed-search-update--force))))))))))))
164 (message "GitHub feed %s [%s]: %d new entries" 166 ;; Start all async fetches
165 (nth 0 result) (nth 1 result) (nth 2 result))) 167 (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries
166 ;; Defer UI update to avoid blocking 168 for i from 0
167 (run-with-idle-timer 0.2 nil 169 for delay = (* i elfeed-custom-github-request-delay)
168 (lambda () 170 do (elfeed-custom-feeds-github--fetch-prs
169 (when (get-buffer "*elfeed-search*") 171 repo query display-name
170 (with-current-buffer "*elfeed-search*" 172 (lambda (r dn items)
171 (elfeed-search-update--force))))))))))))) 173 (elfeed-custom-feeds-github--prs-to-elfeed r dn items completion-callback))
172 174 delay))))))
173 ;; Start all async fetches
174 (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries
175 for i from 0
176 for delay = (* i elfeed-custom-github-request-delay)
177 do (elfeed-custom-feeds-github--fetch-prs
178 repo query display-name
179 (lambda (r dn items)
180 (elfeed-custom-feeds-github--prs-to-elfeed r dn items completion-callback))
181 delay)))
182 nil)
183 175
184(provide 'elfeed-custom-feeds-github) 176(provide 'elfeed-custom-feeds-github)
185 177
diff --git a/elfeed-custom-feeds-huggingface.el b/elfeed-custom-feeds-huggingface.el
index 81f4b5b..703b92e 100644
--- a/elfeed-custom-feeds-huggingface.el
+++ b/elfeed-custom-feeds-huggingface.el
@@ -11,7 +11,7 @@
11;;; Code: 11;;; Code:
12 12
13(require 'elfeed) 13(require 'elfeed)
14(require 'url) 14(require 'elfeed-curl)
15(require 'json) 15(require 'json)
16(require 'cl-lib) 16(require 'cl-lib)
17(require 'dom) 17(require 'dom)
@@ -44,37 +44,33 @@
44 (mid model-id)) 44 (mid model-id))
45 (run-at-time delay nil 45 (run-at-time delay nil
46 (lambda () 46 (lambda ()
47 (url-retrieve url 47 (elfeed-curl-enqueue url
48 (lambda (status) 48 (lambda (status)
49 (if (plist-get status :error) 49 (if (not status)
50 (run-with-idle-timer 0 nil cb nil) 50 (run-with-idle-timer 0 nil cb nil)
51 (goto-char (point-min)) 51 ;; Capture raw HTML for async processing
52 (if (re-search-forward "\n\n" nil t) 52 (let ((html-text (buffer-substring-no-properties (point-min) (point-max))))
53 ;; Capture raw HTML for async processing 53 ;; Process HTML parsing in idle timer to avoid blocking
54 (let ((html-text (buffer-substring-no-properties (point) (point-max)))) 54 (run-with-idle-timer 0 nil
55 ;; Process HTML parsing in idle timer to avoid blocking 55 (lambda ()
56 (run-with-idle-timer 0 nil 56 (condition-case err
57 (lambda () 57 (with-temp-buffer
58 (condition-case err 58 (insert html-text)
59 (with-temp-buffer 59 (let* ((html (libxml-parse-html-region (point-min) (point-max)))
60 (insert html-text) 60 (content-div (or (dom-by-class html "model-card-content")
61 (let* ((html (libxml-parse-html-region (point-min) (point-max))) 61 (dom-by-class html "prose")
62 (content-div (or (dom-by-class html "model-card-content") 62 (dom-by-tag html 'article))))
63 (dom-by-class html "prose") 63 (if content-div
64 (dom-by-tag html 'article)))) 64 (with-temp-buffer
65 (if content-div 65 (dom-print (if (listp content-div)
66 (with-temp-buffer 66 (car content-div)
67 (dom-print (if (listp content-div) 67 content-div))
68 (car content-div) 68 (let ((html-string (buffer-string)))
69 content-div)) 69 (funcall cb (elfeed-custom-feeds-huggingface--strip-svg-tags html-string))))
70 (let ((html-string (buffer-string))) 70 (funcall cb nil))))
71 (funcall cb (elfeed-custom-feeds-huggingface--strip-svg-tags html-string)))) 71 (error
72 (funcall cb nil)))) 72 (message "Error parsing HTML for %s: %s" mid err)
73 (error 73 (funcall cb nil)))))))))))))
74 (message "Error parsing HTML for %s: %s" mid err)
75 (funcall cb nil))))))
76 (run-with-idle-timer 0 nil cb nil))))
77 nil t)))))
78 74
79(defun elfeed-custom-feeds-huggingface--fetch-models (org-name callback) 75(defun elfeed-custom-feeds-huggingface--fetch-models (org-name callback)
80 "Fetch models for ORG-NAME from HuggingFace API and call CALLBACK with results." 76 "Fetch models for ORG-NAME from HuggingFace API and call CALLBACK with results."
@@ -82,28 +78,25 @@
82 (url-hexify-string org-name))) 78 (url-hexify-string org-name)))
83 (cb callback) 79 (cb callback)
84 (org org-name)) 80 (org org-name))
85 (url-retrieve url 81 (elfeed-curl-enqueue url
86 (lambda (status) 82 (lambda (status)
87 (if (plist-get status :error) 83 (if (not status)
88 (message "Error fetching HF org %s: %s" org (plist-get status :error)) 84 (message "Error fetching HF org %s: %s" org elfeed-curl-error-message)
89 (goto-char (point-min)) 85 ;; Capture JSON text for async parsing
90 (re-search-forward "\n\n") 86 (let ((json-text (buffer-substring-no-properties (point-min) (point-max))))
91 ;; Capture JSON text for async parsing 87 ;; Parse JSON in idle timer to avoid blocking
92 (let ((json-text (buffer-substring-no-properties (point) (point-max)))) 88 (run-with-idle-timer 0 nil
93 ;; Parse JSON in idle timer to avoid blocking 89 (lambda ()
94 (run-with-idle-timer 0 nil 90 (condition-case err
95 (lambda () 91 (with-temp-buffer
96 (condition-case err 92 (insert json-text)
97 (with-temp-buffer 93 (goto-char (point-min))
98 (insert json-text) 94 (let* ((json-object-type 'alist)
99 (goto-char (point-min)) 95 (json-array-type 'list)
100 (let* ((json-object-type 'alist) 96 (models (json-read)))
101 (json-array-type 'list) 97 (funcall cb org models)))
102 (models (json-read))) 98 (error
103 (funcall cb org models))) 99 (message "Error parsing JSON for HF org %s: %s" org err)))))))))))
104 (error
105 (message "Error parsing JSON for HF org %s: %s" org err))))))))
106 nil t)))
107 100
108(defun elfeed-custom-feeds-huggingface--create-entry (org-name model model-card-html) 101(defun elfeed-custom-feeds-huggingface--create-entry (org-name model model-card-html)
109 "Create an elfeed entry for MODEL with model card HTML content. 102 "Create an elfeed entry for MODEL with model card HTML content.
@@ -154,13 +147,10 @@ Batches all entries and updates UI once at the end to avoid blocking."
154 (setf (elfeed-feed-url feed) feed-url 147 (setf (elfeed-feed-url feed) feed-url
155 (elfeed-feed-title feed) (format "HuggingFace: %s" org-name))))) 148 (elfeed-feed-title feed) (format "HuggingFace: %s" org-name)))))
156 149
157 ;; Return immediately if no models 150 (if (zerop total)
158 (when (zerop total) 151 (message "HuggingFace feed %s: no models to process" org-name)
159 (message "HuggingFace feed %s: no models to process" org-name) 152 ;; Fetch model cards asynchronously and batch entries
160 (cl-return-from elfeed-custom-feeds-huggingface--models-to-elfeed nil)) 153 (cl-loop for model in models
161
162 ;; Fetch model cards asynchronously and batch entries
163 (cl-loop for model in models
164 for i from 0 154 for i from 0
165 for model-id = (alist-get 'id model) 155 for model-id = (alist-get 'id model)
166 for delay = (* i elfeed-custom-huggingface-request-delay) 156 for delay = (* i elfeed-custom-huggingface-request-delay)
@@ -190,7 +180,7 @@ Batches all entries and updates UI once at the end to avoid blocking."
190 (with-current-buffer "*elfeed-search*" 180 (with-current-buffer "*elfeed-search*"
191 (elfeed-search-update--force))))))))))) 181 (elfeed-search-update--force)))))))))))
192 delay)) 182 delay))
193 nil)) 183 nil)))
194 184
195;;;###autoload 185;;;###autoload
196(defun elfeed-custom-feeds-huggingface-update () 186(defun elfeed-custom-feeds-huggingface-update ()