From: yamaoka Date: Tue, 14 Jan 2003 05:38:16 +0000 (+0000) Subject: Import Oort Gnus v0.11. X-Git-Tag: ognus-0_11~1 X-Git-Url: http://git.chise.org/gitweb/?a=commitdiff_plain;h=deefcadcd864c43eb6dc3191339b0d5132a40cd2;p=elisp%2Fgnus.git- Import Oort Gnus v0.11. --- diff --git a/ChangeLog b/ChangeLog index 420d38a..ceccd8c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2003-01-09 Simon Josefsson + + * etc/gnus/preview.xpm: Add. + +2003-01-06 Simon Josefsson + + * etc/gnus/receipt.xpm: Add. + +2003-01-10 Jesper Harder + + * etc/gnus/preview.xbm: Add. + +2003-01-05 Katsumi Yamaoka + + * etc/gnus/gnus.xpm (oort): Make the color replaceable. + 2002-12-05 Kai Gro,A_(Bjohann * etc/smilies/*.pbm: Made them binary. diff --git a/GNUS-NEWS b/GNUS-NEWS index e6fee1f..4a963c3 100644 --- a/GNUS-NEWS +++ b/GNUS-NEWS @@ -8,6 +8,9 @@ For older news, see Gnus info node "New Features". * Changes in Oort Gnus +** References and X-Draft-Headers are no longer generated when you + start composing messages. + ** Improved anti-speam features. ** Easy inclusion of X-Faces headers. diff --git a/README b/README index b8d2527..6249e88 100644 --- a/README +++ b/README @@ -39,7 +39,10 @@ in your Emacs, you should probably exit that Emacs and start a new one to fire up Gnus. Gnus does absolutely not work with anything older than Emacs 20.3 or -XEmacs 20.0. So you definitely need a new Emacs. +XEmacs 20.0. So you definitely need a new Emacs. + +To compile the Gnus manual, you either need a pretty new Emacs, or a +pretty new version of the texinfo tools. Then you do a `M-x gnus', and everything should... uhm... it should work, but it might not. Set `debug-on-error' to t, and mail me the diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 2ef440a..e6f6717 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,479 @@ +2003-01-12 13:46:20 Lars Magne Ingebrigtsen + + * gnus.el: Oort Gnus v0.11 is released. + +2003-01-12 Jesper Harder + + * message.el (message-fetch-reply-field): Narrow to headers. + + * gnus-msg.el (gnus-inews-do-gcc): Don't try to mark GCC's as read + if Gnus isn't alive. + +2002-01-11 Kevin Greiner + + * gnus-agent.el (gnus-agent-fetch-group-1): Remove downloadable + marks from articles that are already stored in the agent. + (gnus-agent-backup-overview-buffer): New debug tool. Creates a + backup copy of an invalid .overview file for later analysis. + +2003-01-12 Gregorio Gervasio, Jr. + + * gnus-sum.el (gnus-summary-exit): Reverse change to make group + exit work with two frames. + +2003-01-11 Fran,Ag(Bois-David Collin + + * message.el (message-forward-make-body): Use mule4. + +2003-01-11 Lars Magne Ingebrigtsen + + * message.el (message-mode-map): Move wide-reply command. + +2003-01-10 Reiner Steib + + * deuglify.el (gnus-outlook-deuglify-attrib-verb-regexp): Added + castellano. + (gnus-outlook-display-hook): New variable. + (gnus-outlook-display-article-buffer): New function. + (gnus-outlook-unwrap-lines, gnus-outlook-repair-attribution) + (gnus-outlook-deuglify-article): Made them interactive and added + optional arg. Use `g-o-d-a-b'. + (gnus-article-outlook-deuglify-article): Use `g-o-d-a-b'. + + * gnus-sum.el: Added autoloads. + (gnus-summary-mode-map): Added gnus-summary-wash-deuglify-map. + (gnus-summary-make-menu-bar): Added "(Outlook) Deuglify" menu. + +2003-01-11 Lars Magne Ingebrigtsen + + * gnus-art.el (gnus-display-mime): Use the mime emulation + variable. + + * gnus-sum.el (gnus-article-emulate-mime): New variable. + + * gnus-start.el (gnus-read-newsrc-el-file): Make sure that the + newsrc-alist is initialized properly. + + * mail-source.el (mail-sources): Autoload. + + * gnus-sum.el (gnus-summary-make-false-root-always): Default to + nil. + + * gnus-msg.el (gnus-configure-posting-styles): Make sure we don't + insert two newlines. + + * message.el (message-check-news-header-syntax): Compute the + header length correctly. + +2002-01-10 Kevin Greiner + + * gnus-agent.el (gnus-agent-expire): Do not remove article from + alist when keeping fetched article file. + (gnus-agent-retrieve-headers): When parsing response for article + numbers, use the same algorithm as gnus-agent-braid-nov to protect + against garbage in the server's response. + + * gnus-int.el (gnus-request-expire-articles, + gnus-request-move-article): Only expire when the group's server + has been agentized. + +2003-01-10 Lars Magne Ingebrigtsen + + * gnus-cite.el (gnus-cite-delete-overlays): Protect against + errors when deleting overlays. + + * gnus-score.el (gnus-score-followup): Allow tracing. + + * gnus-art.el (gnus-treat-display-face): New variable. + (article-display-face): New command. + + * gnus-fun.el (gnus-face-from-file): New function. + (gnus-convert-face-to-png): Ditto. + + * gnus-art.el (gnus-ignored-headers): Added Face. + +2003-01-10 Simon Josefsson + + * nndraft.el (nndraft-request-group): Avoid crash in + directory-files when draft directory doesn't exists. + + * gnus-sum.el (gnus-select-article-hook): Add :option. + +2003-01-10 Teodor Zlatanov + + * spam.el (spam-use-stat): new variable + (spam-group-spam-processor-stat-p) + (spam-group-ham-processor-stat-p): new convenience functions + (spam-summary-prepare-exit): add spam/ham processors to sequence + (spam-list-of-checks): add spam-use-stat to list of checks + (spam-split): conditionally load the spam-stat tables + (spam-stat-register-spam-routine, spam-stat-register-ham-routine, + spam-check-ifile): new functions + + * spam-stat.el (spam-stat): typo fix + (spam-stat-install-hooks): new variable + (spam-stat-split-fancy-spam-group): added documentation clarification + (spam-stat-split-fancy-spam-threshhold): new variable + (spam-stat-install-hooks): make hooks conditional + (spam-stat-split-fancy): use spam-stat-split-fancy-spam-threshhold + + * gnus.el (gnus-group-ham-exit-processor-stat, spam-process): add + spam-stat ham/spam processor symbols + +2003-01-10 Lars Magne Ingebrigtsen + + * gnus-start.el (gnus-read-newsrc-el-file): Make sure the .eld + file exists. + +2003-01-10 Simon Josefsson + + * gnus-sum.el (gnus-summary-read-group-1): Don't select first + undownloaded/downloadable only when unplugged. + +2003-01-10 Jesper Harder + + * gnus-srvr.el (gnus-browse-foreign-server): Optimize inner loop. + +2003-01-09 Teodor Zlatanov + + * spam.el (spam-check-ifile): fixed call-process-region to use the + db parameter only if it's set + (spam-ifile-register-with-ifile): ditto + +2003-01-09 Alex Schroeder + + * spam-stat.el (spam-stat-save): Set spam-stat-ngood and + spam-stat-nbad before creating the hash table. + (spam-stat-reset): Set spam-stat-ngood and spam-stat-nbad to 0. + Changed copyright statement to FSF. + +2002-01-09 Kevin Greiner + + * gnus-agent.el (gnus-agent-catchup): Do not mark cached nor + processable articles as read. + (gnus-agent-summary-fetch-series): Remove processable and + downloadable marks on all downloaded articles in the series. + + * nntp.el (nntp-report): Throw error after reporting the problem. + (nntp-accept-process-output): Corrected error check to report an + error when the process is nil. + +2003-01-09 Simon Josefsson + + * message.el (message-tool-bar-map): Add preview. + +2003-01-09 Jesper Harder + + * mml.el (mml-preview): Get rid of MIME handles and buffers after + previewing. + +2003-01-08 Paul Jarc + + * nnmaildir.el (nnmaildir--grp-add-art): Fix wrong-type-argument + bug when the (n+1)th article to be added to a group has a smaller + number than the n articles already added. + +2003-01-08 Jesper Harder + + * message.el (message-mode-field-menu): Use backquote. + +2003-01-08 Teodor Zlatanov + + * spam.el: fixed the BBDB autoloads again, using + bbdb-search-simple now (which is not a macro, thank god) + + * lpath.el (bbdb-search): removed function from maybe-fbind list + + * gnus.el (ham-process-destination): added new parameter for + destination of ham articles found in spam groups at summary exit + + * spam.el (spam-get-ifile-database-parameter): use spam-ifile-database-path + (spam-check-ifile, spam-ifile-register-with-ifile): use spam-get-ifile-database-parameter + (spam-ifile-database-path): added new parameter for ifile's database + (spam-move-spam-nonspam-groups-only): new parameter to determine + if spam should be moved from all groups or only some + (spam-summary-prepare-exit): fixed logic to use + spam-move-spam-nonspam-groups-only when deciding to invoke + spam-mark-spam-as-expired-and-move-routine; always invoke that + routine after the spam has been expired-or-moved in case there's + some spam left over; use spam-ham-move-routine in spam groups + (spam-ham-move-routine): new function to move ham articles to the + ham-process-destinations group parameter + +2003-01-08 Lars Magne Ingebrigtsen + + * gnus-spec.el (gnus-parse-complex-format): %~ => ~*. + + * gnus-agent.el (gnus-agent-fetch-selected-article): Use + gnus-summary-update-article-line. + +2003-01-08 Simon Josefsson + + * nnmail.el (nnmail-expiry-target-group): Request group, create it + not successful. + +2003-01-08 Katsumi Yamaoka + + * lpath.el (bbdb-records): Fbind it for both Emacs and XEmacs. + +2003-01-07 Teodor Zlatanov + + * spam.el (spam-check-ifile): fixed the spam-ifile-all-categories + logic, finally + +2003-01-08 Lars Magne Ingebrigtsen + + * gnus-spec.el (gnus-parse-format): %C is a complex format. + (gnus-parse-format): Change to %~. + + * message.el (message-generate-headers): Don't generate optional + empty headers. + +2003-01-07 Reiner Steib + + * message.el (message-cross-post-default) + (message-cross-post-note-function, message-shoot-gnksa-feet) + (message-strip-subject-trailing-was, message-change-subject) + (message-mark-insert-file, message-cross-post-followup-to) + (message-cross-post-followup-to, message-mode-map) + (message-generate-unsubscribed-mail-followup-to) + (message-make-mail-followup-to): Minor changes to doc-strings and + error messages. Updated copyright line. + + * message.el (message-make-mail-followup-to, + message-generate-unsubscribed-mail-followup-to): New function + names. Renamed functions: "-mft" -> "-mail-followup-to". + (message-make-mft, message-gen-unsubscribed-mft): Removed function + names. + + * mml.el (mml-preview-insert-mail-followup-to): New function name. + (mml-preview-insert-mft): Removed function name. + (mml-preview): Use new function names. + + * gnus-art.el (gnus-article-edit-mode-map): Use new function names. + + * message.el (message-mode-field-menu): Moved header related + commands from "Message" to "Field" menu. + +2003-01-07 Reiner Steib + + * message.el (message-generate-headers-first): Added customization + if variable is a list. + +2003-01-07 Michael Shields + + * gnus-art.el (gnus-article-next-page): Correctly handle the case + where the last line of the article is the last line of the window. + +2003-01-08 Lars Magne Ingebrigtsen + + * gnus-msg.el (gnus-debug): Use ignore-errors. + + * gnus-agent.el (gnus-agent-fetch-selected-article): Use + `gnus-summary-update-line'. + +2003-01-08 Simon Josefsson + + * gnus-art.el (gnus-unbuttonized-mime-types) + (gnus-buttonized-mime-types): Doc fix. + +2003-01-08 Jesper Harder + + * mm-decode.el (mm-inline-media-tests): .xpm is 'x-xpixmap'. + +2003-01-07 ShengHuo ZHU + + * nnrss.el (nnrss-group-alist): Add and clear up. + +2003-01-07 Teodor Zlatanov + + * spam.el: removed unnecessary condition-case for loading bbdb-com.el + + * lpath.el (bbdb-search): added BBDB functions for a better way to + fix missing functions + + * spam.el (spam-check-ifile): if should be an unless + + * spam.el: define 'ignore alias for spam-BBDB-register-routine, + spam-enter-ham-BBDB, and bbdb-create-internal initially to hush up warnings + (spam-ifile-all-categories): doc string fixed to be less than 80 chars + +2003-01-07 Lars Magne Ingebrigtsen + + * gnus-sum.el (gnus-summary-make-menu-bar): Added + gnus-summary-refer-thread to thread menu. + +2002-01-07 Kevin Greiner + + * gnus-agent.el (gnus-agent-fetch-group-1): When fetching within a + summary buffer, articles that cannot be fetched are marked as + canceled. + + * nntp.el (nntp-with-open-group): The quit signal handler must + propagate the quit signal to the next outer handler so that the + caller knows that the request aborted abnormally. + +2003-01-07 Teodor Zlatanov + + * spam.el (spam-check-ifile, spam-ifile-register-with-ifile) + (spam-ifile-register-spam-routine) + (spam-ifile-register-ham-routine): added ifile functionality that + does not use ifile-gnus.el to classify and register articles + (spam-get-article-as-string): convenience function + (spam-summary-prepare-exit): added ifile spam and ham registration + (spam-ifile-all-categories, spam-ifile-spam-category) + (spam-ifile-path, spam-ifile): added customization options + + * gnus.el (gnus-group-ham-exit-processor-ifile): added ifile ham + exit processor + (spam-process): added gnus-group-ham-exit-processor-ifile to the + list of choices + +2003-01-07 Lars Magne Ingebrigtsen + + * gnus-score.el (gnus-score-followup): Also score immediate + followups. + +2003-01-06 Lars Magne Ingebrigtsen + + * nnweb.el (nnweb-asynchronous-p): Changed to nil. + +2003-01-07 Simon Josefsson + + * message.el (message-mode-menu): Fix receipt balloon help. + +2003-01-07 Jesper Harder + + * gnus-msg.el (gnus-group-post-news): Don't assume that "" will + always be interpreted as news. + +2003-01-07 Simon Josefsson + + * gnus-sieve.el (gnus-sieve-script): Use the crosspost argument to + gnus-sieve-script, instead of the global variable + gnus-sieve-crosspost. One-line patch from Steinar Bang + . + +2002-01-06 Kevin Greiner + + * gnus.el: Renamed gnus-summary-*-uncached-face as + gnus-summary-*-undownloaded-face to avoid confusing the agent with + the cache. + + * gnus-sum.el: Ditto. + +2002-01-06 Kevin Greiner + + * gnus-agent.el (gnus-agent-fetch-group): Modified to permit execution + in either the group or summary buffer. + New command "JS", in summary buffer, will fetch articles per the + group's category, predicate, and processable flags. + (gnus-agent-summary-fetch-series): Rewritten to call + gnus-agent-session-fetch-group once with all articles in the + series. + (gnus-agent-summary-fetch-group): Fixed bug and modified code to + return list of fetched articles. + (gnus-agent-fetch-articles): Split fetch list into sublists such + that the article buffer is only slightly larger than + gnus-agent-max-fetch-size. Added unwind-protect to ensure that + the group's article alist is saved. + (gnus-agent-fetch-headers): The 'killed' and 'cached' marks no + longer result in the agent trying to fetch an article. + (gnus-agent-fetch-group-1): Can now be called in either the group + or summary buffer. Removed the max-fetch-size code that I added + on 2002-12-13 as that capability is now part of + gnus-agent-fetch-articles. Added code to update summary buffer. + When called in the group buffer, articles that can not be fetched + are AUTOMATICALLY MARKED AS READ. + + * gnus-sum.el (): Modified eval-when-compile to minimize + misleading compilation warnings. + (gnus-update-summary-mark-positions): Changed code to use + gnus-undownloaded-mark rather than gnus-downloaded-mark. + + * nnheader.el (nnheader-insert-nov-file): Do not try to insert an + empty file as the parser assumes that the file isn't empty. + + * nntp.el (nntp-send-string): The process-send-string call can, + because it performs I/O on the process, change the process' state + from open to closed. If this happens, call nntp-report + immediately to report the broken connection. + (nntp-report): Rewritten to avoid needing a global variable to + determine the appropriate course of action. Instead, two function + implementations are provided and the nntp-report function value is + bound to the appropriate implementation. + (nntp-retrieve-data): Moved nntp-report call to end of implementation. + (nntp-with-open-group): Now binds nntp-report's function cell + rather than binding gnus-with-open-group-first-pass. Added a + condition-case to detect a quit during a nntp command. When the + quit occurs, the current connection is closed as a fetch articles + request could have several megabytes queued up for reading. + (nntp-retrieve-headers): Bind articles to itself. If + nntp-with-open-group repeats this command, I must have access to + the original list of articles. + (nntp-retrieve-groups): Ditto for groups. + (nntp-retrieve-articles): Ditto for articles. + (*): Replaced nntp-possibly-change-group calls to + nntp-with-open-group forms in all, but one, occurrance. + (nntp-accept-process-output): Bug fix. Detect when called with + null process. + +2003-01-06 Jesper Harder + + * mm-util.el (mm-find-mime-charset-region): Don't do Latin-9 hack + if we don't need to. + (mm-iso-8859-x-to-15-region): Fix misplaced parenthesis. + +2003-01-06 Lars Magne Ingebrigtsen + + * gnus-group.el (gnus-group-make-web-group): Pass the select + method on to group-create. + (gnus-group-line-format-alist): %U is an integer. + + * gnus-sum.el (gnus-summary-exit-no-update): Don't update + ephemeral groups. + (gnus-summary-read-group-1): Ditto. + (gnus-group-make-articles-read): Ditto. + + * mm-url.el (mm-url-program): Doc fix. + + * message.el (message-mode-map): Rebound + message-insert-wide-reply. + +2003-01-05 Katsumi Yamaoka + + * gnus-xmas.el (gnus-xmas-group-startup-message): Bind the oort + color as `gnus-group-startup-message' does. + +2003-01-05 Teodor Zlatanov + + * spam.el: fixed line lengths to 80 chars or less + + * gnus-sum.el (gnus-read-mark-p): added the spam-mark as a + "not-read" mark + (gnus-summary-mark-forward): added the spam-mark to the list of + marks not to be marked as "read" when viewed + +2003-01-05 Lars Magne Ingebrigtsen + + * gnus-msg.el (gnus-inews-make-draft): Quote article-reply. + + * gnus-group.el (gnus-number-of-unseen-articles-in-group): + Protect against unactive groups. + + * message.el (message-check-news-header-syntax): Check long + header lines. + (message-check-news-header-syntax): Update `start'. + + * gnus-group.el (gnus-group-expire-articles): Doc fix. + (gnus-group-line-format): %U. + (gnus-group-line-format-alist): ?U. + (gnus-number-of-unseen-articles-in-group): New function. + + * nntp.el (nntp-accept-process-output): Use a 0.1 second timeout. + + * gnus.el (gnus-version-number): Bump version number. + 2003-01-05 01:53:30 Lars Magne Ingebrigtsen * gnus.el: Oort Gnus v0.10 is released. @@ -19,18 +495,18 @@ 2003-01-04 Lars Magne Ingebrigtsen - * gnus.el (gnus-variable-list): Write gnus-format-specs last. + * gnus.el (gnus-variable-list): Write gnus-format-specs last. * gnus-sum.el (gnus-summary-goto-subjects): Fix typo. 2003-01-04 Kevin Ryde * gnus-art.el (gnus-mime-jka-compr-maybe-uncompress): New - function. + function. 2003-01-04 Lars Magne Ingebrigtsen - * gnus-sum.el (gnus-summary-exit): Bind gnus-group-is-exiting-p. + * gnus-sum.el (gnus-summary-exit): Bind gnus-group-is-exiting-p. (gnus-summary-read-group-1): Update group line. (gnus-summary-exit-no-update): Update group on exit. @@ -63,12 +539,12 @@ 2003-01-02 Teodor Zlatanov * spam.el (spam-group-spam-contents-p, spam-group-ham-contents-p) - (spam-group-processor-p, spam-group-processor-bogofilter-p) - (spam-group-processor-ifile-p, spam-group-processor-blacklist-p) - (spam-group-processor-whitelist-p, spam-group-processor-BBDB-p) - (spam-mark-spam-as-expired-and-move-routine) - (spam-generic-register-routine, spam-BBDB-register-routine) - (spam-ifile-register-routine, spam-blacklist-register-routine) + (spam-group-processor-p, spam-group-processor-bogofilter-p) + (spam-group-processor-ifile-p, spam-group-processor-blacklist-p) + (spam-group-processor-whitelist-p, spam-group-processor-BBDB-p) + (spam-mark-spam-as-expired-and-move-routine) + (spam-generic-register-routine, spam-BBDB-register-routine) + (spam-ifile-register-routine, spam-blacklist-register-routine) (spam-whitelist-register-routine): new functions (spam-summary-prepare-exit): added summary exit processing (expire or move) of spam-marked articles for spam groups; added slots for @@ -80,7 +556,7 @@ (pop3-read-response): Ditto. * gnus-msg.el (gnus-setup-message): Get the evaliation order - right. + right. (gnus-inews-make-draft): New function. (gnus-setup-message): Use it. diff --git a/lisp/deuglify.el b/lisp/deuglify.el index 81ba9e9..915a625 100644 --- a/lisp/deuglify.el +++ b/lisp/deuglify.el @@ -266,7 +266,7 @@ :group 'gnus-outlook-deuglify) (defcustom gnus-outlook-deuglify-attrib-verb-regexp - "wrote\\|writes\\|says\\|schrieb\\|schreibt\\|meinte\\|skrev\\|a écrit\\|schreef" + "wrote\\|writes\\|says\\|schrieb\\|schreibt\\|meinte\\|skrev\\|a écrit\\|schreef\\|escribió" "Regular expression matching the verb used in an attribution line." :type 'string :group 'gnus-outlook-deuglify) @@ -277,17 +277,35 @@ :type 'string :group 'gnus-outlook-deuglify) +;;;###autoload +(defcustom gnus-outlook-display-hook nil + "A hook called after an deuglified article has been prepared. +It is run after `gnus-article-prepare-hook'." + :type 'hook + :group 'gnus-outlook-deuglify) ;; Functions +(defun gnus-outlook-display-article-buffer () + "Redisplay current buffer or article buffer." + (with-current-buffer (or gnus-article-buffer (current-buffer)) + ;; "Emulate" `gnus-article-prepare-display' without calling + ;; it. Calling `gnus-article-prepare-display' on an already + ;; prepared article removes all MIME parts. I'm unsure whether + ;; this is a bug or not. + (gnus-article-highlight t) + (gnus-treat-article nil) + (gnus-run-hooks 'gnus-article-prepare-hook + 'gnus-outlook-display-hook))) + ;;;###autoload -(defun gnus-outlook-unwrap-lines () - "Unwrap lines that appear to be wrapped citation lines. +(defun gnus-outlook-unwrap-lines (&optional nodisplay) + "Unwrap lines that appear to be wrapped citation lines. You can control what lines will be unwrapped by frobbing -`gnus-outlook-deuglify-unwrap-min' and -`gnus-outlook-deuglify-unwrap-max', indicating the miminum and maximum -length of an unwrapped citation line." - (interactive) +`gnus-outlook-deuglify-unwrap-min' and `gnus-outlook-deuglify-unwrap-max', +indicating the miminum and maximum length of an unwrapped citation line. If +NODISPLAY is non-nil, don't redisplay the article buffer." + (interactive "P") (save-excursion (let ((case-fold-search nil) (inhibit-read-only t) @@ -308,7 +326,8 @@ length of an unwrapped citation line." (< (+ len12 len3) gnus-outlook-deuglify-unwrap-max)) (progn (replace-match "\\1\\2 \\3") - (goto-char (match-beginning 0)))))))))) + (goto-char (match-beginning 0))))))))) + (unless nodisplay (gnus-outlook-display-article-buffer))) (defun gnus-outlook-rearrange-article (attr-start) "Put the text from `attr-start' to the end of buffer at the top of the article buffer." @@ -400,45 +419,46 @@ length of an unwrapped citation line." (match-beginning 0))))))) ;;;###autoload -(defun gnus-outlook-repair-attribution () - "Repair a broken attribution line." - (interactive) - (or - (gnus-outlook-repair-attribution-other) - (gnus-outlook-repair-attribution-block) - (gnus-outlook-repair-attribution-outlook))) - -(defun gnus-outlook-rearrange-citation () - "Repair broken citations." - (let ((attrib-start (gnus-outlook-repair-attribution))) +(defun gnus-outlook-repair-attribution (&optional nodisplay) + "Repair a broken attribution line. +If NODISPLAY is non-nil, don't redisplay the article buffer." + (interactive "P") + (let ((attrib-start + (or + (gnus-outlook-repair-attribution-other) + (gnus-outlook-repair-attribution-block) + (gnus-outlook-repair-attribution-outlook)))) + (unless nodisplay (gnus-outlook-display-article-buffer)) + attrib-start)) + +(defun gnus-outlook-rearrange-citation (&optional nodisplay) + "Repair broken citations. +If NODISPLAY is non-nil, don't redisplay the article buffer." + (interactive "P") + (let ((attrib-start (gnus-outlook-repair-attribution 'nodisplay))) ;; rearrange citations if an attribution line has been recognized (if attrib-start - (gnus-outlook-rearrange-article attrib-start)))) + (gnus-outlook-rearrange-article attrib-start))) + (unless nodisplay (gnus-outlook-display-article-buffer))) ;;;###autoload -(defun gnus-outlook-deuglify-article () - "Deuglify broken Outlook (Express) articles." - (interactive) +(defun gnus-outlook-deuglify-article (&optional nodisplay) + "Deuglify broken Outlook (Express) articles. +If NODISPLAY is non-nil, don't redisplay the article buffer." + (interactive "P") ;; apply treatment of dumb quotes (gnus-article-treat-dumbquotes) ;; repair wrapped cited lines - (gnus-outlook-unwrap-lines) + (gnus-outlook-unwrap-lines 'nodisplay) ;; repair attribution line - (gnus-outlook-rearrange-citation)) + (gnus-outlook-rearrange-citation 'nodisplay) + (unless nodisplay (gnus-outlook-display-article-buffer))) ;;;###autoload (defun gnus-article-outlook-deuglify-article () "Deuglify broken Outlook (Express) articles and redisplay." (interactive) - (gnus-outlook-deuglify-article) - (with-current-buffer (or gnus-article-buffer (current-buffer)) - ;; "Emulate" `gnus-article-prepare-display' without calling - ;; it. Calling `gnus-article-prepare-display' on an already - ;; prepared article removes all MIME parts. I'm unsure whether - ;; this is a bug or not. - (gnus-article-highlight t) - (gnus-treat-article nil) - (gnus-run-hooks 'gnus-article-prepare-hook))) + (gnus-outlook-deuglify-article nil)) (provide 'deuglify) diff --git a/lisp/gnus-agent.el b/lisp/gnus-agent.el index bfcfa28..f4f3cca 100644 --- a/lisp/gnus-agent.el +++ b/lisp/gnus-agent.el @@ -333,6 +333,7 @@ node `(gnus)Server Buffer'.") (gnus-define-keys gnus-agent-summary-mode-map "Jj" gnus-agent-toggle-plugged "Ju" gnus-agent-summary-fetch-group + "JS" gnus-agent-fetch-group "Js" gnus-agent-summary-fetch-series "J#" gnus-agent-mark-article "J\M-#" gnus-agent-unmark-article @@ -532,12 +533,13 @@ be a select method." (error "Groups can't be fetched when Gnus is unplugged")) (gnus-group-iterate n 'gnus-agent-fetch-group)) -(defun gnus-agent-fetch-group (group) +(defun gnus-agent-fetch-group (&optional group) "Put all new articles in GROUP into the Agent." (interactive (list (gnus-group-group-name))) (let ((state gnus-plugged)) (unwind-protect (progn + (setq group (or group gnus-newsgroup-name)) (unless group (error "No group on the current line")) (unless state @@ -768,27 +770,37 @@ article's mark is toggled." (setq gnus-newsgroup-undownloaded (cdr undownloaded)))))) (defun gnus-agent-catchup () - "Mark all undownloaded articles as read." + "Mark all articles as read that are neither cached, downloaded, nor downloadable." (interactive) (save-excursion - (while gnus-newsgroup-undownloaded - (gnus-summary-mark-article - (pop gnus-newsgroup-undownloaded) gnus-catchup-mark))) - (gnus-summary-position-point)) + (let ((articles gnus-newsgroup-undownloaded)) + (when (or gnus-newsgroup-downloadable + gnus-newsgroup-cached) + (setq articles (gnus-sorted-ndifference (gnus-sorted-ndifference (copy-sequence articles) gnus-newsgroup-downloadable) gnus-newsgroup-cached))) + + (while articles + (gnus-summary-mark-article + (pop articles) gnus-catchup-mark))) + (gnus-summary-position-point))) (defun gnus-agent-summary-fetch-series () (interactive) - (let ((dl gnus-newsgroup-downloadable)) - (while gnus-newsgroup-processable - (let* ((art (car (last gnus-newsgroup-processable))) - (gnus-newsgroup-downloadable (list art))) - (gnus-summary-goto-subject art) - (sit-for 0) - (gnus-agent-summary-fetch-group) - (setq dl (delq art dl)) - (gnus-summary-remove-process-mark art) - (sit-for 0))) - (setq gnus-newsgroup-downloadable dl))) + (when gnus-newsgroup-processable + (setq gnus-newsgroup-downloadable + (let* ((dl gnus-newsgroup-downloadable) + (gnus-newsgroup-downloadable (sort (copy-sequence gnus-newsgroup-processable) '<)) + (fetched-articles (gnus-agent-summary-fetch-group))) + ;; The preceeding call to (gnus-agent-summary-fetch-group) + ;; updated gnus-newsgroup-downloadable to remove each + ;; article successfully fetched. + + ;; For each article that I processed, remove its + ;; processable mark IF the article is no longer + ;; downloadable (i.e. it's already downloaded) + (dolist (article gnus-newsgroup-processable) + (unless (memq article gnus-newsgroup-downloadable) + (gnus-summary-remove-process-mark article))) + (gnus-sorted-ndifference dl fetched-articles))))) (defun gnus-agent-summary-fetch-group (&optional all) "Fetch the downloadable articles in the group. @@ -798,7 +810,8 @@ Optional arg ALL, if non-nil, means to fetch all articles." (if all gnus-newsgroup-articles gnus-newsgroup-downloadable)) (gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)) - (state gnus-plugged)) + (state gnus-plugged) + fetched-articles) (unwind-protect (progn (unless state @@ -806,19 +819,22 @@ Optional arg ALL, if non-nil, means to fetch all articles." (unless articles (error "No articles to download")) (gnus-agent-with-fetch - (setq gnus-newsgroup-undownloaded - (gnus-sorted-ndifference gnus-newsgroup-undownloaded - (gnus-agent-fetch-articles gnus-newsgroup-name articles)))) + (setq gnus-newsgroup-undownloaded + (gnus-sorted-ndifference gnus-newsgroup-undownloaded + (setq fetched-articles (gnus-agent-fetch-articles gnus-newsgroup-name articles))))) (save-excursion - (dolist (article articles) + + (dolist (article articles) (setq gnus-newsgroup-downloadable (delq article gnus-newsgroup-downloadable)) - (if gnus-agent-mark-unread-after-downloaded - (gnus-summary-mark-article article gnus-unread-mark)) - (gnus-summary-update-download-mark article)))) + (if gnus-agent-mark-unread-after-downloaded + (gnus-summary-mark-article article gnus-unread-mark)) + (when (gnus-summary-goto-subject article nil t) + (gnus-summary-update-download-mark article))))) (when (and (not state) gnus-plugged) - (gnus-agent-toggle-plugged nil))))) + (gnus-agent-toggle-plugged nil))) + fetched-articles)) (defun gnus-agent-fetch-selected-article () "Fetch the current article as it is selected. @@ -829,8 +845,11 @@ This can be added to `gnus-select-article-hook' or (when (gnus-agent-fetch-articles gnus-newsgroup-name (list gnus-current-article)) - (setq gnus-newsgroup-undownloaded (delq gnus-current-article gnus-newsgroup-undownloaded)) - (gnus-summary-update-article gnus-current-article))))) + (setq gnus-newsgroup-undownloaded + (delq gnus-current-article gnus-newsgroup-undownloaded)) + (gnus-summary-update-article-line + gnus-current-article + (gnus-summary-article-header gnus-current-article)))))) ;;; ;;; Internal functions @@ -958,84 +977,120 @@ This can be added to `gnus-select-article-hook' or (defun gnus-agent-fetch-articles (group articles) "Fetch ARTICLES from GROUP and put them into the Agent." - (gnus-agent-load-alist group) (when articles - ;; Prune off articles that we have already fetched. - (while (and articles - (cdr (assq (car articles) gnus-agent-article-alist))) - (pop articles)) - (let ((arts articles)) - (while (cdr arts) - (if (cdr (assq (cadr arts) gnus-agent-article-alist)) - (setcdr arts (cddr arts)) - (setq arts (cdr arts))))) - (when articles - (let* ((fetched-articles (list nil)) - (tail-fetched-articles fetched-articles) - (dir (concat - (gnus-agent-directory) - (gnus-agent-group-path group) "/")) - (date (time-to-days (current-time))) - (case-fold-search t) - pos crosses id) - (gnus-make-directory dir) - (gnus-message 7 "Fetching articles for %s..." group) - ;; Fetch the articles from the backend. - (if (gnus-check-backend-function 'retrieve-articles group) - (setq pos (gnus-retrieve-articles articles group)) - (with-temp-buffer - (let (article) - (while (setq article (pop articles)) - (gnus-message 10 "Fetching article %s for %s..." - article group) - (when (or - (gnus-backlog-request-article group article - nntp-server-buffer) - (gnus-request-article article group)) - (goto-char (point-max)) - (push (cons article (point)) pos) - (insert-buffer-substring nntp-server-buffer))) - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - (setq pos (nreverse pos))))) - ;; Then save these articles into the Agent. - (save-excursion - (set-buffer nntp-server-buffer) - (while pos - (narrow-to-region (cdar pos) (or (cdadr pos) (point-max))) - (goto-char (point-min)) - (unless (eobp) ;; Don't save empty articles. - (when (search-forward "\n\n" nil t) - (when (search-backward "\nXrefs: " nil t) - ;; Handle cross posting. - (goto-char (match-end 0)) ; move to end of header name - (skip-chars-forward "^ ") ; skip server name - (skip-chars-forward " ") - (setq crosses nil) - (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *") - (push (cons (buffer-substring (match-beginning 1) - (match-end 1)) - (string-to-int (buffer-substring (match-beginning 2) - (match-end 2)))) - crosses) - (goto-char (match-end 0))) - (gnus-agent-crosspost crosses (caar pos) date))) - (goto-char (point-min)) - (if (not (re-search-forward - "^Message-ID: *<\\([^>\n]+\\)>" nil t)) - (setq id "No-Message-ID-in-article") - (setq id (buffer-substring (match-beginning 1) (match-end 1)))) - (let ((coding-system-for-write - gnus-agent-file-coding-system)) - (write-region (point-min) (point-max) - (concat dir (number-to-string (caar pos))) - nil 'silent)) - - (gnus-agent-append-to-list tail-fetched-articles (caar pos))) - (widen) - (pop pos))) - - (gnus-agent-save-alist group (cdr fetched-articles) date) - (cdr fetched-articles))))) + (gnus-agent-load-alist group) + (let* ((alist gnus-agent-article-alist) + (headers (if (< (length articles) 2) nil gnus-newsgroup-headers)) + (selected-sets (list nil)) + (current-set-size 0) + article + header-number) + ;; Check each article + (while (setq article (pop articles)) + ;; Skip alist entries preceeding this article + (while (> article (or (caar alist) (1+ article))) + (setq alist (cdr alist))) + + ;; Prune off articles that we have already fetched. + (unless (and (eq article (caar alist)) + (cdar alist)) + ;; Skip headers preceeding this article + (while (> article + (setq header-number + (let* ((header (car headers))) + (if header + (mail-header-number header) + (1+ article))))) + (setq headers (cdr headers))) + + ;; Add this article to the current set + (setcar selected-sets (cons article (car selected-sets))) + + ;; Update the set size, when the set is too large start a + ;; new one. I do this after adding the article as I want at + ;; least one article in each set. + (when (< gnus-agent-max-fetch-size + (setq current-set-size (+ current-set-size (if (= header-number article) + (mail-header-chars (car headers)) + 0)))) + (setcar selected-sets (nreverse (car selected-sets))) + (setq selected-sets (cons nil selected-sets) + current-set-size 0)))) + + (when (or (cdr selected-sets) (car selected-sets)) + (let* ((fetched-articles (list nil)) + (tail-fetched-articles fetched-articles) + (dir (concat + (gnus-agent-directory) + (gnus-agent-group-path group) "/")) + (date (time-to-days (current-time))) + (case-fold-search t) + pos crosses id) + + (setcar selected-sets (nreverse (car selected-sets))) + (setq selected-sets (nreverse selected-sets)) + + (gnus-make-directory dir) + (gnus-message 7 "Fetching articles for %s..." group) + + (unwind-protect + (while (setq articles (pop selected-sets)) + ;; Fetch the articles from the backend. + (if (gnus-check-backend-function 'retrieve-articles group) + (setq pos (gnus-retrieve-articles articles group)) + (with-temp-buffer + (let (article) + (while (setq article (pop articles)) + (gnus-message 10 "Fetching article %s for %s..." + article group) + (when (or + (gnus-backlog-request-article group article + nntp-server-buffer) + (gnus-request-article article group)) + (goto-char (point-max)) + (push (cons article (point)) pos) + (insert-buffer-substring nntp-server-buffer))) + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + (setq pos (nreverse pos))))) + ;; Then save these articles into the Agent. + (save-excursion + (set-buffer nntp-server-buffer) + (while pos + (narrow-to-region (cdar pos) (or (cdadr pos) (point-max))) + (goto-char (point-min)) + (unless (eobp) ;; Don't save empty articles. + (when (search-forward "\n\n" nil t) + (when (search-backward "\nXrefs: " nil t) + ;; Handle cross posting. + (goto-char (match-end 0)) ; move to end of header name + (skip-chars-forward "^ ") ; skip server name + (skip-chars-forward " ") + (setq crosses nil) + (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *") + (push (cons (buffer-substring (match-beginning 1) + (match-end 1)) + (string-to-int (buffer-substring (match-beginning 2) + (match-end 2)))) + crosses) + (goto-char (match-end 0))) + (gnus-agent-crosspost crosses (caar pos) date))) + (goto-char (point-min)) + (if (not (re-search-forward + "^Message-ID: *<\\([^>\n]+\\)>" nil t)) + (setq id "No-Message-ID-in-article") + (setq id (buffer-substring (match-beginning 1) (match-end 1)))) + (let ((coding-system-for-write + gnus-agent-file-coding-system)) + (write-region (point-min) (point-max) + (concat dir (number-to-string (caar pos))) + nil 'silent)) + + (gnus-agent-append-to-list tail-fetched-articles (caar pos))) + (widen) + (pop pos)))) + + (gnus-agent-save-alist group (cdr fetched-articles) date)) + (cdr fetched-articles)))))) (defun gnus-agent-crosspost (crosses article &optional date) (setq date (or date t)) @@ -1067,11 +1122,24 @@ This can be added to `gnus-select-article-hook' or (gnus-agent-check-overview-buffer)) (pop crosses)))) +(defun gnus-agent-backup-overview-buffer () + (when gnus-newsgroup-name + (let ((root (gnus-agent-article-name ".overview" gnus-newsgroup-name)) + (cnt 0) + name) + (while (file-exists-p (setq name (concat root "~" (int-to-string (setq cnt (1+ cnt))) "~")))) + (write-region (point-min) (point-max) name nil 'no-msg) + (gnus-message 1 "Created backup copy of overview in %s." name) + ) + ) + t) + (defun gnus-agent-check-overview-buffer (&optional buffer) "Check the overview file given for sanity. In particular, checks that the file is sorted by article number and that there are no duplicates." - (let ((prev-num -1)) + (let ((prev-num -1) + (backed-up nil)) (save-excursion (when buffer (set-buffer buffer)) @@ -1087,22 +1155,30 @@ and that there are no duplicates." (cond ((or (not (integerp cur)) (not (eq (char-after) ?\t))) + (or backed-up + (setq backed-up (gnus-agent-backup-overview-buffer))) (gnus-message 1 "Overview buffer contains garbage '%s'." (buffer-substring p (gnus-point-at-eol)))) ((= cur prev-num) - (gnus-message 1 + (or backed-up + (setq backed-up (gnus-agent-backup-overview-buffer))) + (gnus-message 1 "Duplicate overview line for %d" cur) (delete-region (point) (progn (forward-line 1) (point)))) ((< cur 0) - (gnus-message 1 "Junk article number %d" cur) + (or backed-up + (setq backed-up (gnus-agent-backup-overview-buffer))) + (gnus-message 1 "Junk article number %d" cur) (delete-region (point) (progn (forward-line 1) (point)))) ((< cur prev-num) (sort-numeric-fields 1 (point-min) (point-max)) (goto-char (point-min)) (setq prev-num -1) - (gnus-message 1 "Overview buffer not sorted!")) + (or backed-up + (setq backed-up (gnus-agent-backup-overview-buffer))) + (gnus-message 1 "Overview buffer not sorted!")) (t (setq prev-num cur))) (forward-line 1))))))) @@ -1151,7 +1227,7 @@ article numbers will be returned." ;; interesting marks. (We have to fetch articles with boring marks ;; because otherwise the agent will remove their marks.) (dolist (arts (gnus-info-marks (gnus-get-info group))) - (unless (memq (car arts) '(seen recent)) + (unless (memq (car arts) '(seen recent killed cache)) (setq articles (gnus-range-add articles (cdr arts))))) (setq articles (sort (gnus-uncompress-sequence articles) '<))) @@ -1181,6 +1257,9 @@ article numbers will be returned." ;; that no headers need to be fetched. -- Kevin (setq articles (gnus-list-range-intersection articles (list (cons low high))))))) + + (gnus-message 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'" (gnus-compress-sequence articles t)) + (save-excursion (set-buffer nntp-server-buffer) @@ -1449,31 +1528,42 @@ of FILE placing the combined headers in nntp-server-buffer." "Fetch GROUP." (let ((gnus-command-method method) (gnus-newsgroup-name group) - gnus-newsgroup-dependencies gnus-newsgroup-headers - gnus-newsgroup-scored gnus-headers gnus-score - gnus-use-cache articles arts - category predicate info marks score-param + (gnus-newsgroup-dependencies gnus-newsgroup-dependencies) + (gnus-newsgroup-headers gnus-newsgroup-headers) + (gnus-newsgroup-scored gnus-newsgroup-scored) + (gnus-use-cache gnus-use-cache) (gnus-summary-expunge-below gnus-summary-expunge-below) (gnus-summary-mark-below gnus-summary-mark-below) (gnus-orphan-score gnus-orphan-score) ;; Maybe some other gnus-summary local variables should also ;; be put here. + + gnus-headers + gnus-score + articles arts + category predicate info marks score-param ) (unless (gnus-check-group group) (error "Can't open server for %s" group)) ;; Fetch headers. - (when (or (gnus-active group) + (when (or gnus-newsgroup-active + (gnus-active group) (gnus-activate-group group)) - (let ((marked-articles nil)) + (let ((marked-articles gnus-newsgroup-downloadable)) ;; Identify the articles marked for download - (dolist (mark gnus-agent-download-marks) - (let ((arts (cdr (assq mark (gnus-info-marks - (setq info (gnus-get-info group))))))) - (when arts - (setq marked-articles (nconc (gnus-uncompress-range arts) - marked-articles)) - ))) + (unless gnus-newsgroup-active ;; This needs to be a + ;; gnus-summary local variable + ;; that is NOT bound to any + ;; value above (It's global + ;; value should default to nil). + (dolist (mark gnus-agent-download-marks) + (let ((arts (cdr (assq mark (gnus-info-marks + (setq info (gnus-get-info group))))))) + (when arts + (setq marked-articles (nconc (gnus-uncompress-range arts) + marked-articles)) + )))) (setq marked-articles (sort marked-articles '<)) ;; Fetch any new articles from the server @@ -1485,11 +1575,13 @@ of FILE placing the combined headers in nntp-server-buffer." (when articles ;; Parse them and see which articles we want to fetch. (setq gnus-newsgroup-dependencies - (make-vector (length articles) 0)) + (or gnus-newsgroup-dependencies + (make-vector (length articles) 0))) (setq gnus-newsgroup-headers - (gnus-get-newsgroup-headers-xover articles nil nil - group)) + (or gnus-newsgroup-headers + (gnus-get-newsgroup-headers-xover articles nil nil + group))) ;; `gnus-agent-overview-buffer' may be killed for ;; timeout reason. If so, recreate it. (gnus-agent-create-buffer) @@ -1520,66 +1612,74 @@ of FILE placing the combined headers in nntp-server-buffer." (unless (and (eq predicate 'gnus-agent-false) (not marked-articles)) - (let* ((arts (list nil)) - (arts-tail arts) - (chunk-size 0) - (marked-articles marked-articles) - is-marked) - (while (setq gnus-headers (pop gnus-newsgroup-headers)) - (let ((num (mail-header-number gnus-headers))) - ;; Determine if this article was marked for download. - (while (and marked-articles - (cond ((< num (car marked-articles)) - nil) - ((= num (car marked-articles)) - (setq is-marked t) - nil) - (t - (setq marked-articles - (cdr marked-articles)))))) - - ;; When this article is marked, or selected by the - ;; predicate, add it to the download list - (when (or is-marked - (let ((gnus-score - (or (cdr (assq num gnus-newsgroup-scored)) - gnus-summary-default-score))) - (funcall predicate))) - (gnus-agent-append-to-list arts-tail num) - - ;; When the expected size of the fetched articles - ;; exceeds gnus-agent-max-fetch-size, perform the - ;; fetch. - (when (< gnus-agent-max-fetch-size - (setq chunk-size - (+ chunk-size - (mail-header-chars gnus-headers)))) - (gnus-agent-fetch-articles group (cdr arts)) - (setcdr arts nil) - (setq arts-tail arts) - (setq chunk-size 0))))) - - ;; Fetch all remaining articles - (when (cdr arts) - (gnus-agent-fetch-articles group (cdr arts))))) - - ;; When some, or all, of the marked articles came - ;; from the download mark. Remove that mark. I - ;; didn't do this earlier as I only want to remove - ;; the marks after the fetch is completed. - - (when marked-articles - (dolist (mark gnus-agent-download-marks) - (when (eq mark 'download) - (setq arts (assq mark (gnus-info-marks - (setq info (gnus-get-info group))))) - (when (cdr arts) - (setq marks (delq arts (gnus-info-marks info))) - (gnus-info-set-marks info marks) - (gnus-dribble-enter - (concat "(gnus-group-set-info '" - (gnus-prin1-to-string info) - ")"))))))))))) + (let ((arts (list nil))) + (let ((arts-tail arts) + (alist (gnus-agent-load-alist group)) + (marked-articles marked-articles)) + (while (setq gnus-headers (pop gnus-newsgroup-headers)) + (let ((num (mail-header-number gnus-headers))) + ;; Determine if this article is already in the cache + (while (and alist + (> num (caar alist))) + (setq alist (cdr alist))) + + (unless (and (eq num (caar alist)) + (cdar alist)) + + ;; Determine if this article was marked for download. + (while (and marked-articles + (> num (car marked-articles))) + (setq marked-articles + (cdr marked-articles))) + + ;; When this article is marked, or selected by the + ;; predicate, add it to the download list + (when (or (eq num (car marked-articles)) + (let ((gnus-score + (or (cdr (assq num gnus-newsgroup-scored)) + gnus-summary-default-score))) + (funcall predicate))) + (gnus-agent-append-to-list arts-tail num)))))) + + (let (fetched-articles) + ;; Fetch all selected articles + (setq gnus-newsgroup-undownloaded + (gnus-sorted-ndifference gnus-newsgroup-undownloaded + (setq fetched-articles (if (cdr arts) (gnus-agent-fetch-articles group (cdr arts)) nil)))) + + (let ((unfetched-articles (gnus-sorted-ndifference (cdr arts) fetched-articles))) + (if gnus-newsgroup-active + (progn + (dolist (article marked-articles) + (when (gnus-summary-goto-subject article nil t) + (gnus-summary-set-agent-mark article t))) + (dolist (article fetched-articles) + (if gnus-agent-mark-unread-after-downloaded + (gnus-summary-mark-article article gnus-unread-mark))) + (dolist (article unfetched-articles) + (gnus-summary-mark-article article gnus-canceled-mark))) + ;; When some, or all, of the marked articles came + ;; from the download mark. Remove that mark. I + ;; didn't do this earlier as I only want to remove + ;; the marks after the fetch is completed. + + (dolist (mark gnus-agent-download-marks) + (when (eq mark 'download) + (let ((marked-arts (assq mark (gnus-info-marks + (setq info (gnus-get-info group)))))) + (when (cdr marked-arts) + (setq marks (delq marked-arts (gnus-info-marks info))) + (gnus-info-set-marks info marks))))) + (let ((read (gnus-info-read (or info (setq info (gnus-get-info group)))))) + (gnus-info-set-read info (gnus-add-to-range read unfetched-articles))) + + (gnus-group-update-group group t) + (sit-for 0) + + (gnus-dribble-enter + (concat "(gnus-group-set-info '" + (gnus-prin1-to-string info) + ")")))))))))))) ;;; ;;; Agent Category Mode @@ -2099,15 +2199,15 @@ FORCE is equivalent to setting gnus-agent-expire-days to zero(0)." (alist (list nil)) (tail-alist alist)) (while dlist - (let ((new-completed (* 100.0 (/ (setq cnt (1+ cnt)) len)))) + (let ((new-completed (truncate (* 100.0 (/ (setq cnt (1+ cnt)) len))))) (when (> new-completed completed) (setq completed new-completed) (gnus-message 9 "%3d%% completed..." completed))) - (let* ((entry (car dlist)) + (let* ((entry (car dlist)) (article-number (nth 0 entry)) - (fetch-date (nth 1 entry)) - (keep (nth 2 entry)) - (marker (nth 3 entry))) + (fetch-date (nth 1 entry)) + (keep (nth 2 entry)) + (marker (nth 3 entry))) (cond ;; Kept articles are unread, marked, or special. @@ -2158,7 +2258,7 @@ FORCE is equivalent to setting gnus-agent-expire-days to zero(0)." ) (when marker - (push "NOV entry removed" article) + (push "NOV entry removed" actions) (goto-char marker) (gnus-delete-line)) @@ -2170,6 +2270,8 @@ FORCE is equivalent to setting gnus-agent-expire-days to zero(0)." (push (format "Removed %s article number from article alist" type) actions)) (gnus-message 7 "gnus-agent-expire: Article %d: %s" article-number (mapconcat 'identity actions ", ")))) + (t + (gnus-agent-append-to-list tail-alist (cons article-number fetch-date))) ) ;; Clean up markers as I want to recycle this buffer over several groups. @@ -2309,9 +2411,10 @@ FORCE is equivalent to setting gnus-agent-expire-days to zero(0)." ;; Get the list of articles that were fetched (goto-char (point-min)) - (ignore-errors - (while t - (gnus-agent-append-to-list tail-fetched-articles (read (current-buffer))) + (let ((pm (point-max))) + (while (< (point) pm) + (when (looking-at "[0-9]+\t") + (gnus-agent-append-to-list tail-fetched-articles (read (current-buffer)))) (forward-line 1))) ;; Clip this list to the headers that will actually be returned diff --git a/lisp/gnus-art.el b/lisp/gnus-art.el index ada046f..c71dfe0 100644 --- a/lisp/gnus-art.el +++ b/lisp/gnus-art.el @@ -142,7 +142,7 @@ "^X-Request-PGP:" "^X-Fingerprint:" "^X-WRIEnvto:" "^X-WRIEnvfrom:" "^X-Virus-Scanned:" "^X-Delivery-Agent:" "^Posted-Date:" "^X-Gateway:" "^X-Local-Origin:" "^X-Local-Destination:" "^X-UserInfo1:" - "^X-Received-Date:" "^X-Hashcash:") + "^X-Received-Date:" "^X-Hashcash:" "^Face:") "*All headers that start with this regexp will be hidden. This variable can also be a list of regexps of headers to be ignored. If `gnus-visible-headers' is non-nil, this variable will be ignored." @@ -710,7 +710,8 @@ displayed by the first non-nil matching CONTENT face." (defcustom gnus-unbuttonized-mime-types '(".*/.*") "List of MIME types that should not be given buttons when rendered inline. -See also `gnus-buttonized-mime-types' which may override this variable." +See also `gnus-buttonized-mime-types' which may override this variable. +This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil." :version "21.1" :group 'gnus-article-mime :type '(repeat regexp)) @@ -719,7 +720,8 @@ See also `gnus-buttonized-mime-types' which may override this variable." "List of MIME types that should be given buttons when rendered inline. If set, this variable overrides `gnus-unbuttonized-mime-types'. To see e.g. security buttons you could set this to -`(\"multipart/signed\")'." +`(\"multipart/signed\")'. +This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil." :version "21.1" :group 'gnus-article-mime :type '(repeat regexp)) @@ -1146,6 +1148,24 @@ See Info node `(gnus)Customizing Articles' and Info node :type gnus-article-treat-head-custom) (put 'gnus-treat-display-xface 'highlight t) +(defcustom gnus-treat-display-face + (and (not noninteractive) + (or (and (fboundp 'image-type-available-p) + (image-type-available-p 'png)) + (and (featurep 'xemacs) + (featurep 'png))) + 'head) + "Display Face headers. +Valid values are nil, t, `head', `last', an integer or a predicate. +See Info node `(gnus)Customizing Articles' and Info node +`(gnus)X-Face' for details." + :group 'gnus-article-treat + :version "21.1" + :link '(custom-manual "(gnus)Customizing Articles") + :link '(custom-manual "(gnus)X-Face") + :type gnus-article-treat-head-custom) +(put 'gnus-treat-display-xface 'highlight t) + (defcustom gnus-treat-display-grey-xface (and (not noninteractive) (string-match "^0x" (shell-command-to-string "uncompface")) @@ -1329,6 +1349,7 @@ It is a string, such as \"PGP\". If nil, ask user." (gnus-treat-date-user-defined gnus-article-date-user) (gnus-treat-date-iso8601 gnus-article-date-iso8601) (gnus-treat-display-xface gnus-article-display-x-face) + (gnus-treat-display-face gnus-article-display-face) (gnus-treat-hide-headers gnus-article-maybe-hide-headers) (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers) (gnus-treat-hide-signature gnus-article-hide-signature) @@ -1906,6 +1927,28 @@ unfolded." (forward-line 1) (point)))))) +(defun article-display-face () + "Display any Face headers in the header." + (interactive) + (gnus-with-article-headers + (let ((face nil)) + (save-excursion + (when (gnus-buffer-live-p gnus-original-article-buffer) + (set-buffer gnus-original-article-buffer) + (setq face (message-fetch-field "face")))) + (when face + (let ((png (gnus-convert-face-to-png face)) + image) + (when png + (setq image (gnus-create-image png 'png t)) + (gnus-article-goto-header "from") + (when (bobp) + (insert "From: [no `from' set]\n") + (forward-char -17)) + (gnus-add-wash-type 'face) + (gnus-add-image 'face image) + (gnus-put-image image))))))) + (defun article-display-x-face (&optional force) "Look for an X-Face header and display it if present." (interactive (list 'force)) @@ -3340,6 +3383,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is article-remove-cr article-remove-leading-whitespace article-display-x-face + article-display-face article-de-quoted-unreadable article-de-base64-unreadable article-decode-HZ @@ -4299,9 +4343,10 @@ If no internal viewer is available, use an external viewer." ;; We have to do this since selecting the window ;; may change the point. So we set the window point. (set-window-point window point))) - (let* ((handles (or ihandles (mm-dissect-buffer - nil gnus-article-loose-mime) - (mm-uu-dissect))) + (let* ((handles (or ihandles + (mm-dissect-buffer nil gnus-article-loose-mime) + (and gnus-article-emulate-mime + (mm-uu-dissect)))) buffer-read-only handle name type b e display) (when (and (not ihandles) (not gnus-displaying-mime)) @@ -4730,7 +4775,7 @@ Argument LINES specifies lines to be scrolled up." (if (save-excursion (end-of-line) (and (pos-visible-in-window-p) ;Not continuation line. - (eobp))) + (>= (1+ (point)) (point-max)))) ;Allow for trailing newline. ;; Nothing in this page. (if (or (not gnus-page-broken) (save-excursion @@ -5221,7 +5266,7 @@ If given a prefix, show the hidden text instead." "\C-c\C-f\C-k" message-goto-keywords "\C-c\C-f\C-u" message-goto-summary "\C-c\C-f\C-i" message-insert-or-toggle-importance - "\C-c\C-f\C-a" message-gen-unsubscribed-mft + "\C-c\C-f\C-a" message-generate-unsubscribed-mail-followup-to "\C-c\C-b" message-goto-body "\C-c\C-i" message-goto-signature diff --git a/lisp/gnus-cite.el b/lisp/gnus-cite.el index 59ccf01..a5ab854 100644 --- a/lisp/gnus-cite.el +++ b/lisp/gnus-cite.el @@ -671,7 +671,8 @@ See also the documentation for `gnus-article-highlight-citation'." (and (>= (gnus-overlay-end overlay) (point-min)) (<= (gnus-overlay-end overlay) (point-max)))) (setq gnus-cite-overlay-list (delete overlay gnus-cite-overlay-list)) - (gnus-delete-overlay overlay)))) + (ignore-errors + (gnus-delete-overlay overlay))))) (defun gnus-cite-parse-wrapper () ;; Wrap chopped gnus-cite-parse. diff --git a/lisp/gnus-fun.el b/lisp/gnus-fun.el index 190e3c4..aa711da 100644 --- a/lisp/gnus-fun.el +++ b/lisp/gnus-fun.el @@ -40,6 +40,11 @@ :group 'gnus-fun :type 'string) +(defcustom gnus-convert-image-to-face-command "djpeg %s | ppmnorm | pnmscale -width 48 -height 48 | ppmquant %d | pnmtopng" + "Command for converting a GIF to an X-Face." + :group 'gnus-fun + :type 'string) + (defun gnus-shell-command-to-string (command) "Like `shell-command-to-string' except not mingling ERROR." (with-output-to-string @@ -74,6 +79,50 @@ Output to the current buffer, replace text, and don't mingle error." (format gnus-convert-image-to-x-face-command (shell-quote-argument file))))) +(defun gnus-face-from-file (file) + "Return an Face header based on an image file." + (interactive "fImage file name:" ) + (when (file-exists-p file) + (let ((done nil) + (attempt "") + (step 72) + (quant 16)) + (while (and (not done) + (> quant 1)) + (setq attempt + (gnus-shell-command-to-string + (format gnus-convert-image-to-face-command + (shell-quote-argument file) + quant))) + (if (> (length attempt) 740) + (progn + (setq quant (- quant 2)) + (message "Length %d; trying quant %d" + (length attempt) quant)) + (setq done t))) + (if done + (mm-with-unibyte-buffer + (insert attempt) + (base64-encode-region (point-min) (point-max)) + (goto-char (point-min)) + (while (search-forward "\n" nil t) + (replace-match "")) + (goto-char (point-min)) + (while (> (- (point-max) (point)) + step) + (forward-char step) + (insert "\n ") + (setq step 76)) + (buffer-string)) + nil)))) + +;;;###autoload +(defun gnus-convert-face-to-png (face) + (mm-with-unibyte-buffer + (insert face) + (base64-decode-region (point-min) (point-max)) + (buffer-string))) + (defun gnus-convert-image-to-gray-x-face (file depth) (let* ((mapfile (mm-make-temp-file (expand-file-name "gnus." mm-tmp-directory))) diff --git a/lisp/gnus-group.el b/lisp/gnus-group.el index e6fda64..bbfe4db 100644 --- a/lisp/gnus-group.el +++ b/lisp/gnus-group.el @@ -156,6 +156,7 @@ with some simple extensions. %i Number of ticked and dormant (integer) %T Number of ticked articles (integer) %R Number of read articles (integer) +%U Number of unseen articles (integer) %t Estimated total number of articles (integer) %y Number of unread, unticked articles (integer) %G Group name (string) @@ -470,6 +471,7 @@ simple manner.") (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))))) (t number)) ?s) (?R gnus-tmp-number-of-read ?s) + (?U (gnus-number-of-unseen-articles-in-group gnus-tmp-group) ?d) (?t gnus-tmp-number-total ?d) (?y gnus-tmp-number-of-unread ?s) (?I (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) ?d) @@ -1307,6 +1309,18 @@ if it is a string, only list groups matching REGEXP." nil) (gnus-method-simplify (gnus-find-method-for-group group)))))) +(defun gnus-number-of-unseen-articles-in-group (group) + (let* ((info (nth 2 (gnus-group-entry group))) + (marked (gnus-info-marks info)) + (seen (cdr (assq 'seen marked))) + (active (gnus-active group))) + (if (not active) + 0 + (length (gnus-uncompress-range + (gnus-range-difference + (gnus-range-difference (list active) (gnus-info-read info)) + seen)))))) + (defun gnus-group-insert-group-line (gnus-tmp-group gnus-tmp-level gnus-tmp-marked number gnus-tmp-method) @@ -2464,7 +2478,9 @@ If SOLID (the prefix), create a solid group." (nnweb-type ,(intern type)) (nnweb-ephemeral-p t)))) (if solid - (gnus-group-make-group group "nnweb" "" `(,(intern type) ,search)) + (progn + (gnus-pull 'nnweb-ephemeral-p method) + (gnus-group-make-group group method)) (gnus-group-read-ephemeral-group group method t (cons (current-buffer) @@ -3031,7 +3047,8 @@ or nil if no action could be taken." num))) (defun gnus-group-expire-articles (&optional n) - "Expire all expirable articles in the current newsgroup." + "Expire all expirable articles in the current newsgroup. +Uses the process/prefix convention." (interactive "P") (let ((groups (gnus-group-process-prefix n)) group) diff --git a/lisp/gnus-int.el b/lisp/gnus-int.el index da22bb5..e52bb5e 100644 --- a/lisp/gnus-int.el +++ b/lisp/gnus-int.el @@ -507,10 +507,10 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." (gnus-get-function gnus-command-method 'request-expire-articles) articles (gnus-group-real-name group) (nth 1 gnus-command-method) force))) - (when (and gnus-agent gnus-agent-cache - (gnus-sorted-difference articles not-deleted)) - (gnus-agent-expire (gnus-sorted-difference articles not-deleted) - group 'force)) + (when (and gnus-agent gnus-agent-cache (gnus-agent-method-p gnus-command-method)) + (let ((expired-articles (gnus-sorted-difference articles not-deleted))) + (when expired-articles + (gnus-agent-expire expired-articles group 'force)))) not-deleted)) (defun gnus-request-move-article (article group server accept-function &optional last) @@ -518,7 +518,7 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." (result (funcall (gnus-get-function gnus-command-method 'request-move-article) article (gnus-group-real-name group) (nth 1 gnus-command-method) accept-function last))) - (when (and result gnus-agent gnus-agent-cache) + (when (and result gnus-agent gnus-agent-cache (gnus-agent-method-p gnus-command-method)) (gnus-agent-expire (list article) group 'force)) result)) diff --git a/lisp/gnus-msg.el b/lisp/gnus-msg.el index d3238d4..eca83e5 100644 --- a/lisp/gnus-msg.el +++ b/lisp/gnus-msg.el @@ -1,5 +1,5 @@ ;;; gnus-msg.el --- mail and post interface for Gnus -;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 ;; Free Software Foundation, Inc. ;; Author: Masanobu UMEDA @@ -351,7 +351,7 @@ Thank you for your help in stamping out bugs. (defun gnus-inews-make-draft () `(lambda () (gnus-inews-make-draft-meta-information - ,gnus-newsgroup-name ,gnus-article-reply))) + ,gnus-newsgroup-name ',gnus-article-reply))) (defvar gnus-article-reply nil) (defmacro gnus-setup-message (config &rest forms) @@ -590,7 +590,8 @@ a news." "")) ;; make sure last viewed article doesn't affect posting styles: (gnus-article-copy)) - (gnus-post-news 'post gnus-newsgroup-name))) + (gnus-post-news 'post gnus-newsgroup-name nil nil nil nil + (string= gnus-newsgroup-name "")))) (defun gnus-summary-mail-other-window (&optional arg) "Start composing a mail in another window. @@ -1484,17 +1485,15 @@ The source file has to be in the Emacs load path." (insert "------------------ Environment follows ------------------\n\n")) (while olist (if (boundp (car olist)) - (condition-case () - (pp `(setq ,(car olist) - ,(if (or (consp (setq sym (symbol-value (car olist)))) - (and (symbolp sym) - (not (or (eq sym nil) - (eq sym t))))) - (list 'quote (symbol-value (car olist))) - (symbol-value (car olist)))) - (current-buffer)) - (error - (format "(setq %s 'whatever)\n" (car olist)))) + (ignore-errors + (pp `(setq ,(car olist) + ,(if (or (consp (setq sym (symbol-value (car olist)))) + (and (symbolp sym) + (not (or (eq sym nil) + (eq sym t))))) + (list 'quote (symbol-value (car olist))) + (symbol-value (car olist)))) + (current-buffer))) (insert ";; (makeunbound '" (symbol-name (car olist)) ")\n")) (setq olist (cdr olist))) (insert "\n\n") @@ -1613,6 +1612,7 @@ this is a reply." group (gnus-status-message method)) (sit-for 2)) (when (and group-art + (gnus-alive-p) (or gnus-gcc-mark-as-read gnus-inews-mark-gcc-as-read)) (gnus-group-mark-article-read group (cdr group-art))) @@ -1860,7 +1860,9 @@ this is a reply." (let ((value ,(cdr result))) (when value (message-goto-eoh) - (insert ,header ": " value "\n")))))))) + (insert ,header ": " value) + (unless (bolp) + (insert "\n"))))))))) nil 'local)) (when (or name address) (add-hook 'message-setup-hook diff --git a/lisp/gnus-score.el b/lisp/gnus-score.el index 1bad98f..9882bad 100644 --- a/lisp/gnus-score.el +++ b/lisp/gnus-score.el @@ -1723,7 +1723,8 @@ score in `gnus-newsgroup-scored' by SCORE." (setq found t) (when trace (push - (cons (car-safe (rassq alist gnus-score-cache)) kill) + (cons (car-safe (rassq alist gnus-score-cache)) + kill) gnus-score-trace))) ;; Update expire date (unless trace @@ -1829,6 +1830,12 @@ score in `gnus-newsgroup-scored' by SCORE." (setq found (setq arts (get-text-property (point) 'articles))) ;; Found a match, update scores. (while (setq art (pop arts)) + (setcdr art (+ score (cdr art))) + (when trace + (push (cons + (car-safe (rassq alist gnus-score-cache)) + kill) + gnus-score-trace)) (when (setq new (gnus-score-add-followups (car art) score all-scores thread)) (push new news))))) diff --git a/lisp/gnus-sieve.el b/lisp/gnus-sieve.el index 2fb82f9..106160c 100644 --- a/lisp/gnus-sieve.el +++ b/lisp/gnus-sieve.el @@ -226,7 +226,7 @@ This is returned as a string." (when spec (push (concat "if " (gnus-sieve-test spec) " {\n" "\tfileinto \"" (gnus-group-real-name group) "\";\n" - (if gnus-sieve-crosspost + (if crosspost "" "\tstop;\n") "}") diff --git a/lisp/gnus-spec.el b/lisp/gnus-spec.el index cf65253..3f2ec2f 100644 --- a/lisp/gnus-spec.el +++ b/lisp/gnus-spec.el @@ -409,14 +409,14 @@ characters when given a pad value." ;; them will have the balloon-help text property. (let ((case-fold-search nil)) (if (string-match - "\\`\\(.*\\)%[0-9]?[{(«]\\(.*\\)%[0-9]?[»})]\\(.*\n?\\)\\'\\|%[-0-9]*=" + "\\`\\(.*\\)%[0-9]?[{(«]\\(.*\\)%[0-9]?[»})]\\(.*\n?\\)\\'\\|%[-0-9]*=\\|%[-0-9]*\\*" format) (gnus-parse-complex-format format spec-alist) ;; This is a simple format. (gnus-parse-simple-format format spec-alist insert)))) (defun gnus-parse-complex-format (format spec-alist) - (let (found-C) + (let ((cursor-spec nil)) (save-excursion (gnus-set-work-buffer) (insert format) @@ -445,9 +445,9 @@ characters when given a pad value." ;; Convert point position commands. (goto-char (point-min)) (let ((case-fold-search nil)) - (while (re-search-forward "%\\([-0-9]+\\)?C" nil t) + (while (re-search-forward "%\\([-0-9]+\\)?\\*" nil t) (replace-match "\"(point)\"" t t) - (setq found-C t))) + (setq cursor-spec t))) ;; Convert TAB commands. (goto-char (point-min)) (while (re-search-forward "%\\([-0-9]+\\)=" nil t) @@ -455,7 +455,7 @@ characters when given a pad value." ;; Convert the buffer into the spec. (goto-char (point-min)) (let ((form (read (current-buffer)))) - (if found-C + (if cursor-spec `(let (gnus-position) ,@(gnus-complex-form-to-spec form spec-alist) (if gnus-position diff --git a/lisp/gnus-srvr.el b/lisp/gnus-srvr.el index 351eb86..6fb9c43 100644 --- a/lisp/gnus-srvr.el +++ b/lisp/gnus-srvr.el @@ -1,5 +1,5 @@ ;;; gnus-srvr.el --- virtual server support for Gnus -;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 ;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen @@ -767,27 +767,27 @@ The following commands are available: (format "Gnus: %%b {%s:%s}" (car method) (cadr method)))) (let ((buffer-read-only nil) - charset + name (prefix (let ((gnus-select-method orig-select-method)) (gnus-group-prefixed-name "" method)))) - (dolist (group groups) - (setq charset (gnus-group-name-charset method (car group))) + (while (setq group (pop groups)) (gnus-add-text-properties (point) (prog1 (1+ (point)) (insert (format "%c%7d: %s\n" (let ((level (gnus-group-level - (concat prefix (car group))))) + (concat prefix (setq name (car group)))))) (cond ((<= level gnus-level-subscribed) ? ) ((<= level gnus-level-unsubscribed) ?U) ((= level gnus-level-zombie) ?Z) (t ?K))) (max 0 (- (1+ (cddr group)) (cadr group))) - (gnus-group-name-decode (car group) charset)))) - (list 'gnus-group (car group))) - (setq groups (cdr groups)))) + (mm-decode-coding-string + name + (inline (gnus-group-name-charset method name)))))) + (list 'gnus-group name)))) (switch-to-buffer (current-buffer))) (goto-char (point-min)) (gnus-group-position-point) diff --git a/lisp/gnus-start.el b/lisp/gnus-start.el index ecfdabc..124ac58 100644 --- a/lisp/gnus-start.el +++ b/lisp/gnus-start.el @@ -2181,21 +2181,22 @@ If FORCE is non-nil, the .newsrc file is read." (defun gnus-read-newsrc-el-file (file) (let ((ding-file (concat file "d"))) - ;; We always, always read the .eld file. - (gnus-message 5 "Reading %s..." ding-file) - (let (gnus-newsrc-assoc) - (let ((coding-system-for-read gnus-ding-file-coding-system)) - (gnus-load ding-file)) - ;; Older versions of `gnus-format-specs' are no longer valid - ;; in Oort Gnus 0.01. - (let ((version - (and gnus-newsrc-file-version - (gnus-continuum-version gnus-newsrc-file-version)))) - (when (or (not version) - (< version 5.090009)) - (setq gnus-format-specs gnus-default-format-specs))) - (when gnus-newsrc-assoc - (setq gnus-newsrc-alist gnus-newsrc-assoc))) + (when (file-exists-p ding-file) + ;; We always, always read the .eld file. + (gnus-message 5 "Reading %s..." ding-file) + (let (gnus-newsrc-assoc) + (let ((coding-system-for-read gnus-ding-file-coding-system)) + (gnus-load ding-file)) + ;; Older versions of `gnus-format-specs' are no longer valid + ;; in Oort Gnus 0.01. + (let ((version + (and gnus-newsrc-file-version + (gnus-continuum-version gnus-newsrc-file-version)))) + (when (or (not version) + (< version 5.090009)) + (setq gnus-format-specs gnus-default-format-specs))) + (when gnus-newsrc-assoc + (setq gnus-newsrc-alist gnus-newsrc-assoc)))) (gnus-make-hashtable-from-newsrc-alist) (when (file-newer-than-file-p file ding-file) ;; Old format quick file diff --git a/lisp/gnus-sum.el b/lisp/gnus-sum.el index f00538d..442fd00 100644 --- a/lisp/gnus-sum.el +++ b/lisp/gnus-sum.el @@ -46,6 +46,9 @@ (autoload 'gnus-article-outlook-deuglify-article "deuglify" "Deuglify broken Outlook (Express) articles and redisplay." t) +(autoload 'gnus-outlook-unwrap-lines "deuglify" nil t) +(autoload 'gnus-outlook-repair-attribution "deuglify" nil t) +(autoload 'gnus-outlook-rearrange-citation "deuglify" nil t) (defcustom gnus-kill-summary-on-exit t "*If non-nil, kill the summary buffer when you exit from it. @@ -109,7 +112,7 @@ given by the `gnus-summary-same-subject' variable.)" (const adopt) (const empty))) -(defcustom gnus-summary-make-false-root-always t +(defcustom gnus-summary-make-false-root-always nil "Always make a false dummy root." :group 'gnus-thread :type 'boolean) @@ -794,6 +797,7 @@ following hook: (defcustom gnus-select-article-hook nil "*A hook called when an article is selected." :group 'gnus-summary-choose + :options '(gnus-agent-fetch-selected-article) :type 'hook) (defcustom gnus-visual-mark-article-hook @@ -862,11 +866,11 @@ automatically when it is selected." '(((eq mark gnus-canceled-mark) . gnus-summary-cancelled-face) ((and uncached (> score default-high)) - . gnus-summary-high-uncached-face) + . gnus-summary-high-undownloaded-face) ((and uncached (< score default-low)) - . gnus-summary-low-uncached-face) + . gnus-summary-low-undownloaded-face) (uncached - . gnus-summary-normal-uncached-face) + . gnus-summary-normal-undownloaded-face) ((and (> score default-high) (or (eq mark gnus-dormant-mark) (eq mark gnus-ticked-mark))) @@ -1039,6 +1043,14 @@ the MIME-Version header is missed." :type 'boolean :group 'gnus-article) +(defcustom gnus-article-emulate-mime t + "If non-nil, use MIME emulation for uuencode and the like. +This means that Gnus will search message bodies for text that look +like uuencoded bits, yEncoded bits, and so on, and present that using +the normal Gnus MIME machinery." + :type 'boolean + :group 'gnus-article) + ;;; Internal variables (defvar gnus-summary-display-cache nil) @@ -1324,7 +1336,12 @@ buffers. For example: ") ;; Byte-compiler warning. -(eval-when-compile (defvar gnus-article-mode-map)) +;(eval-when-compile (defvar gnus-article-mode-map)) +(eval-when-compile + (let ((features (cons 'gnus-sum features))) + (require 'gnus) + (require 'gnus-agent) + (require 'gnus-art))) ;; MIME stuff. @@ -1779,7 +1796,14 @@ increase the score of each group you read." "a" gnus-article-strip-headers-in-body ;; mnemonic: wash archive "p" gnus-article-verify-x-pgp-sig "d" gnus-article-treat-dumbquotes - "k" gnus-article-outlook-deuglify-article) + "k" gnus-article-outlook-deuglify-article) ;; mnemonic: outloo*k* + + (gnus-define-keys (gnus-summary-wash-deuglify-map "Y" gnus-summary-wash-map) + ;; mnemonic: deuglif*Y* + "u" gnus-outlook-unwrap-lines + "a" gnus-outlook-repair-attribution + "c" gnus-outlook-rearrange-citation + "f" gnus-article-outlook-deuglify-article) ;; mnemonic: full deuglify (gnus-define-keys (gnus-summary-wash-hide-map "W" gnus-summary-wash-map) "a" gnus-article-hide @@ -2093,7 +2117,12 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs)))) ["URLs" gnus-article-unsplit-urls t] ["Verify X-PGP-Sig" gnus-article-verify-x-pgp-sig t] ["HZ" gnus-article-decode-HZ t] - ["OutlooK deuglify" gnus-article-outlook-deuglify-article t] + ("(Outlook) Deuglify" + ["Unwrap lines" gnus-outlook-unwrap-lines t] + ["Repair attribution" gnus-outlook-repair-attribution t] + ["Rearrange citation" gnus-outlook-rearrange-citation t] + ["Full (Outlook) deuglify" + gnus-article-outlook-deuglify-article t]) ) ("Output" ["Save in default format" gnus-summary-save-article @@ -2177,6 +2206,7 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs)))) (easy-menu-define gnus-summary-thread-menu gnus-summary-mode-map "" '("Threads" + ["Find all messages in thread" gnus-summary-refer-thread t] ["Toggle threading" gnus-summary-toggle-threads t] ["Hide threads" gnus-summary-hide-all-threads t] ["Show threads" gnus-summary-show-all-threads t] @@ -2855,6 +2885,7 @@ article number." This is all marks except unread, ticked, dormant, and expirable." (not (or (= mark gnus-unread-mark) (= mark gnus-ticked-mark) + (= mark gnus-spam-mark) (= mark gnus-dormant-mark) (= mark gnus-expirable-mark)))) @@ -3091,7 +3122,7 @@ buffer that was in action when the last article was fetched." (let ((gnus-replied-mark 129) (gnus-score-below-mark 130) (gnus-score-over-mark 130) - (gnus-downloaded-mark 131) + (gnus-undownloaded-mark 131) (spec gnus-summary-line-format-spec) gnus-visual pos) (save-excursion @@ -3100,7 +3131,7 @@ buffer that was in action when the last article was fetched." (gnus-newsgroup-downloadable '(0))) (gnus-summary-insert-line [0 "" "" "05 Apr 2001 23:33:09 +0400" "" "" 0 0 "" nil] - 0 nil nil 128 t nil "" nil 1) + 0 nil t 128 t nil "" nil 1) (goto-char (point-min)) (setq pos (list (cons 'unread (and (search-forward "\200" nil t) (- (point) (point-min) 1))))) @@ -3475,8 +3506,9 @@ If NO-DISPLAY, don't generate a summary buffer." (progn (gnus-configure-windows 'summary) (let ((art (gnus-summary-article-number))) - (unless (or (memq art gnus-newsgroup-undownloaded) - (memq art gnus-newsgroup-downloadable)) + (unless (and (not gnus-plugged) + (or (memq art gnus-newsgroup-undownloaded) + (memq art gnus-newsgroup-downloadable))) (gnus-summary-goto-article art)))) ;; Don't select any articles. (gnus-summary-position-point) @@ -3493,7 +3525,8 @@ If NO-DISPLAY, don't generate a summary buffer." ;; Mark this buffer as "prepared". (setq gnus-newsgroup-prepared t) (gnus-run-hooks 'gnus-summary-prepared-hook) - (gnus-group-update-group group) + (unless (gnus-ephemeral-group-p group) + (gnus-group-update-group group)) t))))) (defun gnus-summary-auto-select-subject () @@ -5554,7 +5587,8 @@ The resulting hash table is returned, or nil if no Xrefs were found." ;; Update the number of unread articles. (setcar entry num) ;; Update the group buffer. - (gnus-group-update-group group t))))) + (unless (gnus-ephemeral-group-p group) + (gnus-group-update-group group t)))))) (defvar gnus-newsgroup-none-id 0) @@ -6378,7 +6412,10 @@ If FORCE (the prefix), also save the .newsrc file(s)." (set-buffer gnus-group-buffer) (gnus-summary-clear-local-variables) (let ((gnus-summary-local-variables gnus-newsgroup-variables)) - (gnus-summary-clear-local-variables))) + (gnus-summary-clear-local-variables)) + ;; Return to group mode buffer. + (when (eq mode 'gnus-summary-mode) + (gnus-kill-buffer buf))) (setq gnus-current-select-method gnus-select-method) (pop-to-buffer gnus-group-buffer) (if (not quit-config) @@ -6386,9 +6423,6 @@ If FORCE (the prefix), also save the .newsrc file(s)." (goto-char group-point) (gnus-configure-windows 'group 'force)) (gnus-handle-ephemeral-exit quit-config)) - ;; Return to group mode buffer. - (when (eq mode 'gnus-summary-mode) - (gnus-kill-buffer buf)) ;; Clear the current group name. (unless quit-config (setq gnus-newsgroup-name nil))))) @@ -6442,7 +6476,8 @@ If FORCE (the prefix), also save the .newsrc file(s)." (gnus-configure-windows 'group 'force) ;; Clear the current group name. (setq gnus-newsgroup-name nil) - (gnus-group-update-group group) + (unless (gnus-ephemeral-group-p group) + (gnus-group-update-group group)) (when (equal (gnus-group-group-name) group) (gnus-group-next-unread-group 1)) (when quit-config @@ -6681,7 +6716,8 @@ Returns the article selected or nil if there are no unread articles." (let ((data gnus-newsgroup-data)) (while (and data (and (not (and undownloaded - (memq (car data) gnus-newsgroup-undownloaded))) + (memq (car data) + gnus-newsgroup-undownloaded))) (if unseen (or (not (memq (gnus-data-number (car data)) @@ -9484,7 +9520,7 @@ If NO-EXPIRE, auto-expiry will be inhibited." (gnus-summary-goto-unread (and gnus-summary-goto-unread (not (eq gnus-summary-goto-unread 'never)) - (not (memq mark (list gnus-unread-mark + (not (memq mark (list gnus-unread-mark gnus-spam-mark gnus-ticked-mark gnus-dormant-mark))))) (n (abs n)) (mark (or mark gnus-del-mark))) @@ -10702,7 +10738,8 @@ If REVERSE, save parts that do not match TYPE." (set-buffer gnus-article-buffer) (let ((handles (or gnus-article-mime-handles (mm-dissect-buffer nil gnus-article-loose-mime) - (mm-uu-dissect)))) + (and gnus-article-emulate-mime + (mm-uu-dissect))))) (when handles (gnus-summary-save-parts-1 type dir handles reverse) (unless gnus-article-mime-handles ;; Don't destroy this case. diff --git a/lisp/gnus-xmas.el b/lisp/gnus-xmas.el index 3f72b5d..cb7e0b0 100644 --- a/lisp/gnus-xmas.el +++ b/lisp/gnus-xmas.el @@ -480,6 +480,7 @@ call it with the value of the `gnus-data' text property." :color-symbols (("thing" . ,(car gnus-logo-colors)) ("shadow" . ,(cadr gnus-logo-colors)) + ("oort" . "#eeeeee") ("background" . ,(face-background 'default)))]) ((featurep 'xbm) `[xbm :file ,logo-xbm]) diff --git a/lisp/gnus.el b/lisp/gnus.el index b9d8202..86a00a9 100644 --- a/lisp/gnus.el +++ b/lisp/gnus.el @@ -282,7 +282,7 @@ is restarted, and sometimes reloaded." :link '(custom-manual "(gnus)Exiting Gnus") :group 'gnus) -(defconst gnus-version-number "0.10" +(defconst gnus-version-number "0.11" "Version number for this version of Gnus.") (defconst gnus-version (format "Oort Gnus v%s" gnus-version-number) @@ -672,7 +672,7 @@ be set in `.emacs' instead." ())) "Face used for normal interest ancient articles.") -(defface gnus-summary-high-uncached-face +(defface gnus-summary-high-undownloaded-face '((((class color) (background light)) (:bold t :foreground "cyan4" :bold nil)) @@ -681,7 +681,7 @@ be set in `.emacs' instead." (t (:inverse-video t :bold t))) "Face used for high interest uncached articles.") -(defface gnus-summary-low-uncached-face +(defface gnus-summary-low-undownloaded-face '((((class color) (background light)) (:italic t :foreground "cyan4" :bold nil)) @@ -690,7 +690,7 @@ be set in `.emacs' instead." (t (:inverse-video t :italic t))) "Face used for low interest uncached articles.") -(defface gnus-summary-normal-uncached-face +(defface gnus-summary-normal-undownloaded-face '((((class color) (background light)) (:foreground "cyan4" :bold nil)) @@ -1795,6 +1795,10 @@ When a spam group is entered, all unread articles are marked as spam.") "The ifile summary exit spam processor. Only applicable to spam groups.") +(defvar gnus-group-spam-exit-processor-stat "stat" + "The spam-stat summary exit spam processor. +Only applicable to spam groups.") + (defvar gnus-group-spam-exit-processor-bogofilter "bogofilter" "The Bogofilter summary exit spam processor. Only applicable to spam groups.") @@ -1803,6 +1807,14 @@ Only applicable to spam groups.") "The Blacklist summary exit spam processor. Only applicable to spam groups.") +(defvar gnus-group-ham-exit-processor-ifile "ifile-ham" + "The ifile summary exit ham processor. +Only applicable to non-spam (unclassified and ham) groups.") + +(defvar gnus-group-ham-exit-processor-stat "stat-ham" + "The spam-stat summary exit ham processor. +Only applicable to non-spam (unclassified and ham) groups.") + (defvar gnus-group-ham-exit-processor-whitelist "whitelist" "The whitelist summary exit ham processor. Only applicable to non-spam (unclassified and ham) groups.") @@ -1819,8 +1831,11 @@ Only applicable to non-spam (unclassified and ham) groups.") (list :tag "Spam Summary Exit Processor Choices" (set (variable-item gnus-group-spam-exit-processor-ifile) + (variable-item gnus-group-spam-exit-processor-stat) (variable-item gnus-group-spam-exit-processor-bogofilter) (variable-item gnus-group-spam-exit-processor-blacklist) + (variable-item gnus-group-ham-exit-processor-ifile) + (variable-item gnus-group-ham-exit-processor-stat) (variable-item gnus-group-ham-exit-processor-whitelist) (variable-item gnus-group-ham-exit-processor-BBDB)))) :function-document @@ -1839,8 +1854,11 @@ for mail groups." (regexp :tag "Group Regexp") (set :tag "Spam/Ham Summary Exit Processor" (variable-item gnus-group-spam-exit-processor-ifile) + (variable-item gnus-group-spam-exit-processor-stat) (variable-item gnus-group-spam-exit-processor-bogofilter) (variable-item gnus-group-spam-exit-processor-blacklist) + (variable-item gnus-group-ham-exit-processor-ifile) + (variable-item gnus-group-ham-exit-processor-stat) (variable-item gnus-group-ham-exit-processor-whitelist) (variable-item gnus-group-ham-exit-processor-BBDB)))) :parameter-document @@ -1863,15 +1881,46 @@ to do spam-processed article moving, associated with the destination group or `nil' for explicit expiration. This only makes sense for mail groups." :variable-group spam - :variable-type '(repeat :tag "Spam-processed articles destination" - (list - (regexp :tag "Group Regexp") - (choice :tag "Destination for spam-processed articles at summary exit" - (string :tag "Move to a group") - (other :tag "Expire" nil)))) + :variable-type '(repeat + :tag "Spam-processed articles destination" + (list + (regexp :tag "Group Regexp") + (choice + :tag "Destination for spam-processed articles at summary exit" + (string :tag "Move to a group") + (other :tag "Expire" nil)))) :parameter-document "Where spam-processed articles will go at summary exit.") +(gnus-define-group-parameter + ham-process-destination + :parameter-type '(choice + :tag "Destination for ham articles at summary exit from a spam group" + (string :tag "Move to a group") + (other :tag "Do nothing" nil)) + :function-document + "Where ham articles will go at summary exit from a spam group." + :variable gnus-ham-process-destinations + :variable-default nil + :variable-document + "*Groups in which to explicitly send ham articles to +another group, or do nothing (the default). If non-nil, this should +be a list of group name regexps that should match all groups in which +to do ham article moving, associated with the destination +group or `nil' for explicit ignoring. This only makes sense for +mail groups, and only works in spam groups." + :variable-group spam + :variable-type '(repeat + :tag "Ham articles destination" + (list + (regexp :tag "Group Regexp") + (choice + :tag "Destination for ham articles at summary exit from spam group" + (string :tag "Move to a group") + (other :tag "Expire" nil)))) + :parameter-document + "Where ham articles will go at summary exit from a spam group.") + (defcustom gnus-group-uncollapsed-levels 1 "Number of group name elements to leave alone when making a short group name." :group 'gnus-group-visual @@ -2262,7 +2311,8 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") ("gnus-demon" :interactive t gnus-demon-init gnus-demon-cancel) ("gnus-fun" gnus-convert-gray-x-face-to-xpm gnus-display-x-face-in-from - gnus-convert-image-to-gray-x-face) + gnus-convert-image-to-gray-x-face gnus-convert-face-to-png + gnus-face-from-file) ("gnus-salt" gnus-highlight-selected-tree gnus-possibly-generate-tree gnus-tree-open gnus-tree-close gnus-carpal-setup-buffer) ("gnus-nocem" gnus-nocem-scan-groups gnus-nocem-close diff --git a/lisp/lpath.el b/lisp/lpath.el index f679d38..dadf0b6 100644 --- a/lisp/lpath.el +++ b/lisp/lpath.el @@ -9,12 +9,14 @@ (defun maybe-bind (args) (mapcar (lambda (var) (unless (boundp var) (set var nil))) args)) -(maybe-fbind '(create-image display-graphic-p +(maybe-fbind '(bbdb-create-internal bbdb-records + create-image display-graphic-p display-time-event-handler find-image image-size image-type-available-p insert-image make-mode-line-mouse-map make-temp-file propertize put-image replace-regexp-in-string rmail-msg-is-pruned rmail-msg-restore-non-pruned-header sort-coding-systems + spam-BBDB-register-routine spam-enter-ham-BBDB tool-bar-add-item tool-bar-add-item-from-menu tool-bar-local-item-from-menu url-http-file-exists-p vcard-pretty-print w32-focus-frame @@ -50,7 +52,7 @@ mouse-selection-click-count-buffer pgg-parse-crc24 temporary-file-directory transient-mark-mode))) (maybe-fbind '(bbdb-complete-name - bbdb-records delete-annotation device-connection dfw-device + delete-annotation device-connection dfw-device events-to-keys font-lock-set-defaults frame-device glyph-height glyph-width mail-aliases-setup make-annotation make-event make-glyph make-network-process map-extents diff --git a/lisp/mail-source.el b/lisp/mail-source.el index e841449..bf9d624 100644 --- a/lisp/mail-source.el +++ b/lisp/mail-source.el @@ -55,6 +55,7 @@ (list 'const (car a))) imap-stream-alist))) +;;;###autoload (defcustom mail-sources nil "*Where the mail backends will look for incoming mail. This variable is a list of mail source specifiers. diff --git a/lisp/message.el b/lisp/message.el index 7a62ec5..fd82439 100644 --- a/lisp/message.el +++ b/lisp/message.el @@ -1,5 +1,5 @@ ;;; message.el --- composing mail and news messages -;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 +;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 ;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen @@ -189,7 +189,7 @@ Checks include `subject-cmsg', `multiple-headers', `sendsys', `approved', `sender', `empty', `empty-headers', `message-id', `from', `subject', `shorten-followup-to', `existing-newsgroups', `buffer-file-name', `unchanged', `newsgroups', `reply-to', -'continuation-headers'." +'continuation-headers', and `long-header-lines'." :group 'message-news :type '(repeat sexp)) ; Fixme: improve this @@ -344,9 +344,9 @@ If nil, don't insert any text in the body." ;;;###autoload (defcustom message-cross-post-default t - "When non-nil `message-cross-post-followup-to' will normally perform a -crosspost. If nil, `message-cross-post-followup-to' will only do a followup. -Note that you can explicitly override this setting by calling + "When non-nil `message-cross-post-followup-to' will perform a crosspost. +If nil, `message-cross-post-followup-to' will only do a followup. Note that +you can explicitly override this setting by calling `message-cross-post-followup-to' with a prefix." :type 'boolean :group 'message-various) @@ -371,7 +371,7 @@ Note that you can explicitly override this setting by calling "Function to use to insert note about Crosspost or Followup-To. The function will be called with four arguments. The function should not only insert a note, but also ensure old notes are deleted. See the documentation -for `message-cross-post-insert-note'. " +for `message-cross-post-insert-note'." :type 'function :group 'message-various) @@ -690,7 +690,9 @@ Note that the variable `message-deletable-headers' specifies headers which are to be deleted and then re-generated before sending, so this variable will not have a visible effect for those headers." :group 'message-headers - :type 'boolean) + :type '(choice (const :tag "None" nil) + (const :tag "All" t) + (repeat (sexp :tag "Header")))) (defcustom message-setup-hook nil "Normal hook, run each time a new outgoing message is initialized. @@ -959,7 +961,7 @@ A value of nil means exclude your own user name only." "*A list of GNKSA feet you are allowed to shoot. Gnus gives you all the opportunity you could possibly want for shooting yourself in the foot. Also, Gnus allows you to shoot the -feet of Good Net-Keeping Seal of Approval. The following are foot +feet of Good Net-Keeping Seal of Approval. The following are foot candidates: `empty-article' Allow you to post an empty article; `quoted-text-only' Allow you to post quoted text only; @@ -1518,7 +1520,9 @@ is used by default." (defun message-fetch-reply-field (header) "Fetch field HEADER from the message we're replying to." (message-with-reply-buffer - (message-fetch-field header))) + (save-restriction + (mail-narrow-to-head) + (message-fetch-field header)))) (defun message-set-work-buffer () (if (get-buffer " *message work*") @@ -1559,7 +1563,7 @@ is used by default." ;;; Start of functions adopted from `message-utils.el'. (defun message-strip-subject-trailing-was (subject) - "Remove trailing \"(Was: )\" from subject lines. + "Remove trailing \"(Was: )\" from SUBJECT lines. Leading \"Re: \" is not stripped by this function. Use the function `message-strip-subject-re' for this." (let* ((query message-subject-trailing-was-query) @@ -1594,7 +1598,7 @@ Leading \"Re: \" is not stripped by this function. Use the function ;;;###autoload (defun message-change-subject (new-subject) - "Ask for new Subject: header, append (was: )." + "Ask for NEW-SUBJECT header, append (was: )." (interactive (list (read-from-minibuffer "New subject: "))) @@ -1604,7 +1608,7 @@ Leading \"Re: \" is not stripped by this function. Use the function (save-excursion (let ((old-subject (message-fetch-field "Subject"))) (cond ((not old-subject) - (error "No current subject.")) + (error "No current subject")) ((not (string-match (concat "^[ \t]*" (regexp-quote new-subject) @@ -1634,7 +1638,7 @@ See `message-mark-insert-begin' and `message-mark-insert-end'." ;;;###autoload (defun message-mark-insert-file (file) - "Inserts FILE at point, marking it with enclosing tags. + "Insert FILE at point, marking it with enclosing tags. See `message-mark-insert-begin' and `message-mark-insert-end'." (interactive "fFile to insert: ") ;; reverse insertion to get correct result. @@ -1744,7 +1748,7 @@ been made to before the user asked for a Crosspost." ;;;###autoload (defun message-cross-post-followup-to (target-group) - "Crossposts message and sets Followup-To to TARGET-GROUP. + "Crossposts message and set Followup-To to TARGET-GROUP. With prefix-argument just set Follow-Up, don't cross-post." (interactive (list ; Completion based on Gnus @@ -1764,7 +1768,7 @@ With prefix-argument just set Follow-Up, don't cross-post." (or old-groups "")))) ;; check whether target exactly matches old Newsgroups (cond ((not old-groups) - (error "No current newsgroup.")) + (error "No current newsgroup")) ((or (not in-old) (not (string-match (concat "^[ \t]*" @@ -1991,8 +1995,10 @@ Point is left at the beginning of the narrowed-to region." (define-key message-mode-map "\C-c\C-f\C-m" 'message-goto-mail-followup-to) (define-key message-mode-map "\C-c\C-f\C-k" 'message-goto-keywords) (define-key message-mode-map "\C-c\C-f\C-u" 'message-goto-summary) - (define-key message-mode-map "\C-c\C-f\C-i" 'message-insert-or-toggle-importance) - (define-key message-mode-map "\C-c\C-f\C-a" 'message-gen-unsubscribed-mft) + (define-key message-mode-map "\C-c\C-f\C-i" + 'message-insert-or-toggle-importance) + (define-key message-mode-map "\C-c\C-f\C-a" + 'message-generate-unsubscribed-mail-followup-to) ;; modify headers (and insert notes in body) (define-key message-mode-map "\C-c\C-fs" 'message-change-subject) @@ -2009,12 +2015,13 @@ Point is left at the beginning of the narrowed-to region." (define-key message-mode-map "\C-c\C-i" 'message-goto-signature) (define-key message-mode-map "\C-c\C-t" 'message-insert-to) - (define-key message-mode-map "\C-c\C-p" 'message-insert-wide-reply) + (define-key message-mode-map "\C-c\C-fw" 'message-insert-wide-reply) (define-key message-mode-map "\C-c\C-n" 'message-insert-newsgroups) (define-key message-mode-map "\C-c\C-l" 'message-to-list-only) (define-key message-mode-map "\C-c\C-u" 'message-insert-or-toggle-importance) - (define-key message-mode-map "\C-c\M-n" 'message-insert-disposition-notification-to) + (define-key message-mode-map "\C-c\M-n" + 'message-insert-disposition-notification-to) (define-key message-mode-map "\C-c\C-y" 'message-yank-original) (define-key message-mode-map "\C-c\M-\C-y" 'message-yank-buffer) @@ -2046,7 +2053,6 @@ Point is left at the beginning of the narrowed-to region." (easy-menu-define message-mode-menu message-mode-map "Message Menu." `("Message" - ["Sort Headers" message-sort-headers t] ["Yank Original" message-yank-original t] ["Fill Yanked Message" message-fill-yanked-message t] ["Insert Signature" message-insert-signature t] @@ -2057,16 +2063,6 @@ Point is left at the beginning of the narrowed-to region." ["Kill To Signature" message-kill-to-signature t] ["Newline and Reformat" message-newline-and-reformat t] ["Rename buffer" message-rename-buffer t] - ["Flag As Important" message-insert-importance-high - ,@(if (featurep 'xemacs) '(t) - '(:help "Mark this message as important"))] - ["Flag As Unimportant" message-insert-importance-low - ,@(if (featurep 'xemacs) '(t) - '(:help "Mark this message as unimportant"))] - ["Request Receipt" - message-insert-disposition-notification-to - ,@(if (featurep 'xemacs) '(t) - '(:help "Request a Disposition Notification of this article"))] ["Spellcheck" ispell-message ,@(if (featurep 'xemacs) '(t) '(:help "Spellcheck this message"))] @@ -2093,7 +2089,7 @@ Point is left at the beginning of the narrowed-to region." (easy-menu-define message-mode-field-menu message-mode-map "" - '("Field" + `("Field" ["Fetch To" message-insert-to t] ["Fetch Newsgroups" message-insert-newsgroups t] "----" @@ -2105,6 +2101,16 @@ Point is left at the beginning of the narrowed-to region." ["Bcc" message-goto-bcc t] ["Fcc" message-goto-fcc t] ["Reply-To" message-goto-reply-to t] + ["Flag As Important" message-insert-importance-high + ,@(if (featurep 'xemacs) '(t) + '(:help "Mark this message as important"))] + ["Flag As Unimportant" message-insert-importance-low + ,@(if (featurep 'xemacs) '(t) + '(:help "Mark this message as unimportant"))] + ["Request Receipt" + message-insert-disposition-notification-to + ,@(if (featurep 'xemacs) '(t) + '(:help "Request a receipt notification"))] "----" ;; (typical) news stuff ["Summary" message-goto-summary t] @@ -2121,8 +2127,9 @@ Point is left at the beginning of the narrowed-to region." ["Mail-Followup-To" message-goto-mail-followup-to t] ["Reduce To: to Cc:" message-reduce-to-to-cc t] "----" - ["Body" message-goto-body t] - ["Signature" message-goto-signature t])) + ["Sort Headers" message-sort-headers t] + ["Goto Body" message-goto-body t] + ["Goto Signature" message-goto-signature t])) (defvar message-tool-bar-map nil) @@ -2419,15 +2426,15 @@ return nil." (goto-char (point-max)) nil)) -(defun message-gen-unsubscribed-mft (&optional include-cc) +(defun message-generate-unsubscribed-mail-followup-to (&optional include-cc) "Insert a reasonable MFT header in a post to an unsubscribed list. When making original posts to a mailing list you are not subscribed to, you have to type in a MFT header by hand. The contents, usually, are the addresses of the list and your own address. This function inserts such a header automatically. It fetches the contents of the To: header -in the current mail buffer, and appends the current user-mail-address. +in the current mail buffer, and appends the current `user-mail-address'. -If the optional argument `include-cc' is non-nil, the addresses in the +If the optional argument INCLUDE-CC is non-nil, the addresses in the Cc: header are also put into the MFT." (interactive "P") @@ -3372,7 +3379,7 @@ It should typically alter the sending method in some way or other." (not (mail-fetch-field "mail-followup-to"))) (setq headers (cons - (cons "Mail-Followup-To" (message-make-mft)) + (cons "Mail-Followup-To" (message-make-mail-followup-to)) message-required-mail-headers)) ;; otherwise, delete the MFT header if the field is empty (when (equal "" (mail-fetch-field "mail-followup-to")) @@ -3771,6 +3778,24 @@ Otherwise, generate and save a value for `canlock-password' first." (y-or-n-p "The control code \"cmsg\" is in the subject. Really post? ") t)) + ;; Check long header lines. + (message-check 'long-header-lines + (let ((start (point)) + (header nil) + (length 0) + found) + (while (and (not found) + (re-search-forward "^\\([^ \t:]+\\): " nil t)) + (if (> (- (point) (match-beginning 0)) 998) + (setq found t + length (- (point) (match-beginning 0))) + (setq header (match-string-no-properties 1))) + (setq start (match-beginning 0)) + (forward-line 1)) + (if found + (y-or-n-p (format "Your %s header is too long (%d). Really post? " + header length)) + t))) ;; Check for multiple identical headers. (message-check 'multiple-headers (let (found) @@ -4474,7 +4499,7 @@ give as trustworthy answer as possible." "Send a message to the list only. Remove all addresses but the list address from To and Cc headers." (interactive) - (let ((listaddr (message-make-mft t))) + (let ((listaddr (message-make-mail-followup-to t))) (when listaddr (save-excursion (message-remove-header "to") @@ -4482,10 +4507,10 @@ Remove all addresses but the list address from To and Cc headers." (message-position-on-field "To" "X-Draft-From") (insert listaddr))))) -(defun message-make-mft (&optional only-show-subscribed) - "Return the Mail-Followup-To header. If passed the optional -argument `only-show-subscribed' only return the subscribed address (and -not the additional To and Cc header contents)." +(defun message-make-mail-followup-to (&optional only-show-subscribed) + "Return the Mail-Followup-To header. +If passed the optional argument ONLY-SHOW-SUBSCRIBED only return the +subscribed address (and not the additional To and Cc header contents)." (let* ((case-fold-search t) (to (message-fetch-field "To")) (cc (message-fetch-field "cc")) @@ -4547,6 +4572,7 @@ Headers already prepared in the buffer are not modified." (User-Agent message-newsreader) (Expires (message-make-expires)) (case-fold-search t) + (optionalp nil) header value elem) ;; First we remove any old generated headers. (let ((headers message-deletable-headers)) @@ -4568,7 +4594,8 @@ Headers already prepared in the buffer are not modified." (setq elem (pop headers)) (if (consp elem) (if (eq (car elem) 'optional) - (setq header (cdr elem)) + (setq header (cdr elem) + optionalp t) (setq header (car elem))) (setq header elem)) (when (or (not (re-search-forward @@ -4584,7 +4611,7 @@ Headers already prepared in the buffer are not modified." ;; The header was found. We insert a space after the ;; colon, if there is none. (if (/= (char-after) ? ) (insert " ") (forward-char 1)) - ;; Find out whether the header is empty... + ;; Find out whether the header is empty. (looking-at "[ \t]*\n[^ \t]"))) ;; So we find out what value we should insert. (setq value @@ -4641,7 +4668,10 @@ Headers already prepared in the buffer are not modified." ;; The value of this header was empty, so we clear ;; totally and insert the new value. (delete-region (point) (gnus-point-at-eol)) - (insert value)) + ;; If the header is optional, and the header was + ;; empty, we con't insert it anyway. + (unless optionalp + (insert value))) ;; Add the deletable property to the headers that require it. (and (memq header message-deletable-headers) (progn (beginning-of-line) (looking-at "[^:]+: ")) @@ -5708,7 +5738,7 @@ Optional DIGEST will use digest to forward." (mm-disable-multibyte-mule4) (insert (with-current-buffer forward-buffer - (mm-string-as-unibyte (buffer-string)))) + (mm-with-unibyte-current-buffer-mule4 (buffer-string)))) (mm-enable-multibyte-mule4) (mime-to-mml) (goto-char (point-min)) @@ -5993,6 +6023,9 @@ which specify the range to operate on." (message-tool-bar-local-item-from-menu 'ispell-message "spell" tool-bar-map message-mode-map) (message-tool-bar-local-item-from-menu + 'mml-preview "preview" + tool-bar-map mml-mode-map) + (message-tool-bar-local-item-from-menu 'message-insert-importance-high "important" tool-bar-map message-mode-map) (message-tool-bar-local-item-from-menu diff --git a/lisp/mm-decode.el b/lisp/mm-decode.el index 0e76d92..6a6d4c8 100644 --- a/lisp/mm-decode.el +++ b/lisp/mm-decode.el @@ -1,5 +1,5 @@ ;;; mm-decode.el --- Functions for decoding MIME things -;; Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; MORIOKA Tomohiko @@ -178,7 +178,7 @@ consider all the urls to be safe." mm-inline-image (lambda (handle) (mm-valid-and-fit-image-p 'xpm handle))) - ("image/x-pixmap" + ("image/x-xpixmap" mm-inline-image (lambda (handle) (mm-valid-and-fit-image-p 'xpm handle))) diff --git a/lisp/mm-url.el b/lisp/mm-url.el index b1978cc..1eeff06 100644 --- a/lisp/mm-url.el +++ b/lisp/mm-url.el @@ -62,7 +62,8 @@ ((executable-find "lynx") 'lynx) ((executable-find "curl") 'curl) (t "GET")) - "The url grab program." + "The url grab program. +Likely values are `wget', `w3m', `lynx' and `curl'." :type '(choice (symbol :tag "wget" wget) (symbol :tag "w3m" w3m) diff --git a/lisp/mm-util.el b/lisp/mm-util.el index 9c9ba85..3ee2493 100644 --- a/lisp/mm-util.el +++ b/lisp/mm-util.el @@ -493,8 +493,8 @@ If the charset is `composition', return the actual one." (forward-char)) (t (insert-before-markers (prog1 (+ c (car (cdr item))) - (delete-char 1)))) - (skip-chars-forward "\0-\177")))) + (delete-char 1))))) + (skip-chars-forward "\0-\177"))) (not inconvertible)))) (defun mm-sort-coding-systems-predicate (a b) @@ -532,7 +532,8 @@ charset, and a longer list means no appropriate charset." (mapcar 'mm-mime-charset (delq 'ascii (mm-find-charset-region b e)))))) - (if (and (memq 'iso-8859-15 charsets) + (if (and (> (length charsets) 1) + (memq 'iso-8859-15 charsets) (memq 'iso-8859-15 hack-charsets) (save-excursion (mm-iso-8859-x-to-15-region b e))) (mapcar (lambda (x) (setq charsets (delq (car x) charsets))) diff --git a/lisp/mml.el b/lisp/mml.el index b2f194c..5c5d885 100644 --- a/lisp/mml.el +++ b/lisp/mml.el @@ -1009,15 +1009,15 @@ TYPE is the MIME type to use." (mml-insert-tag 'part 'type type 'disposition "inline") (forward-line -1)) -(defun mml-preview-insert-mft () +(defun mml-preview-insert-mail-followup-to () "Insert a Mail-Followup-To header before previewing an article. Should be adopted if code in `message-send-mail' is changed." (when (and (message-mail-p) (message-subscribed-p) (not (mail-fetch-field "mail-followup-to")) - (message-make-mft)) + (message-make-mail-followup-to)) (message-position-on-field "Mail-Followup-To" "X-Draft-From") - (insert (message-make-mft)))) + (insert (message-make-mail-followup-to)))) (defun mml-preview (&optional raw) "Display current buffer with Gnus, in a new buffer. @@ -1039,7 +1039,7 @@ If RAW, don't highlight the article." "*MIME preview of ") (buffer-name)))) (erase-buffer) (insert-buffer buf) - (mml-preview-insert-mft) + (mml-preview-insert-mail-followup-to) (let ((message-deletable-headers (if (message-news-p) nil message-deletable-headers))) @@ -1068,6 +1068,10 @@ If RAW, don't highlight the article." (gnus-article-prepare-display)))) ;; Disable article-mode-map. (use-local-map nil) + (make-local-hook 'kill-buffer-hook) + (add-hook 'kill-buffer-hook + (lambda () + (mm-destroy-parts gnus-article-mime-handles)) nil t) (setq buffer-read-only t) (local-set-key "q" (lambda () (interactive) (kill-buffer nil))) (goto-char (point-min))))) diff --git a/lisp/nndraft.el b/lisp/nndraft.el index 56c0120..4f2d648 100644 --- a/lisp/nndraft.el +++ b/lisp/nndraft.el @@ -186,7 +186,8 @@ dir file) (nnheader-re-read-dir pathname) (setq dir (mapcar (lambda (name) (string-to-int (substring name 1))) - (directory-files pathname nil "^#[0-9]+#$" t))) + (ignore-errors (directory-files + pathname nil "^#[0-9]+#$" t)))) (dolist (n dir) (unless (file-exists-p (setq file (expand-file-name (int-to-string n) pathname))) diff --git a/lisp/nnheader.el b/lisp/nnheader.el index 84da0da..adc9c9d 100644 --- a/lisp/nnheader.el +++ b/lisp/nnheader.el @@ -909,21 +909,22 @@ find-file-hooks, etc. (defun nnheader-insert-nov-file (file first) (let ((size (nth 7 (file-attributes file))) (cutoff (* 32 1024))) - (if (< size cutoff) - ;; If the file is small, we just load it. - (nnheader-insert-file-contents file) - ;; We start on the assumption that FIRST is pretty recent. If - ;; not, we just insert the rest of the file as well. - (let (current) - (nnheader-insert-file-contents file nil (- size cutoff) size) - (goto-char (point-min)) - (delete-region (point) (or (search-forward "\n" nil 'move) (point))) - (setq current (ignore-errors (read (current-buffer)))) - (if (and (numberp current) - (< current first)) - t - (delete-region (point-min) (point-max)) - (nnheader-insert-file-contents file)))))) + (when size + (if (< size cutoff) + ;; If the file is small, we just load it. + (nnheader-insert-file-contents file) + ;; We start on the assumption that FIRST is pretty recent. If + ;; not, we just insert the rest of the file as well. + (let (current) + (nnheader-insert-file-contents file nil (- size cutoff) size) + (goto-char (point-min)) + (delete-region (point) (or (search-forward "\n" nil 'move) (point))) + (setq current (ignore-errors (read (current-buffer)))) + (if (and (numberp current) + (< current first)) + t + (delete-region (point-min) (point-max)) + (nnheader-insert-file-contents file))))))) (defun nnheader-find-file-noselect (&rest args) (let ((format-alist nil) diff --git a/lisp/nnmail.el b/lisp/nnmail.el index c5eef6c..032fe93 100644 --- a/lisp/nnmail.el +++ b/lisp/nnmail.el @@ -1748,9 +1748,11 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." (when (nnheader-functionp target) (setq target (funcall target group))) (unless (eq target 'delete) - (let ((group-art (gnus-request-accept-article target nil nil t))) - (when (consp group-art) - (gnus-group-mark-article-read target (cdr group-art))))))) + (when (or (gnus-request-group target) + (gnus-request-create-group target)) + (let ((group-art (gnus-request-accept-article target nil nil t))) + (when (consp group-art) + (gnus-group-mark-article-read target (cdr group-art)))))))) (defun nnmail-fancy-expiry-target (group) "Returns a target expiry group determined by `nnmail-fancy-expiry-targets'." diff --git a/lisp/nnmaildir.el b/lisp/nnmaildir.el index 050010b..dff0443 100644 --- a/lisp/nnmaildir.el +++ b/lisp/nnmaildir.el @@ -462,7 +462,7 @@ by nnmaildir-request-article.") (setq nlist (cons (cons num article) nlist)) (setq insert-nlist t nlist-cdr (cdr nlist)) - (while (< num (caar nlist-cdr)) + (while (and nlist-cdr (< num (caar nlist-cdr))) (setq nlist nlist-cdr nlist-cdr (cdr nlist)))) (let ((inhibit-quit t)) diff --git a/lisp/nnrss.el b/lisp/nnrss.el index 075c637..117db74 100644 --- a/lisp/nnrss.el +++ b/lisp/nnrss.el @@ -103,46 +103,36 @@ ("Reuters.Health.rdf" "http://www.reutershealth.com/eline.rdf" "Consumer-oriented health-related news stories.") -;;("4xt" "http://4xt.org/news/general.rss10" "Resources for XT users.") ("Aaronland" "http://aaronland.net/xml/abhb.rdf" "A boy and his basement.") ("Art of the Mix" "http://www.artofthemix.org/xml/rss.asp" "A website devoted to the art of making mixed tapes and cds.") ("Dave Beckett's RDF Resource Guide" "http://www.ilrt.bristol.ac.uk/discovery/rdf/resources/rss.rdf" "A comprehensive guide to resources about RDF.") ("David Chess" "http://www.davidchess.com/words/log.rss" "Mostly-daily musings on philosophy, children, culture, technology, the emergence of life from matter, chocolate, Nomic, and all that sort of thing.") -;;("Dublin Core Metadata Intitiative" "http://www.dublincore.org/news.rss" "Latest news from DCMI.") ("Figby Articles" "http://www.figby.com/index-rss.php" "A weblog with daily stories about technology, books and publishing, privacy, science, and occasional humor.") -;;("Figby News" "http://www.figby.com/news.php" "Categorized RSS feeds from various sources.") ("Figby Quickies" "http://www.figby.com/quickies-rss.php" "Quick commented links to other sites from Figby.com.") ("Flutterby!" "http://www.flutterby.com/main.rdf" "News and views from Dan Lyke.") - ("Groovelog" "http://groovelog.agora.co.uk/groove+log/groovelog.nsf/today.rss.xml" "The open-access groove users' weblog.") -;;("Groovelog.rss10" "http://groovelog.agora.co.uk/groove+log/groovelog.nsf/today.rss10.xml" "The open-access groove users' weblog.") + ("Groovelog" + "http://groovelog.agora.co.uk/groove+log/groovelog.nsf/today.rss.xml" + "The open-access groove users' weblog.") ("Hit or Miss" "http://hit-or-miss.org/rss/" "Daily weblog and journal.") -;;("Internet.com Feeds" "http://www.webreference.com/services/news/" "News from ") ("Larkfarm News" "http://www.larkfarm.com/Larkfarm.rdf" "Mike Gunderloy's web site.") ("Latest RFCs" "http://x42.com/rss/rfc.rss") ("Linux Today" "http://linuxtoday.com/backend/biglt.rss") ("Linux Today.rdf" "http://linuxtoday.com/backend/my-netscape10.rdf") ("More Like This WebLog" "http://www.whump.com/moreLikeThis/RSS" "Because the more you know, the more jokes you get.") ("Motivational Quotes of the Day" "http://www.quotationspage.com/data/mqotd.rss" "Four motivational quotations each day from the Quotations Page.") -;;("My Netscape Network" "http://www.dmoz.org/Netscape/My_Netscape_Network/") - ;;("My UserLand" "http://my.userland.com/choose") ("Network World Fusion NetFlash" "http://www.nwfusion.com/netflash.rss" "Daily breaking news about networking products, technologies and services.") -;;("News Feeds" "http://newsfeeds.manilasites.com/" "Jeff Barr highlights high quality RSS feeds.") - ;;("News Is Free Export" "http://www.newsisfree.com/export.php3") ("News Is Free" "http://www.newsisfree.com/news.rdf.php3") -;;("News is Free XML Export" "http://www.newsisfree.com/ocs/directory.xml") ("O'Reilly Network Articles" "http://www.oreillynet.com/cs/rss/query/q/260?x-ver=1.0") ("Quotes of the Day" "http://www.quotationspage.com/data/qotd.rss" "Four humorous quotations each day from the Quotations Page.") ("RDF Interest Group" "http://ilrt.org/discovery/rdf-dev/roads/cgi-bin/desire/ig2rss?list=www-rdf-interest" "An experimental channel scraped from the RDF Interest Group mail archives.") ("RDF Logic List" "http://ilrt.org/discovery/rdf-dev/roads/cgi-bin/desire/ig2rss?list=www-rdf-logic" "An experimental channel scraped from the RDF Logic mail archives.") ("RSS Info" "http://www.blogspace.com/rss/rss10" "News and information on the RSS format") -;;("RSS-DEV listing" "http://www.egroups.com/links/rss-dev/Feeds_000966335046/" "A listing of RSS files from the RSS-DEV list.") - ("Semantic Web List" "http://ilrt.org/discovery/rdf-dev/roads/cgi-bin/desire/ig2rss?list=semantic-web" "An experimental channel scraped from the W3C's Semantic Web mail archives.") -;;("Sherch!" "http://www.sherch.com/~pldms/cgi-bin/sherch.pl" "Sherlock for the rest of us.") -;;("Street Fusion Archived Financial Webcasts" "http://partners.streetfusion.com/rdf/archive.rdf") -;;("Street Fusion Upcoming Financial Webcasts" "http://partners.streetfusion.com/rdf/live.rdf") -;;("TNL.net newsletter" "http://www.tnl.net/newsletter/channel100.asp" "A newsletter about Internet technology and issues.") - ("W3C" "http://www.w3.org/2000/08/w3c-synd/home.rss" "The latest news at the World Wide Web Consortium.") -;;("XML News: RSS Live Content" "http://www.xmlnews.org/RSS/content.html" "A listing of well-known RSS feeds.") + ("Semantic Web List" + "http://ilrt.org/discovery/rdf-dev/roads/cgi-bin/desire/ig2rss?list=semantic-web" + "An experimental channel scraped from the W3C's Semantic Web mail archives.") + ("W3C" + "http://www.w3.org/2000/08/w3c-synd/home.rss" + "The latest news at the World Wide Web Consortium.") ("|fr| XMLfr" "http://xmlfr.org/actualites/general.rss10" "French speaking portal site dedicated to XML.") ("XMLhack" "http://xmlhack.com/rss10.php" @@ -162,7 +152,14 @@ ("Jabber Software Foundation News" "http://www.jabber.org/news/rss.xml" "News and announcements from the Jabber Software Foundation.") - )) + ("MacRumors" + "http://www.macrumors.com/macrumors.xml" + "The mac news you care about.") + ("Mac OS X Hints" + "http://www.macosxhints.com/backend/geeklog.rdf" + "Mac OS X Hits.") + ) + "List of RSS addresses.") (defvar nnrss-use-local nil) diff --git a/lisp/nntp.el b/lisp/nntp.el index 46aaad7..ca1c917 100644 --- a/lisp/nntp.el +++ b/lisp/nntp.el @@ -264,7 +264,9 @@ noticing asynchronous data.") nntp-last-command string) (when nntp-record-commands (nntp-record-command string)) - (process-send-string process (concat string nntp-end-of-line))) + (process-send-string process (concat string nntp-end-of-line)) + (or (memq (process-status process) '(open run)) + (nntp-report "Server closed connection"))) (defun nntp-record-command (string) "Record the command STRING." @@ -276,6 +278,27 @@ noticing asynchronous data.") "." (format "%03d" (/ (nth 2 time) 1000)) " " nntp-address " " string "\n")))) +(defun nntp-report (&rest args) + "Report an error from the nntp backend. The first string in ARGS +can be a format string. For some commands, the failed command may be +retried once before actually displaying the error report." + + (when nntp-record-commands + (nntp-record-command "*** CALLED nntp-report ***")) + + (nnheader-report 'nntp args) + + (apply 'error args)) + +(defun nntp-report-1 (&rest args) + "Throws out to nntp-with-open-group-error so that the connection may +be restored and the command retried." + + (when nntp-record-commands + (nntp-record-command "*** CONNECTION LOST ***")) + + (throw 'nntp-with-open-group-error t)) + (defsubst nntp-wait-for (process wait-for buffer &optional decode discard) "Wait for WAIT-FOR to arrive from PROCESS." (save-excursion @@ -361,32 +384,33 @@ noticing asynchronous data.") "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS." (let ((process (or (nntp-find-connection buffer) (nntp-open-connection buffer)))) - (if (not process) - (nnheader-report 'nntp "Couldn't open connection to %s" address) - (unless (or nntp-inhibit-erase nnheader-callback-function) - (save-excursion - (set-buffer (process-buffer process)) - (erase-buffer))) - (condition-case err - (progn - (when command - (nntp-send-string process command)) - (cond - ((eq callback 'ignore) - t) - ((and callback wait-for) - (nntp-async-wait process wait-for buffer decode callback) - t) - (wait-for - (nntp-wait-for process wait-for buffer decode)) - (t t))) - (error - (nnheader-report 'nntp "Couldn't open connection to %s: %s" - address err)) - (quit - (message "Quit retrieving data from nntp") - (signal 'quit nil) - nil))))) + (if process + (progn + (unless (or nntp-inhibit-erase nnheader-callback-function) + (save-excursion + (set-buffer (process-buffer process)) + (erase-buffer))) + (condition-case err + (progn + (when command + (nntp-send-string process command)) + (cond + ((eq callback 'ignore) + t) + ((and callback wait-for) + (nntp-async-wait process wait-for buffer decode callback) + t) + (wait-for + (nntp-wait-for process wait-for buffer decode)) + (t t))) + (error + (nnheader-report 'nntp "Couldn't open connection to %s: %s" + address err)) + (quit + (message "Quit retrieving data from nntp") + (signal 'quit nil) + nil))) + (nnheader-report 'nntp "Couldn't open connection to %s" address)))) (defsubst nntp-send-command (wait-for &rest strings) "Send STRINGS to server and wait until WAIT-FOR returns." @@ -517,214 +541,227 @@ noticing asynchronous data.") (t nil))) -(defvar nntp-with-open-group-first-pass nil) +(eval-when-compile + (defvar nntp-with-open-group-internal nil) + (defvar nntp-report-n nil)) (defmacro nntp-with-open-group (group server &optional connectionless &rest forms) - "Protect against servers that don't like clients that keep idle connections opens. The problem -being that these servers may either close a connection or simply ignore any further requests on a -connection. Closed connections are not detected until accept-process-output has updated the -process-status. Dropped connections are not detected until the connection timeouts (which may be -several minutes) or nntp-connection-timeout has expired. When these occur nntp-with-open-group, -opens a new connection then re-issues the NNTP command whose response triggered the error." + "Protect against servers that don't like clients that keep idle connections opens. +The problem being that these servers may either close a connection or +simply ignore any further requests on a connection. Closed +connections are not detected until accept-process-output has updated +the process-status. Dropped connections are not detected until the +connection timeouts (which may be several minutes) or +nntp-connection-timeout has expired. When these occur +nntp-with-open-group, opens a new connection then re-issues the NNTP +command whose response triggered the error." (when (and (listp connectionless) - (not (eq connectionless nil))) + (not (eq connectionless nil))) (setq forms (cons connectionless forms) - connectionless nil)) - `(let ((nntp-with-open-group-first-pass t) - nntp-with-open-group-internal) + connectionless nil)) + `(letf ((nntp-report-n (symbol-function 'nntp-report)) + ((symbol-function 'nntp-report) (symbol-function 'nntp-report-1)) + (nntp-with-open-group-internal nil)) (while (catch 'nntp-with-open-group-error - ;; Open the connection to the server - ;; NOTE: Existing connections are NOT tested. - (nntp-possibly-change-group ,group ,server ,connectionless) - - (let ((timer - (and nntp-connection-timeout - (nnheader-run-at-time - nntp-connection-timeout nil - '(lambda () - (let ((process (nntp-find-connection nntp-server-buffer)) - (buffer (and process (process-buffer process)))) - ; when I an able to identify the connection to the server AND I've received NO - ; reponse for nntp-connection-timeout seconds. - (when (and buffer (eq 0 (buffer-size buffer))) - ; Close the connection. Take no other action as the accept input code will - ; handle the closed connection. - (nntp-kill-buffer buffer)))))))) - (unwind-protect - (setq nntp-with-open-group-internal (progn ,@forms)) - (when timer - (nnheader-cancel-timer timer))) - nil)) - (setq nntp-with-open-group-first-pass nil)) + ;; Open the connection to the server + ;; NOTE: Existing connections are NOT tested. + (nntp-possibly-change-group ,group ,server ,connectionless) + + (let ((timer + (and nntp-connection-timeout + (nnheader-run-at-time + nntp-connection-timeout nil + '(lambda () + (let ((process (nntp-find-connection + nntp-server-buffer)) + (buffer (and process + (process-buffer process)))) + ; when I an able to identify + ; the connection to the server + ; AND I've received NO reponse + ; for nntp-connection-timeout + ; seconds. + (when (and buffer (eq 0 (buffer-size buffer))) + ; Close the connection. Take + ; no other action as the + ; accept input code will + ; handle the closed + ; connection. + (nntp-kill-buffer buffer)))))))) + (unwind-protect + (setq nntp-with-open-group-internal + (condition-case nil + (progn ,@forms) + (quit + (nntp-close-server) + (signal 'quit nil))) + ) + (when timer + (nnheader-cancel-timer timer))) + nil)) + (setf (symbol-function 'nntp-report) nntp-report-n)) nntp-with-open-group-internal)) -(defsubst nntp-report (&rest args) - "Report an error from the nntp backend. -The first string in ARGS can be a format string. -For some commands, the failed command may be retried once before actually displaying the error report." - - (if nntp-with-open-group-first-pass - (throw 'nntp-with-open-group-error t)) - - (nnheader-report 'nntp args) - ) - (deffoo nntp-retrieve-headers (articles &optional group server fetch-old) "Retrieve the headers of ARTICLES." (nntp-with-open-group - group server - (save-excursion - (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) - (erase-buffer) - (if (and (not gnus-nov-is-evil) - (not nntp-nov-is-evil) - (nntp-retrieve-headers-with-xover articles fetch-old)) - ;; We successfully retrieved the headers via XOVER. - 'nov - ;; XOVER didn't work, so we do it the hard, slow and inefficient - ;; way. - (let ((number (length articles)) - (count 0) - (received 0) - (last-point (point-min)) - (buf (nntp-find-connection-buffer nntp-server-buffer)) - (nntp-inhibit-erase t) - article) - ;; Send HEAD commands. - (while (setq article (pop articles)) - (nntp-send-command - nil - "HEAD" (if (numberp article) - (int-to-string article) - ;; `articles' is either a list of article numbers - ;; or a list of article IDs. - article)) - (incf count) - ;; Every 400 requests we have to read the stream in - ;; order to avoid deadlocks. - (when (or (null articles) ;All requests have been sent. - (zerop (% count nntp-maximum-request))) - (nntp-accept-response) - (while (progn - (set-buffer buf) - (goto-char last-point) - ;; Count replies. - (while (nntp-next-result-arrived-p) - (setq last-point (point)) - (incf received)) - (< received count)) - ;; If number of headers is greater than 100, give - ;; informative messages. - (and (numberp nntp-large-newsgroup) - (> number nntp-large-newsgroup) - (zerop (% received 20)) - (nnheader-message 6 "NNTP: Receiving headers... %d%%" - (/ (* received 100) number))) - (nntp-accept-response)))) - (and (numberp nntp-large-newsgroup) - (> number nntp-large-newsgroup) - (nnheader-message 6 "NNTP: Receiving headers...done")) - - ;; Now all of replies are received. Fold continuation lines. - (nnheader-fold-continuation-lines) - ;; Remove all "\r"'s. - (nnheader-strip-cr) - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - 'headers))))) + group server + (save-excursion + (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) + (erase-buffer) + (if (and (not gnus-nov-is-evil) + (not nntp-nov-is-evil) + (nntp-retrieve-headers-with-xover articles fetch-old)) + ;; We successfully retrieved the headers via XOVER. + 'nov + ;; XOVER didn't work, so we do it the hard, slow and inefficient + ;; way. + (let ((number (length articles)) + (articles articles) + (count 0) + (received 0) + (last-point (point-min)) + (buf (nntp-find-connection-buffer nntp-server-buffer)) + (nntp-inhibit-erase t) + article) + ;; Send HEAD commands. + (while (setq article (pop articles)) + (nntp-send-command + nil + "HEAD" (if (numberp article) + (int-to-string article) + ;; `articles' is either a list of article numbers + ;; or a list of article IDs. + article)) + (incf count) + ;; Every 400 requests we have to read the stream in + ;; order to avoid deadlocks. + (when (or (null articles) ;All requests have been sent. + (zerop (% count nntp-maximum-request))) + (nntp-accept-response) + (while (progn + (set-buffer buf) + (goto-char last-point) + ;; Count replies. + (while (nntp-next-result-arrived-p) + (setq last-point (point)) + (incf received)) + (< received count)) + ;; If number of headers is greater than 100, give + ;; informative messages. + (and (numberp nntp-large-newsgroup) + (> number nntp-large-newsgroup) + (zerop (% received 20)) + (nnheader-message 6 "NNTP: Receiving headers... %d%%" + (/ (* received 100) number))) + (nntp-accept-response)))) + (and (numberp nntp-large-newsgroup) + (> number nntp-large-newsgroup) + (nnheader-message 6 "NNTP: Receiving headers...done")) + + ;; Now all of replies are received. Fold continuation lines. + (nnheader-fold-continuation-lines) + ;; Remove all "\r"'s. + (nnheader-strip-cr) + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + 'headers))))) (deffoo nntp-retrieve-groups (groups &optional server) "Retrieve group info on GROUPS." - (nntp-possibly-change-group nil server) - (when (nntp-find-connection-buffer nntp-server-buffer) - (catch 'done - (save-excursion - ;; Erase nntp-server-buffer before nntp-inhibit-erase. - (set-buffer nntp-server-buffer) - (erase-buffer) - (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) - ;; The first time this is run, this variable is `try'. So we - ;; try. - (when (eq nntp-server-list-active-group 'try) - (nntp-try-list-active (car groups))) - (erase-buffer) - (let ((count 0) - (received 0) - (last-point (point-min)) - (nntp-inhibit-erase t) - (buf (nntp-find-connection-buffer nntp-server-buffer)) - (command (if nntp-server-list-active-group - "LIST ACTIVE" "GROUP"))) - (while groups - ;; Timeout may have killed the buffer. - (unless (gnus-buffer-live-p buf) - (nnheader-report 'nntp "Connection to %s is closed." server) - (throw 'done nil)) - ;; Send the command to the server. - (nntp-send-command nil command (pop groups)) - (incf count) - ;; Every 400 requests we have to read the stream in - ;; order to avoid deadlocks. - (when (or (null groups) ;All requests have been sent. - (zerop (% count nntp-maximum-request))) - (nntp-accept-response) - (while (and (gnus-buffer-live-p buf) - (progn - ;; Search `blue moon' in this file for the - ;; reason why set-buffer here. - (set-buffer buf) - (goto-char last-point) - ;; Count replies. - (while (re-search-forward "^[0-9]" nil t) - (incf received)) - (setq last-point (point)) - (< received count))) - (nntp-accept-response)))) - - ;; Wait for the reply from the final command. - (unless (gnus-buffer-live-p buf) - (nnheader-report 'nntp "Connection to %s is closed." server) - (throw 'done nil)) - (set-buffer buf) - (goto-char (point-max)) - (re-search-backward "^[0-9]" nil t) - (when (looking-at "^[23]") - (while (and (gnus-buffer-live-p buf) - (progn - (set-buffer buf) - (goto-char (point-max)) - (if (not nntp-server-list-active-group) - (not (re-search-backward "\r?\n" (- (point) 3) t)) - (not (re-search-backward "^\\.\r?\n" - (- (point) 4) t))))) - (nntp-accept-response))) - - ;; Now all replies are received. We remove CRs. - (unless (gnus-buffer-live-p buf) - (nnheader-report 'nntp "Connection to %s is closed." server) - (throw 'done nil)) - (set-buffer buf) - (goto-char (point-min)) - (while (search-forward "\r" nil t) - (replace-match "" t t)) - - (if (not nntp-server-list-active-group) - (progn - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - 'group) - ;; We have read active entries, so we just delete the - ;; superfluous gunk. - (goto-char (point-min)) - (while (re-search-forward "^[.2-5]" nil t) - (delete-region (match-beginning 0) - (progn (forward-line 1) (point)))) - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - 'active)))))) + (nntp-with-open-group + nil server + (when (nntp-find-connection-buffer nntp-server-buffer) + (catch 'done + (save-excursion + ;; Erase nntp-server-buffer before nntp-inhibit-erase. + (set-buffer nntp-server-buffer) + (erase-buffer) + (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) + ;; The first time this is run, this variable is `try'. So we + ;; try. + (when (eq nntp-server-list-active-group 'try) + (nntp-try-list-active (car groups))) + (erase-buffer) + (let ((count 0) + (groups groups) + (received 0) + (last-point (point-min)) + (nntp-inhibit-erase t) + (buf (nntp-find-connection-buffer nntp-server-buffer)) + (command (if nntp-server-list-active-group + "LIST ACTIVE" "GROUP"))) + (while groups + ;; Timeout may have killed the buffer. + (unless (gnus-buffer-live-p buf) + (nnheader-report 'nntp "Connection to %s is closed." server) + (throw 'done nil)) + ;; Send the command to the server. + (nntp-send-command nil command (pop groups)) + (incf count) + ;; Every 400 requests we have to read the stream in + ;; order to avoid deadlocks. + (when (or (null groups) ;All requests have been sent. + (zerop (% count nntp-maximum-request))) + (nntp-accept-response) + (while (and (gnus-buffer-live-p buf) + (progn + ;; Search `blue moon' in this file for the + ;; reason why set-buffer here. + (set-buffer buf) + (goto-char last-point) + ;; Count replies. + (while (re-search-forward "^[0-9]" nil t) + (incf received)) + (setq last-point (point)) + (< received count))) + (nntp-accept-response)))) + + ;; Wait for the reply from the final command. + (unless (gnus-buffer-live-p buf) + (nnheader-report 'nntp "Connection to %s is closed." server) + (throw 'done nil)) + (set-buffer buf) + (goto-char (point-max)) + (re-search-backward "^[0-9]" nil t) + (when (looking-at "^[23]") + (while (and (gnus-buffer-live-p buf) + (progn + (set-buffer buf) + (goto-char (point-max)) + (if (not nntp-server-list-active-group) + (not (re-search-backward "\r?\n" (- (point) 3) t)) + (not (re-search-backward "^\\.\r?\n" + (- (point) 4) t))))) + (nntp-accept-response))) + + ;; Now all replies are received. We remove CRs. + (unless (gnus-buffer-live-p buf) + (nnheader-report 'nntp "Connection to %s is closed." server) + (throw 'done nil)) + (set-buffer buf) + (goto-char (point-min)) + (while (search-forward "\r" nil t) + (replace-match "" t t)) + + (if (not nntp-server-list-active-group) + (progn + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + 'group) + ;; We have read active entries, so we just delete the + ;; superfluous gunk. + (goto-char (point-min)) + (while (re-search-forward "^[.2-5]" nil t) + (delete-region (match-beginning 0) + (progn (forward-line 1) (point)))) + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + 'active))))))) (deffoo nntp-retrieve-articles (articles &optional group server) - (nntp-with-open-group + (nntp-with-open-group group server (save-excursion (let ((number (length articles)) + (articles articles) (count 0) (received 0) (last-point (point-min)) @@ -802,16 +839,18 @@ For some commands, the failed command may be retried once before actually displa (deffoo nntp-list-active-group (group &optional server) "Return the active info on GROUP (which can be a regexp)." - (nntp-possibly-change-group nil server) - (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group)) + (nntp-with-open-group + nil server + (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group))) (deffoo nntp-request-group-articles (group &optional server) "Return the list of existing articles in GROUP." - (nntp-possibly-change-group nil server) - (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group)) + (nntp-with-open-group + nil server + (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group))) (deffoo nntp-request-article (article &optional group server buffer command) - (nntp-with-open-group + (nntp-with-open-group group server (when (nntp-send-command-and-decode "\r?\n\\.\r?\n" "ARTICLE" @@ -825,19 +864,21 @@ For some commands, the failed command may be retried once before actually displa (nntp-find-group-and-number group))))) (deffoo nntp-request-head (article &optional group server) - (nntp-possibly-change-group group server) - (when (nntp-send-command - "\r?\n\\.\r?\n" "HEAD" - (if (numberp article) (int-to-string article) article)) - (prog1 - (nntp-find-group-and-number group) - (nntp-decode-text)))) + (nntp-with-open-group + group server + (when (nntp-send-command + "\r?\n\\.\r?\n" "HEAD" + (if (numberp article) (int-to-string article) article)) + (prog1 + (nntp-find-group-and-number group) + (nntp-decode-text))))) (deffoo nntp-request-body (article &optional group server) - (nntp-possibly-change-group group server) - (nntp-send-command-and-decode - "\r?\n\\.\r?\n" "BODY" - (if (numberp article) (int-to-string article) article))) + (nntp-with-open-group + group server + (nntp-send-command-and-decode + "\r?\n\\.\r?\n" "BODY" + (if (numberp article) (int-to-string article) article)))) (deffoo nntp-request-group (group &optional server dont-check) (nntp-with-open-group @@ -902,54 +943,58 @@ For some commands, the failed command may be retried once before actually displa (nntp-kill-buffer (process-buffer process))))) (deffoo nntp-request-list (&optional server) - (nntp-possibly-change-group nil server) - (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST")) + (nntp-with-open-group + nil server + (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST"))) (deffoo nntp-request-list-newsgroups (&optional server) - (nntp-possibly-change-group nil server) - (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS")) + (nntp-with-open-group + nil server + (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS"))) (deffoo nntp-request-newgroups (date &optional server) - (nntp-possibly-change-group nil server) - (save-excursion - (set-buffer nntp-server-buffer) - (let* ((time (date-to-time date)) - (ls (- (cadr time) (nth 8 (decode-time time))))) - (cond ((< ls 0) - (setcar time (1- (car time))) - (setcar (cdr time) (+ ls 65536))) - ((>= ls 65536) - (setcar time (1+ (car time))) - (setcar (cdr time) (- ls 65536))) - (t - (setcar (cdr time) ls))) - (prog1 - (nntp-send-command - "^\\.\r?\n" "NEWGROUPS" - (format-time-string "%y%m%d %H%M%S" time) - "GMT") - (nntp-decode-text))))) + (nntp-with-open-group + nil server + (save-excursion + (set-buffer nntp-server-buffer) + (let* ((time (date-to-time date)) + (ls (- (cadr time) (nth 8 (decode-time time))))) + (cond ((< ls 0) + (setcar time (1- (car time))) + (setcar (cdr time) (+ ls 65536))) + ((>= ls 65536) + (setcar time (1+ (car time))) + (setcar (cdr time) (- ls 65536))) + (t + (setcar (cdr time) ls))) + (prog1 + (nntp-send-command + "^\\.\r?\n" "NEWGROUPS" + (format-time-string "%y%m%d %H%M%S" time) + "GMT") + (nntp-decode-text)))))) (deffoo nntp-request-post (&optional server) - (nntp-possibly-change-group nil server) - (when (nntp-send-command "^[23].*\r?\n" "POST") - (let ((response (with-current-buffer nntp-server-buffer - nntp-process-response)) - server-id) - (when (and response - (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)" - response)) - (setq server-id (match-string 1 response)) - (narrow-to-region (goto-char (point-min)) - (if (search-forward "\n\n" nil t) - (1- (point)) - (point-max))) - (unless (mail-fetch-field "Message-ID") - (goto-char (point-min)) - (insert "Message-ID: " server-id "\n")) - (widen)) - (run-hooks 'nntp-prepare-post-hook) - (nntp-send-buffer "^[23].*\n")))) + (nntp-with-open-group + nil server + (when (nntp-send-command "^[23].*\r?\n" "POST") + (let ((response (with-current-buffer nntp-server-buffer + nntp-process-response)) + server-id) + (when (and response + (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)" + response)) + (setq server-id (match-string 1 response)) + (narrow-to-region (goto-char (point-min)) + (if (search-forward "\n\n" nil t) + (1- (point)) + (point-max))) + (unless (mail-fetch-field "Message-ID") + (goto-char (point-min)) + (insert "Message-ID: " server-id "\n")) + (widen)) + (run-hooks 'nntp-prepare-post-hook) + (nntp-send-buffer "^[23].*\n"))))) (deffoo nntp-request-type (group article) 'news) @@ -1235,11 +1280,15 @@ password contained in '~/.nntp-authinfo'." (unless (< len 10) (setq nntp-have-messaged t) (nnheader-message 7 "nntp read: %dk" len))) - (accept-process-output process (or timeout 1)) - ;; accept-process-output may update status of process to indicate that the server has closed the - ;; connection. This MUST be handled here as the buffer restored by the save-excursion may be the - ;; process's former output buffer (i.e. now killed) - (or (memq (process-status process) '(open run)) + (if timeout + (accept-process-output process timeout) + (accept-process-output process 0 100)) + ;; accept-process-output may update status of process to indicate + ;; that the server has closed the connection. This MUST be + ;; handled here as the buffer restored by the save-excursion may + ;; be the process's former output buffer (i.e. now killed) + (or (and process + (memq (process-status process) '(open run))) (nntp-report "Server closed connection")))) (defun nntp-accept-response () @@ -1398,18 +1447,22 @@ password contained in '~/.nntp-authinfo'." (nntp-accept-response) (set-buffer process-buffer)))) - ;; Some nntp servers seem to have an extension to the XOVER extension. On these - ;; servers, requesting an article range preceeding the active range does not return an - ;; error as specified in the RFC. What we instead get is the NOV entry for the first - ;; available article. Obviously, a client can use that entry to avoid making unnecessary - ;; requests. The only problem is for a client that assumes that the response will always be - ;; within the requested ranage. For such a client, we can get N copies of the same entry - ;; (one for each XOVER command sent to the server). + ;; Some nntp servers seem to have an extension to the XOVER + ;; extension. On these servers, requesting an article range + ;; preceeding the active range does not return an error as + ;; specified in the RFC. What we instead get is the NOV entry + ;; for the first available article. Obviously, a client can + ;; use that entry to avoid making unnecessary requests. The + ;; only problem is for a client that assumes that the response + ;; will always be within the requested ranage. For such a + ;; client, we can get N copies of the same entry (one for each + ;; XOVER command sent to the server). (when (<= count 1) (goto-char (point-min)) (when (re-search-forward "^[0-9][0-9][0-9] .*\n\\([0-9]+\\)" nil t) - (let ((low-limit (string-to-int (buffer-substring (match-beginning 1) (match-end 1))))) + (let ((low-limit (string-to-int (buffer-substring (match-beginning 1) + (match-end 1))))) (while (and articles (<= (car articles) low-limit)) (setq articles (cdr articles)))))) (set-buffer buf)) diff --git a/lisp/nnweb.el b/lisp/nnweb.el index 710b554..e3a9625 100644 --- a/lisp/nnweb.el +++ b/lisp/nnweb.el @@ -203,7 +203,7 @@ Valid types include `google', `dejanews', and `gmane'.") (nnweb-possibly-change-server group server)) (deffoo nnweb-asynchronous-p () - t) + nil) (deffoo nnweb-request-create-group (group &optional server args) (nnweb-possibly-change-server nil server) diff --git a/lisp/spam-stat.el b/lisp/spam-stat.el index fb1c3e5..fc134f9 100644 --- a/lisp/spam-stat.el +++ b/lisp/spam-stat.el @@ -1,14 +1,12 @@ ;;; spam-stat.el --- detecting spam based on statistics -;; Copyright (C) 2002 Alex Schroeder +;; Copyright (C) 2002 Free Software Foundation, Inc. ;; Author: Alex Schroeder -;; Maintainer: Alex Schroeder -;; Version: 0.3.5 -;; Keywords: spam filtering gnus +;; Keywords: network ;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?SpamStat -;; This file is NOT part of GNU Emacs. +;; This file is part of GNU Emacs. ;; This is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by @@ -41,7 +39,7 @@ ;; considered to be a new spam mail; use this for new mail that has ;; not been processed before ;; -;; `spam-stat-buffer-is-no-spam' -- called in a buffer, that buffer +;; `spam-stat-buffer-is-non-spam' -- called in a buffer, that buffer ;; is considered to be a new non-spam mail; use this for new mail that ;; has not been processed before ;; @@ -77,7 +75,7 @@ ;; Typical test will involve calls to the following functions: ;; -;; Reset: (setq spam-stat (make-hash-table :test 'equal)) +;; Reset: (spam-stat-reset) ;; Learn spam: (spam-stat-process-spam-directory "~/Mail/mail/spam") ;; Learn non-spam: (spam-stat-process-non-spam-directory "~/Mail/mail/misc") ;; Save table: (spam-stat-save) @@ -98,7 +96,7 @@ ;; rules in `nnmail-split-fancy'. Somewhere among these rules, you ;; will filter spam. Here is how you would create your dictionary: -;; Reset: (setq spam-stat (make-hash-table :test 'equal)) +;; Reset: (spam-stat-reset) ;; Learn spam: (spam-stat-process-spam-directory "~/Mail/mail/spam") ;; Learn non-spam: (spam-stat-process-non-spam-directory "~/Mail/mail/misc") ;; Repeat for any other non-spam group you need... @@ -118,6 +116,8 @@ ;; Ted Zlatanov ;; Jesper Harder ;; Dan Schmidt +;; Lasse Rasinen +;; Milan Zamazal @@ -127,7 +127,7 @@ "Statistical spam detection for Emacs. Use the functions to build a dictionary of words and their statistical distribution in spam and non-spam mails. Then use a function to determine -wether a buffer contains spam or not." +whether a buffer contains spam or not." :group 'gnus) (defcustom spam-stat-file "~/.spam-stat.el" @@ -136,6 +136,12 @@ See `spam-stat-to-hash-table' for the format of the file." :type 'file :group 'spam-stat) +(defcustom spam-stat-install-hooks t + "Whether spam-stat should install its hooks in Gnus. +This is set to nil if you use spam-stat through spam.el." + :type 'boolean + :group 'spam-stat) + (defcustom spam-stat-unknown-word-score 0.2 "The score to use for unknown words. Also used for words that don't appear often enough." @@ -155,10 +161,16 @@ This variable says how many characters this will be." (defcustom spam-stat-split-fancy-spam-group "mail.spam" "Name of the group where spam should be stored, if -`spam-stat-split-fancy' is used in fancy splitting rules." +`spam-stat-split-fancy' is used in fancy splitting rules. Has no +effect when spam-stat is invoked through spam.el." :type 'string :group 'spam-stat) +(defcustom spam-stat-split-fancy-spam-threshhold 0.9 + "Spam score threshhold in spam-stat-split-fancy." + :type 'number + :group 'spam-stat) + (defvar spam-stat-syntax-table (let ((table (copy-syntax-table text-mode-syntax-table))) (modify-syntax-entry ?- "w" table) @@ -226,10 +238,11 @@ This uses `gnus-article-buffer'." (set-buffer gnus-original-article-buffer) (spam-stat-store-current-buffer))) -(add-hook 'nnmail-prepare-incoming-message-hook - 'spam-stat-store-current-buffer) -(add-hook 'gnus-select-article-hook - 'spam-stat-store-gnus-article-buffer) +(when spam-stat-install-hooks + (add-hook 'nnmail-prepare-incoming-message-hook + 'spam-stat-store-current-buffer) + (add-hook 'gnus-select-article-hook + 'spam-stat-store-gnus-article-buffer)) ;; Data -- not using defstruct in order to save space and time @@ -386,17 +399,17 @@ Use `spam-stat-ngood', `spam-stat-nbad', `spam-stat-good', (interactive) (with-temp-buffer (let ((standard-output (current-buffer))) - (insert "(setq spam-stat (spam-stat-to-hash-table '(") + (insert "(setq spam-stat-ngood " + (number-to-string spam-stat-ngood) + " spam-stat-nbad " + (number-to-string spam-stat-nbad) + " spam-stat (spam-stat-to-hash-table '(") (maphash (lambda (word entry) (prin1 (list word (spam-stat-good entry) (spam-stat-bad entry)))) spam-stat) - (insert ")) spam-stat-ngood " - (number-to-string spam-stat-ngood) - " spam-stat-nbad " - (number-to-string spam-stat-nbad) - ")")) + (insert ")))")) (write-file spam-stat-file))) (defun spam-stat-load () @@ -422,7 +435,9 @@ has appeared in bad mails." "Reset `spam-stat' to an empty hash-table. This deletes all the statistics." (interactive) - (setq spam-stat (make-hash-table :test 'equal))) + (setq spam-stat (make-hash-table :test 'equal) + spam-stat-ngood 0 + spam-stat-nbad 0)) ;; Scoring buffers @@ -469,7 +484,7 @@ check the variable `spam-stat-score-data'." (progn (set-buffer spam-stat-buffer) (goto-char (point-min)) - (when (> (spam-stat-score-buffer) 0.9) + (when (> (spam-stat-score-buffer) spam-stat-split-fancy-spam-threshhold) (when (boundp 'nnmail-split-trace) (mapc (lambda (entry) (push entry nnmail-split-trace)) diff --git a/lisp/spam.el b/lisp/spam.el index 65387d0..cb171df 100644 --- a/lisp/spam.el +++ b/lisp/spam.el @@ -38,26 +38,13 @@ (require 'gnus-uu) ; because of key prefix issues (require 'gnus) ; for the definitions of group content classification and spam processors - -;; FIXME! We should not require `message' until we actually need -;; them. Best would be to declare needed functions as auto-loadable. -(require 'message) - -;; Attempt to load BBDB macros -(eval-when-compile - (condition-case nil - (require 'bbdb-com) - (file-error (defalias 'bbdb-search 'ignore)))) +(require 'message) ;for the message-fetch-field functions ;; autoload executable-find (eval-and-compile ;; executable-find is not autoloaded in Emacs 20 (autoload 'executable-find "executable")) -;; autoload ifile-spam-filter -(eval-and-compile - (autoload 'ifile-spam-filter "ifile-gnus")) - ;; autoload query-dig (eval-and-compile (autoload 'query-dig "dig")) @@ -76,6 +63,14 @@ :type 'directory :group 'spam) +(defcustom spam-move-spam-nonspam-groups-only t + "Whether spam should be moved in non-spam groups only. +When nil, only ham and unclassified groups will have their spam moved +to the spam-process-destination. When t, spam will also be moved from +spam groups." + :type 'boolean + :group 'spam-ifile) + (defcustom spam-whitelist (expand-file-name "whitelist" spam-directory) "The location of the whitelist. The file format is one regular expression per line. @@ -125,25 +120,31 @@ The regular expression is matched against the address." :type 'boolean :group 'spam) +(defcustom spam-use-stat nil + "Whether spam-stat should be used by spam-split." + :type 'boolean + :group 'spam) + (defcustom spam-split-group "spam" "Group name where incoming spam should be put by spam-split." :type 'string :group 'spam) -;; FIXME! The mailgroup list evidently depends on other choices made by the -;; user, so the built-in default below is not likely to be appropriate. (defcustom spam-junk-mailgroups (cons spam-split-group '("mail.junk" "poste.pourriel")) "Mailgroups with spam contents. All unmarked article in such group receive the spam mark on group entry." :type '(repeat (string :tag "Group")) :group 'spam) -(defcustom spam-blackhole-servers '("bl.spamcop.net" "relays.ordb.org" "dev.null.dk" "relays.visi.com") +(defcustom spam-blackhole-servers '("bl.spamcop.net" "relays.ordb.org" + "dev.null.dk" "relays.visi.com") "List of blackhole servers." :type '(repeat (string :tag "Server")) :group 'spam) -(defcustom spam-ham-marks (list 'gnus-del-mark 'gnus-read-mark 'gnus-killed-mark 'gnus-kill-file-mark 'gnus-low-score-mark) +(defcustom spam-ham-marks (list 'gnus-del-mark 'gnus-read-mark + 'gnus-killed-mark 'gnus-kill-file-mark + 'gnus-low-score-mark) "Marks considered as being ham (positively not spam). Such articles will be processed as ham (non-spam) on group exit." :type '(set @@ -169,6 +170,34 @@ Such articles will be transmitted to `bogofilter -s' on group exit." :type 'face :group 'spam) +(defgroup spam-ifile nil + "Spam ifile configuration." + :group 'spam) + +(defcustom spam-ifile-path (executable-find "ifile") + "File path of the ifile executable program." + :type '(choice (file :tag "Location of ifile") + (const :tag "ifile is not installed")) + :group 'spam-ifile) + +(defcustom spam-ifile-database-path nil + "File path of the ifile database." + :type '(choice (file :tag "Location of the ifile database") + (const :tag "Use the default")) + :group 'spam-ifile) + +(defcustom spam-ifile-spam-category "spam" + "Name of the spam ifile category." + :type 'string + :group 'spam-ifile) + +(defcustom spam-ifile-all-categories nil + "Whether the ifile check will return all categories, or just spam. +Set this to t if you want to use the spam-split invocation of ifile as +your main source of newsgroup names." + :type 'boolean + :group 'spam-ifile) + (defgroup spam-bogofilter nil "Spam bogofilter configuration." :group 'spam) @@ -199,7 +228,8 @@ Such articles will be transmitted to `bogofilter -s' on group exit." ;; (and previously X-NoSpam) are produced by the `NoSpam' tool, which has ;; never been published, so it might not be reasonable leaving it in the ;; list. -(defcustom spam-bogofilter-spaminfo-header-regexp "^X-\\(jf\\|Junk\\|NoSpam\\|Spam\\|SB\\)[^:]*:" +(defcustom spam-bogofilter-spaminfo-header-regexp + "^X-\\(jf\\|Junk\\|NoSpam\\|Spam\\|SB\\)[^:]*:" "Regexp for spam markups in headers. Markup from spam recognisers, as well as `Xref', are to be removed from articles before they get registered by Bogofilter." @@ -226,12 +256,14 @@ articles before they get registered by Bogofilter." (defun spam-group-spam-contents-p (group) (if (stringp group) (or (member group spam-junk-mailgroups) - (memq 'gnus-group-spam-classification-spam (gnus-parameter-spam-contents group))) + (memq 'gnus-group-spam-classification-spam + (gnus-parameter-spam-contents group))) nil)) (defun spam-group-ham-contents-p (group) (if (stringp group) - (memq 'gnus-group-spam-classification-ham (gnus-parameter-spam-contents group)) + (memq 'gnus-group-spam-classification-ham + (gnus-parameter-spam-contents group)) nil)) (defun spam-group-processor-p (group processor) @@ -240,53 +272,84 @@ articles before they get registered by Bogofilter." (member processor (car (gnus-parameter-spam-process group))) nil)) -(defun spam-group-processor-bogofilter-p (group) +(defun spam-group-spam-processor-bogofilter-p (group) (spam-group-processor-p group 'gnus-group-spam-exit-processor-bogofilter)) -(defun spam-group-processor-ifile-p (group) +(defun spam-group-spam-processor-blacklist-p (group) + (spam-group-processor-p group 'gnus-group-spam-exit-processor-blacklist)) + +(defun spam-group-spam-processor-ifile-p (group) (spam-group-processor-p group 'gnus-group-spam-exit-processor-ifile)) -(defun spam-group-processor-blacklist-p (group) - (spam-group-processor-p group 'gnus-group-spam-exit-processor-blacklist)) +(defun spam-group-ham-processor-ifile-p (group) + (spam-group-processor-p group 'gnus-group-ham-exit-processor-ifile)) + +(defun spam-group-spam-processor-stat-p (group) + (spam-group-processor-p group 'gnus-group-spam-exit-processor-stat)) -(defun spam-group-processor-whitelist-p (group) +(defun spam-group-ham-processor-stat-p (group) + (spam-group-processor-p group 'gnus-group-ham-exit-processor-stat)) + +(defun spam-group-ham-processor-whitelist-p (group) (spam-group-processor-p group 'gnus-group-ham-exit-processor-whitelist)) -(defun spam-group-processor-BBDB-p (group) +(defun spam-group-ham-processor-BBDB-p (group) (spam-group-processor-p group 'gnus-group-ham-exit-processor-BBDB)) -;;; Hooks dispatching. A bit raw for now. +;;; Summary entry and exit processing. (defun spam-summary-prepare () (spam-mark-junk-as-spam-routine)) +(add-hook 'gnus-summary-prepare-hook 'spam-summary-prepare) + (defun spam-summary-prepare-exit () ;; The spam processors are invoked for any group, spam or ham or neither (when (and spam-bogofilter-path - (spam-group-processor-bogofilter-p gnus-newsgroup-name)) + (spam-group-spam-processor-bogofilter-p gnus-newsgroup-name)) (spam-bogofilter-register-routine)) - (when (spam-group-processor-ifile-p gnus-newsgroup-name) - (spam-ifile-register-routine)) + (when (and spam-ifile-path + (spam-group-spam-processor-ifile-p gnus-newsgroup-name)) + (spam-ifile-register-spam-routine)) - (when (spam-group-processor-bogofilter-p gnus-newsgroup-name) + (when (spam-group-spam-processor-stat-p gnus-newsgroup-name) + (spam-stat-register-spam-routine)) + + (when (spam-group-spam-processor-bogofilter-p gnus-newsgroup-name) (spam-blacklist-register-routine)) - ;; Only for spam groups, we expire and maybe move articles - (when (spam-group-spam-contents-p gnus-newsgroup-name) - (spam-mark-spam-as-expired-and-move-routine (gnus-parameter-spam-process-destination gnus-newsgroup-name))) + (if spam-move-spam-nonspam-groups-only + (when (not (spam-group-spam-contents-p gnus-newsgroup-name)) + (spam-mark-spam-as-expired-and-move-routine + (gnus-parameter-spam-process-destination gnus-newsgroup-name))) + (spam-mark-spam-as-expired-and-move-routine + (gnus-parameter-spam-process-destination gnus-newsgroup-name))) + + ;; now we redo spam-mark-spam-as-expired-and-move-routine to only + ;; expire spam, in case the above did not expire them + (spam-mark-spam-as-expired-and-move-routine nil) (when (spam-group-ham-contents-p gnus-newsgroup-name) - (when (spam-group-processor-whitelist-p gnus-newsgroup-name) + (when (spam-group-ham-processor-whitelist-p gnus-newsgroup-name) (spam-whitelist-register-routine)) - (when (spam-group-processor-BBDB-p gnus-newsgroup-name) - (spam-BBDB-register-routine)))) + (when (spam-group-ham-processor-ifile-p gnus-newsgroup-name) + (spam-ifile-register-ham-routine)) + (when (spam-group-ham-processor-stat-p gnus-newsgroup-name) + (spam-stat-register-ham-routine)) + (when (spam-group-ham-processor-BBDB-p gnus-newsgroup-name) + (spam-BBDB-register-routine))) + + ;; now move all ham articles out of spam groups + (when (spam-group-spam-contents-p gnus-newsgroup-name) + (spam-ham-move-routine + (gnus-parameter-ham-process-destination gnus-newsgroup-name)))) -(add-hook 'gnus-summary-prepare-hook 'spam-summary-prepare) (add-hook 'gnus-summary-prepare-exit-hook 'spam-summary-prepare-exit) (defun spam-mark-junk-as-spam-routine () - ;; check the global list of group names spam-junk-mailgroups and the group parameters + ;; check the global list of group names spam-junk-mailgroups and the + ;; group parameters (when (spam-group-spam-contents-p gnus-newsgroup-name) (let ((articles gnus-newsgroup-articles) article) @@ -306,12 +369,27 @@ articles before they get registered by Bogofilter." (let ((gnus-current-article article)) (gnus-summary-move-article nil group))))))) +(defun spam-ham-move-routine (&optional group) + (let ((articles gnus-newsgroup-articles) + article ham-mark-values mark) + (dolist (mark spam-ham-marks) + (push (symbol-value mark) ham-mark-values)) + + (while articles + (setq article (pop articles)) + (when (and (memq mark ham-mark-values) + (stringp group)) + (let ((gnus-current-article article)) + (gnus-summary-move-article nil group)))))) + (defun spam-generic-register-routine (spam-func ham-func) (let ((articles gnus-newsgroup-articles) - article mark ham-articles spam-articles spam-mark-values ham-mark-values) + article mark ham-articles spam-articles spam-mark-values + ham-mark-values) - ;; marks are stored as symbolic values, so we have to dereference them for memq to work - ;; we wouldn't have to do this if gnus-summary-article-mark returned a symbol. + ;; marks are stored as symbolic values, so we have to dereference + ;; them for memq to work. we wouldn't have to do this if + ;; gnus-summary-article-mark returned a symbol. (dolist (mark spam-ham-marks) (push (symbol-value mark) ham-mark-values)) @@ -325,17 +403,43 @@ articles before they get registered by Bogofilter." ((memq article gnus-newsgroup-saved)) ((memq mark ham-mark-values) (push article ham-articles)))) (when (and ham-articles ham-func) - (mapc ham-func ham-articles)) ; we use mapc because unlike mapcar it discards the return values + (mapc ham-func ham-articles)) ; we use mapc because unlike + ; mapcar it discards the + ; return values (when (and spam-articles spam-func) - (mapc spam-func spam-articles)))) ; we use mapc because unlike mapcar it discards the return values + (mapc spam-func spam-articles)))) ; we use mapc because unlike + ; mapcar it discards the + ; return values + +(eval-and-compile + (defalias 'spam-point-at-eol (if (fboundp 'point-at-eol) + 'point-at-eol + 'line-end-position))) + +(defun spam-get-article-as-string (article) + (let ((article-string)) + (when (numberp article) + (save-window-excursion + (gnus-summary-goto-subject article) + (gnus-summary-show-article t) + (set-buffer gnus-article-buffer) + (setq article-string (buffer-string)))) + article-string)) (defun spam-fetch-field-from-fast (article) - "Fetch the `from' field quickly, using the Gnus internal gnus-data-list function" + "Fetch the `from' field quickly, using the internal gnus-data-list function" (if (and (numberp article) (assoc article (gnus-data-list nil))) (mail-header-from (gnus-data-header (assoc article (gnus-data-list nil)))) nil)) +(defun spam-fetch-field-subject-fast (article) + "Fetch the `subject' field quickly, using the internal gnus-data-list function" + (if (and (numberp article) + (assoc article (gnus-data-list nil))) + (mail-header-subject (gnus-data-header (assoc article (gnus-data-list nil)))) + nil)) + ;;;; Spam determination. @@ -344,6 +448,7 @@ articles before they get registered by Bogofilter." (spam-use-whitelist . spam-check-whitelist) (spam-use-BBDB . spam-check-BBDB) (spam-use-ifile . spam-check-ifile) + (spam-use-stat . spam-check-stat) (spam-use-blackholes . spam-check-blackholes) (spam-use-bogofilter . spam-check-bogofilter)) "The spam-list-of-checks list contains pairs associating a parameter @@ -365,6 +470,9 @@ example like this: (: spam-split) See the Info node `(gnus)Fancy Mail Splitting' for more details." (interactive) + + ;; load the spam-stat tables if needed + (when spam-use-stat (spam-stat-load)) (let ((list-of-checks spam-list-of-checks) decision) @@ -399,7 +507,7 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." (if spam-use-dig (let ((query-result (query-dig query-string))) (when query-result - (message "spam detected with blackhole check of relay %s (dig query result '%s')" query-string query-result) + (message "spam: positive blackhole check '%s'" query-result) (push (list ip server query-result) matches))) ;; else, if not using dig.el @@ -409,66 +517,169 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." (when matches spam-split-group))) -;;;; BBDB -;;; original idea for spam-check-BBDB from Alexander Kotelnikov +;;;; BBDB + +;;; original idea for spam-check-BBDB from Alexander Kotelnikov +;;; ;; all this is done inside a condition-case to trap errors + (condition-case nil (progn - + (require 'bbdb) (require 'bbdb-com) - - (defun spam-enter-ham-BBDB (from) - "Enter an address into the BBDB; implies ham (non-spam) sender" - (when (stringp from) - (let* ((parsed-address (gnus-extract-address-components from)) - (name (or (car parsed-address) "Ham Sender")) - (net-address (car (cdr parsed-address)))) - (message "Adding address %s to BBDB" from) - (when (and net-address - (not (bbdb-search (bbdb-records) nil nil net-address))) - (bbdb-create-internal name nil net-address nil nil "ham sender added by spam.el"))))) - - (defun spam-BBDB-register-routine () - (spam-generic-register-routine - ;; spam function - nil - ;; ham function - (lambda (article) - (spam-enter-ham-BBDB (spam-fetch-field-from-fast article))))) - - (defun spam-check-BBDB () - "Mail from people in the BBDB is never considered spam" - (let ((who (message-fetch-field "from"))) - (when who - (setq who (regexp-quote (cadr (gnus-extract-address-components who)))) - (if (bbdb-search (bbdb-records) nil nil who) nil spam-split-group))))) + + (defun spam-enter-ham-BBDB (from) + "Enter an address into the BBDB; implies ham (non-spam) sender" + (when (stringp from) + (let* ((parsed-address (gnus-extract-address-components from)) + (name (or (car parsed-address) "Ham Sender")) + (net-address (car (cdr parsed-address)))) + (message "Adding address %s to BBDB" from) + (when (and net-address + (not (bbdb-search-simple nil net-address))) + (bbdb-create-internal name nil net-address nil nil + "ham sender added by spam.el"))))) + + (defun spam-BBDB-register-routine () + (spam-generic-register-routine + ;; spam function + nil + ;; ham function + (lambda (article) + (spam-enter-ham-BBDB (spam-fetch-field-from-fast article))))) + + (defun spam-check-BBDB () + "Mail from people in the BBDB is never considered spam" + (let ((who (message-fetch-field "from"))) + (when who + (setq who (regexp-quote (cadr + (gnus-extract-address-components who)))) + (if (bbdb-search-simple nil who) + nil spam-split-group))))) (file-error (progn - (setq spam-list-of-checks - (delete (assoc 'spam-use-BBDB spam-list-of-checks) - spam-list-of-checks)) - (defun spam-check-BBDB () - message "spam-check-BBDB was invoked, but it shouldn't have") - (defun spam-BBDB-register-routine () - (spam-generic-register-routine nil nil))))) + (defalias 'bbdb-search-simple 'ignore) + (defalias 'spam-check-BBDB 'ignore) + (defalias 'spam-BBDB-register-routine 'ignore) + (defalias 'spam-enter-ham-BBDB 'ignore) + (defalias 'bbdb-create-internal 'ignore) + (defalias 'bbdb-records 'ignore)))) ;;;; ifile -;;; uses ifile-gnus.el from http://www.ai.mit.edu/people/jhbrown/ifile-gnus.html -;;; check the ifile backend; return nil if the mail was NOT classified as spam -;;; TODO: we can't (require 'ifile-gnus), because it will insinuate itself automatically + +;;; check the ifile backend; return nil if the mail was NOT classified +;;; as spam + +(defun spam-get-ifile-database-parameter () + "Get the command-line parameter for ifile's database from spam-ifile-database-path." + (if spam-ifile-database-path + (format "--db-file=%s" spam-ifile-database-path) + nil)) + (defun spam-check-ifile () - (let ((ifile-primary-spam-group spam-split-group)) - (ifile-spam-filter nil))) + "Check the ifile backend for the classification of this message" + (let ((article-buffer-name (buffer-name)) + category return) + (with-temp-buffer + (let ((temp-buffer-name (buffer-name)) + (db-param (spam-get-ifile-database-parameter))) + (save-excursion + (set-buffer article-buffer-name) + (if db-param + (call-process-region (point-min) (point-max) spam-ifile-path + nil temp-buffer-name nil "-q" "-c" db-param) + (call-process-region (point-min) (point-max) spam-ifile-path + nil temp-buffer-name nil "-q" "-c"))) + (goto-char (point-min)) + (if (not (eobp)) + (setq category (buffer-substring (point) (spam-point-at-eol)))) + (when (not (zerop (length category))) ; we need a category here + (if spam-ifile-all-categories + (setq return category) + ;; else, if spam-ifile-all-categories is not set... + (when (string-equal spam-ifile-spam-category category) + (setq return spam-split-group)))))) + return)) + +(defun spam-ifile-register-with-ifile (article-string category) + "Register an article, given as a string, with a category. +Uses `gnus-newsgroup-name' if category is nil (for ham registration)." + (when (stringp article-string) + (let ((category (or category gnus-newsgroup-name)) + (db-param (spam-get-ifile-database-parameter))) + (with-temp-buffer + (insert-string article-string) + (if db-param + (call-process-region (point-min) (point-max) spam-ifile-path + nil nil nil + "-h" "-i" category db-param) + (call-process-region (point-min) (point-max) spam-ifile-path + nil nil nil + "-h" "-i" category)))))) + +(defun spam-ifile-register-spam-routine () + (spam-generic-register-routine + (lambda (article) + (spam-ifile-register-with-ifile + (spam-get-article-as-string article) spam-ifile-spam-category)) + nil)) -;; TODO: add ifile registration -;; We need ifile-gnus.el to support nnimap; we could feel the message -;; directly to ifile like we do with bogofilter but that's ugly. -(defun spam-ifile-register-routine () - (spam-generic-register-routine nil nil)) +(defun spam-ifile-register-ham-routine () + (spam-generic-register-routine + nil + (lambda (article) + (spam-ifile-register-with-ifile + (spam-get-article-as-string article) nil)))) +;;;; spam-stat + +(condition-case nil + (progn + (let ((spam-stat-install-hooks nil)) + (require 'spam-stat)) + + (defun spam-check-stat () + "Check the spam-stat backend for the classification of this message" + (let ((spam-stat-split-fancy-spam-group spam-split-group) ; override + (spam-stat-buffer (buffer-name)) ; stat the current buffer + category return) + (spam-stat-split-fancy))) + + (defun spam-stat-register-spam-routine () + (spam-generic-register-routine + (lambda (article) + (let ((article-string (spam-get-article-as-string article))) + (with-temp-buffer + (insert-string article-string) + (spam-stat-buffer-is-spam)))) + nil) + (spam-stat-save)) + + (defun spam-stat-register-ham-routine () + (spam-generic-register-routine + nil + (lambda (article) + (let ((article-string (spam-get-article-as-string article))) + (with-temp-buffer + (insert-string article-string) + (spam-stat-buffer-is-non-spam))))) + (spam-stat-save))) + + (file-error (progn + (defalias 'spam-stat-register-ham-routine 'ignore) + (defalias 'spam-stat-register-spam-routine 'ignore) + (defalias 'spam-stat-buffer-is-spam 'ignore) + (defalias 'spam-stat-buffer-is-non-spam 'ignore) + (defalias 'spam-stat-split-fancy 'ignore) + (defalias 'spam-stat-load 'ignore) + (defalias 'spam-stat-save 'ignore) + (defalias 'spam-check-stat 'ignore)))) + + + ;;;; Blacklists and whitelists. (defvar spam-whitelist-cache nil) @@ -512,11 +723,6 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." (setq spam-blacklist-cache (spam-parse-list spam-blacklist))) (and (spam-from-listed-p spam-blacklist-cache) spam-split-group)) -(eval-and-compile - (defalias 'spam-point-at-eol (if (fboundp 'point-at-eol) - 'point-at-eol - 'line-end-position))) - (defun spam-parse-list (file) (when (file-readable-p file) (let (contents address) @@ -566,14 +772,15 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." ;;; See Paul Graham article, at `http://www.paulgraham.com/spam.html'. -;;; This page is for those wanting to control spam with the help of Eric -;;; Raymond's speedy Bogofilter, see http://www.tuxedo.org/~esr/bogofilter. -;;; This has been tested with a locally patched copy of version 0.4. +;;; This page is for those wanting to control spam with the help of +;;; Eric Raymond's speedy Bogofilter, see +;;; http://www.tuxedo.org/~esr/bogofilter. This has been tested with +;;; a locally patched copy of version 0.4. -;;; Make sure Bogofilter is installed. Bogofilter internally uses Judy fast -;;; associative arrays, so you need to install Judy first, and Bogofilter -;;; next. Fetch both distributions by visiting the following links and -;;; downloading the latest version of each: +;;; Make sure Bogofilter is installed. Bogofilter internally uses +;;; Judy fast associative arrays, so you need to install Judy first, +;;; and Bogofilter next. Fetch both distributions by visiting the +;;; following links and downloading the latest version of each: ;;; ;;; http://sourceforge.net/projects/judy/ ;;; http://www.tuxedo.org/~esr/bogofilter/ @@ -584,14 +791,15 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." ;;; make ;;; make install ;;; -;;; You will likely need to become super-user for the last step. Then, unpack -;;; the Bogofilter distribution and enter its main directory: +;;; You will likely need to become super-user for the last step. +;;; Then, unpack the Bogofilter distribution and enter its main +;;; directory: ;;; ;;; make ;;; make install ;;; -;;; Here as well, you need to become super-user for the last step. Now, -;;; initialize your word lists by doing, under your own identity: +;;; Here as well, you need to become super-user for the last step. +;;; Now, initialize your word lists by doing, under your own identity: ;;; ;;; mkdir ~/.bogofilter ;;; touch ~/.bogofilter/badlist @@ -599,52 +807,59 @@ See the Info node `(gnus)Fancy Mail Splitting' for more details." ;;; ;;; These two files are text files you may edit, but you normally don't! -;;; The `M-d' command gets added to Gnus summary mode, marking current article -;;; as spam, showing it with the `H' mark. Whenever you see a spam article, -;;; make sure to mark its summary line with `M-d' before leaving the group. -;;; Some groups, as per variable `spam-junk-mailgroups' below, receive articles -;;; from Gnus splitting on clues added by spam recognisers, so for these -;;; groups, we tack an `H' mark at group entry for all summary lines which -;;; would otherwise have no other mark. Make sure to _remove_ `H' marks for -;;; any article which is _not_ genuine spam, before leaving such groups: you -;;; may use `M-u' to "unread" the article, or `d' for declaring it read the -;;; non-spam way. When you leave a group, all `H' marked articles, saved or -;;; unsaved, are sent to Bogofilter which will study them as spam samples. +;;; The `M-d' command gets added to Gnus summary mode, marking current +;;; article as spam, showing it with the `H' mark. Whenever you see a +;;; spam article, make sure to mark its summary line with `M-d' before +;;; leaving the group. Some groups, as per variable +;;; `spam-junk-mailgroups' below, receive articles from Gnus splitting +;;; on clues added by spam recognisers, so for these groups, we tack +;;; an `H' mark at group entry for all summary lines which would +;;; otherwise have no other mark. Make sure to _remove_ `H' marks for +;;; any article which is _not_ genuine spam, before leaving such +;;; groups: you may use `M-u' to "unread" the article, or `d' for +;;; declaring it read the non-spam way. When you leave a group, all +;;; `H' marked articles, saved or unsaved, are sent to Bogofilter +;;; which will study them as spam samples. ;;; Messages may also be deleted in various other ways, and unless -;;; `spam-ham-marks-form' gets overridden below, marks `R' and `r' for default -;;; read or explicit delete, marks `X' and 'K' for automatic or explicit -;;; kills, as well as mark `Y' for low scores, are all considered to be -;;; associated with articles which are not spam. This assumption might be -;;; false, in particular if you use kill files or score files as means for -;;; detecting genuine spam, you should then adjust `spam-ham-marks-form'. When -;;; you leave a group, all _unsaved_ articles bearing any the above marks are -;;; sent to Bogofilter which will study these as not-spam samples. If you -;;; explicit kill a lot, you might sometimes end up with articles marked `K' -;;; which you never saw, and which might accidentally contain spam. Best is -;;; to make sure that real spam is marked with `H', and nothing else. - -;;; All other marks do not contribute to Bogofilter pre-conditioning. In -;;; particular, ticked, dormant or souped articles are likely to contribute -;;; later, when they will get deleted for real, so there is no need to use -;;; them prematurely. Explicitly expired articles do not contribute, command -;;; `E' is a way to get rid of an article without Bogofilter ever seeing it. - -;;; In a word, with a minimum of care for associating the `H' mark for spam -;;; articles only, Bogofilter training all gets fairly automatic. You should -;;; do this until you get a few hundreds of articles in each category, spam -;;; or not. The shell command `head -1 ~/.bogofilter/*' shows both article -;;; counts. The command `S S' in summary mode, either for debugging or for -;;; curiosity, triggers Bogofilter into displaying in another buffer the -;;; "spamicity" score of the current article (between 0.0 and 1.0), together -;;; with the article words which most significantly contribute to the score. - -;;; The real way for using Bogofilter, however, is to have some use tool like -;;; `procmail' for invoking it on message reception, then adding some -;;; recognisable header in case of detected spam. Gnus splitting rules might -;;; later trip on these added headers and react by sorting such articles into -;;; specific junk folders as per `spam-junk-mailgroups'. Here is a possible -;;; `.procmailrc' contents (still untested -- please tell me how it goes): +;;; `spam-ham-marks-form' gets overridden below, marks `R' and `r' for +;;; default read or explicit delete, marks `X' and 'K' for automatic +;;; or explicit kills, as well as mark `Y' for low scores, are all +;;; considered to be associated with articles which are not spam. +;;; This assumption might be false, in particular if you use kill +;;; files or score files as means for detecting genuine spam, you +;;; should then adjust `spam-ham-marks-form'. When you leave a group, +;;; all _unsaved_ articles bearing any the above marks are sent to +;;; Bogofilter which will study these as not-spam samples. If you +;;; explicit kill a lot, you might sometimes end up with articles +;;; marked `K' which you never saw, and which might accidentally +;;; contain spam. Best is to make sure that real spam is marked with +;;; `H', and nothing else. + +;;; All other marks do not contribute to Bogofilter pre-conditioning. +;;; In particular, ticked, dormant or souped articles are likely to +;;; contribute later, when they will get deleted for real, so there is +;;; no need to use them prematurely. Explicitly expired articles do +;;; not contribute, command `E' is a way to get rid of an article +;;; without Bogofilter ever seeing it. + +;;; In a word, with a minimum of care for associating the `H' mark for +;;; spam articles only, Bogofilter training all gets fairly automatic. +;;; You should do this until you get a few hundreds of articles in +;;; each category, spam or not. The shell command `head -1 +;;; ~/.bogofilter/*' shows both article counts. The command `S S' in +;;; summary mode, either for debugging or for curiosity, triggers +;;; Bogofilter into displaying in another buffer the "spamicity" score +;;; of the current article (between 0.0 and 1.0), together with the +;;; article words which most significantly contribute to the score. + +;;; The real way for using Bogofilter, however, is to have some use +;;; tool like `procmail' for invoking it on message reception, then +;;; adding some recognisable header in case of detected spam. Gnus +;;; splitting rules might later trip on these added headers and react +;;; by sorting such articles into specific junk folders as per +;;; `spam-junk-mailgroups'. Here is a possible `.procmailrc' contents +;;; (still untested -- please tell me how it goes): ;;; ;;; :0HBf: ;;; * ? bogofilter @@ -681,10 +896,12 @@ spamicity coefficient of each, and the overall article spamicity." (defun spam-bogofilter-register-routine () (let ((articles gnus-newsgroup-articles) - article mark ham-articles spam-articles spam-mark-values ham-mark-values) + article mark ham-articles spam-articles spam-mark-values + ham-mark-values) - ;; marks are stored as symbolic values, so we have to dereference them for memq to work - ;; we wouldn't have to do this if gnus-summary-article-mark returned a symbol. + ;; marks are stored as symbolic values, so we have to dereference + ;; them for memq to work we wouldn't have to do this if + ;; gnus-summary-article-mark returned a symbol. (dolist (mark spam-ham-marks) (push (symbol-value mark) ham-mark-values)) @@ -705,7 +922,8 @@ spamicity coefficient of each, and the overall article spamicity." (defun spam-bogofilter-articles (type option articles) (let ((output-buffer (get-buffer-create spam-bogofilter-output-buffer-name)) (article-copy (get-buffer-create " *Bogofilter Article Copy*")) - (remove-regexp (concat spam-bogofilter-spaminfo-header-regexp "\\|Xref:")) + (remove-regexp (concat spam-bogofilter-spaminfo-header-regexp + "\\|Xref:")) (counter 0) prefix process article) (when type diff --git a/texi/ChangeLog b/texi/ChangeLog index db01533..a96296f 100644 --- a/texi/ChangeLog +++ b/texi/ChangeLog @@ -1,3 +1,111 @@ +2003-01-11 Lars Magne Ingebrigtsen + + * message.texi (Header Commands): Addition. + +2003-01-10 Reiner Steib + + * gnus.texi (Article Washing): Added gnus-outlook-unwrap-lines, + gnus-outlook-repair-attribution, gnus-outlook-rearrange-citation. + +2003-01-11 Lars Magne Ingebrigtsen + + * gnus.texi: Change .gnus to .gnus.el. + (Agent Commands): Remove batch here. + (MIME Commands): Add. + + * message.texi (Canceling News): Document canlock. + +2003-01-10 Alex Schroeder + + * gnus.texi (Creating a spam-stat dictionary): Explain how using + the Gnus Agent with nnimap might work to do this. + (Splitting mail using spam-stat): Use nnimap-split-fancy if you + use the nnimap back end. + +2003-01-10 Katsumi Yamaoka + + * gnus.texi (Filtering Spam Using spam.el): Trivial fix. + +2003-01-10 Simon Josefsson + + * gnus.texi (Choosing Variables, Agent Caveats): Add. + +2003-01-09 Teodor Zlatanov + + * gnus.texi (Filtering Spam Using spam.el, ifile spam filtering) + (spam-stat spam filtering): added new functionality and explained + old functionality better, especially where it has changed in + ham/spam/unclassified group exit processing. + +2003-01-09 Alex Schroeder + + * gnus.texi (Splitting mail using spam-stat): Fix typo. + (Creating a spam-stat dictionary, Splitting mail using spam-stat): + Fix markup in a few places. + +2003-01-08 Lars Magne Ingebrigtsen + + * gnus.texi (Positioning Point): %~ => ~*. + +2003-01-07 Reiner Steib + + * message.texi: Updated copyright line. + (Mailing Lists): Updated (renamed) function names. + (Header Commands): Updated (renamed) function names. + (Header Commands): Added message-to-list-only, + message-change-subject, message-cross-post-followup-to, + message-reduce-to-to-cc and message-add-archive-header. Moved + message-sort-headers, message-insert-to, message-insert-newsgroups + and message-insert-disposition-notification-to from other + sections. + (Insertion): Added message-mark-inserted-region, + message-mark-insert-file. Moved + message-insert-disposition-notification-to to section (Header + Commands). + (Various Commands): Moved message-insert-wide-reply, + message-insert-to, message-insert-newsgroups and + message-sort-headers to (Header Commands) section. + (Message Headers): Added message-subject-trailing-was-query. + (Insertion Variables): Added message-mark-insert-begin and + message-mark-insert-end. + +2003-01-08 Lars Magne Ingebrigtsen + + * gnus.texi (Optional Back End Functions): Addition. + (Positioning Point): Changed to %~. + +2003-01-08 Simon Josefsson + + * gnus.texi (MIME Commands): Add. + +2003-01-06 Lars Magne Ingebrigtsen + + * message.texi (Various Commands): Addition. + +2003-01-05 Teodor Zlatanov + + * gnus.texi (Filtering Spam Using spam.el) + (Blacklists and Whitelists, BBDB Whitelists, Blackholes) + (Bogofilter, Ifile spam filtering, Extending spam.el): updated + documentation for the new spam.el functionality + +2003-01-05 Jesper Harder + + * refcard.tex: Fix pagebreak. + + * gnusref.tex: Additions and fixes. + + * booklet.tex: Add missing sections. + + * gnus.texi (Mail Group Commands): Fix typo. + (XEmacs): Add sh-script. + (Article Washing): Fix. + +2003-01-05 Lars Magne Ingebrigtsen + + * gnus.texi (Setting Process Marks): Addition. + (Group Line Specification): Addition. + 2003-01-04 Lars Magne Ingebrigtsen * gnus.texi (Group Line Specification): Addition. diff --git a/texi/booklet.tex b/texi/booklet.tex index 5ae0821..b6ee207 100644 --- a/texi/booklet.tex +++ b/texi/booklet.tex @@ -43,7 +43,7 @@ \GroupTopicsGeneral \subsubsection*{Topic Sorting} \TopicSorting -% +\pagebreak % summary-mode \section*{Summary-Mode} \SummaryModeGeneral @@ -100,6 +100,12 @@ \ArticleModeGeneral \subsection*{Wash the Article-Buffer} \WashArticle + \subsubsection*{Blank Lines and Whitespace} + \BlankAndWhitespace + \subsubsection*{Picons, X-faces, Smileys} + \Picons + \subsubsection*{Time and Date} + \TimeAndDate \subsection*{Hide/Highlight Parts of the Article} \HideHighlightArticle \subsection*{MIME operations from the Article-Buffer (reading)} diff --git a/texi/gnus.texi b/texi/gnus.texi index 3ad4657..f7fed78 100644 --- a/texi/gnus.texi +++ b/texi/gnus.texi @@ -33,7 +33,7 @@ \makeindex \begin{document} -\newcommand{\gnusversionname}{Oort Gnus v0.10} +\newcommand{\gnusversionname}{Oort Gnus v0.11} \newcommand{\gnuschaptername}{} \newcommand{\gnussectionname}{} @@ -288,7 +288,8 @@ \thispagestyle{empty} -Copyright \copyright{} 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +Copyright \copyright{} 1995, 1996, 1997, 1998, 1999, 2000, 2001, +2002, 2003 Free Software Foundation, Inc. @@ -317,7 +318,7 @@ license to the document, as described in section 6 of the license. This file documents Gnus, the GNU Emacs newsreader. -Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document @@ -347,7 +348,8 @@ license to the document, as described in section 6 of the license. @page @vskip 0pt plus 1filll -Copyright @copyright{} 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +Copyright @copyright{} 1995, 1996, 1997, 1998, 1999, 2000, 2001, +2002, 2003 Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document @@ -383,7 +385,7 @@ can be gotten by any nefarious means you can think of---@sc{nntp}, local spool or your mbox file. All at the same time, if you want to push your luck. -This manual corresponds to Oort Gnus v0.10. +This manual corresponds to Oort Gnus v0.11. @end ifinfo @@ -1778,6 +1780,9 @@ Number of ticked articles. @item R Number of read articles. +@item U +Number of unseen articles. + @item t Estimated total number of articles. (This is really @var{max-number} minus @var{min-number} plus 1.) @@ -2878,7 +2883,7 @@ See also @code{gnus-group-ignored-charsets-alist}. @item posting-style @cindex posting-style -You can store additional posting style information for this group only +You can store additional posting style information for this group here (@pxref{Posting Styles}). The format is that of an entry in the @code{gnus-posting-styles} alist, except that there's no regexp matching the group name (of course). Style elements in this group parameter will @@ -2891,6 +2896,7 @@ like this in the group parameters: @example (posting-style (name "Funky Name") + ("X-My-Header" "Funky Value") (signature "Funky Signature")) @end example @@ -4960,7 +4966,9 @@ the server and display it in the article buffer. @item gnus-select-article-hook @vindex gnus-select-article-hook This hook is called whenever an article is selected. By default it -exposes any threads hidden under the selected article. +exposes any threads hidden under the selected article. If you wish +that the Agent saves all articles you read, putting +@code{gnus-agent-fetch-selected-article} on this hook should do it. @item gnus-mark-article-hook @vindex gnus-mark-article-hook @@ -5937,6 +5945,13 @@ or @subsection Setting Process Marks @cindex setting process marks +Process marks are displayed as @code{#} in the summary buffer, and are +used for marking articles in such a way that other commands will +process these articles. For instance, if you process mark four +articles and then use the @kbd{*} command, Gnus will enter these four +commands into the cache. For more information, +@pxref{Process/Prefix}. + @table @kbd @item M P p @@ -8283,11 +8298,34 @@ apostrophe or quotation mark, then try this wash. @item W k @kindex W k (Summary) +@kindex W Y f (Summary) @findex gnus-article-outlook-deuglify-article @cindex Outlook Express -Deuglify broken Outlook (Express) articles and redisplay +Deuglify broken Outlook (Express) articles. (@code{gnus-article-outlook-deuglify-article}). +@item W Y u +@kindex W Y u (Summary) +@findex gnus-outlook-unwrap-lines +Unwrap lines that appear to be wrapped citation lines. You can control +what lines will be unwrapped by frobbing +@code{gnus-outlook-deuglify-unwrap-min} and +@code{gnus-outlook-deuglify-unwrap-max}, indicating the miminum and +maximum length of an unwrapped citation line. +(@code{gnus-outlook-deuglify-article}). + +@item W Y a +@kindex W Y a (Summary) +@findex gnus-outlook-repair-attribution +Repair a broken attribution line +(@code{gnus-outlook-repair-attribution}). + +@item W Y c +@kindex W Y c (Summary) +@findex gnus-outlook-rearrange-citation +Repair broken citations by rearranging the text. +(@code{gnus-outlook-rearrange-citation}). + @item W w @kindex W w (Summary) @findex gnus-article-fill-cited-article @@ -8364,10 +8402,10 @@ If a prefix is given, a charset will be asked for. @vindex gnus-article-wash-function The default is to use the function specified by -@code{mm-inline-text-html-renderer} (@pxref{Customization, , , emacs-mime}) -to convert the @sc{html}, but this is controlled by the -@code{gnus-article-wash-function} variable. Pre-defined functions you -can use include: +@code{mm-text-html-renderer} (@pxref{(emacs-mime)Display +Customization}) to convert the @sc{html}, but this is controlled by +the @code{gnus-article-wash-function} variable. Pre-defined functions +you can use include: @table @code @item w3 @@ -8926,13 +8964,29 @@ To have all Vcards be ignored, you'd say something like this: '("text/x-vcard")) @end lisp +@item gnus-article-loose-mime +@vindex gnus-article-loose-mime +If non-@code{nil}, Gnus won't required the @samp{MIME-Version} header +before interpreting the message as a @sc{mime} message. This helps +when reading messages from certain broken mail user agents. The +default is @code{nil}. + +@item gnus-article-emulate-mime +@vindex gnus-article-emulate-mime +There are other, non-@sc{mime} encoding methods used. The most common +is @samp{uuencode}, but yEncode is also getting to be popular. If +This variable is non-@code{nil}, Gnus will look in message bodies to +see if it finds these encodings, and if so, it'll run them through the +Gnus @sc{mime} machinery. The default is @code{t}. + @item gnus-unbuttonized-mime-types @vindex gnus-unbuttonized-mime-types This is a list of regexps. @sc{mime} types that match a regexp from this list won't have @sc{mime} buttons inserted unless they aren't displayed or this variable is overridden by @code{gnus-buttonized-mime-types}. The default value is -@code{(".*/.*")}. +@code{(".*/.*")}. This variable is only used when +@code{gnus-inhibit-mime-unbuttonizing} is nil. @item gnus-buttonized-mime-types @vindex gnus-buttonized-mime-types @@ -8940,6 +8994,8 @@ This is a list of regexps. @sc{mime} types that match a regexp from this list will have @sc{mime} buttons inserted unless they aren't displayed. This variable overrides @code{gnus-unbuttonized-mime-types}. The default value is @code{nil}. +This variable is only used when @code{gnus-inhibit-mime-unbuttonizing} +is nil. To see e.g. security buttons but no other buttons, you could set this variable to @code{("multipart/signed")} and leave @@ -9009,7 +9065,7 @@ such unenlightened users, you can make live easier by adding @end lisp @noindent -to your @file{.gnus} file. +to your @file{.gnus.el} file. @end table @@ -9655,7 +9711,7 @@ will tell you (@code{gnus-summary-respool-query}). @kindex B t (Summary) @findex gnus-summary-respool-trace Similarly, this command will display all fancy splitting patterns used -when repooling, if any (@code{gnus-summary-respool-trace}). +when respooling, if any (@code{gnus-summary-respool-trace}). @item B p @kindex B p (Summary) @@ -12547,7 +12603,7 @@ mail back end of your choice into @code{gnus-secondary-select-methods}, and things will happen automatically. For instance, if you want to use @code{nnml} (which is a "one file per -mail" back end), you could put the following in your @file{.gnus} file: +mail" back end), you could put the following in your @file{.gnus.el} file: @lisp (setq gnus-secondary-select-methods '((nnml ""))) @@ -13608,7 +13664,7 @@ However, if you change group parameters, you'd have to update @code{nnmail-split-fancy} manually. You can do it by running @code{gnus-group-split-update}. If you'd rather have it updated automatically, just tell @code{gnus-group-split-setup} to do it for -you. For example, add to your @file{.gnus}: +you. For example, add to your @file{.gnus.el}: @lisp (gnus-group-split-setup AUTO-UPDATE CATCH-ALL) @@ -13738,7 +13794,7 @@ By default, if you have auto expiry switched on, Gnus will mark all the articles you read as expirable, no matter if they were read or unread before. To avoid having articles marked as read marked as expirable automatically, you can put something like the following in your -@file{.gnus} file: +@file{.gnus.el} file: @vindex gnus-mark-article-hook @lisp @@ -16956,13 +17012,6 @@ toggles the plugged/unplugged state of the Gnus Agent. * Server Agent Commands:: @end menu -You can run a complete batch command from the command line with the -following incantation: - -@cindex gnus-agent-batch -@example -$ emacs -batch -l ~/.gnus.el -f gnus-agent-batch -@end example @@ -17303,9 +17352,12 @@ Having the Gnus Agent fetch articles (and post whatever messages you've written) is quite easy once you've gotten things set up properly. The following shell script will do everything that is necessary: +You can run a complete batch command from the command line with the +following incantation: + @example #!/bin/sh -emacs -batch -l ~/.emacs -f gnus-agent-batch >/dev/null +emacs -batch -l ~/.emacs -f -l ~/.gnus.el gnus-agent-batch >/dev/null 2>&1 @end example @@ -17319,7 +17371,9 @@ may ask: @table @dfn @item If I read an article while plugged, do they get entered into the Agent? -@strong{No}. +@strong{No}. If you want this behaviour, add +@code{gnus-agent-fetch-selected-article} to +@code{gnus-select-article-hook}. @item If I read an article while plugged, and the article already exists in the Agent, will it get downloaded once more? @@ -19601,8 +19655,8 @@ You can redefine the function that moves the point to the colon. The function is called @code{gnus-goto-colon}. But perhaps the most convenient way to deal with this, if you don't want -to have a colon in your line, is to use the @samp{%C} specifier. If you -put a @samp{%C} somewhere in your format line definition, Gnus will +to have a colon in your line, is to use the @samp{%*} specifier. If you +put a @samp{%*} somewhere in your format line definition, Gnus will place point there. @@ -20213,7 +20267,7 @@ seconds. This is 60 by default. If you change that variable, all the timings in the handlers will be affected.) So, if you want to add a handler, you could put something like this in -your @file{.gnus} file: +your @file{.gnus.el} file: @findex gnus-demon-add-handler @lisp @@ -20230,7 +20284,7 @@ Some ready-made functions to do this have been created: @code{gnus-demon-add-nntp-close-connection}, @code{gnus-demon-add-scan-timestamps}, @code{gnus-demon-add-rescan}, and @code{gnus-demon-add-scanmail}. Just put those functions in your -@file{.gnus} if you want those abilities. +@file{.gnus.el} if you want those abilities. @findex gnus-demon-init @findex gnus-demon-cancel @@ -21344,7 +21398,9 @@ a useful contribution, however. The idea behind @code{spam.el} is to have a control center for spam detection and filtering in Gnus. To that end, @code{spam.el} does two things: it -filters incoming mail, and it analyzes mail known to be spam. +filters incoming mail, and it analyzes mail known to be spam or ham. +@emph{Ham} is the name used throughout @code{spam.el} to indicate +non-spam messages. So, what happens when you load @code{spam.el}? First of all, you get the following keyboard commands: @@ -21362,7 +21418,8 @@ the following keyboard commands: Mark current article as spam, showing it with the @samp{H} mark. Whenever you see a spam article, make sure to mark its summary line -with @kbd{M-d} before leaving the group. +with @kbd{M-d} before leaving the group. This is done automatically +for unread articles in @emph{spam} groups. @item M s t @itemx S t @@ -21378,44 +21435,89 @@ properly. @end table -Gnus can learn from the spam you get. All you have to do is collect -your spam in one or more spam groups, and set the variable -@code{spam-junk-mailgroups} as appropriate. In these groups, all messages -are considered to be spam by default: they get the @samp{H} mark. You must -review these messages from time to time and remove the @samp{H} mark for -every message that is not spam after all. When you leave a spam -group, all messages that continue with the @samp{H} mark, are passed on to -the spam-detection engine (bogofilter, ifile, and others). To remove -the @samp{H} mark, you can use @kbd{M-u} to "unread" the article, or @kbd{d} for -declaring it read the non-spam way. When you leave a group, all @samp{H} -marked articles, saved or unsaved, are sent to Bogofilter or ifile -(depending on @code{spam-use-bogofilter} and @code{spam-use-ifile}), which will study -them as spam samples. +Also, when you load @code{spam.el}, you will be able to customize its +variables. Try @code{customize-group} on the @samp{spam} variable +group. + +The concepts of ham processors and spam processors are very important. +Ham processors and spam processors for a group can be set with the +@code{spam-process} group parameter, or the +@code{gnus-spam-process-newsgroups} variable. Ham processors take +mail known to be non-spam (@emph{ham}) and process it in some way so +that later similar mail will also be considered non-spam. Spam +processors take mail known to be spam and process it so similar spam +will be detected later. + +Gnus learns from the spam you get. You have to collect your spam in +one or more spam groups, and set or customize the variable +@code{spam-junk-mailgroups} as appropriate. You can also declare +groups to contain spam by setting their group parameter +@code{spam-contents} to @code{gnus-group-spam-classification-spam}, or +by customizing the corresponding variable +@code{gnus-spam-newsgroup-contents}. The @code{spam-contents} group +parameter and the @code{gnus-spam-newsgroup-contents} variable can +also be used to declare groups as @emph{ham} groups if you set their +classification to @code{gnus-group-spam-classification-ham}. If +groups are not classified by means of @code{spam-junk-mailgroups}, +@code{spam-contents}, or @code{gnus-spam-newsgroup-contents}, they are +considered @emph{unclassified}. All groups are unclassified by +default. + +In spam groups, all messages are considered to be spam by default: +they get the @samp{H} mark when you enter the group. You must review +these messages from time to time and remove the @samp{H} mark for +every message that is not spam after all. To remove the @samp{H} +mark, you can use @kbd{M-u} to "unread" the article, or @kbd{d} for +declaring it read the non-spam way. When you leave a group, all +spam-marked (@samp{H}) articles are sent to a spam processor which +will study them as spam samples. Messages may also be deleted in various other ways, and unless -@code{spam-ham-marks-form} gets overridden below, marks @samp{R} and @samp{r} for -default read or explicit delete, marks @samp{X} and @samp{K} for automatic or -explicit kills, as well as mark @samp{Y} for low scores, are all considered -to be associated with articles which are not spam. This assumption -might be false, in particular if you use kill files or score files as -means for detecting genuine spam, you should then adjust -@code{spam-ham-marks-form}. When you leave a group, all _unsaved_ articles -bearing any the above marks are sent to Bogofilter or ifile, which -will study these as not-spam samples. If you explicit kill a lot, you -might sometimes end up with articles marked @samp{K} which you never saw, -and which might accidentally contain spam. Best is to make sure that -real spam is marked with @samp{H}, and nothing else. - -All other marks do not contribute to Bogofilter or ifile -pre-conditioning. In particular, ticked, dormant or souped articles -are likely to contribute later, when they will get deleted for real, -so there is no need to use them prematurely. Explicitly expired -articles do not contribute, command @kbd{E} is a way to get rid of an -article without Bogofilter or ifile ever seeing it. - -@strong{TODO: @code{spam-use-ifile} does not process spam articles on group exit. -I'm waiting for info from the author of @code{ifile-gnus.el}, because I think -that functionality should go in @code{ifile-gnus.el} rather than @code{spam.el}.} +@code{spam-ham-marks} gets overridden below, marks @samp{R} and +@samp{r} for default read or explicit delete, marks @samp{X} and +@samp{K} for automatic or explicit kills, as well as mark @samp{Y} for +low scores, are all considered to be associated with articles which +are not spam. This assumption might be false, in particular if you +use kill files or score files as means for detecting genuine spam, you +should then adjust the @code{spam-ham-marks} variable. + +@defvar spam-ham-marks +You can customize this variable to be the list of marks you want to +consider ham. By default, the list contains the deleted, read, +killed, kill-filed, and low-score marks. +@end defvar + +@defvar spam-spam-marks +You can customize this variable to be the list of marks you want to +consider spam. By default, the list contains only the spam mark. +@end defvar + +When you leave @emph{any} group, regardless of its +@code{spam-contents} classification, all spam-marked articles are sent +to a spam processor, which will study these as spam samples. If you +explicit kill a lot, you might sometimes end up with articles marked +@samp{K} which you never saw, and which might accidentally contain +spam. Best is to make sure that real spam is marked with @samp{H}, +and nothing else. + +When you leave a @emph{spam} group, all spam-marked articles are +marked as expired after processing with the spam processor. This is +not done for @emph{unclassified} or @emph{ham} groups. Also, any +@strong{ham} articles in a spam group will be moved to a location +determined by either the @code{ham-process-destination} group +parameter or the @code{gnus-ham-process-destinations} variable. The +location is a group name. If the @code{ham-process-destination} +parameter is not set, spam articles are only expired. + +When you leave a @emph{ham} group, all ham-marked articles are sent to +a ham processor, which will study these as non-spam samples. + +When you leave a @emph{ham} or @emph{unclassified} group, all +@strong{spam} articles are moved to a location determined by either +the @code{spam-process-destination} group parameter or the +@code{gnus-spam-process-destinations} variable. The location is a +group name. If the @code{spam-process-destination} parameter is not +set, the spam articles are only expired. To use the @code{spam.el} facilities for incoming mail filtering, you must add the following to your fancy split list @@ -21429,19 +21531,29 @@ Note that the fancy split may be called @code{nnmail-split-fancy} or @code{nnimap-split-fancy}, depending on whether you use the nnmail or nnimap back ends to retrieve your mail. -The @code{spam-split} function will process incoming mail and send the mail -considered to be spam into the group name given by the variable -@code{spam-split-group}. Usually that group name is @samp{spam}. +The @code{spam-split} function will process incoming mail and send the +mail considered to be spam into the group name given by the variable +@code{spam-split-group}. By default that group name is @samp{spam}, +but you can customize it. + +@emph{TODO: Currently, spam.el only supports insertion of articles +into a backend. There is no way to tell spam.el that an article is no +longer spam or ham.} + +@emph{TODO: spam.el needs to provide a uniform way of training all the +statistical databases. Some have that functionality built-in, others +don't.} The following are the methods you can use to control the behavior of -@code{spam-split}: +@code{spam-split} and their corresponding spam and ham processors: @menu * Blacklists and Whitelists:: * BBDB Whitelists:: * Blackholes:: * Bogofilter:: -* Ifile spam filtering:: +* ifile spam filtering:: +* spam-stat spam filtering:: * Extending spam.el:: @end menu @@ -21453,30 +21565,57 @@ The following are the methods you can use to control the behavior of @cindex spam.el @defvar spam-use-blacklist -Set this variables to t (the default) if you want to use blacklists. +Set this variable to t if you want to use blacklists when splitting +incoming mail. Messages whose senders are in the blacklist will be +sent to the @code{spam-split-group}. This is an explicit filter, +meaning that it acts only on mail senders @emph{declared} to be +spammers. @end defvar @defvar spam-use-whitelist -Set this variables to t if you want to use whitelists. +Set this variable to t if you want to use whitelists when splitting +incoming mail. Messages whose senders are not in the whitelist will +be sent to the @code{spam-split-group}. This is an implicit filter, +meaning it believes everyone to be a spammer unless told otherwise. +Use with care. +@end defvar + +@defvar gnus-group-spam-exit-processor-blacklist +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, the senders of +spam-marked articles will be added to the blacklist. +@end defvar + +@defvar gnus-group-ham-exit-processor-whitelist +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, the senders of +ham-marked articles in @emph{ham} groups will be added to the +whitelist. Note that this ham processor has no effect in @emph{spam} +or @emph{unclassified} groups. @end defvar Blacklists are lists of regular expressions matching addresses you consider to be spam senders. For instance, to block mail from any sender at @samp{vmadmin.com}, you can put @samp{vmadmin.com} in your -blacklist. Since you start out with an empty blacklist, no harm is -done by having the @code{spam-use-blacklist} variable set, so it is -set by default. Blacklist entries use the Emacs regular expression -syntax. +blacklist. You start out with an empty blacklist. Blacklist entries +use the Emacs regular expression syntax. Conversely, whitelists tell Gnus what addresses are considered legitimate. All non-whitelisted addresses are considered spammers. This option is probably not useful for most Gnus users unless the -whitelists is very comprehensive. Also see @ref{BBDB Whitelists}. -Whitelist entries use the Emacs regular expression syntax. +whitelists is very comprehensive or permissive. Also see @ref{BBDB +Whitelists}. Whitelist entries use the Emacs regular expression +syntax. -The Blacklist and whitelist location can be customized with the -@code{spam-directory} variable (@file{~/News/spam} by default). The whitelist -and blacklist files will be in that directory, named @file{whitelist} and +The blacklist and whitelist file locations can be customized with the +@code{spam-directory} variable (@file{~/News/spam} by default), or +the @code{spam-whitelist} and @code{spam-blacklist} variables +directly. The whitelist and blacklist files will by default be in the +@code{spam-directory} directory, named @file{whitelist} and @file{blacklist} respectively. @node BBDB Whitelists @@ -21486,16 +21625,26 @@ and blacklist files will be in that directory, named @file{whitelist} and @cindex BBDB, spam filtering @cindex spam.el -@defvar spam-use-bbdb +@defvar spam-use-BBDB Analogous to @code{spam-use-whitelist} (@pxref{Blacklists and Whitelists}), but uses the BBDB as the source of whitelisted addresses, without regular expressions. You must have the BBDB loaded for -@code{spam-use-bbdb} to work properly. Only addresses in the BBDB +@code{spam-use-BBDB} to work properly. Only addresses in the BBDB will be allowed through; all others will be classified as spam. @end defvar +@defvar gnus-group-ham-exit-processor-BBDB +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, the senders of +ham-marked articles in @emph{ham} groups will be added to the +BBDB. Note that this ham processor has no effect in @emph{spam} +or @emph{unclassified} groups. +@end defvar + @node Blackholes @subsubsection Blackholes @cindex spam filtering @@ -21520,6 +21669,22 @@ but you can try it and see if it works for you. @end defvar +@defvar spam-blackhole-servers + +The list of servers to consult for blackhole checks. + +@end defvar + +@defvar spam-use-dig + +Use the @code{dig.el} package instead of the @code{dns.el} package. +The default setting of t is recommended. + +@end defvar + +Blackhole checks are done only on incoming mail. There is no spam or +ham processor for blackholes. + @node Bogofilter @subsubsection Bogofilter @cindex spam filtering @@ -21528,9 +21693,10 @@ but you can try it and see if it works for you. @defvar spam-use-bogofilter -Set this variable if you want to use Eric Raymond's speedy Bogofilter. -This has been tested with a locally patched copy of version 0.4. Make -sure to read the installation comments in @code{spam.el}. +Set this variable if you want @code{spam-split} to use Eric Raymond's +speedy Bogofilter. This has been tested with a locally patched copy +of version 0.4. Make sure to read the installation comments in +@code{spam.el}. With a minimum of care for associating the @samp{H} mark for spam articles only, Bogofilter training all gets fairly automatic. You @@ -21542,34 +21708,114 @@ Bogofilter into displaying in another buffer the @emph{spamicity} score of the current article (between 0.0 and 1.0), together with the article words which most significantly contribute to the score. +If the @code{bogofilter} executable is not in your path, Bogofilter +processing will be turned off. + @end defvar -@node Ifile spam filtering -@subsubsection Ifile spam filtering + +@defvar gnus-group-spam-exit-processor-bogofilter +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, spam-marked articles +will be added to the bogofilter spam database, and ham-marked articles +will be added to the bogofilter ham database. @strong{Note that the +Bogofilter spam processor is the only spam processor to also do ham +processing.} +@end defvar + +@node ifile spam filtering +@subsubsection ifile spam filtering @cindex spam filtering @cindex ifile, spam filtering @cindex spam.el @defvar spam-use-ifile -Enable this variable if you want to use Ifile, a statistical analyzer -similar to Bogofilter. Currently you must have @code{ifile-gnus.el} -loaded. The integration of Ifile with @code{spam.el} is not finished -yet, but you can use @code{ifile-gnus.el} on its own if you like. +Enable this variable if you want @code{spam-split} to use ifile, a +statistical analyzer similar to Bogofilter. + +@end defvar + +@defvar spam-ifile-all-categories + +Enable this variable if you want @code{spam-use-ifile} to give you all +the ifile categories, not just spam/non-spam. If you use this, make +sure you train ifile as described in its documentation. + +@end defvar + +@defvar spam-ifile-spam-category + +This is the category of spam messages as far as ifile is concerned. +The actual string used is irrelevant, but you probably want to leave +the default value of @samp{spam}. +@end defvar + +@defvar spam-ifile-database-path + +This is the filename for the ifile database. It is not specified by +default, so ifile will use its own default database name. @end defvar +The ifile mail classifier is similar to Bogofilter in intent and +purpose. A ham and a spam processor are provided, plus the +@code{spam-use-ifile} variable to indicate to spam-split that ifile +should be used. The 1.2.1 version of ifile was used to test this +functionality. + +@node spam-stat spam filtering +@subsubsection spam-stat spam filtering +@cindex spam filtering +@cindex spam-stat, spam filtering +@cindex spam-stat.el +@cindex spam.el + +@xref{Filtering Spam Using Statistics (spam-stat.el)}. + +@defvar spam-use-stat + +Enable this variable if you want @code{spam-split} to use +spam-stat.el, an Emacs Lisp statistical analyzer. + +@end defvar + +@defvar gnus-group-spam-exit-processor-stat +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, the spam-marked +articles will be added to the spam-stat database of spam messages. +@end defvar + +@defvar gnus-group-ham-exit-processor-stat +Add this symbol to a group's @code{spam-process} parameter by +customizing the group parameters or the +@code{gnus-spam-process-newsgroups} variable. When this symbol is +added to a group's @code{spam-process} parameter, the ham-marked +articles in @emph{ham} groups will be added to the spam-stat database +of non-spam messages. Note that this ham processor has no effect in +@emph{spam} or @emph{unclassified} groups. +@end defvar + +This enables spam.el to cooperate with spam-stat.el. spam-stat.el +provides an internal (Lisp-only) spam database, which unlike ifile or +Bogofilter does not require external programs. A spam and a ham +processor, and the @code{spam-use-stat} variable for @code{spam-split} +are provided. + @node Extending spam.el @subsubsection Extending spam.el @cindex spam filtering @cindex spam.el, extending @cindex extending spam.el -Say you want to add a new back end called blackbox. Provide the following: +Say you want to add a new back end called blackbox. For filtering +incoming mail, provide the following: @enumerate -@item -documentation @item code @@ -21593,6 +21839,62 @@ Write the @code{spam-check-blackbox} function. It should return @code{spam-check-*} functions for examples of what you can do. @end enumerate +For processing spam and ham messages, provide the following: + +@enumerate + +@item +code + +Note you don't have to provide a spam or a ham processor. Only +provide them if Blackbox supports spam or ham processing. + +@example +(defvar gnus-group-spam-exit-processor-blackbox "blackbox" + "The Blackbox summary exit spam processor. +Only applicable to spam groups.") + +(defvar gnus-group-ham-exit-processor-blackbox "blackbox" + "The whitelist summary exit ham processor. +Only applicable to non-spam (unclassified and ham) groups.") + +@end example + +@item +functionality + +@example +(defun spam-blackbox-register-spam-routine () + (spam-generic-register-routine + ;; the spam function + (lambda (article) + (let ((from (spam-fetch-field-from-fast article))) + (when (stringp from) + (blackbox-do-something-with-this-spammer from)))) + ;; the ham function + nil)) + +(defun spam-blackbox-register-ham-routine () + (spam-generic-register-routine + ;; the spam function + nil + ;; the ham function + (lambda (article) + (let ((from (spam-fetch-field-from-fast article))) + (when (stringp from) + (blackbox-do-something-with-this-ham-sender from)))))) +@end example + +Write the @code{blackbox-do-something-with-this-ham-sender} and +@code{blackbox-do-something-with-this-spammer} functions. You can add +more complex code than fetching the message sender, but keep in mind +that retrieving the whole message takes significantly longer than the +sender through @code{spam-fetch-field-from-fast}, because the message +senders are kept in memory by Gnus. + +@end enumerate + + @node Filtering Spam Using Statistics (spam-stat.el) @subsection Filtering Spam Using Statistics (spam-stat.el) @cindex Paul Graham @@ -21663,19 +21965,25 @@ the the group @samp{nnml:mail.spam}), and you would call @file{~/Mail/mail/misc} (this usually corresponds the the group @samp{nnml:mail.misc}). +When you are using IMAP, you won't have the mails available locally, +so that will not work. One solution is to use the Gnus Agent to cache +the articles. Then you can use directories such as +@file{"~/News/agent/nnimap/mail.yourisp.com/personal_spam"} for +@code{spam-stat-process-spam-directory}. @xref{Agent as Cache}. + @defvar spam-stat This variable holds the hash-table with all the statistics -- the dictionary we have been talking about. For every word in either collection, this hash-table stores a vector describing how often the word appeared in spam and often it appeared in non-spam mails. +@end defvar If you want to regenerate the statistics from scratch, you need to reset the dictionary. -@end defvar - @defun spam-stat-reset Reset the @code{spam-stat} hash-table, deleting all the statistics. +@end defun When you are done, you must save the dictionary. The dictionary may be rather large. If you will not update the dictionary incrementally @@ -21683,7 +21991,6 @@ be rather large. If you will not update the dictionary incrementally can reduce the size of the dictionary by deleting all words that did not appear often enough or that do not clearly belong to only spam or only non-spam mails. -@end defun @defun spam-stat-reduce-size Reduce the size of the dictionary. Use this only if you do not want @@ -21714,10 +22021,14 @@ This will load the necessary Gnus code, and the dictionary you created. Next, you need to adapt your fancy splitting rules: You need to -determine how to use @code{spam-stat}. In the simplest case, you only have -two groups, @samp{mail.misc} and @samp{mail.spam}. The following expression says -that mail is either spam or it should go into @samp{mail.misc}. If it is -spam, then @code{spam-stat-split-fancy} will return @samp{mail.spam}. +determine how to use @code{spam-stat}. The following examples are for +the nnml back end. Using the nnimap back end works just as well. Just +use @code{nnimap-split-fancy} instead of @code{nnmail-split-fancy}. + +In the simplest case, you only have two groups, @samp{mail.misc} and +@samp{mail.spam}. The following expression says that mail is either +spam or it should go into @samp{mail.misc}. If it is spam, then +@code{spam-stat-split-fancy} will return @samp{mail.spam}. @example (setq nnmail-split-fancy @@ -21730,7 +22041,7 @@ The group to use for spam. Default is @samp{mail.spam}. @end defvar If you also filter mail with specific subjects into other groups, use -the following expression. It only the mails not matching the regular +the following expression. Only mails not matching the regular expression are considered potential spam. @example @@ -21775,66 +22086,58 @@ dictionary! The main interface to using @code{spam-stat}, are the following functions: @defun spam-stat-buffer-is-spam -called in a buffer, that buffer is considered to be a new spam mail; -use this for new mail that has not been processed before - +Called in a buffer, that buffer is considered to be a new spam mail. +Use this for new mail that has not been processed before. @end defun @defun spam-stat-buffer-is-no-spam -called in a buffer, that buffer is considered to be a new non-spam -mail; use this for new mail that has not been processed before - +Called in a buffer, that buffer is considered to be a new non-spam +mail. Use this for new mail that has not been processed before. @end defun @defun spam-stat-buffer-change-to-spam -called in a buffer, that buffer is no longer considered to be normal -mail but spam; use this to change the status of a mail that has -already been processed as non-spam - +Called in a buffer, that buffer is no longer considered to be normal +mail but spam. Use this to change the status of a mail that has +already been processed as non-spam. @end defun @defun spam-stat-buffer-change-to-non-spam -called in a buffer, that buffer is no longer considered to be spam but -normal mail; use this to change the status of a mail that has already -been processed as spam - +Called in a buffer, that buffer is no longer considered to be spam but +normal mail. Use this to change the status of a mail that has already +been processed as spam. @end defun @defun spam-stat-save -save the hash table to the file; the filename used is stored in the -variable @code{spam-stat-file} - +Save the hash table to the file. The filename used is stored in the +variable @code{spam-stat-file}. @end defun @defun spam-stat-load -load the hash table from a file; the filename used is stored in the -variable @code{spam-stat-file} - +Load the hash table from a file. The filename used is stored in the +variable @code{spam-stat-file}. @end defun @defun spam-stat-score-word -return the spam score for a word - +Return the spam score for a word. @end defun @defun spam-stat-score-buffer -return the spam score for a buffer - +Return the spam score for a buffer. @end defun @defun spam-stat-split-fancy -for fancy mail splitting; add the rule @samp{(: spam-stat-split-fancy)} to -@code{nnmail-split-fancy} +Use this function for fancy mail splitting. Add the rule @samp{(: +spam-stat-split-fancy)} to @code{nnmail-split-fancy} +@end defun -This requires the following in your @file{~/.gnus} file: +Make sure you load the dictionary before using it. This requires the +following in your @file{~/.gnus} file: @example (require 'spam-stat) (spam-stat-load) @end example -@end defun - Typical test will involve calls to the following functions: @example @@ -22027,8 +22330,8 @@ XEmacs is distributed as a collection of packages. You should install whatever packages the Gnus XEmacs package requires. The current requirements are @samp{gnus}, @samp{w3}, @samp{mh-e}, @samp{mailcrypt}, @samp{rmail}, @samp{eterm}, @samp{mail-lib}, -@samp{xemacs-base}, and @samp{fsf-compat}. The @samp{misc-games} -package is required for Morse decoding. +@samp{xemacs-base}, @samp{sh-script} and @samp{fsf-compat}. The +@samp{misc-games} package is required for Morse decoding. @node History @@ -22762,7 +23065,7 @@ manner, so it should be difficult to lose much data on what you have read if your machine should go down (@pxref{Auto Save}). @item -Gnus now has its own startup file (@file{.gnus}) to avoid cluttering up +Gnus now has its own startup file (@file{.gnus.el}) to avoid cluttering up the @file{.emacs} file. @item @@ -24630,6 +24933,9 @@ this function in short order. The function should return a cons where the @code{car} is the group name and the @code{cdr} is the article number that the article was entered as. +The group should exist before the backend is asked to accept the +article for that group. + There should be no data returned. diff --git a/texi/gnusref.tex b/texi/gnusref.tex index 4ccd3a6..32d11be 100644 --- a/texi/gnusref.tex +++ b/texi/gnusref.tex @@ -1,7 +1,7 @@ %% include file for the Gnus refcard and booklet \def\progver{5.10}\def\refver{5.10-1} % program and refcard versions -\def\date{Dec 15th, 2002} +\def\date{Jan, 2003} \def\author{Gnus Bugfixing Girls + Boys $<$bugs@gnus.org$>$} %% @@ -37,7 +37,7 @@ Copyright \copyright\ 1995 Vladimir Alexiev $<$vladimir@cs.ualberta.ca$>$.\\* Copyright \copyright\ 2000 Felix Natter $<$fnatter@gmx.net$>$.\\* - Copyright \copyright\ 2001, 2002 \author.\\* + Copyright \copyright\ 2001, 2002, 2003 \author.\\* Created from the Gnus manual Copyright \copyright\ 1994 Lars Magne Ingebrigtsen.\\* and the Emacs Help Bindings feature (C-h b).\\* @@ -331,7 +331,7 @@ \newcommand{\GroupTopicsGeneral}{% {\esamepage Topics are ``categories'' for groups. Press t in the group-buffer to - toggle gnus-topic-mode (C-c C-i g Group Topics RET). + toggle gnus-topic-mode (C-c C-i g Group Topics RET).\\* \begin{keys}{C-c C-x} T n & Prompt for topic {\bf name} and create it.\\ T m & {\bf Move} the current group to some other topic [p/p].\\ @@ -488,6 +488,7 @@ C-c C-s C-c & Sort the summary-buffer by length.\\ C-c C-s C-n & Sort the summary-buffer by article {\bf number}.\\ C-c C-s C-s & Sort the summary-buffer by {\bf subject}.\\ + C-c C-s C-r & Sort the summary-buffer {\bf randomly}.\\ C-c C-s C-o & Sort the summary-buffer using the default method.\\ \end{keys} With a prefix these functions sort in reverse order. @@ -791,6 +792,7 @@ /x & Limit depending on ``extra'' headers.\\ /u & (x) Limit to {\bf unread} articles. [Prefix: also exclude ticked and dormant articles]\\ + /. & Limit to unseen articles.\\ /m & Limit to articles marked with specified {\bf mark}.\\ /t & Ask for a number and exclude articles younger than that many days. [Prefix: exclude older articles]\\ @@ -835,7 +837,7 @@ \newcommand{\PostReplyetc}{% formerly \Ssubmap {\esamepage These commands put you in a separate news or mail buffer. See the section - about composing messages for more information. + about composing messages for more information.\\* %After %editing the article, send it by pressing C-c C-c. If you are in a %foreign group and want to post the article using the foreign server, give @@ -1010,7 +1012,7 @@ } \newcommand{\MsgCompositionGeneral}{% - Press C-c ? in the composition-buffer to get this information. + Press C-c ? in the composition-buffer to get this information.\\* {\esamepage \begin{keys}{C-c C-m} % sending @@ -1038,7 +1040,7 @@ } \newcommand{\MsgCompositionMovementArticle}{% - The following functions create the header-field if necessary. + The following functions create the header-field if necessary.\\* {\esamepage \begin{keys}{C-c C-f C-u} C-c TAB & Move to \textbf{signature}.\\ diff --git a/texi/message.texi b/texi/message.texi index 85f3e7b..1e6cd65 100644 --- a/texi/message.texi +++ b/texi/message.texi @@ -18,7 +18,8 @@ This file documents Message, the Emacs message composition mode. -Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 +Free Software Foundation, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or @@ -234,6 +235,23 @@ The value of @code{message-cancel-message} is inserted in the body of the cancel message. The default is @samp{I am canceling my own article.}. +@cindex Cancel Locks +@vindex message-insert-canlock +@cindex canlock +When Message posts news messages, it inserts @code{Cancel-Lock} +headers by default. This is a cryptographic header that ensures that +only you can cancel your own messages, which is nice. The downside +is that if you lose your @file{.emacs} file (which is where Gnus +stores the secret cancel lock password (which is generated +automatically the first time you use this feature)), you won't be +able to cancel your message. + +Whether to insert the header or not is controlled by the +@code{message-insert-canlock} variable. + +Not many news servers respect the @code{Cancel-Lock} header yet, but +this is expected to change in the future. + @node Superseding @section Superseding @@ -437,17 +455,17 @@ address, then no MFT is generated; otherwise, a MFT is added to the other headers and set to the value of all addresses in To: and Cc: @kindex C-c C-f C-a -@findex message-gen-unsubscribed-mft +@findex message-generate-unsubscribed-mail-followup-to @kindex C-c C-f C-m @findex message-goto-mail-followup-to Hm. ``So'', you ask, ``what if I send an email to a list I am not -subscribed to? I want my MFT to say that I want an extra copy.'' -(This is supposed to be interpreted by others the same way as if there -were no MFT, but you can use an explicit MFT to override someone -else's to-address group parameter.) The function -@code{message-gen-unsubscribed-mft} might come in handy. It is bound -to @kbd{C-c C-f C-a} by default. In any case, you can insert a MFT of -your own choice; @kbd{C-c C-f C-m} +subscribed to? I want my MFT to say that I want an extra copy.'' (This +is supposed to be interpreted by others the same way as if there were no +MFT, but you can use an explicit MFT to override someone else's +to-address group parameter.) The function +@code{message-generate-unsubscribed-mail-followup-to} might come in +handy. It is bound to @kbd{C-c C-f C-a} by default. In any case, you +can insert a MFT of your own choice; @kbd{C-c C-f C-m} (@code{message-goto-mail-followup-to}) will help you get started. @c @node Honoring an MFT post @@ -480,7 +498,7 @@ better than you do. @menu * Buffer Entry:: Commands after entering a Message buffer. -* Header Commands:: Commands for moving to headers. +* Header Commands:: Commands for moving headers or changing headers. * Movement:: Moving around in message buffers. * Insertion:: Inserting things into message buffers. * MIME:: @sc{mime} considerations. @@ -510,9 +528,10 @@ times, you will get back the un-edited message you're responding to. @node Header Commands @section Header Commands -All these commands move to the header in question (except for the -@samp{Importance:} related commands). If it doesn't exist, it will be -inserted. +@subsection Commands for moving to headers + +These following commands move to the header in question. If it doesn't +exist, it will be inserted. @table @kbd @@ -591,17 +610,123 @@ message to the receiver. If the header is already present in the buffer, it cycles between the three valid values according to RFC 1376: @samp{low}, @samp{normal} and @samp{high}. +@item C-c C-f C-a +@kindex C-c C-f C-a +@findex message-generate-unsubscribed-mail-followup-to +Insert a reasonable @samp{Mail-Followup-To:} header +(@pxref{Mailing Lists}) in a post to an +unsubscribed list. When making original posts to a mailing list you are +not subscribed to, you have to type in a @samp{Mail-Followup-To:} header +by hand. The contents, usually, are the addresses of the list and your +own address. This function inserts such a header automatically. It +fetches the contents of the @samp{To:} header in the current mail +buffer, and appends the current @code{user-mail-address}. + +If the optional argument @code{include-cc} is non-nil, the addresses in +the @samp{Cc:} header are also put into the @samp{Mail-Followup-To:} +header. + +@end table + +@subsection Commands to change headers + +@table @kbd + +@item C-c C-o +@kindex C-c C-o +@findex message-sort-headers +@vindex message-header-format-alist +Sort headers according to @code{message-header-format-alist} +(@code{message-sort-headers}). + +@item C-c C-t +@kindex C-c C-t +@findex message-insert-to +Insert a @code{To} header that contains the @code{Reply-To} or +@code{From} header of the message you're following up +(@code{message-insert-to}). + +@item C-c C-n +@kindex C-c C-n +@findex message-insert-newsgroups +Insert a @code{Newsgroups} header that reflects the @code{Followup-To} +or @code{Newsgroups} header of the article you're replying to +(@code{message-insert-newsgroups}). + +@item C-c C-l +@kindex C-c C-l +@findex message-to-list-only +Send a message to the list only. Remove all addresses but the list +address from @code{To:} and @code{Cc:} headers. + +@item C-c M-n +@kindex C-c M-n +@findex message-insert-disposition-notification-to +Insert a request for a disposition +notification. (@code{message-insert-disposition-notification-to}). +This means that if the recipient support RFC 2298 she might send you a +notification that she received the message. + @item M-x message-insert-importance-high @kindex M-x message-insert-importance-high @findex message-insert-importance-high -Insert a @samp{Importance:} header with a value of @samp{high}, +@cindex Importance +Insert an @samp{Importance} header with a value of @samp{high}, deleting headers if necessary. @item M-x message-insert-importance-low @kindex M-x message-insert-importance-low @findex message-insert-importance-low -Insert a @samp{Importance:} header with a value of @samp{low}, -deleting headers if necessary. +@cindex Importance +Insert an @samp{Importance} header with a value of @samp{low}, deleting +headers if necessary. + +@item C-c C-f s +@kindex C-c C-f s +@findex message-change-subject +@cindex Subject +Change the current @samp{Subject} header. Ask for new @samp{Subject} +header and append @code{(was: )}. The old subject can be +stripped on replying, see @code{message-subject-trailing-was-query} +(@pxref{Message Headers}). + +@item C-c C-f x +@kindex C-c C-f x +@findex message-cross-post-followup-to +@vindex message-cross-post-default +@cindex X-Post +@cindex cross-post +Ask for an additional @samp{Newsgroups} and @samp{FollowUp-To} for a +cross-post. @code{message-cross-post-followup-to} mangles +@samp{FollowUp-To} and @samp{Newsgroups} header to point to group. +If @code{message-cross-post-default} is @code{nil} or if called with a +prefix-argument @samp{Follow-Up} is set, but the message is not +cross-posted. + +@item C-c C-f t +@kindex C-c C-f t +@findex message-reduce-to-to-cc +Replace contents of @samp{To} header with contents of @samp{Cc} or +@samp{Bcc} header. + +@item C-c C-f w +@kindex C-c C-f w +@findex message-insert-wide-reply +Insert @samp{To} and @samp{Cc} headers as if you were doing a wide +reply. + +@item C-c C-f a +@kindex C-c C-f a +@findex message-add-archive-header +@vindex message-archive-header +@vindex message-archive-note +@cindex X-No-Archive +Insert @samp{X-No-Archive: Yes} in the header and a note in the body. +The header and the note can be customized using +@code{message-archive-header} and @code{message-archive-note}. When +called with a prefix argument, ask for a text to insert. If you don't +want the note in the body, set @code{message-archive-note} to +@code{nil}. @end table @@ -670,13 +795,17 @@ Insert a signature at the end of the buffer @findex message-insert-headers Insert the message headers (@code{message-insert-headers}). -@item C-c M-n -@kindex C-c M-n -@findex message-insert-disposition-notification-to -Insert a request for a disposition -notification. (@code{message-insert-disposition-notification-to}). -This means that if the recipient support RFC 2298 she might send you a -notification that she received the message. +@item C-c M-m +@kindex C-c M-m +@findex message-mark-inserted-region +Mark some region in the current article with enclosing tags. +See @code{message-mark-insert-begin} and @code{message-mark-insert-end}. + +@item C-c M-f +@kindex C-c M-f +@findex message-mark-insert-file +Insert a file in the current article with enclosing tags. +See @code{message-mark-insert-begin} and @code{message-mark-insert-end}. @end table @@ -944,27 +1073,6 @@ If point is before @samp{And} and you press @kbd{M-RET}, you'll get: @samp{*} says where point will be placed. -@item C-c C-t -@kindex C-c C-t -@findex message-insert-to -Insert a @code{To} header that contains the @code{Reply-To} or -@code{From} header of the message you're following up -(@code{message-insert-to}). - -@item C-c C-n -@kindex C-c C-n -@findex message-insert-newsgroups -Insert a @code{Newsgroups} header that reflects the @code{Followup-To} -or @code{Newsgroups} header of the article you're replying to -(@code{message-insert-newsgroups}). - -@item C-c C-o -@kindex C-c C-o -@findex message-sort-headers -@vindex message-header-format-alist -Sort headers according to @code{message-header-format-alist} -(@code{message-sort-headers}). - @item C-c M-r @kindex C-c M-r @findex message-rename-buffer @@ -1199,6 +1307,18 @@ responding to a message: "^\\(\\(\\([Rr][Ee]\\|[Ss][Vv]\\|[Aa][Ww]\\): *\\)+\\)") @end lisp +@item message-subject-trailing-was-query +@vindex message-subject-trailing-was-query +@vindex message-subject-trailing-was-ask-regexp +@vindex message-subject-trailing-was-regexp +Controls what to do with trailing @samp{(was: )} in subject +lines. If @code{nil}, leave the subject unchanged. If it is the symbol +@code{ask}, query the user what do do. In this case, the subject is +matched against @code{message-subject-trailing-was-ask-regexp}. If +@code{message-subject-trailing-was-query} is t, always strip the +trailing old subject. In this case, +@code{message-subject-trailing-was-regexp} is used. + @item message-alternative-emails @vindex message-alternative-emails A regexp to match the alternative email addresses. The first matched @@ -1592,6 +1712,14 @@ This can also be a list of functions. Each function can find the citation between @code{(point)} and @code{(mark t)}. And each function should leave point and mark around the citation text as modified. +@item message-mark-insert-begin +@vindex message-mark-insert-begin +String to mark the beginning of some inserted text. + +@item message-mark-insert-end +@vindex message-mark-insert-end +String to mark the end of some inserted text. + @item message-signature @vindex message-signature String to be inserted at the end of the message buffer. If @code{t} diff --git a/texi/refcard.tex b/texi/refcard.tex index cf72fb3..92ed19d 100644 --- a/texi/refcard.tex +++ b/texi/refcard.tex @@ -140,7 +140,6 @@ % \subsection*{Summary-Unplugged} \SummaryUnplugged -\pagebreak \subsection*{Mail-Group Commands} \MailGroups \subsection*{Draft-Group Commands}