diff options
Diffstat (limited to 'lib/net/nnnotmuch.el')
-rw-r--r-- | lib/net/nnnotmuch.el | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/lib/net/nnnotmuch.el b/lib/net/nnnotmuch.el new file mode 100644 index 0000000..7aa62eb --- /dev/null +++ b/lib/net/nnnotmuch.el @@ -0,0 +1,276 @@ +;;; nnnotmuch.el --- Gnus backend for notmuch -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 jao + +;; Author: jao <mail@jao.io> +;; Keywords: mail + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; A Gnus mail backend using notmuch. + +;;; Code: + +(require 'gnus) +(require 'nnheader) +(require 'nnmail) +(require 'nnoo) +(require 'notmuch) + +(nnoo-declare nnnotmuch) + +(defvoo nnnotmuch-directory message-directory + "Spool directory for the nnnotmuch mail backend.") + +(defvoo nnnotmuch-inhibit-expiry nil + "If non-nil, inhibit expiry.") + +(defvoo nnnotmuch-saved-searches nil) + + + +;;; Interface functions. + +(nnoo-define-basics nnnotmuch) + +(deffoo nnnotmuch-retrieve-headers (sequence &optional group server fetch-old) + (when (nnnotmuch-set-group-context group server) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (let* ((file nil) + (number (length sequence)) + (count 0) + (file-name-coding-system nnmail-pathname-coding-system) + beg article) + (if (stringp (car sequence)) + 'headers + (while sequence + (setq article (car sequence)) + (setq file (nnnotmuch-article-to-file article)) + (when (and file + (file-exists-p file) + (not (file-directory-p file))) + (insert (format "221 %d Article retrieved.\n" article)) + (setq beg (point)) + (nnheader-insert-head file) + (goto-char beg) + (if (re-search-forward "\n\r?\n" nil t) + (forward-char -1) + (goto-char (point-max)) + (insert "\n\n")) + (insert ".\n") + (delete-region (point) (point-max))) + (setq sequence (cdr sequence)) + (setq count (1+ count)) + (and (numberp nnmail-large-newsgroup) + (> number nnmail-large-newsgroup) + (zerop (% count 20)) + (nnheader-message 6 "nnnotmuch: Receiving headers... %d%%" + (floor (* count 100.0) number)))) + (and (numberp nnmail-large-newsgroup) + (> number nnmail-large-newsgroup) + (nnheader-message 6 "nnml: Receiving headers...done")) + (nnheader-fold-continuation-lines) + 'headers))))) + +(deffoo nnnotmuch-open-server (server &optional defs) + (nnoo-change-server 'nnnotmuch server defs) + (cond + ((not (file-exists-p nnnotmuch-directory)) + (nnnotmuch-close-server) + (nnheader-report 'nnnotmuch "Directory doesn't exist: %s" nnnotmuch-directory)) + ((not (file-directory-p (file-truename nnnotmuch-directory))) + (nnnotmuch-close-server) + (nnheader-report 'nnnotmuch "Not a directory: %s" nnnotmuch-directory)) + (t + (nnheader-report 'nnnotmuch "Opened server %s using directory %s" + server nnnotmuch-directory) + t))) + +(deffoo nnnotmuch-request-regenerate (server) + (nnnotmuch-set-group-context nil server) + t) + +(deffoo nnnotmuch-request-article (id &optional group server buffer) + (nnnotmuch-set-group-context group server) + (let* ((nntp-server-buffer (or buffer nntp-server-buffer)) + (file-name-coding-system nnmail-pathname-coding-system) + (path (if (stringp id) + (nnnotmuch--find-message-file id) + (nnnotmuch-article-to-file id)))) + (cond + ((not path) + (nnheader-report 'nnnotmuch "No such article: %s" id)) + ((not (file-exists-p path)) + (nnheader-report 'nnnotmuch "No such file: %s" path)) + ((file-directory-p path) + (nnheader-report 'nnnotmuch "File is a directory: %s" path)) + ((not (save-excursion (nnmail-find-file path))) + (nnheader-report 'nnnotmuch "Couldn't read file: %s" path)) + (t + (nnheader-report 'nnnotmuch "Article %s retrieved" id) + (cons group id))))) + +(deffoo nnnotmuch-request-group (group &optional server _dont-check info) + (cond + ((not (nnnotmuch-set-group-context group server)) + (nnheader-report 'nnnotmuch "Invalid group")) + (t + (let* ((info (or info (gnus-get-info group))) + (num-headers (length nnnotmuch-article-files)) + (status (format "211 %d %d %d %s" num-headers 1 num-headers group))) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (nnheader-insert "%s\n" status) + ;; (when info + ;; (gnus-info-set-marks + ;; info + ;; (append (assq-delete-all 'seen (gnus-info-marks info)) + ;; (list `(seen (1 . ,num-headers)))) + ;; t)) + )) + t))) + +(deffoo nnnotmuch-request-group-scan (group &optional server info) + (setq nnnotmuch-article-files nil) + (nnnotmuch-set-group-context group server) + (gnus-get-unread-articles-in-group + (or info (gnus-get-info gnus-newsgroup-name)) + (gnus-active (gnus-info-group info)))) + +(deffoo nnnotmuch-request-scan (&optional group server) + (nnnotmuch-set-group-context group server)) + +(deffoo nnnotmuch-close-group (_group &optional _server) + (setq nnnotmuch-article-files nil) + t) + +(deffoo nnnotmuch-request-create-group (group &optional server _args) + (nnnotmuch-set-group-context nil server) + (nnmail-activate 'nnnotmuch) + (error "Not implemented yet")) + +(deffoo nnnotmuch-request-list (&optional _server) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (dolist (s nnnotmuch-saved-searches) + (when-let (q (plist-get s :query)) + (let* ((name (plist-get s :name)) + (total (nnnotmuch--count-files q)) + (new (nnnotmuch--count-files q t)) + (first (1+ (- total new)))) + (insert (format "%s %d %d y\n" name total first)))))) + t) + +(deffoo nnnotmuch-request-newgroups (_date &optional server) + (nnnotmuch-request-list server)) + +(deffoo nnnotmuch-request-list-newsgroups (&optional _server) + nil) + +(deffoo nnnotmuch-request-expire-articles (articles group &optional server force) + (nnnotmuch-set-group-context group server) + (when (eq 'delete nnmail-expiry-target) + (error "Not implemented yet"))) + +(deffoo nnnotmuch-request-move-article + (article group server accept-form &optional last _move-is-internal) + (error "Not implemented yet")) + +(deffoo nnnotmuch-request-accept-article (group &optional server last) + (error "Not implemented yet")) + +(deffoo nnnotmuch-request-post (&optional server) + (nnmail-do-request-post 'nnnotmuch-request-accept-article server)) + +(deffoo nnnotmuch-request-update-info (group info &optional server) + (when-let (n (nnnotmuch--count-files (nnnotmuch--find-query group))) + (gnus-set-active (gnus-group-prefixed-name group `(nnnotmuch ,server)) + (cons 1 n)))) + +(deffoo nnnotmuch-request-replace-article (article group buffer) + (error "Not implemented yet")) + +(deffoo nnnotmuch-request-delete-group (group &optional force server) + (error "Not implemented yet")) + +(deffoo nnnotmuch-request-rename-group (group new-name &optional server) + (error "Not implemented yet")) + +(deffoo nnnotmuch-set-status (article name value &optional group server) + (nnnotmuch-set-group-context group server) + (let ((file (nnnotmuch-article-to-file article))) + (cond + ((not (file-exists-p file)) + (nnheader-report 'nnnotmuch "File %s does not exist" file)) + (t + (message "Setting status %s to %s" name value) + ;; (with-temp-file file + ;; (nnheader-insert-file-contents file) + ;; (nnmail-replace-status name value)) + t)))) + + + +(defvar nnnotmuch-article-files nil) + +(defun nnnotmuch-article-to-file (article) + (when (numberp article) + (elt nnnotmuch-article-files (1- article)))) + +(defun nnnotmuch--find-query (name) + (when-let (s (seq-find (lambda (s) (string= (plist-get s :name) name)) + nnnotmuch-saved-searches)) + (plist-get s :query))) + +(defun nnnotmuch--find-group-message-ids (group &optional search) + (when-let (q (or search (nnnotmuch--find-query group))) + (notmuch-query-get-message-ids q))) + +(defun nnnotmuch--find-message-file (id) + (let ((q (notmuch-id-to-query id))) + (string-trim + (shell-command-to-string + (format "notmuch search --output=files %s" q))))) + +(defun nnnotmuch--count-files (query &optional unread) + (let ((cmd (format "notmuch count --output=files -- %s%s" + (shell-quote-argument (format "(%s)" query)) + (if unread " AND tag:unread" "")))) + (string-to-number (shell-command-to-string cmd)))) + +(defun nnnotmuch--query-files (query) + (let ((cmd (format "notmuch search --sort=oldest-first --output=files -- %s" + (shell-quote-argument query)))) + (split-string (shell-command-to-string cmd)))) + +(defun nnnotmuch--find-group-files (group) + (when-let (q (nnnotmuch--find-query group)) + (nnnotmuch--query-files q))) + +(defun nnnotmuch-set-group-context (group &optional server) + (when (and server + (not (nnnotmuch-server-opened server))) + (nnnotmuch-open-server server)) + (setq nnnotmuch-current-group group + nnnotmuch-article-files (nnnotmuch--find-group-files group)) + t) + + + + +(provide 'nnnotmuch) +;;; nnnotmuch.el ends here |