]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-address.el
emacs: Use 'and' instead of 'when' when the return value matters
[notmuch] / emacs / notmuch-address.el
index b3c56cfaad9dc212b990559171c1df2499b39722..2dd086618e9edf9898e13f565059ed1804f03bda 100644 (file)
 (declare-function company-manual-begin "company")
 
 (defvar notmuch-address-last-harvest 0
 (declare-function company-manual-begin "company")
 
 (defvar notmuch-address-last-harvest 0
-  "Time of last address harvest")
+  "Time of last address harvest.")
 
 (defvar notmuch-address-completions (make-hash-table :test 'equal)
   "Hash of email addresses for completion during email composition.
 
 (defvar notmuch-address-completions (make-hash-table :test 'equal)
   "Hash of email addresses for completion during email composition.
-  This variable is set by calling `notmuch-address-harvest'.")
+This variable is set by calling `notmuch-address-harvest'.")
 
 (defvar notmuch-address-full-harvest-finished nil
   "t indicates that full completion address harvesting has been
 
 (defvar notmuch-address-full-harvest-finished nil
   "t indicates that full completion address harvesting has been
-finished")
+finished. Use notmuch-address--harvest-ready to access as that
+will load a saved hash if necessary (and available).")
+
+(defun notmuch-address--harvest-ready ()
+  "Return t if there is a full address hash available.
+
+If the hash is not present it attempts to load a saved hash."
+  (or notmuch-address-full-harvest-finished
+      (notmuch-address--load-address-hash)))
 
 (defcustom notmuch-address-command 'internal
   "Determines how address completion candidates are generated.
 
 (defcustom notmuch-address-command 'internal
   "Determines how address completion candidates are generated.
@@ -58,17 +66,18 @@ disabled."
          (const :tag "Disable address completion" nil)
          (string :tag "Use external completion command"))
   :group 'notmuch-send
          (const :tag "Disable address completion" nil)
          (string :tag "Use external completion command"))
   :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
 (defcustom notmuch-address-internal-completion '(sent nil)
   "Determines how internal address completion generates candidates.
 
 This should be a list of the form '(DIRECTION FILTER), where
   :group 'notmuch-external)
 
 (defcustom notmuch-address-internal-completion '(sent nil)
   "Determines how internal address completion generates candidates.
 
 This should be a list of the form '(DIRECTION FILTER), where
- DIRECTION is either sent or received and specifies whether the
- candidates are searched in messages sent by the user or received
- by the user (note received by is much faster), and FILTER is
- either nil or a filter-string, such as \"date:1y..\" to append
- to the query."
+DIRECTION is either sent or received and specifies whether the
+candidates are searched in messages sent by the user or received
+by the user (note received by is much faster), and FILTER is
+either nil or a filter-string, such as \"date:1y..\" to append
+to the query."
   :type '(list :tag "Use internal address completion"
               (radio
                :tag "Base completion on messages you have"
   :type '(list :tag "Use internal address completion"
               (radio
                :tag "Base completion on messages you have"
@@ -85,6 +94,19 @@ This should be a list of the form '(DIRECTION FILTER), where
         (setq notmuch-address-completions (clrhash notmuch-address-completions))
         (setq notmuch-address-full-harvest-finished nil))
   :group 'notmuch-send
         (setq notmuch-address-completions (clrhash notmuch-address-completions))
         (setq notmuch-address-full-harvest-finished nil))
   :group 'notmuch-send
+  :group 'notmuch-address
+  :group 'notmuch-external)
+
+(defcustom notmuch-address-save-filename nil
+  "Filename to save the cached completion addresses.
+
+All the addresses notmuch uses for address completion will be
+cached in this file. This has obvious privacy implications so you
+should make sure it is not somewhere publicly readable."
+  :type '(choice (const :tag "Off" nil)
+                (file :tag "Filename"))
+  :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
 (defcustom notmuch-address-selection-function 'notmuch-address-selection-function
   :group 'notmuch-external)
 
 (defcustom notmuch-address-selection-function 'notmuch-address-selection-function
@@ -96,6 +118,7 @@ See documentation of function `notmuch-address-selection-function'
 to know how address selection is made by default."
   :type 'function
   :group 'notmuch-send
 to know how address selection is made by default."
   :type 'function
   :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
 (defcustom notmuch-address-post-completion-functions nil
   :group 'notmuch-external)
 
 (defcustom notmuch-address-post-completion-functions nil
@@ -103,8 +126,7 @@ to know how address selection is made by default."
 
 The completed address is passed as an argument to each function.
 Note that this hook will be invoked for completion in headers
 
 The completed address is passed as an argument to each function.
 Note that this hook will be invoked for completion in headers
-matching `notmuch-address-completion-headers-regexp'.
-"
+matching `notmuch-address-completion-headers-regexp'."
   :type 'hook
   :group 'notmuch-address
   :group 'notmuch-hooks)
   :type 'hook
   :group 'notmuch-address
   :group 'notmuch-hooks)
@@ -124,20 +146,21 @@ matching `notmuch-address-completion-headers-regexp'.
   (message "calling notmuch-address-message-insinuate is no longer needed"))
 
 (defcustom notmuch-address-use-company t
   (message "calling notmuch-address-message-insinuate is no longer needed"))
 
 (defcustom notmuch-address-use-company t
-  "If available, use company mode for address completion"
+  "If available, use company mode for address completion."
   :type 'boolean
   :type 'boolean
-  :group 'notmuch-send)
+  :group 'notmuch-send
+  :group 'notmuch-address)
 
 (defun notmuch-address-setup ()
   (let* ((setup-company (and notmuch-address-use-company
 
 (defun notmuch-address-setup ()
   (let* ((setup-company (and notmuch-address-use-company
-                          (require 'company nil t)))
+                            (require 'company nil t)))
         (pair (cons notmuch-address-completion-headers-regexp
         (pair (cons notmuch-address-completion-headers-regexp
-                      #'notmuch-address-expand-name)))
-      (when setup-company
-       (notmuch-company-setup))
-      (unless (memq pair message-completion-alist)
-       (setq message-completion-alist
-             (push pair message-completion-alist)))))
+                    #'notmuch-address-expand-name)))
+    (when setup-company
+      (notmuch-company-setup))
+    (unless (member pair message-completion-alist)
+      (setq message-completion-alist
+           (push pair message-completion-alist)))))
 
 (defun notmuch-address-toggle-internal-completion ()
   "Toggle use of internal completion for current buffer.
 
 (defun notmuch-address-toggle-internal-completion ()
   "Toggle use of internal completion for current buffer.
@@ -170,12 +193,13 @@ elisp-based implementation or older implementation requiring
 external commands."
   (cond
    ((eq notmuch-address-command 'internal)
 external commands."
   (cond
    ((eq notmuch-address-command 'internal)
-    (when (not notmuch-address-full-harvest-finished)
+    (unless (notmuch-address--harvest-ready)
       ;; First, run quick synchronous harvest based on what the user
       ;; First, run quick synchronous harvest based on what the user
-      ;; entered so far
+      ;; entered so far.
       (notmuch-address-harvest original t))
     (prog1 (notmuch-address-matching original)
       (notmuch-address-harvest original t))
     (prog1 (notmuch-address-matching original)
-      ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+      ;; Then start the (potentially long-running) full asynchronous
+      ;; harvest if necessary.
       (notmuch-address-harvest-trigger)))
    (t
     (process-lines notmuch-address-command original))))
       (notmuch-address-harvest-trigger)))
    (t
     (process-lines notmuch-address-command original))))
@@ -218,7 +242,8 @@ external commands."
            (push chosen notmuch-address-history)
            (delete-region beg end)
            (insert chosen)
            (push chosen notmuch-address-history)
            (delete-region beg end)
            (insert chosen)
-           (run-hook-with-args 'notmuch-address-post-completion-functions chosen))
+           (run-hook-with-args 'notmuch-address-post-completion-functions
+                               chosen))
        (message "No matches.")
        (ding))))
    (t nil)))
        (message "No matches.")
        (ding))))
    (t nil)))
@@ -227,20 +252,20 @@ external commands."
 (defun notmuch-address-locate-command (command)
   "Return non-nil if `command' is an executable either on
 `exec-path' or an absolute pathname."
 (defun notmuch-address-locate-command (command)
   "Return non-nil if `command' is an executable either on
 `exec-path' or an absolute pathname."
-  (when (stringp command)
-    (if (and (file-name-absolute-p command)
-            (file-executable-p command))
-       command
-      (setq command (file-name-nondirectory command))
-      (catch 'found-command
-       (let (bin)
-         (dolist (dir exec-path)
-           (setq bin (expand-file-name command dir))
-           (when (or (and (file-executable-p bin)
-                          (not (file-directory-p bin)))
-                     (and (file-executable-p (setq bin (concat bin ".exe")))
-                          (not (file-directory-p bin))))
-             (throw 'found-command bin))))))))
+  (and (stringp command)
+       (if (and (file-name-absolute-p command)
+               (file-executable-p command))
+          command
+        (setq command (file-name-nondirectory command))
+        (catch 'found-command
+          (let (bin)
+            (dolist (dir exec-path)
+              (setq bin (expand-file-name command dir))
+              (when (or (and (file-executable-p bin)
+                             (not (file-directory-p bin)))
+                        (and (file-executable-p (setq bin (concat bin ".exe")))
+                             (not (file-directory-p bin))))
+                (throw 'found-command bin))))))))
 
 (defun notmuch-address-harvest-addr (result)
   (let ((name-addr (plist-get result :name-addr)))
 
 (defun notmuch-address-harvest-addr (result)
   (let ((name-addr (plist-get result :name-addr)))
@@ -261,7 +286,7 @@ external commands."
 (defvar notmuch-address-harvest-procs '(nil . nil)
   "The currently running harvests.
 
 (defvar notmuch-address-harvest-procs '(nil . nil)
   "The currently running harvests.
 
-The car is a partial harvest, and the cdr is a full harvest")
+The car is a partial harvest, and the cdr is a full harvest.")
 
 (defun notmuch-address-harvest (&optional addr-prefix synchronous callback)
   "Collect addresses completion candidates.
 
 (defun notmuch-address-harvest (&optional addr-prefix synchronous callback)
   "Collect addresses completion candidates.
@@ -277,29 +302,30 @@ matching ADDR-PREFIX*' are queried.
 Address harvesting may take some time so the address collection runs
 asynchronously unless SYNCHRONOUS is t. In case of asynchronous
 execution, CALLBACK is called when harvesting finishes."
 Address harvesting may take some time so the address collection runs
 asynchronously unless SYNCHRONOUS is t. In case of asynchronous
 execution, CALLBACK is called when harvesting finishes."
-
   (let* ((sent (eq (car notmuch-address-internal-completion) 'sent))
         (config-query (cadr notmuch-address-internal-completion))
   (let* ((sent (eq (car notmuch-address-internal-completion) 'sent))
         (config-query (cadr notmuch-address-internal-completion))
-        (prefix-query (when addr-prefix
-                        (format "%s:%s*" (if sent "to" "from") addr-prefix)))
+        (prefix-query (and addr-prefix
+                           (format "%s:%s*"
+                                   (if sent "to" "from")
+                                   addr-prefix)))
         (from-or-to-me-query
          (mapconcat (lambda (x)
                       (concat (if sent "from:" "to:") x))
                     (notmuch-user-emails) " or "))
         (query (if (or prefix-query config-query)
                    (concat (format "(%s)" from-or-to-me-query)
         (from-or-to-me-query
          (mapconcat (lambda (x)
                       (concat (if sent "from:" "to:") x))
                     (notmuch-user-emails) " or "))
         (query (if (or prefix-query config-query)
                    (concat (format "(%s)" from-or-to-me-query)
-                           (when prefix-query
-                             (format " and (%s)" prefix-query))
-                           (when config-query
-                             (format " and (%s)" config-query)))
+                           (and prefix-query
+                                (format " and (%s)" prefix-query))
+                           (and config-query
+                                (format " and (%s)" config-query)))
                  from-or-to-me-query))
                  from-or-to-me-query))
-        (args `("address" "--format=sexp" "--format-version=2"
+        (args `("address" "--format=sexp" "--format-version=4"
                 ,(if sent "--output=recipients" "--output=sender")
                 "--deduplicate=address"
                 ,query)))
     (if synchronous
        (mapc #'notmuch-address-harvest-addr
                 ,(if sent "--output=recipients" "--output=sender")
                 "--deduplicate=address"
                 ,query)))
     (if synchronous
        (mapc #'notmuch-address-harvest-addr
-                                  (apply 'notmuch-call-notmuch-sexp args))
+             (apply 'notmuch-call-notmuch-sexp args))
       ;; Asynchronous
       (let* ((current-proc (if addr-prefix
                               (car notmuch-address-harvest-procs)
       ;; Asynchronous
       (let* ((current-proc (if addr-prefix
                               (car notmuch-address-harvest-procs)
@@ -310,7 +336,6 @@ execution, CALLBACK is called when harvesting finishes."
        ;; Kill any existing process
        (when current-proc
          (kill-buffer (process-buffer current-proc))) ; this also kills the process
        ;; Kill any existing process
        (when current-proc
          (kill-buffer (process-buffer current-proc))) ; this also kills the process
-
        (setq current-proc
              (apply 'notmuch-start-notmuch proc-name proc-buf
                     callback                           ; process sentinel
        (setq current-proc
              (apply 'notmuch-start-notmuch proc-name proc-buf
                     callback                           ; process sentinel
@@ -323,18 +348,80 @@ execution, CALLBACK is called when harvesting finishes."
   ;; return value
   nil)
 
   ;; return value
   nil)
 
+(defvar notmuch-address--save-hash-version 1
+  "Version format of the save hash.")
+
+(defun notmuch-address--get-address-hash ()
+  "Returns the saved address hash as a plist.
+
+Returns nil if the save file does not exist, or it does not seem
+to be a saved address hash."
+  (and notmuch-address-save-filename
+       (condition-case nil
+          (with-temp-buffer
+            (insert-file-contents notmuch-address-save-filename)
+            (let ((name (read (current-buffer)))
+                  (plist (read (current-buffer))))
+              ;; We do two simple sanity checks on the loaded file.
+              ;; We just check a version is specified, not that
+              ;; it is the current version, as we are allowed to
+              ;; over-write and a save-file with an older version.
+              (and (string= name "notmuch-address-hash")
+                   (plist-get plist :version)
+                   plist)))
+        ;; The error case catches any of the reads failing.
+        (error nil))))
+
+(defun notmuch-address--load-address-hash ()
+  "Read the saved address hash and set the corresponding variables."
+  (let ((load-plist (notmuch-address--get-address-hash)))
+    (when (and load-plist
+              ;; If the user's setting have changed, or the version
+              ;; has changed, return nil to make sure the new settings
+              ;; take effect.
+              (equal (plist-get load-plist :completion-settings)
+                     notmuch-address-internal-completion)
+              (equal (plist-get load-plist :version)
+                     notmuch-address--save-hash-version))
+      (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest))
+      (setq notmuch-address-completions (plist-get load-plist :completions))
+      (setq notmuch-address-full-harvest-finished t)
+      ;; Return t to say load was successful.
+      t)))
+
+(defun notmuch-address--save-address-hash ()
+  (when notmuch-address-save-filename
+    (if (or (not (file-exists-p notmuch-address-save-filename))
+           ;; The file exists, check it is a file we saved
+           (notmuch-address--get-address-hash))
+       (with-temp-file notmuch-address-save-filename
+         (let ((save-plist
+                (list :version notmuch-address--save-hash-version
+                      :completion-settings notmuch-address-internal-completion
+                      :last-harvest notmuch-address-last-harvest
+                      :completions notmuch-address-completions)))
+           (print "notmuch-address-hash" (current-buffer))
+           (print save-plist (current-buffer))))
+      (message "\
+Warning: notmuch-address-save-filename %s exists but doesn't
+appear to be an address savefile.  Not overwriting."
+              notmuch-address-save-filename))))
+
 (defun notmuch-address-harvest-trigger ()
   (let ((now (float-time)))
     (when (> (- now notmuch-address-last-harvest) 86400)
       (setq notmuch-address-last-harvest now)
 (defun notmuch-address-harvest-trigger ()
   (let ((now (float-time)))
     (when (> (- now notmuch-address-last-harvest) 86400)
       (setq notmuch-address-last-harvest now)
-      (notmuch-address-harvest nil nil
-                              (lambda (proc event)
-                                ;; If harvest fails, we want to try
-                                ;; again when the trigger is next
-                                ;; called
-                                (if (string= event "finished\n")
-                                    (setq notmuch-address-full-harvest-finished t)
-                                  (setq notmuch-address-last-harvest 0)))))))
+      (notmuch-address-harvest
+       nil nil
+       (lambda (proc event)
+        ;; If harvest fails, we want to try
+        ;; again when the trigger is next
+        ;; called
+        (if (string= event "finished\n")
+            (progn
+              (notmuch-address--save-address-hash)
+              (setq notmuch-address-full-harvest-finished t))
+          (setq notmuch-address-last-harvest 0)))))))
 
 ;;
 
 
 ;;