diff options
| author | vin <git@vineetk.net> | 2025-12-08 16:51:59 -0500 |
|---|---|---|
| committer | vin <git@vineetk.net> | 2025-12-08 16:55:05 -0500 |
| commit | af9627b43eaf4e40fadc9be82ccc2f10f33746a3 (patch) | |
| tree | d66c4aed05c37f159718e4a8af6b8b80134f4116 | |
| parent | 2113d0279b035f183c0c05627bbd2b0779e98f35 (diff) | |
attempt to make the plugin more async-y
| -rw-r--r-- | elfeed-custom-feeds-github.el | 76 | ||||
| -rw-r--r-- | elfeed-custom-feeds-huggingface.el | 83 | ||||
| -rw-r--r-- | elfeed-custom-feeds.el | 3 |
3 files changed, 106 insertions, 56 deletions
diff --git a/elfeed-custom-feeds-github.el b/elfeed-custom-feeds-github.el index 660350d..a857963 100644 --- a/elfeed-custom-feeds-github.el +++ b/elfeed-custom-feeds-github.el | |||
| @@ -57,15 +57,15 @@ | |||
| 57 | (funcall cb r dn items)))) | 57 | (funcall cb r dn items)))) |
| 58 | nil t))))) | 58 | nil t))))) |
| 59 | 59 | ||
| 60 | (defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs) | 60 | (defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs completion-callback) |
| 61 | "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME." | 61 | "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME. |
| 62 | Call COMPLETION-CALLBACK with (repo display-name new-entries) when done." | ||
| 62 | (let ((feed-url (format "https://github.com/%s/pulls" repo)) | 63 | (let ((feed-url (format "https://github.com/%s/pulls" repo)) |
| 63 | (new-count 0) | ||
| 64 | (new-entries ())) | 64 | (new-entries ())) |
| 65 | (let ((feed (elfeed-db-get-feed feed-url))) | 65 | (let ((feed (elfeed-db-get-feed feed-url))) |
| 66 | (setf (elfeed-feed-url feed) feed-url | 66 | (setf (elfeed-feed-url feed) feed-url |
| 67 | (elfeed-feed-title feed) (format "GitHub: %s [%s]" repo display-name))) | 67 | (elfeed-feed-title feed) (format "GitHub: %s [%s]" repo display-name))) |
| 68 | 68 | ||
| 69 | (dolist (pr prs) | 69 | (dolist (pr prs) |
| 70 | (let* ((number (alist-get 'number pr)) | 70 | (let* ((number (alist-get 'number pr)) |
| 71 | (title-text (alist-get 'title pr)) | 71 | (title-text (alist-get 'title pr)) |
| @@ -86,7 +86,7 @@ | |||
| 86 | (format "<p><strong>Labels:</strong> %s</p>" | 86 | (format "<p><strong>Labels:</strong> %s</p>" |
| 87 | (mapconcat 'identity labels ", ")) | 87 | (mapconcat 'identity labels ", ")) |
| 88 | "") | 88 | "") |
| 89 | (replace-regexp-in-string "<" "<" | 89 | (replace-regexp-in-string "<" "<" |
| 90 | (replace-regexp-in-string ">" ">" body))))) | 90 | (replace-regexp-in-string ">" ">" body))))) |
| 91 | (unless (elfeed-db-get-entry entry-id) | 91 | (unless (elfeed-db-get-entry entry-id) |
| 92 | (let ((entry (elfeed-entry--create | 92 | (let ((entry (elfeed-entry--create |
| @@ -98,27 +98,59 @@ | |||
| 98 | :content-type 'html | 98 | :content-type 'html |
| 99 | :feed-id feed-url | 99 | :feed-id feed-url |
| 100 | :tags '(unread github)))) | 100 | :tags '(unread github)))) |
| 101 | (push entry new-entries) | 101 | (push entry new-entries))))) |
| 102 | (setq new-count (1+ new-count)))))) | 102 | |
| 103 | 103 | ;; Call completion callback with results | |
| 104 | (when new-entries | 104 | (funcall completion-callback repo display-name (nreverse new-entries)))) |
| 105 | (elfeed-db-add (nreverse new-entries))) | ||
| 106 | |||
| 107 | (message "GitHub feed %s [%s]: %d new entries" repo display-name new-count) | ||
| 108 | (when (get-buffer "*elfeed-search*") | ||
| 109 | (with-current-buffer "*elfeed-search*" | ||
| 110 | (elfeed-search-update--force))))) | ||
| 111 | 105 | ||
| 112 | ;;;###autoload | 106 | ;;;###autoload |
| 113 | (defun elfeed-custom-feeds-github-update () | 107 | (defun elfeed-custom-feeds-github-update () |
| 114 | "Update all GitHub PR feeds." | 108 | "Update all GitHub PR feeds asynchronously. |
| 109 | Batches all entries and updates UI once at the end to avoid blocking." | ||
| 115 | (interactive) | 110 | (interactive) |
| 116 | (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries | 111 | (let* ((total (length elfeed-custom-github-pr-queries)) |
| 117 | for i from 0 | 112 | (processed 0) |
| 118 | for delay = (* i elfeed-custom-github-request-delay) | 113 | (all-new-entries '()) |
| 119 | do (elfeed-custom-feeds-github--fetch-prs repo query display-name | 114 | (results '())) |
| 120 | #'elfeed-custom-feeds-github--prs-to-elfeed | 115 | |
| 121 | delay))) | 116 | (when (zerop total) |
| 117 | (message "No GitHub PR queries configured") | ||
| 118 | (cl-return-from elfeed-custom-feeds-github-update nil)) | ||
| 119 | |||
| 120 | ;; Completion callback that batches everything | ||
| 121 | (let ((completion-callback | ||
| 122 | (lambda (repo display-name new-entries) | ||
| 123 | (setq processed (1+ processed)) | ||
| 124 | (when new-entries | ||
| 125 | (setq all-new-entries (append all-new-entries new-entries))) | ||
| 126 | (push (list repo display-name (length new-entries)) results) | ||
| 127 | |||
| 128 | ;; When all queries are done, batch add to DB and update UI once | ||
| 129 | (when (= processed total) | ||
| 130 | (let ((total-count (length all-new-entries))) | ||
| 131 | (when (> total-count 0) | ||
| 132 | (elfeed-db-add all-new-entries)) | ||
| 133 | ;; Report results for each feed | ||
| 134 | (dolist (result (nreverse results)) | ||
| 135 | (message "GitHub feed %s [%s]: %d new entries" | ||
| 136 | (nth 0 result) (nth 1 result) (nth 2 result))) | ||
| 137 | ;; Defer UI update to avoid blocking | ||
| 138 | (run-with-idle-timer 0.1 nil | ||
| 139 | (lambda () | ||
| 140 | (when (get-buffer "*elfeed-search*") | ||
| 141 | (with-current-buffer "*elfeed-search*" | ||
| 142 | (elfeed-search-update--force)))))))))) | ||
| 143 | |||
| 144 | ;; Start all async fetches | ||
| 145 | (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries | ||
| 146 | for i from 0 | ||
| 147 | for delay = (* i elfeed-custom-github-request-delay) | ||
| 148 | do (elfeed-custom-feeds-github--fetch-prs | ||
| 149 | repo query display-name | ||
| 150 | (lambda (r dn items) | ||
| 151 | (elfeed-custom-feeds-github--prs-to-elfeed r dn items completion-callback)) | ||
| 152 | delay))) | ||
| 153 | nil)) | ||
| 122 | 154 | ||
| 123 | (provide 'elfeed-custom-feeds-github) | 155 | (provide 'elfeed-custom-feeds-github) |
| 124 | 156 | ||
diff --git a/elfeed-custom-feeds-huggingface.el b/elfeed-custom-feeds-huggingface.el index a3bfdb0..43f64c8 100644 --- a/elfeed-custom-feeds-huggingface.el +++ b/elfeed-custom-feeds-huggingface.el | |||
| @@ -84,7 +84,8 @@ | |||
| 84 | nil t))) | 84 | nil t))) |
| 85 | 85 | ||
| 86 | (defun elfeed-custom-feeds-huggingface--create-entry (org-name model model-card-html) | 86 | (defun elfeed-custom-feeds-huggingface--create-entry (org-name model model-card-html) |
| 87 | "Create an elfeed entry for MODEL with model card HTML content." | 87 | "Create an elfeed entry for MODEL with model card HTML content. |
| 88 | Returns the entry if it's new, nil otherwise." | ||
| 88 | (let* ((feed-url (format "https://huggingface.co/%s" org-name)) | 89 | (let* ((feed-url (format "https://huggingface.co/%s" org-name)) |
| 89 | (model-id (alist-get 'id model)) | 90 | (model-id (alist-get 'id model)) |
| 90 | (last-modified (alist-get 'lastModified model)) | 91 | (last-modified (alist-get 'lastModified model)) |
| @@ -96,64 +97,80 @@ | |||
| 96 | (title (format "[%s] %s" org-name model-id)) | 97 | (title (format "[%s] %s" org-name model-id)) |
| 97 | (date (float-time (date-to-time last-modified))) | 98 | (date (float-time (date-to-time last-modified))) |
| 98 | (metadata (format "<h2>%s</h2>\n<p><strong>Type:</strong> %s</p>\n<p><strong>Downloads:</strong> %d</p>\n<p><strong>Last Modified:</strong> %s</p>\n%s\n<hr>\n" | 99 | (metadata (format "<h2>%s</h2>\n<p><strong>Type:</strong> %s</p>\n<p><strong>Downloads:</strong> %d</p>\n<p><strong>Last Modified:</strong> %s</p>\n%s\n<hr>\n" |
| 99 | model-id | 100 | model-id |
| 100 | pipeline | 101 | pipeline |
| 101 | downloads | 102 | downloads |
| 102 | last-modified | 103 | last-modified |
| 103 | (if tags | 104 | (if tags |
| 104 | (format "<p><strong>Tags:</strong> %s</p>" | 105 | (format "<p><strong>Tags:</strong> %s</p>" |
| 105 | (mapconcat 'identity (cl-subseq tags 0 (min 10 (length tags))) ", ")) | 106 | (mapconcat 'identity (cl-subseq tags 0 (min 10 (length tags))) ", ")) |
| 106 | ""))) | 107 | ""))) |
| 107 | (content (concat metadata | 108 | (content (concat metadata |
| 108 | (or model-card-html "<p><em>Model card not available</em></p>")))) | 109 | (or model-card-html "<p><em>Model card not available</em></p>")))) |
| 109 | (unless (elfeed-db-get-entry entry-id) | 110 | (unless (elfeed-db-get-entry entry-id) |
| 110 | (let ((entry (elfeed-entry--create | 111 | (elfeed-entry--create |
| 111 | :id entry-id | 112 | :id entry-id |
| 112 | :title title | 113 | :title title |
| 113 | :link link | 114 | :link link |
| 114 | :date date | 115 | :date date |
| 115 | :content content | 116 | :content content |
| 116 | :content-type 'html | 117 | :content-type 'html |
| 117 | :feed-id feed-url | 118 | :feed-id feed-url |
| 118 | :tags '(unread huggingface)))) | 119 | :tags '(unread huggingface))))) |
| 119 | (elfeed-db-add (list entry)) | ||
| 120 | (when (get-buffer "*elfeed-search*") | ||
| 121 | (with-current-buffer "*elfeed-search*" | ||
| 122 | (elfeed-search-update--force))) | ||
| 123 | entry)))) | ||
| 124 | 120 | ||
| 125 | (defun elfeed-custom-feeds-huggingface--models-to-elfeed (org-name models) | 121 | (defun elfeed-custom-feeds-huggingface--models-to-elfeed (org-name models) |
| 126 | "Convert HuggingFace MODELS to elfeed entries for ORG-NAME." | 122 | "Convert HuggingFace MODELS to elfeed entries for ORG-NAME. |
| 127 | (let ((feed-url (format "https://huggingface.co/%s" org-name)) | 123 | Batches all entries and updates UI once at the end to avoid blocking." |
| 128 | (new-count 0) | 124 | (let* ((feed-url (format "https://huggingface.co/%s" org-name)) |
| 129 | (total (length models)) | 125 | (total (length models)) |
| 130 | (org org-name)) | 126 | (new-entries '()) |
| 127 | (processed 0)) | ||
| 128 | ;; Set up feed metadata | ||
| 131 | (let ((feed (elfeed-db-get-feed feed-url))) | 129 | (let ((feed (elfeed-db-get-feed feed-url))) |
| 132 | (setf (elfeed-feed-url feed) feed-url | 130 | (setf (elfeed-feed-url feed) feed-url |
| 133 | (elfeed-feed-title feed) (format "HuggingFace: %s" org-name))) | 131 | (elfeed-feed-title feed) (format "HuggingFace: %s" org-name))) |
| 134 | 132 | ||
| 133 | ;; Return immediately if no models | ||
| 134 | (when (zerop total) | ||
| 135 | (message "HuggingFace feed %s: no models to process" org-name) | ||
| 136 | (cl-return-from elfeed-custom-feeds-huggingface--models-to-elfeed nil)) | ||
| 137 | |||
| 138 | ;; Fetch model cards asynchronously and batch entries | ||
| 135 | (cl-loop for model in models | 139 | (cl-loop for model in models |
| 136 | for i from 0 | 140 | for i from 0 |
| 137 | for model-id = (alist-get 'id model) | 141 | for model-id = (alist-get 'id model) |
| 138 | for delay = (* i elfeed-custom-huggingface-request-delay) | 142 | for delay = (* i elfeed-custom-huggingface-request-delay) |
| 139 | do (elfeed-custom-feeds-huggingface--fetch-model-card | 143 | do (elfeed-custom-feeds-huggingface--fetch-model-card |
| 140 | model-id | 144 | model-id |
| 141 | (let ((m model)) | 145 | (let ((m model)) |
| 142 | (lambda (html) | 146 | (lambda (html) |
| 143 | (when (elfeed-custom-feeds-huggingface--create-entry org m html) | 147 | (let ((entry (elfeed-custom-feeds-huggingface--create-entry org-name m html))) |
| 144 | (setq new-count (1+ new-count))) | 148 | (when entry |
| 145 | (when (= new-count total) | 149 | (push entry new-entries))) |
| 146 | (message "HuggingFace feed %s: %d new entries" org new-count)))) | 150 | (setq processed (1+ processed)) |
| 151 | ;; When all are processed, batch add to DB and update UI once | ||
| 152 | (when (= processed total) | ||
| 153 | (let ((count (length new-entries))) | ||
| 154 | (when (> count 0) | ||
| 155 | (elfeed-db-add (nreverse new-entries))) | ||
| 156 | (message "HuggingFace feed %s: %d new entries" org-name count) | ||
| 157 | ;; Defer UI update to avoid blocking | ||
| 158 | (run-with-idle-timer 0.1 nil | ||
| 159 | (lambda () | ||
| 160 | (when (get-buffer "*elfeed-search*") | ||
| 161 | (with-current-buffer "*elfeed-search*" | ||
| 162 | (elfeed-search-update--force))))))))) | ||
| 147 | delay)) | 163 | delay)) |
| 148 | |||
| 149 | nil)) | 164 | nil)) |
| 150 | 165 | ||
| 151 | ;;;###autoload | 166 | ;;;###autoload |
| 152 | (defun elfeed-custom-feeds-huggingface-update () | 167 | (defun elfeed-custom-feeds-huggingface-update () |
| 153 | "Update all HuggingFace organization feeds." | 168 | "Update all HuggingFace organization feeds asynchronously. |
| 169 | Returns immediately while updates happen in the background." | ||
| 154 | (interactive) | 170 | (interactive) |
| 155 | (dolist (org elfeed-custom-huggingface-orgs) | 171 | (dolist (org elfeed-custom-huggingface-orgs) |
| 156 | (elfeed-custom-feeds-huggingface--fetch-models org #'elfeed-custom-feeds-huggingface--models-to-elfeed))) | 172 | (elfeed-custom-feeds-huggingface--fetch-models org #'elfeed-custom-feeds-huggingface--models-to-elfeed)) |
| 173 | nil) | ||
| 157 | 174 | ||
| 158 | (provide 'elfeed-custom-feeds-huggingface) | 175 | (provide 'elfeed-custom-feeds-huggingface) |
| 159 | 176 | ||
diff --git a/elfeed-custom-feeds.el b/elfeed-custom-feeds.el index a64cc69..0e8ff6e 100644 --- a/elfeed-custom-feeds.el +++ b/elfeed-custom-feeds.el | |||
| @@ -31,7 +31,8 @@ | |||
| 31 | (interactive) | 31 | (interactive) |
| 32 | (elfeed-custom-feeds-huggingface-update) | 32 | (elfeed-custom-feeds-huggingface-update) |
| 33 | (elfeed-custom-feeds-github-update) | 33 | (elfeed-custom-feeds-github-update) |
| 34 | (message "Updating custom feeds...")) | 34 | (message "Updating custom feeds...") |
| 35 | nil) | ||
| 35 | 36 | ||
| 36 | (provide 'elfeed-custom-feeds) | 37 | (provide 'elfeed-custom-feeds) |
| 37 | 38 | ||
