1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
|
;;; mu4e-update.el --- Update the mu4e message store -*- lexical-binding: t -*-
;; Copyright (C) 2011-2025 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e 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.
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Updating the mu4e message store: calling a mail retrieval program and
;; re-running the index.
;;; Code:
(require 'mu4e-helpers)
(require 'mu4e-server)
;;; Customization
(defcustom mu4e-get-mail-command "true"
"Shell command for retrieving new mail or a function.
Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
arbitrary shell commands can be used. If it is a function, it should
return a string specifying a shell command.
The default is the string \"true\", which refers to the
`man:true(1)' shell command, which does nothing but return
success. This default is all you need if mail is already
retrieved in some other way outside mu4e, such as through a local
MDA, but otherwise you need to customize it as described."
:type '(choice
(string :tag "Shell command")
(function :tag "Function that returns a string (shell command)"))
:group 'mu4e
:safe 'stringp)
(defcustom mu4e-index-update-error-warning t
"Whether to display warnings during the retrieval process.
This depends on the `mu4e-get-mail-command' exit code."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-error-continue t
"Whether to continue with indexing after an error during retrieval."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-in-background t
"Whether to retrieve mail in the background."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-cleanup t
"Whether to run a cleanup phase after indexing.
That is, validate that each message in the message store has a
corresponding message file in the filesystem.
Having this option as t ensures that no non-existing messages are
shown but can slow with large message stores on slow file-systems."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-lazy-check nil
"Whether to only use a \"lazy\" check during reindexing.
This influences how we decide whether a message
needs (re)indexing or not.
When this is set to non-nil, mu only uses the directory
timestamps to decide whether it needs to check the messages
beneath it. This makes indexing much faster, but might miss some
changes. For this, you might want to occasionally call
`mu4e-update-index-nonlazy'; `mu4e-update-pre-hook' can be used
to automate this."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-update-interval nil
"Number of seconds between mail retrieval/indexing.
If nil, don't update automatically. Note, changes in
`mu4e-update-interval' only take effect after restarting mu4e.
Important, the automatic update *only* works when `mu4e' is
running."
:type '(choice (const :tag "No automatic update" nil)
(integer :tag "Seconds"))
:group 'mu4e
:safe 'integerp)
(defvar mu4e-update-pre-hook nil
"Hook run just *before* the mail-retrieval / database updating process starts.
You can use this hook for example to update
`mu4e-get-mail-command' with some specific setting.")
(defcustom mu4e-hide-index-messages nil
"Whether to hide the \"Indexing...\" and contacts messages."
:type 'boolean
:group 'mu4e)
(defvar mu4e-index-updated-hook nil
"Hook run when the indexing process has completed.
The variable `mu4e-index-update-status' can be used to get
information about what changed.")
(defvar mu4e-message-changed-hook nil
"Hook run when there is a message changed in the data store.
For new messages, it depends on `mu4e-index-updated-hook'. This
can be used as a simple way to invoke some action when a message
changed")
(defvar mu4e-index-update-status nil
"Last-seen completed update status, based on server status messages.
If non-nil, this is a plist of the form:
\(
:checked <number of messages processed> (checked whether up-to-date)
:updated <number of messages updated/added
:cleaned-up <number of stale messages removed from store
:stamp <emacs (current-time) timestamp for the status)")
(defconst mu4e-last-update-buffer "*mu4e-last-update*"
"Name of buffer with cloned from the last update buffer.
Useful for diagnosing update problems.")
;;; Internal variables / const
(defconst mu4e--update-name " *mu4e-update*"
"Name of the process and buffer to update mail.")
(defvar mu4e--progress-reporter nil
"Internal, the progress reporter object.")
(defvar mu4e--update-timer nil
"The mu4e update timer.")
(defconst mu4e--update-buffer-height 8
"Height of the mu4e message retrieval/update buffer.")
(defvar mu4e--get-mail-ask-password "mu4e get-mail: Enter password: "
"Query string for `mu4e-get-mail-command' password.")
(defvar mu4e--get-mail-password-regexp "^Remote: Enter password: $"
"Regexp for a `mu4e-get-mail-command' password query.")
(defun mu4e--get-mail-process-filter (proc msg)
"Filter the MSG output of the `mu4e-get-mail-command' PROC.
Currently the filter only checks if the command asks for a
password by matching the output against
`mu4e~get-mail-password-regexp'. The messages are inserted into
the process buffer.
Also scrolls to the final line, and update the progress
throbber."
(when mu4e--progress-reporter
(progress-reporter-update mu4e--progress-reporter))
(when (string-match mu4e--get-mail-password-regexp msg)
(if (process-get proc 'x-interactive)
(process-send-string proc
(concat (read-passwd mu4e--get-mail-ask-password)
"\n"))
;; TODO kill process?
(mu4e-error "Unrecognized password request")))
(when (process-buffer proc)
(let ((inhibit-read-only t)
(procwin (get-buffer-window (process-buffer proc))))
;; Insert at end of buffer. Leave point alone.
(with-current-buffer (process-buffer proc)
(goto-char (point-max))
(if (string-match ".*\r\\(.*\\)" msg)
(progn
;; kill even with \r
(end-of-line)
(let ((end (point)))
(beginning-of-line)
(delete-region (point) end))
(insert (match-string 1 msg)))
(insert msg)))
;; Auto-scroll unless user is interacting with the window.
(when (and (window-live-p procwin)
(not (eq (selected-window) procwin)))
(with-selected-window procwin
(goto-char (point-max)))))))
(defun mu4e-index-message (frm &rest args)
"Display FRM with ARGS like `mu4e-message' for index messages.
However, if `mu4e-hide-index-messages' is non-nil, do not display anything."
(unless mu4e-hide-index-messages
(apply 'mu4e-message frm args)))
(defun mu4e-update-index ()
"Update the mu4e index."
(interactive)
(mu4e--server-index mu4e-index-cleanup mu4e-index-lazy-check))
(defun mu4e-update-index-nonlazy ()
"Update the mu4e index non-lazily.
This is just a convenience wrapper for indexing the non-lazy way
if you otherwise want to use `mu4e-index-lazy-check'."
(interactive)
(let ((mu4e-index-cleanup t) (mu4e-index-lazy-check nil))
(mu4e-update-index)))
(defvar mu4e--update-buffer nil
"The buffer of the update process when updating.")
(define-derived-mode mu4e--update-mail-mode special-mode "mu4e:update"
"Major mode used for retrieving new e-mail messages in `mu4e'.")
(define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail)
(defun mu4e--temp-window (buf height)
"Create a temporary window with HEIGHT at the bottom BUF.
This function uses `display-buffer' with a default preset.
To override this behavior, customize `display-buffer-alist'."
(display-buffer buf `(display-buffer-at-bottom
(preserve-size . (nil . t))
(height . ,height)
(inhibit-same-window . t)
(window-height . fit-window-to-buffer)))
(set-window-buffer (get-buffer-window buf) buf))
(defun mu4e--update-sentinel-func (proc _msg)
"Sentinel function for the update process PROC."
(when mu4e--progress-reporter
(progress-reporter-done mu4e--progress-reporter)
(setq mu4e--progress-reporter nil))
(unless mu4e-hide-index-messages
(message nil))
(if (or (not (eq (process-status proc) 'exit))
(/= (process-exit-status proc) 0))
(progn
(when mu4e-index-update-error-warning
(mu4e-message "Update process returned with non-zero exit code")
(sit-for 5))
(when mu4e-index-update-error-continue
(mu4e-update-index)))
(mu4e-update-index))
(when (buffer-live-p mu4e--update-buffer)
(delete-windows-on mu4e--update-buffer)
;; clone the update buffer for diagnosis
(when (get-buffer mu4e-last-update-buffer)
(kill-buffer mu4e-last-update-buffer))
(with-current-buffer mu4e--update-buffer
(special-mode)
(clone-buffer mu4e-last-update-buffer))
;; and kill the buffer itself; the cloning is needed
;; so the temp window handling works as expected.
(kill-buffer mu4e--update-buffer)))
;; complicated function, as it:
;; - needs to check for errors
;; - (optionally) pop-up a window
;; - (optionally) check password requests
(defun mu4e--update-mail-and-index-real (run-in-background)
"Get a new mail by running `mu4e-get-mail-command'.
If
RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
run in the background; otherwise, pop up a window."
(let* ((process-connection-type t)
(cmd (mu4e--fun-val mu4e-get-mail-command))
(proc (start-process-shell-command
mu4e--update-name mu4e--update-name cmd))
(buf (process-buffer proc))
(win (or run-in-background
(mu4e--temp-window buf mu4e--update-buffer-height))))
(set-process-query-on-exit-flag proc nil)
(setq mu4e--update-buffer buf)
(when (window-live-p win)
(with-selected-window win
(erase-buffer)
(insert "\n") ;; FIXME -- needed so output starts
(mu4e--update-mail-mode)))
(setq mu4e--progress-reporter
(unless mu4e-hide-index-messages
(make-progress-reporter
(mu4e-format "Retrieving mail..."))))
(set-process-sentinel proc 'mu4e--update-sentinel-func)
;; if we're running in the foreground, handle password requests
(unless run-in-background
(process-put proc 'x-interactive (not run-in-background))
(set-process-filter proc 'mu4e--get-mail-process-filter))))
(defun mu4e-update-mail-and-index (run-in-background)
"Retrieve new mail by running `mu4e-get-mail-command'.
If RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
run in the background; otherwise, pop up a window."
(interactive "P")
(unless mu4e-get-mail-command
(mu4e-error "`mu4e-get-mail-command' is not defined"))
(if (and (buffer-live-p mu4e--update-buffer)
(process-live-p (get-buffer-process mu4e--update-buffer)))
(mu4e-message "Update process is already running")
(progn
(run-hooks 'mu4e-update-pre-hook)
(mu4e--update-mail-and-index-real run-in-background))))
(defun mu4e-kill-update-mail ()
"Stop the update process by killing it."
(interactive)
(let* ((proc (and (buffer-live-p mu4e--update-buffer)
(get-buffer-process mu4e--update-buffer))))
(when (process-live-p proc)
(kill-process proc t))))
(define-minor-mode mu4e-update-minor-mode
"Mode for triggering mu4e updates."
:global nil
:init-value nil ;; disabled by default
:group 'mu4e
:lighter ""
:keymap
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-S-u") #'mu4e-update-mail-and-index)
;; for terminal users
(define-key map (kbd "C-c C-u") #'mu4e-update-mail-and-index)
map))
(provide 'mu4e-update)
;;; mu4e-update.el ends here
|