* wl-vars.el (wl-message-use-header-narrowing): New user option.
[elisp/wanderlust.git] / wl / wl-expire.el
1 ;;; wl-expire.el --- Message expire modules for Wanderlust.
2
3 ;; Copyright (C) 1998,1999,2000 Masahiro MURATA <muse@ba2.so-net.ne.jp>
4 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
5
6 ;; Author: Masahiro MURATA <muse@ba2.so-net.ne.jp>
7 ;; Keywords: mail, net news
8
9 ;; This file is part of Wanderlust (Yet Another Message Interface on Emacsen).
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15 ;;
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20 ;;
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25 ;;
26
27 ;;; Commentary:
28 ;;
29
30 (require 'wl-summary)
31 (require 'wl-thread)
32 (require 'wl-folder)
33 (require 'elmo)
34
35 ;;; Code:
36
37 (eval-when-compile
38   (require 'wl-util)
39   (require 'elmo-archive))
40
41 ;; Variables
42
43 (defvar wl-expired-alist nil)
44 (defvar wl-expired-alist-file-name "expired-alist")
45 (defvar wl-expired-log-alist nil)
46 (defvar wl-expired-log-alist-file-name "expired-log")
47 (defvar wl-expire-test nil)     ;; for debug (no execute)
48
49 (defun wl-expired-alist-load ()
50   (elmo-object-load (expand-file-name
51                      wl-expired-alist-file-name
52                      elmo-msgdb-directory)))
53
54 (defun wl-expired-alist-save (&optional alist)
55   (elmo-object-save (expand-file-name
56                      wl-expired-alist-file-name
57                      elmo-msgdb-directory)
58                     (or alist wl-expired-alist)))
59
60 (defsubst wl-expire-msg-p (msg-num mark-alist)
61   (cond ((consp wl-summary-expire-reserve-marks)
62          (let ((mark (nth 1 (assq msg-num mark-alist))))
63            (not (or (member mark wl-summary-expire-reserve-marks)
64                     (and wl-summary-buffer-disp-msg
65                          (eq msg-num wl-summary-buffer-current-msg))))))
66         ((eq wl-summary-expire-reserve-marks 'all)
67          (not (or (assq msg-num mark-alist)
68                   (and wl-summary-buffer-disp-msg
69                        (eq msg-num wl-summary-buffer-current-msg)))))
70         ((eq wl-summary-expire-reserve-marks 'none)
71          t)
72         (t
73          (error "Invalid marks: %s" wl-summary-expire-reserve-marks))))
74
75 (defmacro wl-expire-make-sortable-date (date)
76   (` (timezone-make-sortable-date
77       (aref (, date) 0) (aref (, date) 1) (aref (, date) 2)
78       (timezone-make-time-string
79        (aref (, date) 3) (aref (, date) 4) (aref (, date) 5)))))
80
81 (defsubst wl-expire-date-p (key-datevec date)
82   (let ((datevec (condition-case nil
83                      (timezone-fix-time date nil nil)
84                    (error nil))))
85     (and
86      datevec (> (aref datevec 1) 0)
87      (string<
88       (wl-expire-make-sortable-date datevec)
89       (wl-expire-make-sortable-date key-datevec)))))
90
91 ;; New functions to avoid accessing to the msgdb directly.
92 (defsubst wl-expire-message-p (folder number)
93   "Return non-nil when a message in the FOLDER with NUMBER can be expired."
94   (cond ((consp wl-summary-expire-reserve-marks)
95          (let ((mark (wl-summary-message-mark folder number)))
96            (not (or (member mark wl-summary-expire-reserve-marks)
97                     (and wl-summary-buffer-disp-msg
98                          (eq number wl-summary-buffer-current-msg))))))
99         ((eq wl-summary-expire-reserve-marks 'all)
100          (not (or (wl-summary-message-mark folder number)
101                   (and wl-summary-buffer-disp-msg
102                        (eq number wl-summary-buffer-current-msg)))))
103         ((eq wl-summary-expire-reserve-marks 'none)
104          t)
105         (t
106          (error "Invalid marks: %s" wl-summary-expire-reserve-marks))))
107
108 (defun wl-expire-delete-reserved-messages (msgs folder)
109   "Delete a number from NUMBERS when a message with the number is reserved."
110   (let ((dlist msgs))
111     (while dlist
112       (unless (wl-expire-message-p folder (car dlist))
113         (setq msgs (delq (car dlist) msgs)))
114       (setq dlist (cdr dlist)))
115     msgs))
116 ;; End New functions.
117
118 (defun wl-expire-delete (folder delete-list &optional no-reserve-marks)
119   "Delete message for expire."
120   (unless no-reserve-marks
121     (setq delete-list
122           (wl-expire-delete-reserved-messages delete-list folder)))
123   (when delete-list
124     (let ((mess
125            (format "Expiring (delete) %s msgs..."
126                    (length delete-list))))
127       (message "%s" mess)
128       (if (elmo-folder-move-messages folder delete-list 'null)
129           (progn
130             (wl-expire-append-log
131              (elmo-folder-name-internal folder)
132              delete-list nil 'delete)
133             (message "%sdone" mess))
134         (error "%sfailed!" mess))))
135   (cons delete-list (length delete-list)))
136
137 (defun wl-expire-refile (folder refile-list dst-folder
138                                 &optional no-reserve-marks preserve-number copy)
139   "Refile message for expire. If COPY is non-nil, copy message."
140   (when (not (string= (elmo-folder-name-internal folder) dst-folder))
141     (unless no-reserve-marks
142       (setq refile-list
143             (wl-expire-delete-reserved-messages refile-list folder)))
144     (when refile-list
145       (let* ((doingmes (if copy
146                            "Copying %s"
147                          "Expiring (move %s)"))
148              (dst-folder (wl-folder-get-elmo-folder dst-folder))
149              (mess (format (concat doingmes " %s msgs...")
150                            (elmo-folder-name-internal dst-folder)
151                            (length refile-list))))
152         (message "%s" mess)
153         (if wl-expire-test
154             nil
155           (unless (or (elmo-folder-exists-p dst-folder)
156                       (elmo-folder-create dst-folder))
157             (error "%s: create folder failed"
158                    (elmo-folder-name-internal dst-folder)))
159           (if (elmo-folder-move-messages folder
160                                          refile-list
161                                          dst-folder
162                                          copy
163                                          preserve-number)
164               (progn
165                 (wl-expire-append-log
166                  (elmo-folder-name-internal folder)
167                  refile-list
168                  (elmo-folder-name-internal dst-folder)
169                  (if copy 'copy 'move))
170                 (message "%sdone" mess))
171             (error "%sfailed!" mess)))))
172     (cons refile-list (length refile-list))))
173
174 (defun wl-expire-refile-with-copy-reserve-msg
175   (folder refile-list dst-folder
176           &optional no-reserve-marks preserve-number copy)
177   "Refile message for expire.
178 If REFILE-LIST includes reserve mark message, so copy."
179   (when (not (string= (elmo-folder-name-internal folder) dst-folder))
180     (let ((msglist refile-list)
181           (dst-folder (wl-folder-get-elmo-folder dst-folder))
182           (ret-val t)
183           (copy-reserve-message)
184           (copy-len 0)
185           msg msg-id)
186       (message "Expiring (move %s) %s msgs..."
187                (elmo-folder-name-internal dst-folder) (length refile-list))
188       (if wl-expire-test
189           (setq copy-len (length refile-list))
190         (unless (or (elmo-folder-exists-p dst-folder)
191                   (elmo-folder-create dst-folder))
192         (error "%s: create folder failed" (elmo-folder-name-internal
193                                            dst-folder)))
194         (while (setq msg (wl-pop msglist))
195           (unless (wl-expire-message-p folder msg)
196             (setq msg-id (elmo-message-field folder msg 'message-id))
197             (if (assoc msg-id wl-expired-alist)
198                 ;; reserve mark message already refiled or expired
199                 (setq refile-list (delq msg refile-list))
200               ;; reserve mark message not refiled
201               (wl-append wl-expired-alist (list
202                                            (cons msg-id
203                                                  (elmo-folder-name-internal
204                                                   dst-folder))))
205               (setq copy-reserve-message t))))
206         (when refile-list
207           (unless
208               (setq ret-val
209                     (elmo-folder-move-messages folder
210                                                refile-list
211                                                dst-folder
212                                                copy-reserve-message
213                                                preserve-number))
214             (error "Expire: move msgs to %s failed"
215                    (elmo-folder-name-internal dst-folder)))
216           (wl-expire-append-log (elmo-folder-name-internal folder)
217                                 refile-list
218                                 (elmo-folder-name-internal dst-folder)
219                                 (if copy-reserve-message 'copy 'move))
220           (setq copy-len (length refile-list))
221           (when copy-reserve-message
222             (setq refile-list
223                   (wl-expire-delete-reserved-messages refile-list folder))
224             (when refile-list
225               (if (setq ret-val
226                         (elmo-folder-move-messages folder refile-list 'null))
227                   (progn
228                     (wl-expire-append-log
229                      (elmo-folder-name-internal folder)
230                      refile-list nil 'delete))))))
231         (let ((mes (format "Expiring (move %s) %s msgs..."
232                            (elmo-folder-name-internal dst-folder)
233                            (length refile-list))))
234           (if ret-val
235               (message "%sdone" mes)
236             (error "%sfailed!" mes))))
237       (cons refile-list copy-len))))
238
239 (defun wl-expire-archive-get-folder (src-folder &optional fmt dst-folder-arg)
240   "Get archive folder name from SRC-FOLDER."
241   (let* ((fmt (or fmt wl-expire-archive-folder-name-fmt))
242          (src-folde-name (substring
243                           (elmo-folder-name-internal src-folder)
244                           (length (elmo-folder-prefix-internal src-folder))))
245          (archive-spec (char-to-string
246                         (car (rassq 'archive elmo-folder-type-alist))))
247          dst-folder-base dst-folder-fmt prefix)
248     (cond (dst-folder-arg
249            (setq dst-folder-base (concat archive-spec dst-folder-arg)))
250           ((eq (elmo-folder-type-internal src-folder) 'localdir)
251            (setq dst-folder-base
252                  (concat archive-spec src-folde-name)))
253           (t
254            (setq dst-folder-base
255                  (elmo-concat-path
256                   (format "%s%s" archive-spec (elmo-folder-type-internal
257                                                src-folder))
258                   src-folde-name))))
259     (setq dst-folder-fmt (format fmt
260                                  dst-folder-base
261                                  wl-expire-archive-folder-type))
262     (setq dst-folder-base (format "%s;%s"
263                                   dst-folder-base
264                                   wl-expire-archive-folder-type))
265     (when wl-expire-archive-folder-prefix
266       (cond ((eq wl-expire-archive-folder-prefix 'short)
267              (setq prefix (file-name-nondirectory
268                            src-folde-name)))
269             (t
270              (setq prefix src-folde-name)))
271       (setq dst-folder-fmt (concat dst-folder-fmt ";" prefix))
272       (setq dst-folder-base (concat dst-folder-base ";" prefix)))
273     (cons dst-folder-base dst-folder-fmt)))
274
275 (defsubst wl-expire-archive-get-max-number (dst-folder-base &optional regexp)
276   (let ((files (reverse (sort (elmo-folder-list-subfolders
277                                (elmo-make-folder dst-folder-base))
278                               'string<)))
279         (regexp (or regexp wl-expire-archive-folder-num-regexp))
280         filenum in-folder)
281     (catch 'done
282       (while files
283         (when (string-match regexp (car files))
284           (setq filenum (elmo-match-string 1 (car files)))
285           (setq in-folder (elmo-folder-status
286                            (wl-folder-get-elmo-folder (car files))))
287           (throw 'done (cons in-folder filenum)))
288         (setq files (cdr files))))))
289
290 (defun wl-expire-archive-number-delete-old (dst-folder-base
291                                             preserve-number msgs folder
292                                             &optional no-confirm regexp file)
293   (let ((len 0) (max-num 0)
294         folder-info dels)
295     (if (or (and file (setq folder-info
296                             (cons (elmo-folder-status
297                                    (wl-folder-get-elmo-folder file))
298                                   nil)))
299             (setq folder-info (wl-expire-archive-get-max-number
300                                dst-folder-base
301                                regexp)))
302         (progn
303           (setq len (cdar folder-info))
304           (when preserve-number
305             ;; delete small number than max number of dst-folder
306             (setq max-num (caar folder-info))
307             (while (and msgs (>= max-num (car msgs)))
308               (wl-append dels (list (car msgs)))
309               (setq msgs (cdr msgs)))
310             (setq dels (wl-expire-delete-reserved-messages dels folder))
311             (unless (and dels
312                          (or (or no-confirm (not
313                                              wl-expire-delete-oldmsg-confirm))
314                              (progn
315                                (if (eq major-mode 'wl-summary-mode)
316                                    (wl-thread-jump-to-msg (car dels)))
317                                (y-or-n-p (format "Delete old messages %s? "
318                                                  dels)))))
319               (setq dels nil)))
320           (list msgs dels max-num (cdr folder-info) len))
321       (list msgs dels 0 "0" 0))))
322
323 (defun wl-expire-archive-number1 (folder delete-list
324                                   &optional preserve-number dst-folder-arg
325                                             no-delete)
326   "Standard function for `wl-summary-expire'.
327 Refile to archive folder followed message number."
328   (let* ((elmo-archive-treat-file t)    ;; treat archive folder as a file.
329          (dst-folder-expand (and dst-folder-arg
330                                  (wl-expand-newtext
331                                   dst-folder-arg
332                                   (elmo-folder-name-internal folder))))
333          (dst-folder-fmt (funcall
334                           wl-expire-archive-get-folder-function
335                           folder nil dst-folder-expand))
336          (dst-folder-base (car dst-folder-fmt))
337          (dst-folder-fmt (cdr dst-folder-fmt))
338          (refile-func (if no-delete
339                           'wl-expire-refile
340                         'wl-expire-refile-with-copy-reserve-msg))
341          tmp dels dst-folder
342          prev-arcnum arcnum msg arcmsg-list
343          deleted-list ret-val)
344     (setq tmp (wl-expire-archive-number-delete-old
345                dst-folder-base preserve-number delete-list
346                folder
347                no-delete))
348     (when (and (not no-delete)
349                (setq dels (nth 1 tmp)))
350       (wl-append deleted-list (car (wl-expire-delete folder dels))))
351     (setq delete-list (car tmp))
352     (catch 'done
353       (while t
354         (if (setq msg (wl-pop delete-list))
355             (setq arcnum (/ msg wl-expire-archive-files))
356           (setq arcnum nil))
357         (when (and prev-arcnum
358                    (not (eq arcnum prev-arcnum)))
359           (setq dst-folder (format dst-folder-fmt
360                                    (* prev-arcnum wl-expire-archive-files)))
361           (and (setq ret-val
362                      (funcall
363                       refile-func
364                       folder arcmsg-list dst-folder t preserve-number
365                       no-delete))
366                (wl-append deleted-list (car ret-val)))
367           (setq arcmsg-list nil))
368         (if (null msg)
369             (throw 'done t))
370         (wl-append arcmsg-list (list msg))
371         (setq prev-arcnum arcnum)))
372     deleted-list))
373
374 (defun wl-expire-archive-number2 (folder delete-list
375                                   &optional preserve-number dst-folder-arg
376                                             no-delete)
377   "Standard function for `wl-summary-expire'.
378 Refile to archive folder followed the number of message in one archive folder."
379   (let* ((elmo-archive-treat-file t)    ;; treat archive folder as a file.
380          (dst-folder-expand (and dst-folder-arg
381                                  (wl-expand-newtext
382                                   dst-folder-arg
383                                   (elmo-folder-name-internal folder))))
384          (dst-folder-fmt (funcall
385                           wl-expire-archive-get-folder-function
386                           folder nil dst-folder-expand))
387          (dst-folder-base (car dst-folder-fmt))
388          (dst-folder-fmt (cdr dst-folder-fmt))
389          (refile-func (if no-delete
390                           'wl-expire-refile
391                         'wl-expire-refile-with-copy-reserve-msg))
392          (len 0) (filenum 0)
393          tmp dels dst-folder
394          arc-len msg arcmsg-list
395          deleted-list ret-val)
396     (setq tmp (wl-expire-archive-number-delete-old
397                dst-folder-base preserve-number delete-list
398                folder
399                no-delete))
400     (when (and (not no-delete)
401                (setq dels (nth 1 tmp)))
402       (wl-append deleted-list (car (wl-expire-delete folder dels))))
403     (setq delete-list (car tmp)
404           filenum (string-to-int (nth 3 tmp))
405           len (nth 4 tmp)
406           arc-len len)
407     (catch 'done
408       (while t
409         (if (setq msg (wl-pop delete-list))
410             (setq len (1+ len))
411           (setq len (1+ wl-expire-archive-files)))
412         (when (> len wl-expire-archive-files)
413           (when arcmsg-list
414             (setq dst-folder (format dst-folder-fmt filenum))
415             (and (setq ret-val
416                        (funcall
417                         refile-func
418                         folder arcmsg-list dst-folder t preserve-number
419                         no-delete))
420                  (wl-append deleted-list (car ret-val)))
421             (setq arc-len (+ arc-len (cdr ret-val))))
422           (setq arcmsg-list nil)
423           (if (< arc-len wl-expire-archive-files)
424               (setq len (1+ arc-len))
425             (setq filenum (+ filenum wl-expire-archive-files)
426                   len (- len arc-len)   ;; maybe 1
427                   arc-len (1- len)      ;; maybe 0
428                   )))
429         (if (null msg)
430             (throw 'done t))
431         (wl-append arcmsg-list (list msg))))
432     deleted-list))
433
434 (defun wl-expire-archive-date (folder delete-list
435                                &optional preserve-number dst-folder-arg
436                                          no-delete)
437   "Standard function for `wl-summary-expire'.
438 Refile to archive folder followed message date."
439   (let* ((elmo-archive-treat-file t)    ;; treat archive folder as a file.
440          (dst-folder-expand (and dst-folder-arg
441                                  (wl-expand-newtext
442                                   dst-folder-arg
443                                   (elmo-folder-name-internal folder))))
444          (dst-folder-fmt (funcall
445                           wl-expire-archive-get-folder-function
446                           folder
447                           wl-expire-archive-date-folder-name-fmt
448                           dst-folder-expand
449                           ))
450          (dst-folder-base (car dst-folder-fmt))
451          (dst-folder-fmt (cdr dst-folder-fmt))
452          (refile-func (if no-delete
453                           'wl-expire-refile
454                         'wl-expire-refile-with-copy-reserve-msg))
455          tmp dels dst-folder date time
456          msg arcmsg-alist arcmsg-list
457          deleted-list ret-val)
458     (setq tmp (wl-expire-archive-number-delete-old
459                dst-folder-base preserve-number delete-list
460                folder
461                no-delete
462                wl-expire-archive-date-folder-num-regexp))
463     (when (and (not no-delete)
464                (setq dels (nth 1 tmp)))
465       (wl-append deleted-list (car (wl-expire-delete folder dels))))
466     (setq delete-list (car tmp))
467     (while (setq msg (wl-pop delete-list))
468       (setq date (elmo-message-field folder msg 'date))
469       (setq time
470             (condition-case nil
471                 (timezone-fix-time date nil nil)
472               (error [0 0 0 0 0 0 0])))
473       (if (= (aref time 1) 0)   ;; if (month == 0)
474           (aset time 0 0))      ;;    year = 0
475       (setq dst-folder (format dst-folder-fmt
476                                (aref time 0)  ;; year
477                                (aref time 1)  ;; month
478                                ))
479       (setq arcmsg-alist
480             (wl-append-assoc-list
481              dst-folder
482              msg
483              arcmsg-alist)))
484     (while arcmsg-alist
485       (setq dst-folder (caar arcmsg-alist))
486       (setq arcmsg-list (cdar arcmsg-alist))
487       (and (setq ret-val
488                  (funcall
489                   refile-func
490                   folder arcmsg-list dst-folder t preserve-number
491                   no-delete))
492            (wl-append deleted-list (car ret-val)))
493       (setq arcmsg-alist (cdr arcmsg-alist)))
494     deleted-list))
495
496 ;;; wl-expire-localdir-date
497 (defvar wl-expire-localdir-date-folder-name-fmt "%s/%%04d_%%02d")
498
499 (defcustom wl-expire-localdir-get-folder-function
500   'wl-expire-localdir-get-folder
501   "*A function to get localdir folder name."
502   :type 'function
503   :group 'wl-expire)
504
505 (defun wl-expire-localdir-get-folder (src-folder fmt dst-folder-arg)
506   "Get localdir folder name from src-folder."
507   (let* ((src-folder-name (substring
508                            (elmo-folder-name-internal src-folder)
509                            (length (elmo-folder-prefix-internal src-folder))))
510          (dst-folder-spec (char-to-string
511                            (car (rassq 'localdir elmo-folder-type-alist))))
512          dst-folder-base dst-folder-fmt)
513     (cond (dst-folder-arg
514            (setq dst-folder-base (concat dst-folder-spec dst-folder-arg)))
515           ((eq (elmo-folder-type-internal src-folder) 'localdir)
516            (setq dst-folder-base (concat dst-folder-spec src-folder-name)))
517           (t
518            (setq dst-folder-base
519                  (elmo-concat-path
520                   (format "%s%s"
521                           dst-folder-spec
522                           (elmo-folder-type-internal src-folder))
523                   src-folder-name))))
524     (setq dst-folder-fmt
525           (format fmt dst-folder-base))
526     (cons dst-folder-base dst-folder-fmt)))
527
528 (defun wl-expire-localdir-date (folder delete-list
529                                        &optional preserve-number dst-folder-arg
530                                        no-delete)
531   "Function for `wl-summary-expire'.
532 Refile to localdir folder by message date.
533 ex. +ml/wl/1999_11/, +ml/wl/1999_12/."
534   (let* ((dst-folder-expand (and dst-folder-arg
535                                  (wl-expand-newtext
536                                   dst-folder-arg
537                                   (elmo-folder-name-internal folder))))
538          (dst-folder-fmt (funcall
539                           wl-expire-localdir-get-folder-function
540                           folder
541                           wl-expire-localdir-date-folder-name-fmt
542                           dst-folder-expand))
543          (dst-folder-base (car dst-folder-fmt))
544          (dst-folder-fmt (cdr dst-folder-fmt))
545          (refile-func (if no-delete
546                           'wl-expire-refile
547                         'wl-expire-refile-with-copy-reserve-msg))
548          tmp dels dst-folder date time
549          msg arcmsg-alist arcmsg-list
550          deleted-list ret-val)
551     (while (setq msg (wl-pop delete-list))
552       (setq date (elmo-message-field folder msg 'date))
553       (setq time
554             (condition-case nil
555                 (timezone-fix-time date nil nil)
556               (error [0 0 0 0 0 0 0])))
557       (if (= (aref time 1) 0)   ;; if (month == 0)
558           (aset time 0 0))      ;;    year = 0
559       (setq dst-folder (format dst-folder-fmt
560                                (aref time 0);; year
561                                (aref time 1);; month
562                                ))
563       (setq arcmsg-alist
564             (wl-append-assoc-list
565              dst-folder
566              msg
567              arcmsg-alist)))
568     (while arcmsg-alist
569       (setq dst-folder (caar arcmsg-alist))
570       (setq arcmsg-list (cdar arcmsg-alist))
571       (and (setq ret-val
572                  (funcall
573                   refile-func
574                   folder arcmsg-list dst-folder t preserve-number
575                   no-delete))
576            (wl-append deleted-list (car ret-val)))
577       (setq arcmsg-alist (cdr arcmsg-alist)))
578     deleted-list))
579
580 (defun wl-expire-hide (folder hide-list &optional no-reserve-marks)
581   "Hide message for expire."
582   (unless no-reserve-marks
583     (setq hide-list
584           (wl-expire-delete-reserved-messages hide-list folder)))
585   (let ((mess (format "Hiding %s msgs..." (length hide-list))))
586     (message "%s" mess)
587     (elmo-folder-detach-messages folder hide-list)
588     (elmo-folder-kill-messages folder hide-list)
589     (elmo-folder-commit folder)
590     (message "%sdone" mess)
591     (cons hide-list (length hide-list))))
592
593 (defsubst wl-expire-folder-p (entity)
594   "Return non-nil, when ENTITY matched `wl-expire-alist'."
595   (wl-get-assoc-list-value wl-expire-alist entity))
596
597 (defsubst wl-archive-folder-p (entity)
598   "Return non-nil, when ENTITY matched `wl-archive-alist'."
599   (wl-get-assoc-list-value wl-archive-alist entity))
600
601 (defun wl-summary-expire (&optional folder notsummary nolist)
602   ""
603   (interactive)
604   (let ((folder (or folder wl-summary-buffer-elmo-folder))
605         (deleting-info "Expiring...")
606         expires)
607     (when (and (or (setq expires (wl-expire-folder-p
608                                   (elmo-folder-name-internal folder)))
609                    (progn (and (interactive-p)
610                                (message "no match %s in wl-expire-alist"
611                                         (elmo-folder-name-internal folder)))
612                           nil))
613                (or (not (interactive-p))
614                    (y-or-n-p (format "Expire %s? " (elmo-folder-name-internal
615                                                     folder)))))
616       (let* (expval rm-type val-type value more args
617                     delete-list)
618         (save-excursion
619           (setq expval (car expires)
620                 rm-type (nth 1 expires)
621                 args (cddr expires))
622           (setq val-type (car expval)
623                 value (nth 1 expval)
624                 more (nth 2 expval))
625           (run-hooks 'wl-summary-expire-pre-hook)
626           (cond
627            ((eq val-type nil))
628            ((eq val-type 'number)
629             (let* ((msgs (if (not nolist)
630                              (elmo-folder-list-messages folder)
631                            (elmo-folder-list-messages folder 'visible
632                                                       'in-msgdb)))
633                    (msglen (length msgs))
634                    (more (or more (1+ value)))
635                    count)
636               (when (>= msglen more)
637                 (setq count (- msglen value))
638                 (while (and msgs (> count 0))
639                   (when (elmo-message-entity folder (car msgs))
640                     ;; don't expire new message
641                     (wl-append delete-list (list (car msgs)))
642                     (when (or (not wl-expire-number-with-reserve-marks)
643                               (wl-expire-message-p folder (car msgs)))
644                       (setq count (1- count))))
645                   (setq msgs (cdr msgs))))))
646            ((eq val-type 'date)
647             (let* ((key-date (elmo-date-get-offset-datevec
648                               (timezone-fix-time (current-time-string)
649                                                  (current-time-zone) nil)
650                               value t)))
651               (elmo-folder-do-each-message-entity (entity folder)
652                 (when (wl-expire-date-p
653                        key-date
654                        (elmo-message-entity-field entity 'date))
655                   (wl-append delete-list
656                              (list (elmo-message-entity-number entity)))))))
657            (t
658             (error "%s: not supported" val-type)))
659           (when delete-list
660             (or wl-expired-alist
661                 (setq wl-expired-alist (wl-expired-alist-load)))
662             ;; evaluate string-match for wl-expand-newtext
663             (wl-expire-folder-p
664              (elmo-folder-name-internal folder))
665             (setq delete-list
666                   (cond ((eq rm-type nil) nil)
667                         ((eq rm-type 'remove)
668                          (setq deleting-info "Deleting...")
669                          (car (wl-expire-delete folder delete-list)))
670                         ((eq rm-type 'trash)
671                          (setq deleting-info "Deleting...")
672                          (car (wl-expire-refile folder
673                                                 delete-list
674                                                 wl-trash-folder)))
675                         ((eq rm-type 'hide)
676                          (setq deleting-info "Hiding...")
677                          (car (wl-expire-hide folder delete-list)))
678                         ((stringp rm-type)
679                          (setq deleting-info "Refiling...")
680                          (car (wl-expire-refile folder delete-list
681                                                 (wl-expand-newtext
682                                                  rm-type
683                                                  (elmo-folder-name-internal
684                                                   folder)))))
685                         ((fboundp rm-type)
686                          (apply rm-type (append (list folder delete-list)
687                                                 args)))
688                         (t
689                          (error "%s: invalid type" rm-type))))
690             (when (and (not wl-expire-test) (not notsummary) delete-list)
691               (wl-summary-delete-messages-on-buffer delete-list deleting-info)
692               (wl-summary-folder-info-update)
693               (wl-summary-set-message-modified)
694               (sit-for 0)
695               (set-buffer-modified-p nil))
696             (wl-expired-alist-save))
697           (run-hooks 'wl-summary-expire-hook)
698           (if delete-list
699               (message "Expiring %s is done" (elmo-folder-name-internal
700                                               folder))
701             (and (interactive-p)
702                  (message "No expire"))))
703         delete-list))))
704
705 (defun wl-folder-expire-entity (entity)
706   (cond
707    ((consp entity)
708     (let ((flist (nth 2 entity)))
709       (while flist
710         (wl-folder-expire-entity (car flist))
711         (setq flist (cdr flist)))))
712    ((stringp entity)
713     (when (wl-expire-folder-p entity)
714       (let* ((folder (wl-folder-get-elmo-folder entity))
715              (update-msgdb (cond
716                            ((consp wl-expire-folder-update-msgdb)
717                             (wl-string-match-member
718                              entity
719                              wl-expire-folder-update-msgdb))
720                            (t
721                             wl-expire-folder-update-msgdb)))
722             (wl-summary-highlight (if (or (wl-summary-sticky-p folder)
723                                           (wl-summary-always-sticky-folder-p
724                                            folder))
725                                       wl-summary-highlight))
726             wl-auto-select-first ret-val)
727         (save-window-excursion
728           (save-excursion
729             (and update-msgdb
730                  (wl-summary-goto-folder-subr entity 'force-update nil))
731             (setq ret-val (wl-summary-expire folder (not update-msgdb)))
732             (if update-msgdb
733                 (progn
734                   (wl-summary-save-view)
735                   (elmo-folder-commit wl-summary-buffer-elmo-folder))
736               (if ret-val
737                   (wl-folder-check-entity entity))))))))))
738
739 ;; Command
740
741 (defun wl-folder-expire-current-entity ()
742   (interactive)
743   (let ((entity-name (wl-folder-get-entity-from-buffer))
744         (type (if (wl-folder-buffer-group-p)
745                   'group
746                 'folder)))
747     (when (and entity-name
748                (or (not (interactive-p))
749                    (y-or-n-p (format "Expire %s? " entity-name))))
750       (wl-folder-expire-entity
751        (wl-folder-search-entity-by-name entity-name
752                                         wl-folder-entity
753                                         type))
754       (if (get-buffer wl-summary-buffer-name)
755           (kill-buffer wl-summary-buffer-name))
756       (message "Expiring %s is done" entity-name))))
757
758 ;;; Archive
759
760 (defun wl-folder-archive-current-entity ()
761   (interactive)
762   (let ((entity-name (wl-folder-get-entity-from-buffer))
763         (type (if (wl-folder-buffer-group-p)
764                   'group
765                 'folder)))
766     (when (and entity-name
767                (or (not (interactive-p))
768                    (y-or-n-p (format "Archive %s? " entity-name))))
769       (wl-folder-archive-entity
770        (wl-folder-search-entity-by-name entity-name
771                                         wl-folder-entity
772                                         type))
773       (message "Archiving %s is done" entity-name))))
774
775 (defun wl-archive-number1 (folder archive-list &optional dst-folder-arg)
776   (wl-expire-archive-number1 folder archive-list t dst-folder-arg t))
777
778 (defun wl-archive-number2 (folder archive-list &optional dst-folder-arg)
779   (wl-expire-archive-number2 folder archive-list t dst-folder-arg t))
780
781 (defun wl-archive-date (folder archive-list &optional dst-folder-arg)
782   (wl-expire-archive-date folder archive-list t dst-folder-arg t))
783
784 (defun wl-archive-folder (folder archive-list dst-folder)
785   (let* ((elmo-archive-treat-file t)    ;; treat archive folder as a file.
786          copied-list ret-val)
787     (setq archive-list
788           (car (wl-expire-archive-number-delete-old
789                 nil t archive-list
790                 folder
791                 t ;; no-confirm
792                 nil dst-folder)))
793     (when archive-list
794       (and (setq ret-val
795                  (wl-expire-refile
796                   folder archive-list dst-folder t t t)) ;; copy!!
797            (wl-append copied-list ret-val)))
798     copied-list))
799
800 (defun wl-summary-archive (&optional arg folder notsummary nolist)
801   ""
802   (interactive "P")
803   (let* ((folder (or folder wl-summary-buffer-elmo-folder))
804          (msgs (if (not nolist)
805                    (elmo-folder-list-messages folder)
806                  (elmo-folder-list-messages folder 'visible 'in-msgdb)))
807          (alist wl-archive-alist)
808          archives func args dst-folder archive-list)
809     (if arg
810         (let ((wl-default-spec (char-to-string
811                                 (car (rassq 'archive
812                                             elmo-folder-type-alist)))))
813           (setq dst-folder (wl-summary-read-folder
814                             (concat wl-default-spec
815                                     (substring
816                                      (elmo-folder-name-internal folder) 1))
817                             "for archive"))))
818     (run-hooks 'wl-summary-archive-pre-hook)
819     (if dst-folder
820         (wl-archive-folder folder msgs dst-folder)
821       (when (and (or (setq archives (wl-archive-folder-p
822                                      (elmo-folder-name-internal folder)))
823                      (progn (and (interactive-p)
824                                  (message "No match %s in wl-archive-alist"
825                                           (elmo-folder-name-internal folder)))
826                             nil))
827                  (or (not (interactive-p))
828                      (y-or-n-p (format "Archive %s? "
829                                        (elmo-folder-name-internal folder)))))
830         (setq func (car archives)
831               args (cdr archives))
832         (setq archive-list
833               (apply func (append (list folder msgs) args)))
834         (run-hooks 'wl-summary-archive-hook)
835         (if archive-list
836             (message "Archiving %s is done" (elmo-folder-name-internal folder))
837           (and (interactive-p)
838                (message "No archive")))))))
839
840 (defun wl-folder-archive-entity (entity)
841   (cond
842    ((consp entity)
843     (let ((flist (nth 2 entity)))
844       (while flist
845         (wl-folder-archive-entity (car flist))
846         (setq flist (cdr flist)))))
847    ((stringp entity)
848     (wl-summary-archive nil (wl-folder-get-elmo-folder entity) t))))
849
850 ;; append log
851
852 (defun wl-expire-append-log (src-folder msgs dst-folder action)
853   (when wl-expire-use-log
854     (save-excursion
855       (let ((tmp-buf (get-buffer-create " *wl-expire work*"))
856             (filename (expand-file-name wl-expired-log-alist-file-name
857                                         elmo-msgdb-directory)))
858         (set-buffer tmp-buf)
859         (erase-buffer)
860         (if dst-folder
861             (insert (format "%s\t%s -> %s\t%s\n"
862                             action
863                             src-folder dst-folder msgs))
864           (insert (format "%s\t%s\t%s\n"
865                           action
866                           src-folder msgs)))
867         (if (file-writable-p filename)
868             (write-region (point-min) (point-max)
869                           filename t 'no-msg)
870           (message "%s is not writable." filename))
871         (kill-buffer tmp-buf)))))
872
873 (require 'product)
874 (product-provide (provide 'wl-expire) (require 'wl-version))
875
876 ;;; wl-expire.el ends here