diff options
| author | Eric Abrahamsen | 2023-06-17 18:47:59 -0700 |
|---|---|---|
| committer | Eric Abrahamsen | 2023-06-17 18:47:59 -0700 |
| commit | 5c0cf970799d4d20473b6d232c1061ac0366cd85 (patch) | |
| tree | 0b0a2fe08467cfe77921d72ddc84fadd73995585 | |
| parent | 1b0348d95934a66d9991a7331ab55e1b9a6c1367 (diff) | |
| download | emacs-gnus/nnatom.tar.gz emacs-gnus/nnatom.zip | |
Add nnatom.elgnus/nnatom
Direct copy from https://git.sr.ht/~dsemy/nnatom, with updated
copyright header.
| -rw-r--r-- | lisp/gnus/nnatom.el | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/lisp/gnus/nnatom.el b/lisp/gnus/nnatom.el new file mode 100644 index 00000000000..83ef1c3f28d --- /dev/null +++ b/lisp/gnus/nnatom.el | |||
| @@ -0,0 +1,540 @@ | |||
| 1 | ;;; nnatom.el --- Atom backend for Gnus -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2023 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Daniel Semyonov <daniel@dsemy.com> | ||
| 6 | |||
| 7 | ;; This file is not part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; nnatom is free software: you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; nnatom is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with nnatom. If not, see <https://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary: | ||
| 23 | |||
| 24 | ;; Gnus backend for Atom feeds. | ||
| 25 | |||
| 26 | ;;; Code: | ||
| 27 | |||
| 28 | (eval-when-compile | ||
| 29 | (require 'cl-lib) | ||
| 30 | (require 'gv) | ||
| 31 | (require 'subr-x)) | ||
| 32 | |||
| 33 | (require 'gnus) | ||
| 34 | (require 'nnheader) | ||
| 35 | (require 'nnoo) | ||
| 36 | (require 'gnus-group) | ||
| 37 | (require 'mm-url) | ||
| 38 | |||
| 39 | (defgroup nnatom nil | ||
| 40 | "Atom backend for Gnus." | ||
| 41 | :group 'gnus) | ||
| 42 | |||
| 43 | (nnoo-declare nnatom) | ||
| 44 | |||
| 45 | (defvoo nnatom-backend 'nnatom | ||
| 46 | "Symbol which identifies this backend.") | ||
| 47 | |||
| 48 | (defvoo nnatom-status-string nil | ||
| 49 | "Last status message reported by this backend.") | ||
| 50 | |||
| 51 | (defsubst nnatom--backend-prefix (backend) | ||
| 52 | (concat (symbol-name backend) ":")) | ||
| 53 | |||
| 54 | ;;;; Atom feed parser: | ||
| 55 | |||
| 56 | (defsubst nnatom--node-attributes (node subnode) | ||
| 57 | (cadr (assq subnode node))) | ||
| 58 | |||
| 59 | (defsubst nnatom--node-subnodes (node subnode) | ||
| 60 | (let ((sub (cddr (assq subnode node)))) | ||
| 61 | (or (and (atom (car sub)) (car sub)) sub))) | ||
| 62 | |||
| 63 | (defun nnatom--parse-feed (feed) | ||
| 64 | "Return a list structure representing FEED, or nil." | ||
| 65 | (if (string-match-p "^https?://" feed) | ||
| 66 | (nnheader-report | ||
| 67 | nnatom-backend | ||
| 68 | "Address shouldn't start with \"http://\" or \"https://\"") | ||
| 69 | (with-temp-buffer | ||
| 70 | (condition-case e | ||
| 71 | (if (file-readable-p feed) | ||
| 72 | (insert-file-contents feed) | ||
| 73 | (mm-url-insert-file-contents (concat "https://" feed))) | ||
| 74 | (file-error (nnheader-report nnatom-backend (cdr e))) | ||
| 75 | (:success (cddr (libxml-parse-xml-region (point-min) (point-max)))))))) | ||
| 76 | |||
| 77 | (defun nnatom--read-article (data _) | ||
| 78 | "Return the next article and the remaining DATA in a cons cell, or nil." | ||
| 79 | (and data | ||
| 80 | `(,(let (article) | ||
| 81 | (while (not (eq (car (setq article (car data))) 'entry)) | ||
| 82 | (setq data (cdr data))) | ||
| 83 | (setq data (cdr data)) | ||
| 84 | article) | ||
| 85 | . ,data))) | ||
| 86 | |||
| 87 | (defun nnatom--read-title (feed) | ||
| 88 | "Return the title of FEED, or nil." | ||
| 89 | (nnatom--node-subnodes feed 'title)) | ||
| 90 | |||
| 91 | (defun nnatom--read-article-or-feed-author (article-or-feed) | ||
| 92 | "Return the author of ARTICLE-OR-FEED, or nil." | ||
| 93 | (let* ((author (nnatom--node-subnodes article-or-feed 'author)) | ||
| 94 | (name (nnatom--node-subnodes author 'name)) | ||
| 95 | (email (nnatom--node-subnodes author 'email))) | ||
| 96 | (or (and name email (format "%s <%s>" name email)) | ||
| 97 | name email))) | ||
| 98 | |||
| 99 | (defun nnatom--read-id (article) | ||
| 100 | "Return the ID of ARTICLE. | ||
| 101 | If the ARTICLE doesn't contain an ID but it does contain a subject, | ||
| 102 | return the subject. Otherwise, return nil." | ||
| 103 | (or (nnatom--node-subnodes article 'id) | ||
| 104 | (nnatom--read-subject article))) | ||
| 105 | |||
| 106 | (defun nnatom--read-subject (article) | ||
| 107 | "Return the subject of ARTICLE, or nil." | ||
| 108 | (nnatom--node-subnodes article 'title)) | ||
| 109 | |||
| 110 | (defun nnatom--read-publish (article) | ||
| 111 | "Return the date and time ARTICLE was published, or nil." | ||
| 112 | (when-let ((pub (nnatom--node-subnodes article 'published))) | ||
| 113 | (and (stringp pub) (date-to-time pub)))) | ||
| 114 | |||
| 115 | (defun nnatom--read-update (article) | ||
| 116 | "Return the date and time of the last update to ARTICLE, or nil." | ||
| 117 | (when-let ((update (nnatom--node-subnodes article 'updated))) | ||
| 118 | (and (stringp update) (date-to-time update)))) | ||
| 119 | |||
| 120 | (defun nnatom--read-link (article) | ||
| 121 | "Return the link to ARTICLE, or nil." | ||
| 122 | (alist-get 'href (or (nnatom--node-attributes article 'link) | ||
| 123 | (nnatom--node-subnodes article 'link)))) | ||
| 124 | |||
| 125 | (defun nnatom--read-summary-or-content-type (article summary-or-content) | ||
| 126 | "Return the type of SUMMARY-OR-CONTENT of ARTICLE, or \"plain\"." | ||
| 127 | (or (cdr (assq 'type | ||
| 128 | (nnatom--node-attributes article summary-or-content))) | ||
| 129 | "plain")) | ||
| 130 | |||
| 131 | (defun nnatom--read-summary-or-content (article summary-or-content) | ||
| 132 | "Return SUMMARY-OR-CONTENT of ARTICLE, or nil." | ||
| 133 | (nnatom--node-subnodes article summary-or-content)) | ||
| 134 | |||
| 135 | ;;;; Feed I/O: | ||
| 136 | |||
| 137 | (defvoo nnatom-read-feed-function #'nnatom--parse-feed | ||
| 138 | "Function returning a Lisp object representing a feed (or part of it). | ||
| 139 | It should accept a single argument, the address of a feed.") | ||
| 140 | |||
| 141 | (defvoo nnatom-read-article-function #'nnatom--read-article | ||
| 142 | "Function returning a cons cell of an article and remaining data from a feed. | ||
| 143 | It should accept a two arguments, a Lisp object representing a feed, | ||
| 144 | and a flag indicating whether the last article was stale (not new or updated). | ||
| 145 | If there are no remaining articles, it should return nil.") | ||
| 146 | |||
| 147 | (defvoo nnatom-read-title-function #'nnatom--read-title | ||
| 148 | "Function returning the title of a feed (a string). | ||
| 149 | It should accept a single argument, a Lisp object representing a feed.") | ||
| 150 | |||
| 151 | (defvoo nnatom-read-feed-author-function #'nnatom--read-article-or-feed-author | ||
| 152 | "Function returning the author of a feed (a string). | ||
| 153 | It should accept a single argument, a Lisp object representing a feed.") | ||
| 154 | |||
| 155 | (defvoo nnatom-read-id-function #'nnatom--read-id | ||
| 156 | "Function returning the ID of an article. | ||
| 157 | It should accept a single argument, a Lisp object representing an article.") | ||
| 158 | |||
| 159 | (defvoo nnatom-read-subject-function #'nnatom--read-subject | ||
| 160 | "Function returning the subject of an article (a string). | ||
| 161 | It should accept a single argument, a Lisp object representing an article.") | ||
| 162 | |||
| 163 | (defvoo nnatom-read-publish-date-function #'nnatom--read-publish | ||
| 164 | "Function returning the publish date of an article (a time value). | ||
| 165 | It should accept a single argument, a Lisp object representing an article.") | ||
| 166 | |||
| 167 | (defvoo nnatom-read-update-date-function #'nnatom--read-update | ||
| 168 | "Function returning the update date of an article (a time value). | ||
| 169 | It should accept a single argument, a Lisp object representing an article.") | ||
| 170 | |||
| 171 | (defvoo nnatom-read-author-function #'nnatom--read-article-or-feed-author | ||
| 172 | "Function returning the author of an article (a string). | ||
| 173 | It should accept a single argument, a Lisp object representing an article.") | ||
| 174 | |||
| 175 | (defvoo nnatom-read-link-function #'nnatom--read-link | ||
| 176 | "Function returning the link contained in an article (a string). | ||
| 177 | It should accept a single argument, a Lisp object representing an article.") | ||
| 178 | |||
| 179 | (defvoo nnatom-read-summary-type-function | ||
| 180 | (lambda (article) | ||
| 181 | (nnatom--read-summary-or-content-type article 'summary)) | ||
| 182 | "Function returning the type of the summary of an article (a string). | ||
| 183 | This is appended to \"text/\" to form the MIME type of the summary. | ||
| 184 | It should accept a single argument, a Lisp object representing an article.") | ||
| 185 | |||
| 186 | (defvoo nnatom-read-content-type-function | ||
| 187 | (lambda (article) | ||
| 188 | (nnatom--read-summary-or-content-type article 'content)) | ||
| 189 | "Function returning the type of the content of an article (a string). | ||
| 190 | This is appended to \"text/\" to form the MIME type of the content. | ||
| 191 | It should accept a single argument, a Lisp object representing an article.") | ||
| 192 | |||
| 193 | (defvoo nnatom-read-summary-function | ||
| 194 | (lambda (article) | ||
| 195 | (nnatom--read-summary-or-content article 'summary)) | ||
| 196 | "Function returning the summary of an article (a string). | ||
| 197 | It should accept a single argument, a Lisp object representing an article.") | ||
| 198 | |||
| 199 | (defvoo nnatom-read-content-function | ||
| 200 | (lambda (article) | ||
| 201 | (nnatom--read-summary-or-content article 'content)) | ||
| 202 | "Function returning the content of an article (a string). | ||
| 203 | It should accept a single argument, a Lisp object representing an article.") | ||
| 204 | |||
| 205 | (defvoo nnatom-groups (make-hash-table :test 'equal) | ||
| 206 | "Hash table mapping known group names to their data. | ||
| 207 | |||
| 208 | Each value in this table should be a vector of the form | ||
| 209 | [GROUP IDS ARTICLES MAX MIN], where: | ||
| 210 | - GROUP is the name of the group. | ||
| 211 | - IDS is a hash table mapping article IDs to their numbers. | ||
| 212 | - ARTICLES is a hash table mapping article numbers to articles and | ||
| 213 | their attributes (see `nnatom-group-articles'). | ||
| 214 | - MAX is the maximum article number. | ||
| 215 | - MIN is the minimum article number.") | ||
| 216 | |||
| 217 | (defun nnatom-group-file (group) | ||
| 218 | "Return the file containing feed data for GROUP." | ||
| 219 | (expand-file-name (format "%s/%s.eld" | ||
| 220 | (string-trim (symbol-name nnatom-backend) | ||
| 221 | "nn") | ||
| 222 | (gnus-newsgroup-savable-name group)) | ||
| 223 | gnus-directory)) | ||
| 224 | |||
| 225 | (defun nnatom--read-feed (feed &optional group) | ||
| 226 | "Read FEED into a new or existing GROUP." | ||
| 227 | (let ((prefix (nnatom--backend-prefix nnatom-backend))) | ||
| 228 | (when (string-suffix-p "-ephemeral" feed) | ||
| 229 | (setq feed (or (cadr (assq (nnoo-symbol nnatom-backend 'address) | ||
| 230 | (cddr (gnus-server-to-method | ||
| 231 | (concat prefix feed))))) | ||
| 232 | feed))) | ||
| 233 | (when-let ((data (funcall nnatom-read-feed-function feed)) | ||
| 234 | (group (or (and group (string-trim group prefix)) | ||
| 235 | (cadr (assq (nnoo-symbol nnatom-backend 'name) | ||
| 236 | (gnus-server-to-method | ||
| 237 | (concat prefix feed)))) | ||
| 238 | (funcall nnatom-read-title-function data)))) | ||
| 239 | (let* ((info (gnus-get-info (concat prefix group))) | ||
| 240 | (g (or (gethash group nnatom-groups) | ||
| 241 | (nnatom-read-group group) | ||
| 242 | `[ ,group ,(make-hash-table :test 'equal) | ||
| 243 | ,(make-hash-table :test 'eql) nil 1])) | ||
| 244 | (ids (aref g 1)) | ||
| 245 | (articles (aref g 2)) | ||
| 246 | (max (aref g 3)) | ||
| 247 | (max (if max max | ||
| 248 | (setq max 0) ; Find max article number | ||
| 249 | (dolist ; remembered by Gnus. | ||
| 250 | ( r (cons (gnus-info-read info) | ||
| 251 | (gnus-info-marks info)) | ||
| 252 | max) | ||
| 253 | (mapc (lambda (x) | ||
| 254 | (let ((x (if (consp x) | ||
| 255 | (if (< (car x) (cdr x)) | ||
| 256 | (cdr x) (car x)) | ||
| 257 | x))) | ||
| 258 | (when (< max x) (setq max x)))) | ||
| 259 | (if (symbolp (car r)) (cdr r) r))))) | ||
| 260 | (feed-author (funcall nnatom-read-feed-author-function data)) | ||
| 261 | article stale) | ||
| 262 | (while (setq article (funcall nnatom-read-article-function data stale) | ||
| 263 | data (cdr article) | ||
| 264 | article (car article)) | ||
| 265 | (when-let ((id (funcall nnatom-read-id-function article)) | ||
| 266 | (id (format "<%s@%s.%s>" id group nnatom-backend))) | ||
| 267 | (let* ((num (gethash id ids)) | ||
| 268 | (update (funcall nnatom-read-update-date-function article)) | ||
| 269 | (prev-update (aref (gethash num articles | ||
| 270 | '[nil nil nil nil nil]) | ||
| 271 | 4))) | ||
| 272 | (if (or (null num) ; New article ID. | ||
| 273 | (and (null prev-update) update) | ||
| 274 | (and prev-update update | ||
| 275 | (time-less-p prev-update update))) | ||
| 276 | (let* ((num (or num (aset g 3 (cl-incf max)))) | ||
| 277 | (publish (funcall nnatom-read-publish-date-function | ||
| 278 | article))) | ||
| 279 | (setf | ||
| 280 | (gethash id (aref g 1)) num | ||
| 281 | (gethash num (aref g 2)) | ||
| 282 | `[ ,id | ||
| 283 | ,(or (funcall nnatom-read-author-function article) | ||
| 284 | feed-author group) | ||
| 285 | ,(or (funcall nnatom-read-subject-function article) | ||
| 286 | "no subject") | ||
| 287 | ,(or publish update '(0 0)) ; published | ||
| 288 | ,(or update publish '(0 0)) ; updated | ||
| 289 | ,(funcall nnatom-read-link-function article) | ||
| 290 | (,(funcall nnatom-read-summary-function article) | ||
| 291 | . ,(funcall nnatom-read-summary-type-function article)) | ||
| 292 | (,(funcall nnatom-read-content-function article) | ||
| 293 | . ,(funcall | ||
| 294 | nnatom-read-content-type-function | ||
| 295 | article))] | ||
| 296 | stale nil)) | ||
| 297 | (setq stale t))))) | ||
| 298 | (puthash group g nnatom-groups))))) | ||
| 299 | |||
| 300 | (defun nnatom-read-group (group) | ||
| 301 | "Read GROUP's information from `nnatom-directory'." | ||
| 302 | (if-let ((f (nnatom-group-file group)) | ||
| 303 | ((file-readable-p f))) | ||
| 304 | (with-temp-buffer | ||
| 305 | (insert-file-contents f) | ||
| 306 | (goto-char (point-min)) | ||
| 307 | (puthash group (read (current-buffer)) nnatom-groups)) | ||
| 308 | (nnheader-report nnatom-backend "Can't read %s"))) | ||
| 309 | |||
| 310 | (defun nnatom-write-group (group) | ||
| 311 | "Write GROUP's information to `nnatom-directory'." | ||
| 312 | (if-let ((f (nnatom-group-file group)) | ||
| 313 | ((file-writable-p f))) | ||
| 314 | (if-let ((g (gethash group nnatom-groups)) | ||
| 315 | ((vectorp g))) | ||
| 316 | (with-temp-file f | ||
| 317 | (insert ";;;; -*- mode: lisp-data -*- DO NOT EDIT\n" | ||
| 318 | (prin1-to-string g)) | ||
| 319 | t) | ||
| 320 | t) | ||
| 321 | (nnheader-report nnatom-backend "Can't write %s" f))) | ||
| 322 | |||
| 323 | ;;;; Gnus backend functions: | ||
| 324 | |||
| 325 | (defvoo nnatom-group nil | ||
| 326 | "Name of the current group.") | ||
| 327 | |||
| 328 | (defvoo nnatom-group-article-ids (make-hash-table :test 'equal) | ||
| 329 | "Hash table mapping article IDs to their article number.") | ||
| 330 | |||
| 331 | (defvoo nnatom-group-articles (make-hash-table :test 'eql) | ||
| 332 | "Hash table mapping article numbers to articles and their attributes. | ||
| 333 | |||
| 334 | Each value in this table should be a vector of the form | ||
| 335 | [ID FROM SUBJECT DATE UPDATED LINK SUMMARY CONTENT], where: | ||
| 336 | - ID is the ID of the article. | ||
| 337 | - FROM is the author of the article or group. | ||
| 338 | - SUBJECT is the subject of the article. | ||
| 339 | - DATE is the date the article was published, or last updated (time value). | ||
| 340 | - UPDATE is the date the article was last updated, or published (time value). | ||
| 341 | - LINK is the URL to the full (remote) version of the article. | ||
| 342 | - SUMMARY and CONTENT are cons cells where the car is the raw | ||
| 343 | summary/content of the article and the cdr is the \"type\" of | ||
| 344 | the summary/content (appended to \"text/\" to form the MIME type).") | ||
| 345 | |||
| 346 | (defvoo nnatom-group-article-max-num 0 | ||
| 347 | "Maximum article number for the current group.") | ||
| 348 | |||
| 349 | (defvoo nnatom-group-article-min-num 1 | ||
| 350 | "Minimum article number for the current group.") | ||
| 351 | |||
| 352 | (defvar nnatom-date-format "%F %X" | ||
| 353 | "Format of displayed dates.") | ||
| 354 | |||
| 355 | (nnoo-define-basics nnatom) | ||
| 356 | |||
| 357 | (defun nnatom-retrieve-article (article group) | ||
| 358 | (if-let ((a (gethash article (aref group 2)))) | ||
| 359 | (insert (format "221 %s Article retrieved. | ||
| 360 | From: %s\nSubject: %s\nDate: %s\nMessage-ID: %s\n.\n" | ||
| 361 | article | ||
| 362 | (aref a 1) | ||
| 363 | (aref a 2) | ||
| 364 | (format-time-string nnatom-date-format (aref a 3)) | ||
| 365 | (aref a 0))) | ||
| 366 | (insert "404 Article not found.\n.\n"))) | ||
| 367 | |||
| 368 | (deffoo nnatom-retrieve-headers (articles &optional group _server _fetch-old) | ||
| 369 | (if-let ((g (or (gethash group nnatom-groups) | ||
| 370 | (nnatom-read-group group) | ||
| 371 | `[ nil ,nnatom-group-article-ids | ||
| 372 | ,nnatom-group-articles nil nil]))) | ||
| 373 | (with-current-buffer nntp-server-buffer | ||
| 374 | (erase-buffer) | ||
| 375 | (or (and (stringp (car articles)) | ||
| 376 | (mapc (lambda (a) | ||
| 377 | (nnatom-retrieve-article | ||
| 378 | (gethash a (aref g 1)) g)) | ||
| 379 | articles)) | ||
| 380 | (and (numberp (car articles)) | ||
| 381 | (range-map (lambda (a) (nnatom-retrieve-article a g)) | ||
| 382 | articles))) | ||
| 383 | 'headers) | ||
| 384 | (nnheader-report nnatom-backend "Group %s not found" (or group "")))) | ||
| 385 | |||
| 386 | (deffoo nnatom-request-close () | ||
| 387 | (setq nnatom-groups (make-hash-table :test 'equal) | ||
| 388 | nnatom-status-string nil) | ||
| 389 | t) | ||
| 390 | |||
| 391 | (defun nnatom--print-content (content type link) | ||
| 392 | "Return CONTENT with LINK appended and formatted according to TYPE." | ||
| 393 | (when (and content type) | ||
| 394 | (let ((html (or (string= type "html") (string= type "xhtml")))) | ||
| 395 | (concat (when html "<html><head></head><body>") content "\n\n" | ||
| 396 | (and (stringp link) | ||
| 397 | (if html (format "<p><a href=\"%s\">[Link]</a></p>" link) | ||
| 398 | link)) | ||
| 399 | (when html "</body></html>"))))) | ||
| 400 | |||
| 401 | (deffoo nnatom-request-article (article &optional group _server to-buffer) | ||
| 402 | (if-let ((g (or (gethash group nnatom-groups) | ||
| 403 | (and nnatom-group | ||
| 404 | `[ nil ,nnatom-group-article-ids | ||
| 405 | ,nnatom-group-articles | ||
| 406 | ,nnatom-group-article-max-num | ||
| 407 | ,nnatom-group-article-min-num]))) | ||
| 408 | (num (or (and (stringp article) | ||
| 409 | (gethash article (aref g 1))) | ||
| 410 | (and (numberp article) article))) | ||
| 411 | ((and (<= num (aref g 3)) | ||
| 412 | (>= num (aref g 4)))) | ||
| 413 | (a (gethash num (aref g 2)))) | ||
| 414 | (with-current-buffer (or to-buffer nntp-server-buffer) | ||
| 415 | (erase-buffer) | ||
| 416 | (let* ((boundary (format "-_%s_-" nnatom-backend)) | ||
| 417 | (link (aref a 5)) | ||
| 418 | (summary (aref a 6)) | ||
| 419 | (summary-type (cdr summary)) | ||
| 420 | (summary | ||
| 421 | (nnatom--print-content (car summary) summary-type link)) | ||
| 422 | (content (aref a 7)) | ||
| 423 | (content-type (cdr content)) | ||
| 424 | (content | ||
| 425 | (nnatom--print-content (car content) content-type link))) | ||
| 426 | (insert (format | ||
| 427 | "Subject: %s\nFrom: %s\nDate: %s\nMessage-ID: %s\n" | ||
| 428 | (aref a 2) (aref a 1) | ||
| 429 | (format-time-string | ||
| 430 | nnatom-date-format (or (aref a 3) '(0 0))) | ||
| 431 | (aref a 0)) | ||
| 432 | "MIME-Version: 1.0\nContent-Type: " | ||
| 433 | (concat (cond ((and summary content) | ||
| 434 | (format "multipart/alternative; boundary=%s | ||
| 435 | --%s\nContent-Type: text/%s\n\n%s\n--%s\nContent-Type: text/%s\n\n%s\n--%s--\n" | ||
| 436 | boundary boundary summary-type | ||
| 437 | summary boundary content-type | ||
| 438 | content boundary)) | ||
| 439 | (summary (format "text/%s\n\n%s\n" | ||
| 440 | summary-type summary)) | ||
| 441 | (content (format "text/%s\n\n%s\n" | ||
| 442 | content-type content)) | ||
| 443 | (link (format "text/plain\n\n%s\n" link)) | ||
| 444 | (t "text/plain\n\n"))))) | ||
| 445 | `(,group . ,num)) | ||
| 446 | (nnheader-report nnatom-backend "No such article"))) | ||
| 447 | |||
| 448 | (deffoo nnatom-request-group (group &optional server fast _info) | ||
| 449 | (with-current-buffer nntp-server-buffer | ||
| 450 | (erase-buffer) | ||
| 451 | (if-let ((s (or server (nnoo-current-server nnatom-backend))) | ||
| 452 | (g (or (if fast | ||
| 453 | (or (gethash group nnatom-groups) | ||
| 454 | (nnatom-read-group group)) | ||
| 455 | (nnatom--read-feed s group)) | ||
| 456 | `[ ,group ,(make-hash-table :test 'equal) | ||
| 457 | ,(make-hash-table :test 'eql) 0 1]))) | ||
| 458 | (progn | ||
| 459 | (setq nnatom-group group | ||
| 460 | nnatom-group-article-ids (aref g 1) | ||
| 461 | nnatom-group-articles (aref g 2) | ||
| 462 | nnatom-group-article-max-num (aref g 3) | ||
| 463 | nnatom-group-article-min-num (aref g 4)) | ||
| 464 | (insert (format "211 %s %s %s \"%s\"" | ||
| 465 | (hash-table-count nnatom-group-article-ids) | ||
| 466 | nnatom-group-article-min-num | ||
| 467 | nnatom-group-article-max-num group)) | ||
| 468 | t) | ||
| 469 | (insert "404 group not found") | ||
| 470 | (nnheader-report nnatom-backend "Group %s not found" group)))) | ||
| 471 | |||
| 472 | (deffoo nnatom-close-group (group &optional _server) | ||
| 473 | (if (nnatom-write-group group) | ||
| 474 | (setq nnatom-group nil | ||
| 475 | nnatom-group-article-ids (make-hash-table :test 'equal) | ||
| 476 | nnatom-group-articles (make-hash-table :test 'eql) | ||
| 477 | nnatom-group-article-max-num 0 | ||
| 478 | nnatom-group-article-min-num 1) | ||
| 479 | (nnheader-report nnatom-backend "Couldn't write group %s" group))) | ||
| 480 | |||
| 481 | (deffoo nnatom-request-list (&optional server) | ||
| 482 | (with-current-buffer nntp-server-buffer | ||
| 483 | (erase-buffer) | ||
| 484 | (when-let ((g (nnatom--read-feed | ||
| 485 | (or server (nnoo-current-server nnatom-backend))))) | ||
| 486 | (insert (format "\"%s\" %s %s y\n" | ||
| 487 | (aref g 0) (aref g 3) (aref g 4))) | ||
| 488 | t))) | ||
| 489 | |||
| 490 | (deffoo nnatom-request-post (&optional _server) | ||
| 491 | (nnheader-report nnatom-backend "%s is a read only backend" nnatom-backend)) | ||
| 492 | |||
| 493 | ;;;; Optional back end functions: | ||
| 494 | |||
| 495 | (deffoo nnatom-retrieve-groups (_groups &optional server) | ||
| 496 | (nnatom-request-list (or server (nnoo-current-server nnatom-backend))) | ||
| 497 | 'active) | ||
| 498 | |||
| 499 | (deffoo nnatom-request-type (_group &optional _article) | ||
| 500 | 'unknown) | ||
| 501 | |||
| 502 | (deffoo nnatom-request-delete-group (group _force &optional _server) | ||
| 503 | (delete-file (nnatom-group-file group)) | ||
| 504 | (remhash group nnatom-groups) | ||
| 505 | (when (string= group nnatom-group) | ||
| 506 | (setq nnatom-group nil | ||
| 507 | nnatom-group-article-ids (make-hash-table :test 'equal) | ||
| 508 | nnatom-group-articles (make-hash-table :test 'eql) | ||
| 509 | nnatom-group-article-max-num 0 | ||
| 510 | nnatom-group-article-min-num 1)) | ||
| 511 | t) | ||
| 512 | |||
| 513 | (gnus-declare-backend (symbol-name nnatom-backend) 'address) | ||
| 514 | |||
| 515 | ;;;; Utilities: | ||
| 516 | |||
| 517 | (defmacro nnatom-define-basic-backend-interface (backend) | ||
| 518 | "Define a basic set of functions and variables for BACKEND." | ||
| 519 | (let ((bp (symbol-name backend))) | ||
| 520 | `(progn | ||
| 521 | (defvoo ,(intern (concat bp "-backend")) ',backend nil nnatom-backend) | ||
| 522 | (defvoo ,(intern (concat bp "-status-string")) | ||
| 523 | nil nil nnatom-status-string) | ||
| 524 | (defvoo ,(intern (concat bp "-group")) nil nil nnatom-group) | ||
| 525 | (defvoo ,(intern (concat bp "-groups")) | ||
| 526 | (make-hash-table :test 'equal) nil nnatom-groups) | ||
| 527 | (defvoo ,(intern (concat bp "-group-article-ids")) | ||
| 528 | (make-hash-table :test 'equal) nil nnatom-group-article-ids) | ||
| 529 | (defvoo ,(intern (concat bp "-group-articles")) | ||
| 530 | (make-hash-table :test 'eql) nil nnatom-group-articles) | ||
| 531 | (defvoo ,(intern (concat bp "-group-article-max-num")) 0 nil | ||
| 532 | nnatom-group-article-max-num) | ||
| 533 | (defvoo ,(intern (concat bp "-group-article-mix-num")) 1 nil | ||
| 534 | nnatom-group-article-min-num) | ||
| 535 | (nnoo-define-basics ,backend) | ||
| 536 | (nnoo-import ,backend (nnatom))))) | ||
| 537 | |||
| 538 | (provide 'nnatom) | ||
| 539 | |||
| 540 | ;;; nnatom.el ends here | ||