* elmo-internal.el (elmo-folder-list-subfolders): Rewrite; Fixed
[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 (msgdb-path)
54         (elmo-flag-folder-set-flag-internal folder (intern name))
55         ;; must be AFTER set flag slot.
56         (setq msgdb-path (elmo-folder-msgdb-path folder))
57         (unless (file-directory-p msgdb-path)
58           (elmo-make-directory msgdb-path))
59         (elmo-localdir-folder-set-dir-name-internal
60          folder
61          msgdb-path)
62         (elmo-localdir-folder-set-directory-internal
63          folder
64          msgdb-path)
65         (if (file-exists-p (expand-file-name ".minfo" msgdb-path))
66             (elmo-flag-folder-set-minfo-internal
67              folder
68              (elmo-object-load (expand-file-name ".minfo" msgdb-path))))
69         (elmo-flag-folder-set-minfo-hash-internal
70          folder
71          (elmo-make-hash (length (elmo-flag-folder-minfo-internal folder))))
72         (dolist (elem (elmo-flag-folder-minfo-internal folder))
73           (elmo-set-hash-val (nth 1 elem) elem
74                              (elmo-flag-folder-minfo-hash-internal folder))
75           (elmo-set-hash-val (concat "#" (number-to-string (nth 2 elem)))
76                              elem
77                              (elmo-flag-folder-minfo-hash-internal folder))
78           (dolist (pair (car elem))
79             (elmo-set-hash-val (concat (number-to-string (cdr pair))
80                                        ":" (car pair))
81                                elem
82                                (elmo-flag-folder-minfo-hash-internal folder))))
83         (setq elmo-global-flag-folder-alist
84               (cons (cons (intern name) folder) elmo-global-flag-folder-alist))
85         folder)))
86
87 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-flag-folder))
88   (expand-file-name (concat "flag/"
89                             (symbol-name
90                              (elmo-flag-folder-flag-internal folder)))
91                     elmo-msgdb-directory))
92
93 (luna-define-method elmo-folder-commit :after ((folder
94                                                 elmo-flag-folder))
95   (elmo-object-save
96    (expand-file-name ".minfo" (elmo-folder-msgdb-path folder))
97    (elmo-flag-folder-minfo-internal folder)))
98
99 (luna-define-method elmo-folder-list-subfolders ((folder elmo-flag-folder)
100                                                  &optional one-level)
101   (mapcar (lambda (flag)
102             (concat
103              (elmo-folder-prefix-internal folder)
104              (symbol-name (elmo-folder-type-internal folder))
105              "/"
106              (symbol-name flag)))
107           elmo-global-flag-list))
108
109 (defun elmo-flag-folder-delete-message (folder number
110                                                &optional keep-referrer)
111   (let* ((elem (elmo-get-hash-val (concat "#" (number-to-string number))
112                                   (elmo-flag-folder-minfo-hash-internal
113                                    folder)))
114          target-folder key)
115     (dolist (pair (car elem))
116       (when (and (car pair) (cdr pair))
117         (elmo-clear-hash-val (concat (number-to-string (cdr pair)) ":"
118                                      (car pair))
119                              (elmo-flag-folder-minfo-hash-internal
120                               folder))
121         (unless keep-referrer
122           (setq target-folder (elmo-make-folder (car pair)))
123           (elmo-folder-open target-folder 'load-msgdb)
124           ;; Unset the flag of the original folder.
125           ;; (XXX Should the message-id checked?)
126           (elmo-message-unset-flag target-folder (cdr pair)
127                                    (elmo-flag-folder-flag-internal folder))
128           (elmo-folder-close target-folder))))
129     (elmo-clear-hash-val (concat "#" (number-to-string number))
130                          (elmo-flag-folder-minfo-hash-internal
131                           folder))
132     (elmo-clear-hash-val (nth 1 elem) (elmo-flag-folder-minfo-hash-internal
133                                        folder))
134     (elmo-flag-folder-set-minfo-internal
135      folder
136      (delq elem (elmo-flag-folder-minfo-internal folder))))
137   t)
138
139 (luna-define-method elmo-folder-delete-messages ((folder
140                                                   elmo-flag-folder)
141                                                  numbers)
142   (dolist (number numbers)
143     (elmo-flag-folder-delete-message folder number)
144     (elmo-localdir-delete-message folder number))
145   (elmo-folder-commit folder)
146   t)
147
148 ;; Same as localdir except that the flag is always the flag.
149 (luna-define-method elmo-folder-msgdb-create ((folder elmo-flag-folder)
150                                               numbers
151                                               flag-table)
152   (when numbers
153     (let ((dir (elmo-localdir-folder-directory-internal folder))
154           (new-msgdb (elmo-make-msgdb))
155           entity (i 0)
156           (len (length numbers)))
157       (message "Creating msgdb...")
158       (while numbers
159         (when (setq entity (elmo-localdir-msgdb-create-entity
160                             new-msgdb dir (car numbers)))
161           (elmo-msgdb-append-entity new-msgdb entity
162                                     (list (elmo-flag-folder-flag-internal
163                                            folder))))
164         (when (> len elmo-display-progress-threshold)
165           (setq i (1+ i))
166           (elmo-display-progress
167            'elmo-flag-folder-msgdb-create "Creating msgdb..."
168            (/ (* i 100) len)))
169         (setq numbers (cdr numbers)))
170       (message "Creating msgdb...done")
171       new-msgdb)))
172
173 (luna-define-method elmo-folder-append-messages ((folder elmo-flag-folder)
174                                                  src-folder
175                                                  numbers
176                                                  &optional same-number)
177   (dolist (number numbers)
178     (elmo-global-flag-set (elmo-flag-folder-flag-internal folder)
179                           src-folder number (elmo-message-field
180                                              src-folder
181                                              number
182                                              'message-id)))
183   numbers)
184
185 (luna-define-method elmo-folder-append-buffer ((folder elmo-flag-folder)
186                                                unread
187                                                &optional number)
188   (error "Cannot append to the flag folder"))
189
190 ;;; Utilities
191
192 (defmacro elmo-flag-get-folder (flag)
193   "Get the flag folder structure for FLAG."
194   `(when (memq ,flag elmo-global-flag-list)
195      (elmo-make-folder (concat  "'flag/" (symbol-name ,flag)))))
196
197 (defun elmo-flag-folder-referrer (folder number)
198   "Return a list of referrer message information.
199 Each element is a cons cell like following:
200 \(FNAME . NUMBER\)
201 FNAME is the name of the folder which the message is contained.
202 NUMBER is the number of the message."
203   (when (eq (elmo-folder-type-internal folder) 'flag)
204     (car (elmo-get-hash-val (concat "#" (number-to-string number))
205                             (elmo-flag-folder-minfo-hash-internal
206                              folder)))))
207
208 ;;; Global-Flag API
209 (defun elmo-global-flag-p (flag)
210   "Return non-nil when FLAG is global."
211   (memq flag elmo-global-flag-list))
212
213 (defun elmo-global-flags (fname number)
214   "Return a list of global flags for the message.
215 FNAME is the name string of the folder.
216 NUMBER is the number of the message."
217   (let ((flag-list elmo-global-flag-list)
218         folder matches)
219     (while flag-list
220       (setq folder (elmo-flag-get-folder (car flag-list)))
221       (when (elmo-get-hash-val
222              (concat (number-to-string number) ":" fname)
223              (elmo-flag-folder-minfo-hash-internal folder))
224         (setq matches (cons (elmo-flag-folder-flag-internal folder)
225                             matches)))
226       (setq flag-list (cdr flag-list)))
227     matches))
228
229 (defun elmo-folder-list-global-flag-messages (folder flag)
230   "List messages which have global flag.
231 FOLDER is the elmo folder structure.
232 FLAG is the symbol of the flag."
233   (when (elmo-global-flag-p flag)
234     (let ((flag-folder (elmo-flag-get-folder flag))
235           result entity)
236       (dolist (elem (elmo-flag-folder-minfo-internal flag-folder))
237         (if (setq entity (elmo-message-entity folder (nth 1 elem)))
238             (setq result (cons (elmo-message-entity-number entity)
239                                result))))
240       result)))
241
242 ;;;
243 ;; minfo is a list of following cell.
244 ;; ((((FNAME . NUMBER)...(FNAME . NUMBER)) MESSAGE-ID NUMBER-IN-FLAG-FOLDER)
245 ;; minfo-index is the hash table of above with following indice;
246 (defun elmo-global-flags-set (flags folder number message-id)
247   "Set global flags to the message.
248 FLAGS is a list of symbol of the flag.
249 FOLDER is the elmo folder structure.
250 NUMBER is the message number."
251   (dolist (flag flags)
252     (elmo-global-flag-set flag folder number message-id)))
253
254 (defsubst elmo-global-flag-set-internal (flag folder number message-id)
255   (when message-id
256     (let ((flag-folder (elmo-flag-get-folder flag))
257           cache new-file new-number elem)
258       (if (setq elem (elmo-get-hash-val
259                       message-id
260                       (elmo-flag-folder-minfo-hash-internal
261                        flag-folder)))
262           ;; Same ID already exists.
263           (when (and folder number
264                      (not (member (cons (elmo-folder-name-internal folder)
265                                         number) (car elem))))
266             (setcar elem
267                     (cons (cons (elmo-folder-name-internal folder)
268                                 number) (car elem)))
269             (setq new-number (nth 2 elem))
270             (elmo-set-hash-val (concat (number-to-string number)
271                                        ":" (elmo-folder-name-internal
272                                             folder))
273                                elem
274                                (elmo-flag-folder-minfo-hash-internal
275                                 flag-folder)))
276         ;; Append new element.
277         (setq new-file
278               (expand-file-name
279                (int-to-string
280                 (setq new-number (1+ (car (elmo-folder-status flag-folder)))))
281                (elmo-localdir-folder-directory-internal flag-folder)))
282         (with-temp-buffer
283           (setq cache (and message-id (elmo-file-cache-get message-id)))
284           (if (and cache (eq (elmo-file-cache-status cache) 'entire))
285               (elmo-copy-file (elmo-file-cache-path cache)
286                               new-file)
287             (when (and folder number)
288               (elmo-message-fetch folder number (elmo-make-fetch-strategy
289                                                  'entire)
290                                   nil (current-buffer))
291               (write-region-as-binary (point-min) (point-max) new-file nil
292                                       'no-msg))))
293         (elmo-flag-folder-set-minfo-internal
294          flag-folder
295          (cons
296           (setq elem (list
297                       (when (and folder number)
298                         (list (cons (elmo-folder-name-internal folder)
299                                     number)))
300                       message-id
301                       new-number))
302           (elmo-flag-folder-minfo-internal flag-folder)))
303         (when (and folder number)
304           (elmo-set-hash-val (concat (number-to-string number)
305                                      ":" (elmo-folder-name-internal
306                                           folder))
307                              elem
308                              (elmo-flag-folder-minfo-hash-internal
309                               flag-folder)))
310         (elmo-set-hash-val message-id elem
311                            (elmo-flag-folder-minfo-hash-internal
312                             flag-folder))
313         (elmo-set-hash-val (concat "#" (number-to-string new-number)) elem
314                            (elmo-flag-folder-minfo-hash-internal
315                             flag-folder)))
316       (elmo-folder-commit flag-folder)
317       new-number)))
318
319 (defun elmo-global-flag-set (flag folder number message-id)
320   "Set global flag to the message.
321 FLAG is a symbol of the flag.
322 FOLDER is the elmo folder structure.
323 NUMBER is the message number.
324 MESSAGE-ID is the message-id of the message."
325   (when (elmo-global-flag-p flag)
326     (elmo-global-flag-set-internal flag folder number message-id)))
327
328 (defun elmo-global-flag-detach (flag folder number &optional delete-if-none)
329   "Detach the message from the global flag.
330 FOLDER is the folder structure.
331 NUMBERS is the message number.
332 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
333 the message is not flagged in any folder.
334 If DELETE-IF-NONE is a symbol `always',
335 delete message without flagged in other folder."
336   (unless (eq (elmo-folder-type-internal folder) 'flag)
337     (let ((flag-folder (elmo-flag-get-folder flag))
338           elem key)
339       (when flag-folder
340         (setq key (concat (number-to-string number) ":"
341                           (elmo-folder-name-internal folder))
342               elem (elmo-get-hash-val
343                     key
344                     (elmo-flag-folder-minfo-hash-internal flag-folder)))
345         (when elem
346           (setcar elem (delete (cons (elmo-folder-name-internal folder)
347                                      number) (car elem)))
348           (elmo-clear-hash-val key (elmo-flag-folder-minfo-hash-internal
349                                     flag-folder))
350           ;; Does not have any referrer, remove.
351           (when (and delete-if-none
352                      (or (eq delete-if-none 'always)
353                          (null (car elem))))
354             (elmo-flag-folder-delete-message flag-folder (nth 2 elem)
355                                              (null (car elem)))
356             (elmo-localdir-delete-message flag-folder (nth 2 elem))
357             (elmo-folder-commit flag-folder)))))))
358
359 (defun elmo-global-flag-detach-messages (folder numbers &optional
360                                                 delete-if-none)
361   "Detach all messages specified from all global flags.
362 FOLDER is the folder structure.
363 NUMBERS is the message number list.
364 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
365 the message is not flagged in any folder."
366   (unless (eq (elmo-folder-type-internal folder) 'flag)
367     (dolist (flag elmo-global-flag-list)
368       (dolist (number numbers)
369         (elmo-global-flag-detach flag folder number delete-if-none)))))
370
371 ;;; To migrate from global mark folder
372 (defvar elmo-global-mark-filename "global-mark"
373   "Obsolete variable. (Just for migration)")
374
375 (defun elmo-global-mark-migrate ()
376   "Migrate from 'mark to 'flag. For automatic migration."
377   (when (and (file-exists-p (expand-file-name elmo-global-mark-filename
378                                               elmo-msgdb-directory))
379              (elmo-global-flag-p 'important)
380              (not (file-exists-p (elmo-folder-expand-msgdb-path
381                                   (elmo-flag-get-folder 'important)))))
382     (elmo-global-mark-upgrade)))
383
384 (defun elmo-global-mark-upgrade ()
385   "Upgrade old `global-mark' structure."
386   (interactive)
387   (when (file-exists-p (expand-file-name
388                         elmo-global-mark-filename elmo-msgdb-directory))
389     (message "Upgrading flag structure...")
390     (when (elmo-global-flag-p 'important)
391       (let ((global-marks
392              (elmo-object-load
393               (expand-file-name
394                elmo-global-mark-filename elmo-msgdb-directory)))
395             (folder (elmo-flag-get-folder 'important))
396             file-cache)
397         (dolist (elem global-marks)
398           (setq file-cache (elmo-file-cache-get (car elem)))
399           (when (eq (elmo-file-cache-status file-cache) 'entire)
400             (elmo-global-flag-set 'important nil nil (car elem))))))
401     (message "Upgrading flag structure...done")))
402
403 (require 'product)
404 (product-provide (provide 'elmo-flag) (require 'elmo-version))
405
406 ;;; elmo-flag.el ends here