* wl.el (wl-init): Setup faces accoding to wl-summary-flag-alist.
[elisp/wanderlust.git] / elmo / elmo-flag.el
1 ;;; elmo-flag.el --- global flag handling.
2
3 ;; Copyright (C) 2003 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14 ;;
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24 ;;
25
26 ;;; Commentary:
27 ;;
28 (require 'elmo-util)
29 (require 'elmo-localdir)
30 (eval-when-compile (require 'cl))
31
32 ;;; Code:
33 (defcustom elmo-global-flag-list '(important)
34   "A list of flag symbol which is managed globally by the flag folder."
35   :type '(repeat symbol)
36   :group 'elmo)
37
38 (defvar elmo-global-flag-folder-alist nil
39   "Internal variable to hold global-flag-folder structures.")
40
41 (eval-and-compile
42   (luna-define-class elmo-flag-folder (elmo-localdir-folder)
43                      (flag minfo minfo-hash))
44   (luna-define-internal-accessors 'elmo-flag-folder))
45
46 (luna-define-method elmo-folder-initialize ((folder
47                                              elmo-flag-folder)
48                                             name)
49   (if (string-match "flag/\\([a-z]+\\)" name)
50       (setq name (match-string 1 name))
51     (setq name (symbol-name (car elmo-global-flag-list))))
52   (or (cdr (assq (intern name) elmo-global-flag-folder-alist))
53       (let ((flag (intern name))
54             msgdb-path)
55         (elmo-flag-folder-set-flag-internal folder flag)
56         (unless (elmo-global-flag-p flag)
57           (setq elmo-global-flag-list
58                 (nconc elmo-global-flag-list (list flag))))
59         ;; must be AFTER set flag slot.
60         (setq msgdb-path (elmo-folder-msgdb-path folder))
61         (unless (file-directory-p msgdb-path)
62           (elmo-make-directory msgdb-path))
63         (elmo-localdir-folder-set-dir-name-internal
64          folder
65          msgdb-path)
66         (elmo-localdir-folder-set-directory-internal
67          folder
68          msgdb-path)
69         (if (file-exists-p (expand-file-name ".minfo" msgdb-path))
70             (elmo-flag-folder-set-minfo-internal
71              folder
72              (elmo-object-load (expand-file-name ".minfo" msgdb-path))))
73         (elmo-flag-folder-set-minfo-hash-internal
74          folder
75          (elmo-make-hash (length (elmo-flag-folder-minfo-internal folder))))
76         (dolist (elem (elmo-flag-folder-minfo-internal folder))
77           (elmo-set-hash-val (nth 1 elem) elem
78                              (elmo-flag-folder-minfo-hash-internal folder))
79           (elmo-set-hash-val (concat "#" (number-to-string (nth 2 elem)))
80                              elem
81                              (elmo-flag-folder-minfo-hash-internal folder))
82           (dolist (pair (car elem))
83             (elmo-set-hash-val (concat (number-to-string (cdr pair))
84                                        ":" (car pair))
85                                elem
86                                (elmo-flag-folder-minfo-hash-internal folder))))
87         (setq elmo-global-flag-folder-alist
88               (cons (cons (intern name) folder) elmo-global-flag-folder-alist))
89         folder)))
90
91 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-flag-folder))
92   (expand-file-name (concat "flag/"
93                             (symbol-name
94                              (elmo-flag-folder-flag-internal folder)))
95                     elmo-msgdb-directory))
96
97 (luna-define-method elmo-folder-commit :after ((folder
98                                                 elmo-flag-folder))
99   (elmo-object-save
100    (expand-file-name ".minfo" (elmo-folder-msgdb-path folder))
101    (elmo-flag-folder-minfo-internal folder)))
102
103 (luna-define-method elmo-folder-list-subfolders ((folder elmo-flag-folder)
104                                                  &optional one-level)
105   (mapcar (lambda (flag)
106             (concat
107              (elmo-folder-prefix-internal folder)
108              (symbol-name (elmo-folder-type-internal folder))
109              "/"
110              (symbol-name flag)))
111           elmo-global-flag-list))
112
113 (defun elmo-flag-folder-delete-message (folder number
114                                                &optional keep-referrer)
115   (let* ((elem (elmo-get-hash-val (concat "#" (number-to-string number))
116                                   (elmo-flag-folder-minfo-hash-internal
117                                    folder)))
118          target-folder key)
119     (dolist (pair (car elem))
120       (when (and (car pair) (cdr pair))
121         (elmo-clear-hash-val (concat (number-to-string (cdr pair)) ":"
122                                      (car pair))
123                              (elmo-flag-folder-minfo-hash-internal
124                               folder))
125         (unless keep-referrer
126           (setq target-folder (elmo-make-folder (car pair)))
127           (elmo-folder-open target-folder 'load-msgdb)
128           ;; Unset the flag of the original folder.
129           ;; (XXX Should the message-id checked?)
130           (elmo-message-unset-flag target-folder (cdr pair)
131                                    (elmo-flag-folder-flag-internal folder))
132           (elmo-folder-close target-folder))))
133     (elmo-clear-hash-val (concat "#" (number-to-string number))
134                          (elmo-flag-folder-minfo-hash-internal
135                           folder))
136     (elmo-clear-hash-val (nth 1 elem) (elmo-flag-folder-minfo-hash-internal
137                                        folder))
138     (elmo-flag-folder-set-minfo-internal
139      folder
140      (delq elem (elmo-flag-folder-minfo-internal folder))))
141   t)
142
143 (luna-define-method elmo-folder-delete-messages ((folder
144                                                   elmo-flag-folder)
145                                                  numbers)
146   (dolist (number numbers)
147     (elmo-flag-folder-delete-message folder number)
148     (elmo-localdir-delete-message folder number))
149   (elmo-folder-commit folder)
150   t)
151
152 ;; Same as localdir except that the flag is always the flag.
153 (luna-define-method elmo-folder-msgdb-create ((folder elmo-flag-folder)
154                                               numbers
155                                               flag-table)
156   (when numbers
157     (let ((dir (elmo-localdir-folder-directory-internal folder))
158           (new-msgdb (elmo-make-msgdb))
159           entity (i 0)
160           (len (length numbers)))
161       (message "Creating msgdb...")
162       (while numbers
163         (when (setq entity (elmo-localdir-msgdb-create-entity
164                             new-msgdb dir (car numbers)))
165           (elmo-msgdb-append-entity new-msgdb entity
166                                     (list (elmo-flag-folder-flag-internal
167                                            folder))))
168         (when (> len elmo-display-progress-threshold)
169           (setq i (1+ i))
170           (elmo-display-progress
171            'elmo-flag-folder-msgdb-create "Creating msgdb..."
172            (/ (* i 100) len)))
173         (setq numbers (cdr numbers)))
174       (message "Creating msgdb...done")
175       new-msgdb)))
176
177 (luna-define-method elmo-folder-append-messages ((folder elmo-flag-folder)
178                                                  src-folder
179                                                  numbers
180                                                  &optional same-number)
181   (dolist (number numbers)
182     (elmo-global-flag-set (elmo-flag-folder-flag-internal folder)
183                           src-folder number (elmo-message-field
184                                              src-folder
185                                              number
186                                              'message-id)))
187   (elmo-folder-set-flag src-folder
188                         numbers
189                         (elmo-flag-folder-flag-internal folder))
190   numbers)
191
192 (luna-define-method elmo-folder-append-buffer ((folder elmo-flag-folder)
193                                                unread
194                                                &optional number)
195   (error "Cannot append to the flag folder"))
196
197 ;;; Utilities
198
199 (defmacro elmo-flag-get-folder (flag)
200   "Get the flag folder structure for FLAG."
201   `(when (memq ,flag elmo-global-flag-list)
202      (elmo-make-folder (concat  "'flag/" (symbol-name ,flag)))))
203
204 (defun elmo-flag-folder-referrer (folder number)
205   "Return a list of referrer message information.
206 Each element is a cons cell like following:
207 \(FNAME . NUMBER\)
208 FNAME is the name of the folder which the message is contained.
209 NUMBER is the number of the message."
210   (when (eq (elmo-folder-type-internal folder) 'flag)
211     (car (elmo-get-hash-val (concat "#" (number-to-string number))
212                             (elmo-flag-folder-minfo-hash-internal
213                              folder)))))
214
215 ;;; Global-Flag API
216 (defun elmo-global-flag-p (flag)
217   "Return non-nil when FLAG is global."
218   (memq flag elmo-global-flag-list))
219
220 (defun elmo-global-flags (fname number)
221   "Return a list of global flags for the message.
222 FNAME is the name string of the folder.
223 NUMBER is the number of the message."
224   (let ((flag-list elmo-global-flag-list)
225         folder matches)
226     (while flag-list
227       (setq folder (elmo-flag-get-folder (car flag-list)))
228       (when (elmo-get-hash-val
229              (concat (number-to-string number) ":" fname)
230              (elmo-flag-folder-minfo-hash-internal folder))
231         (setq matches (cons (elmo-flag-folder-flag-internal folder)
232                             matches)))
233       (setq flag-list (cdr flag-list)))
234     matches))
235
236 (defun elmo-folder-list-global-flag-messages (folder flag)
237   "List messages which have global flag.
238 FOLDER is the elmo folder structure.
239 FLAG is the symbol of the flag."
240   (when (elmo-global-flag-p flag)
241     (let ((flag-folder (elmo-flag-get-folder flag))
242           result entity)
243       (dolist (elem (elmo-flag-folder-minfo-internal flag-folder))
244         (if (setq entity (elmo-message-entity folder (nth 1 elem)))
245             (setq result (cons (elmo-message-entity-number entity)
246                                result))))
247       result)))
248
249 ;;;
250 ;; minfo is a list of following cell.
251 ;; ((((FNAME . NUMBER)...(FNAME . NUMBER)) MESSAGE-ID NUMBER-IN-FLAG-FOLDER)
252 ;; minfo-index is the hash table of above with following indice;
253 (defun elmo-global-flags-set (flags folder number message-id)
254   "Set global flags to the message.
255 FLAGS is a list of symbol of the flag.
256 FOLDER is the elmo folder structure.
257 NUMBER is the message number."
258   (dolist (flag flags)
259     (elmo-global-flag-set flag folder number message-id)))
260
261 (defsubst elmo-global-flag-set-internal (flag folder number message-id)
262   (when message-id
263     (let ((flag-folder (elmo-flag-get-folder flag))
264           cache new-file new-number elem)
265       (if (setq elem (elmo-get-hash-val
266                       message-id
267                       (elmo-flag-folder-minfo-hash-internal
268                        flag-folder)))
269           ;; Same ID already exists.
270           (when (and folder number
271                      (not (member (cons (elmo-folder-name-internal folder)
272                                         number) (car elem))))
273             (setcar elem
274                     (cons (cons (elmo-folder-name-internal folder)
275                                 number) (car elem)))
276             (setq new-number (nth 2 elem))
277             (elmo-set-hash-val (concat (number-to-string number)
278                                        ":" (elmo-folder-name-internal
279                                             folder))
280                                elem
281                                (elmo-flag-folder-minfo-hash-internal
282                                 flag-folder)))
283         ;; Append new element.
284         (setq new-file
285               (expand-file-name
286                (int-to-string
287                 (setq new-number (1+ (car (elmo-folder-status flag-folder)))))
288                (elmo-localdir-folder-directory-internal flag-folder)))
289         (with-temp-buffer
290           (setq cache (and message-id (elmo-file-cache-get message-id)))
291           (if (and cache (eq (elmo-file-cache-status cache) 'entire))
292               (elmo-copy-file (elmo-file-cache-path cache)
293                               new-file)
294             (when (and folder number)
295               (elmo-message-fetch folder number (elmo-make-fetch-strategy
296                                                  'entire)
297                                   nil (current-buffer))
298               (write-region-as-binary (point-min) (point-max) new-file nil
299                                       'no-msg))))
300         (elmo-flag-folder-set-minfo-internal
301          flag-folder
302          (cons
303           (setq elem (list
304                       (when (and folder number)
305                         (list (cons (elmo-folder-name-internal folder)
306                                     number)))
307                       message-id
308                       new-number))
309           (elmo-flag-folder-minfo-internal flag-folder)))
310         (when (and folder number)
311           (elmo-set-hash-val (concat (number-to-string number)
312                                      ":" (elmo-folder-name-internal
313                                           folder))
314                              elem
315                              (elmo-flag-folder-minfo-hash-internal
316                               flag-folder)))
317         (elmo-set-hash-val message-id elem
318                            (elmo-flag-folder-minfo-hash-internal
319                             flag-folder))
320         (elmo-set-hash-val (concat "#" (number-to-string new-number)) elem
321                            (elmo-flag-folder-minfo-hash-internal
322                             flag-folder)))
323       (elmo-folder-commit flag-folder)
324       new-number)))
325
326 (defun elmo-global-flag-set (flag folder number message-id)
327   "Set global flag to the message.
328 FLAG is a symbol of the flag.
329 FOLDER is the elmo folder structure.
330 NUMBER is the message number.
331 MESSAGE-ID is the message-id of the message."
332   (when (elmo-global-flag-p flag)
333     (elmo-global-flag-set-internal flag folder number message-id)))
334
335 (defun elmo-global-flag-detach (flag folder number &optional delete-if-none)
336   "Detach the message from the global flag.
337 FOLDER is the folder structure.
338 NUMBERS is the message number.
339 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
340 the message is not flagged in any folder.
341 If DELETE-IF-NONE is a symbol `always',
342 delete message without flagged in other folder."
343   (unless (eq (elmo-folder-type-internal folder) 'flag)
344     (let ((flag-folder (elmo-flag-get-folder flag))
345           elem key)
346       (when flag-folder
347         (setq key (concat (number-to-string number) ":"
348                           (elmo-folder-name-internal folder))
349               elem (elmo-get-hash-val
350                     key
351                     (elmo-flag-folder-minfo-hash-internal flag-folder)))
352         (when elem
353           (setcar elem (delete (cons (elmo-folder-name-internal folder)
354                                      number) (car elem)))
355           (elmo-clear-hash-val key (elmo-flag-folder-minfo-hash-internal
356                                     flag-folder))
357           ;; Does not have any referrer, remove.
358           (when (and delete-if-none
359                      (or (eq delete-if-none 'always)
360                          (null (car elem))))
361             (elmo-flag-folder-delete-message flag-folder (nth 2 elem)
362                                              (null (car elem)))
363             (elmo-localdir-delete-message flag-folder (nth 2 elem))
364             (elmo-folder-commit flag-folder)))))))
365
366 (defun elmo-global-flag-detach-messages (folder numbers &optional
367                                                 delete-if-none)
368   "Detach all messages specified from all global flags.
369 FOLDER is the folder structure.
370 NUMBERS is the message number list.
371 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
372 the message is not flagged in any folder."
373   (unless (eq (elmo-folder-type-internal folder) 'flag)
374     (dolist (flag elmo-global-flag-list)
375       (dolist (number numbers)
376         (elmo-global-flag-detach flag folder number delete-if-none)))))
377
378 (defun elmo-get-global-flags (&optional flags ignore-preserved)
379   "Get global flags.
380 Return value is a subset of optional argument FLAGS.
381 If FLAGS is `t', all global flags becomes candidates.
382 If optional IGNORE-PRESERVED is non-nil, preserved flags
383 \(answered, cached, new, unread\) are not included."
384   (let ((result (copy-sequence (if (eq flags t)
385                                    (setq flags elmo-global-flag-list)
386                                  flags))))
387     (while flags
388       (unless (elmo-global-flag-p (car flags))
389         (setq result (delq (car flags) result)))
390       (setq flags (cdr flags)))
391     (when ignore-preserved
392       (dolist (flag '(answered cached new unread))
393         (setq result (delq flag result))))
394     result))
395
396 ;;; To migrate from global mark folder
397 (defvar elmo-global-mark-filename "global-mark"
398   "Obsolete variable. (Just for migration)")
399
400 (defun elmo-global-mark-migrate ()
401   "Migrate from 'mark to 'flag. For automatic migration."
402   (when (and (file-exists-p (expand-file-name elmo-global-mark-filename
403                                               elmo-msgdb-directory))
404              (elmo-global-flag-p 'important)
405              (not (file-exists-p (elmo-folder-expand-msgdb-path
406                                   (elmo-flag-get-folder 'important)))))
407     (elmo-global-mark-upgrade)))
408
409 (defun elmo-global-mark-upgrade ()
410   "Upgrade old `global-mark' structure."
411   (interactive)
412   (when (file-exists-p (expand-file-name
413                         elmo-global-mark-filename elmo-msgdb-directory))
414     (message "Upgrading flag structure...")
415     (when (elmo-global-flag-p 'important)
416       (let ((global-marks
417              (elmo-object-load
418               (expand-file-name
419                elmo-global-mark-filename elmo-msgdb-directory)))
420             (folder (elmo-flag-get-folder 'important))
421             file-cache)
422         (dolist (elem global-marks)
423           (setq file-cache (elmo-file-cache-get (car elem)))
424           (when (eq (elmo-file-cache-status file-cache) 'entire)
425             (elmo-global-flag-set 'important nil nil (car elem))))))
426     (message "Upgrading flag structure...done")))
427
428 (require 'product)
429 (product-provide (provide 'elmo-flag) (require 'elmo-version))
430
431 ;;; elmo-flag.el ends here