abd65d39943fc657bcdaf7997c0bbbb8932e3f36
[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-flags '(important)
34   "A list of flag symbol which is managed globally by the flag folder."
35   :type '(repeat symbol)
36   :group 'elmo)
37
38 (defcustom elmo-local-flags '(unread any digest)
39   "A list of flag symbol which is not treated as global flag."
40   :type '(repeat symbol)
41   :group 'elmo)
42
43 (defvar elmo-global-flag-folder-alist nil
44   "Internal variable to hold global-flag-folder structures.")
45
46 (eval-and-compile
47   (defconst elmo-flag-char-regexp "]!#$&'+,./0-9:;<=>?@A-Z[^_`a-z|}~-"))
48
49 (defun elmo-flag-valid-p (flag)
50   (string-match (eval-when-compile
51                   (concat "^[" elmo-flag-char-regexp "]+$"))
52                 (if (stringp flag) flag (symbol-name flag))))
53
54 (eval-and-compile
55   (luna-define-class elmo-flag-folder (elmo-localdir-folder)
56                      (flag minfo minfo-hash max-number))
57   (luna-define-internal-accessors 'elmo-flag-folder))
58
59 (luna-define-method elmo-folder-initialize ((folder
60                                              elmo-flag-folder)
61                                             name)
62   (unless (string-match (eval-when-compile
63                           (concat "^flag\\(/\\(["
64                                   elmo-flag-char-regexp
65                                   "]+\\)\\)?$"))
66                         name)
67     (error "Error in folder name `%s'" (elmo-folder-name-internal folder)))
68   (if (match-beginning 1)
69       (setq name (match-string 2 name))
70     (setq name (symbol-name (car elmo-global-flags)))
71     (elmo-folder-set-name-internal
72      folder
73      (concat (elmo-folder-name-internal folder) "/" name)))
74   (or (cdr (assq (intern name) elmo-global-flag-folder-alist))
75       (let ((flag (intern name))
76             msgdb-path)
77         (elmo-flag-folder-set-flag-internal folder flag)
78         (unless (elmo-global-flag-p flag)
79           (setq elmo-global-flags
80                 (nconc elmo-global-flags (list flag))))
81         ;; must be AFTER set flag slot.
82         (setq msgdb-path (elmo-folder-msgdb-path folder))
83         (unless (file-directory-p msgdb-path)
84           (elmo-make-directory msgdb-path))
85         (elmo-localdir-folder-set-dir-name-internal
86          folder
87          msgdb-path)
88         (elmo-localdir-folder-set-directory-internal
89          folder
90          msgdb-path)
91         (if (file-exists-p (expand-file-name "max" msgdb-path))
92             (elmo-flag-folder-set-max-number-internal
93              folder
94              (elmo-object-load (expand-file-name "max" msgdb-path))))
95         (if (file-exists-p (expand-file-name ".minfo" msgdb-path))
96             (elmo-flag-folder-set-minfo-internal
97              folder
98              (elmo-object-load (expand-file-name ".minfo" msgdb-path))))
99         (elmo-flag-folder-set-minfo-hash-internal
100          folder
101          (elmo-make-hash (length (elmo-flag-folder-minfo-internal folder))))
102         (dolist (elem (elmo-flag-folder-minfo-internal folder))
103           (elmo-set-hash-val (nth 1 elem) elem
104                              (elmo-flag-folder-minfo-hash-internal folder))
105           (elmo-set-hash-val (concat "#" (number-to-string (nth 2 elem)))
106                              elem
107                              (elmo-flag-folder-minfo-hash-internal folder))
108           (dolist (pair (car elem))
109             (elmo-set-hash-val (concat (number-to-string (cdr pair))
110                                        ":" (car pair))
111                                elem
112                                (elmo-flag-folder-minfo-hash-internal folder))))
113         (setq elmo-global-flag-folder-alist
114               (cons (cons flag folder) elmo-global-flag-folder-alist))
115         folder)))
116
117 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-flag-folder))
118   (expand-file-name (concat "flag/"
119                             (symbol-name
120                              (elmo-flag-folder-flag-internal folder)))
121                     elmo-msgdb-directory))
122
123 (luna-define-method elmo-folder-commit :after ((folder
124                                                 elmo-flag-folder))
125   (elmo-object-save
126    (expand-file-name ".minfo" (elmo-folder-msgdb-path folder))
127    (elmo-flag-folder-minfo-internal folder))
128   (if (elmo-flag-folder-max-number-internal folder)
129       (elmo-object-save
130        (expand-file-name "max" (elmo-folder-msgdb-path folder))
131        (elmo-flag-folder-max-number-internal folder))))
132
133 (luna-define-method elmo-folder-list-subfolders ((folder elmo-flag-folder)
134                                                  &optional one-level)
135   (let ((dir (expand-file-name "flag" elmo-msgdb-directory)))
136     (mapcar (lambda (flag)
137               (concat
138                (elmo-folder-prefix-internal folder)
139                (symbol-name (elmo-folder-type-internal folder))
140                "/"
141                (symbol-name flag)))
142             (elmo-uniq-list
143              (append
144               (mapcar 'intern (delete ".." (delete "." (directory-files dir))))
145               (copy-sequence elmo-global-flags))))))
146
147 (defun elmo-flag-folder-delete-message (folder number
148                                                &optional keep-referrer)
149   (let* ((elem (elmo-get-hash-val (concat "#" (number-to-string number))
150                                   (elmo-flag-folder-minfo-hash-internal
151                                    folder)))
152          target-folder key)
153     (dolist (pair (car elem))
154       (when (and (car pair) (cdr pair))
155         (elmo-clear-hash-val (concat (number-to-string (cdr pair)) ":"
156                                      (car pair))
157                              (elmo-flag-folder-minfo-hash-internal
158                               folder))
159         (unless keep-referrer
160           (setq target-folder (elmo-make-folder (car pair)))
161           (elmo-folder-open target-folder 'load-msgdb)
162           ;; Unset the flag of the original folder.
163           ;; (XXX Should the message-id checked?)
164           (elmo-message-unset-flag target-folder (cdr pair)
165                                    (elmo-flag-folder-flag-internal folder))
166           (elmo-folder-close target-folder))))
167     (elmo-clear-hash-val (concat "#" (number-to-string number))
168                          (elmo-flag-folder-minfo-hash-internal
169                           folder))
170     (elmo-clear-hash-val (nth 1 elem) (elmo-flag-folder-minfo-hash-internal
171                                        folder))
172     (elmo-flag-folder-set-minfo-internal
173      folder
174      (delq elem (elmo-flag-folder-minfo-internal folder))))
175   t)
176
177 (luna-define-method elmo-folder-delete-messages-internal ((folder
178                                                            elmo-flag-folder)
179                                                           numbers)
180   (dolist (number numbers)
181     (elmo-flag-folder-delete-message folder number)
182     (elmo-localdir-delete-message folder number))
183   (elmo-folder-commit folder)
184   t)
185
186 ;; Same as localdir except that the flag is always the flag.
187 (luna-define-method elmo-folder-msgdb-create ((folder elmo-flag-folder)
188                                               numbers
189                                               flag-table)
190   (when numbers
191     (let ((dir (elmo-localdir-folder-directory-internal folder))
192           (new-msgdb (elmo-make-msgdb))
193           entity (i 0)
194           (len (length numbers)))
195       (message "Creating msgdb...")
196       (while numbers
197         (when (setq entity (elmo-localdir-msgdb-create-entity
198                             new-msgdb dir (car numbers)))
199           (elmo-msgdb-append-entity new-msgdb entity
200                                     (list (elmo-flag-folder-flag-internal
201                                            folder))))
202         (when (> len elmo-display-progress-threshold)
203           (setq i (1+ i))
204           (elmo-display-progress
205            'elmo-flag-folder-msgdb-create "Creating msgdb..."
206            (/ (* i 100) len)))
207         (setq numbers (cdr numbers)))
208       (message "Creating msgdb...done")
209       new-msgdb)))
210
211 (luna-define-method elmo-folder-append-messages ((folder elmo-flag-folder)
212                                                  src-folder
213                                                  numbers
214                                                  &optional same-number)
215   (dolist (number numbers)
216     (elmo-global-flag-set (elmo-flag-folder-flag-internal folder)
217                           src-folder number (elmo-message-field
218                                              src-folder
219                                              number
220                                              'message-id)))
221   (elmo-folder-set-flag src-folder
222                         numbers
223                         (elmo-flag-folder-flag-internal folder))
224   numbers)
225
226 (luna-define-method elmo-folder-append-buffer ((folder elmo-flag-folder)
227                                                &optional flag number)
228   (error "Cannot append to the flag folder"))
229
230 (luna-define-method elmo-folder-unset-flag :before ((folder elmo-flag-folder)
231                                                     numbers
232                                                     flag
233                                                     &optional is-local)
234   (when (eq flag (elmo-flag-folder-flag-internal folder))
235     (error "Cannot unset flag `%s' in this folder" flag)))
236
237 ;;; Utilities
238
239 (defmacro elmo-flag-get-folder (flag)
240   "Get the flag folder structure for FLAG."
241   `(when (memq ,flag elmo-global-flags)
242      (elmo-make-folder (concat  "'flag/" (symbol-name ,flag)))))
243
244 (defun elmo-flag-folder-referrer (folder number)
245   "Return a list of referrer message information.
246 Each element is a cons cell like following:
247 \(FNAME . NUMBER\)
248 FNAME is the name of the folder which the message is contained.
249 NUMBER is the number of the message."
250   (when (eq (elmo-folder-type-internal folder) 'flag)
251     (car (elmo-get-hash-val (concat "#" (number-to-string number))
252                             (elmo-flag-folder-minfo-hash-internal
253                              folder)))))
254
255 ;;; Global-Flag API
256 (defun elmo-global-flag-p (flag)
257   "Return non-nil when FLAG is global."
258   (memq flag elmo-global-flags))
259
260 (defun elmo-global-flags (fname number)
261   "Return a list of global flags for the message.
262 FNAME is the name string of the folder.
263 NUMBER is the number of the message."
264   (let ((flag-list elmo-global-flags)
265         folder matches)
266     (while flag-list
267       (setq folder (elmo-flag-get-folder (car flag-list)))
268       (when (elmo-get-hash-val
269              (concat (number-to-string number) ":" fname)
270              (elmo-flag-folder-minfo-hash-internal folder))
271         (setq matches (cons (elmo-flag-folder-flag-internal folder)
272                             matches)))
273       (setq flag-list (cdr flag-list)))
274     matches))
275
276 (defun elmo-folder-list-global-flag-messages (folder flag)
277   "List messages which have global flag.
278 FOLDER is the elmo folder structure.
279 FLAG is the symbol of the flag."
280   (when (elmo-global-flag-p flag)
281     (let ((flag-folder (elmo-flag-get-folder flag))
282           result number)
283       (dolist (elem (elmo-flag-folder-minfo-internal flag-folder))
284         (if (setq number (elmo-message-number folder (nth 1 elem)))
285             (setq result (cons number result))))
286       result)))
287
288 ;;;
289 ;; minfo is a list of following cell.
290 ;; ((((FNAME . NUMBER)...(FNAME . NUMBER)) MESSAGE-ID NUMBER-IN-FLAG-FOLDER)
291 ;; minfo-index is the hash table of above with following indice;
292 (defun elmo-global-flags-set (flags folder number message-id)
293   "Set global flags to the message.
294 FLAGS is a list of symbol of the flag.
295 FOLDER is the elmo folder structure.
296 NUMBER is the message number."
297   (dolist (flag flags)
298     (elmo-global-flag-set flag folder number message-id)))
299
300 (defun elmo-local-flag-p (flag)
301   "Return non-nil when flag is not appropriate for global flag."
302   (memq flag elmo-local-flags))
303
304 (defsubst elmo-global-flag-set-internal (flag folder number message-id)
305   (when (elmo-local-flag-p flag)
306     (error "Cannot treat `%s' as global flag" flag))
307   (when message-id
308     (let ((flag-folder (elmo-flag-get-folder flag))
309           cache new-file new-number elem)
310       (if (setq elem (elmo-get-hash-val
311                       message-id
312                       (elmo-flag-folder-minfo-hash-internal
313                        flag-folder)))
314           ;; Same ID already exists.
315           (when (and folder number
316                      (not (member (cons (elmo-folder-name-internal folder)
317                                         number) (car elem))))
318             (setcar elem
319                     (cons (cons (elmo-folder-name-internal folder)
320                                 number) (car elem)))
321             (setq new-number (nth 2 elem))
322             (elmo-set-hash-val (concat (number-to-string number)
323                                        ":" (elmo-folder-name-internal
324                                             folder))
325                                elem
326                                (elmo-flag-folder-minfo-hash-internal
327                                 flag-folder)))
328         ;; Append new element.
329         (elmo-flag-folder-set-max-number-internal
330          flag-folder
331          (+ (or (elmo-flag-folder-max-number-internal flag-folder)
332                 ;; This is the first time.
333                 (car (elmo-folder-status flag-folder)))
334             1))
335         (setq new-file
336               (expand-file-name
337                (int-to-string
338                 (setq new-number
339                       (elmo-flag-folder-max-number-internal flag-folder)))
340                (elmo-localdir-folder-directory-internal flag-folder)))
341         (with-temp-buffer
342           (setq cache (and message-id (elmo-file-cache-get message-id)))
343           (if (and cache (eq (elmo-file-cache-status cache) 'entire))
344               (elmo-copy-file (elmo-file-cache-path cache)
345                               new-file)
346             (when (and folder number)
347               (elmo-message-fetch folder number
348                                   (elmo-make-fetch-strategy 'entire))
349               (write-region-as-binary (point-min) (point-max) new-file nil
350                                       'no-msg))))
351         (elmo-flag-folder-set-minfo-internal
352          flag-folder
353          (cons
354           (setq elem (list
355                       (when (and folder number)
356                         (list (cons (elmo-folder-name-internal folder)
357                                     number)))
358                       message-id
359                       new-number))
360           (elmo-flag-folder-minfo-internal flag-folder)))
361         (when (and folder number)
362           (elmo-set-hash-val (concat (number-to-string number)
363                                      ":" (elmo-folder-name-internal
364                                           folder))
365                              elem
366                              (elmo-flag-folder-minfo-hash-internal
367                               flag-folder)))
368         (elmo-set-hash-val message-id elem
369                            (elmo-flag-folder-minfo-hash-internal
370                             flag-folder))
371         (elmo-set-hash-val (concat "#" (number-to-string new-number)) elem
372                            (elmo-flag-folder-minfo-hash-internal
373                             flag-folder)))
374       (elmo-folder-commit flag-folder)
375       new-number)))
376
377 (defun elmo-global-flag-set (flag folder number message-id)
378   "Set global flag to the message.
379 FLAG is a symbol of the flag.
380 FOLDER is the elmo folder structure.
381 NUMBER is the message number.
382 MESSAGE-ID is the message-id of the message."
383   (when (elmo-global-flag-p flag)
384     (elmo-global-flag-set-internal flag folder number message-id)))
385
386 (defun elmo-global-flag-detach (flag folder number &optional delete-if-none)
387   "Detach the message from the global flag.
388 FOLDER is the folder structure.
389 NUMBERS is the message number.
390 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
391 the message is not flagged in any folder.
392 If DELETE-IF-NONE is a symbol `always',
393 delete message without flagged in other folder."
394   (unless (and (eq (elmo-folder-type-internal folder) 'flag)
395                (eq (elmo-flag-folder-flag-internal folder) flag))
396     (let ((flag-folder (elmo-flag-get-folder flag))
397           elem key)
398       (when flag-folder
399         (setq key (concat (number-to-string number) ":"
400                           (elmo-folder-name-internal folder))
401               elem (elmo-get-hash-val
402                     key
403                     (elmo-flag-folder-minfo-hash-internal flag-folder)))
404         (when elem
405           (setcar elem (delete (cons (elmo-folder-name-internal folder)
406                                      number) (car elem)))
407           (elmo-clear-hash-val key (elmo-flag-folder-minfo-hash-internal
408                                     flag-folder))
409           ;; Does not have any referrer, remove.
410           (when (and delete-if-none
411                      (or (eq delete-if-none 'always)
412                          (null (car elem))))
413             (elmo-flag-folder-delete-message flag-folder (nth 2 elem)
414                                              (null (car elem)))
415             (elmo-localdir-delete-message flag-folder (nth 2 elem))
416             (elmo-folder-commit flag-folder)))))))
417
418 (defun elmo-global-flag-detach-messages (folder numbers &optional
419                                                 delete-if-none)
420   "Detach all messages specified from all global flags.
421 FOLDER is the folder structure.
422 NUMBERS is the message number list.
423 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
424 the message is not flagged in any folder."
425   (unless (eq (elmo-folder-type-internal folder) 'flag)
426     (dolist (flag elmo-global-flags)
427       (dolist (number numbers)
428         (elmo-global-flag-detach flag folder number delete-if-none)))))
429
430 (defun elmo-get-global-flags (&optional flags ignore-preserved)
431   "Get global flags.
432 Return value is a subset of optional argument FLAGS.
433 If FLAGS is `t', all global flags becomes candidates.
434 If optional IGNORE-PRESERVED is non-nil, preserved flags
435 \(answered, cached, new, unread\) are not included."
436   (let ((result (copy-sequence (if (eq flags t)
437                                    (setq flags elmo-global-flags)
438                                  flags))))
439     (while flags
440       (unless (elmo-global-flag-p (car flags))
441         (setq result (delq (car flags) result)))
442       (setq flags (cdr flags)))
443     (when ignore-preserved
444       (dolist (flag elmo-preserved-flags)
445         (setq result (delq flag result))))
446     result))
447
448 ;;; To migrate from global mark folder
449 (defvar elmo-global-mark-filename "global-mark"
450   "Obsolete variable. (Just for migration)")
451
452 (defun elmo-global-mark-migrate ()
453   "Migrate from 'mark to 'flag. For automatic migration."
454   (when (and (file-exists-p (expand-file-name elmo-global-mark-filename
455                                               elmo-msgdb-directory))
456              (elmo-global-flag-p 'important)
457              (not (file-exists-p (elmo-folder-msgdb-path
458                                   (elmo-flag-get-folder 'important)))))
459     (elmo-global-mark-upgrade)))
460
461 (defun elmo-global-mark-upgrade ()
462   "Upgrade old `global-mark' structure."
463   (interactive)
464   (when (file-exists-p (expand-file-name
465                         elmo-global-mark-filename elmo-msgdb-directory))
466     (message "Upgrading flag structure...")
467     (when (elmo-global-flag-p 'important)
468       (let ((global-marks
469              (elmo-object-load
470               (expand-file-name
471                elmo-global-mark-filename elmo-msgdb-directory)))
472             (folder (elmo-flag-get-folder 'important))
473             file-cache)
474         (dolist (elem global-marks)
475           (setq file-cache (elmo-file-cache-get (car elem)))
476           (when (eq (elmo-file-cache-status file-cache) 'entire)
477             (elmo-global-flag-set 'important nil nil (car elem))))))
478     (message "Upgrading flag structure...done")))
479
480 (luna-define-method elmo-folder-delete :around ((folder elmo-flag-folder))
481   (let ((flag (elmo-flag-folder-flag-internal folder)))
482     (when (luna-call-next-method)
483       (setq elmo-global-flags (delq flag elmo-global-flags))
484       (setq elmo-global-flag-folder-alist
485             (delq (assq flag elmo-global-flag-folder-alist)
486                   elmo-global-flag-folder-alist))
487       t)))
488
489 (require 'product)
490 (product-provide (provide 'elmo-flag) (require 'elmo-version))
491
492 ;;; elmo-flag.el ends here