1 ;;; elmo-flag.el --- global flag handling.
3 ;; Copyright (C) 2003 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
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)
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.
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.
29 (require 'elmo-localdir)
30 (eval-when-compile (require 'cl))
33 (defcustom elmo-global-flags '(important)
34 "A list of flag symbol which is managed globally by the flag folder."
35 :type '(repeat symbol)
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)
43 (defvar elmo-global-flag-folder-alist nil
44 "Internal variable to hold global-flag-folder structures.")
47 (defconst elmo-flag-char-regexp "]!#$&'+,./0-9:;<=>?@A-Z[^_`a-z|}~-"))
49 (defun elmo-flag-valid-p (flag)
50 (unless (stringp flag)
51 (setq flag (symbol-name flag)))
52 (string-match (eval-when-compile
53 (concat "^[" elmo-flag-char-regexp "]+$"))
57 (luna-define-class elmo-flag-folder (elmo-localdir-folder)
58 (flag minfo minfo-hash max-number))
59 (luna-define-internal-accessors 'elmo-flag-folder))
61 (luna-define-method elmo-folder-initialize ((folder
64 (unless (string-match (eval-when-compile
65 (concat "^flag\\(/\\(["
69 (error "Error in folder name `%s'" (elmo-folder-name-internal folder)))
70 (if (match-beginning 1)
71 (setq name (match-string 2 name))
72 (setq name (symbol-name (car elmo-global-flags)))
73 (elmo-folder-set-name-internal
75 (concat (elmo-folder-name-internal folder) "/" name)))
76 (or (cdr (assq (intern name) elmo-global-flag-folder-alist))
77 (let ((flag (intern name))
79 (elmo-flag-folder-set-flag-internal folder flag)
80 (unless (elmo-global-flag-p flag)
81 (setq elmo-global-flags
82 (nconc elmo-global-flags (list flag))))
83 ;; must be AFTER set flag slot.
84 (setq msgdb-path (elmo-folder-msgdb-path folder))
85 (unless (file-directory-p msgdb-path)
86 (elmo-make-directory msgdb-path))
87 (elmo-localdir-folder-set-dir-name-internal
90 (elmo-localdir-folder-set-directory-internal
93 (if (file-exists-p (expand-file-name "max" msgdb-path))
94 (elmo-flag-folder-set-max-number-internal
96 (elmo-object-load (expand-file-name "max" msgdb-path))))
97 (if (file-exists-p (expand-file-name ".minfo" msgdb-path))
98 (elmo-flag-folder-set-minfo-internal
100 (elmo-object-load (expand-file-name ".minfo" msgdb-path))))
101 (elmo-flag-folder-set-minfo-hash-internal
103 (elmo-make-hash (length (elmo-flag-folder-minfo-internal folder))))
104 (dolist (elem (elmo-flag-folder-minfo-internal folder))
105 (elmo-set-hash-val (nth 1 elem) elem
106 (elmo-flag-folder-minfo-hash-internal folder))
107 (elmo-set-hash-val (concat "#" (number-to-string (nth 2 elem)))
109 (elmo-flag-folder-minfo-hash-internal folder))
110 (dolist (pair (car elem))
111 (elmo-set-hash-val (concat (number-to-string (cdr pair))
114 (elmo-flag-folder-minfo-hash-internal folder))))
115 (setq elmo-global-flag-folder-alist
116 (cons (cons flag folder) elmo-global-flag-folder-alist))
119 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-flag-folder))
120 (expand-file-name (concat "flag/"
122 (elmo-flag-folder-flag-internal folder)))
123 elmo-msgdb-directory))
125 (luna-define-method elmo-folder-commit :after ((folder
128 (expand-file-name ".minfo" (elmo-folder-msgdb-path folder))
129 (elmo-flag-folder-minfo-internal folder))
130 (if (elmo-flag-folder-max-number-internal folder)
132 (expand-file-name "max" (elmo-folder-msgdb-path folder))
133 (elmo-flag-folder-max-number-internal folder))))
135 (luna-define-method elmo-folder-list-subfolders ((folder elmo-flag-folder)
137 (let ((dir (expand-file-name "flag" elmo-msgdb-directory)))
138 (mapcar (lambda (flag)
140 (elmo-folder-prefix-internal folder)
141 (symbol-name (elmo-folder-type-internal folder))
146 (mapcar 'intern (delete ".." (delete "." (directory-files dir))))
147 elmo-global-flags)))))
149 (defun elmo-flag-folder-delete-message (folder number
150 &optional keep-referrer)
151 (let* ((elem (elmo-get-hash-val (concat "#" (number-to-string number))
152 (elmo-flag-folder-minfo-hash-internal
155 (dolist (pair (car elem))
156 (when (and (car pair) (cdr pair))
157 (elmo-clear-hash-val (concat (number-to-string (cdr pair)) ":"
159 (elmo-flag-folder-minfo-hash-internal
161 (unless keep-referrer
162 (setq target-folder (elmo-make-folder (car pair)))
163 (elmo-folder-open target-folder 'load-msgdb)
164 ;; Unset the flag of the original folder.
165 ;; (XXX Should the message-id checked?)
166 (elmo-message-unset-flag target-folder (cdr pair)
167 (elmo-flag-folder-flag-internal folder))
168 (elmo-folder-close target-folder))))
169 (elmo-clear-hash-val (concat "#" (number-to-string number))
170 (elmo-flag-folder-minfo-hash-internal
172 (elmo-clear-hash-val (nth 1 elem) (elmo-flag-folder-minfo-hash-internal
174 (elmo-flag-folder-set-minfo-internal
176 (delq elem (elmo-flag-folder-minfo-internal folder))))
179 (luna-define-method elmo-folder-delete-messages-internal ((folder
182 (dolist (number numbers)
183 (elmo-flag-folder-delete-message folder number)
184 (elmo-localdir-delete-message folder number))
185 (elmo-folder-commit folder)
188 ;; Same as localdir except that the flag is always the flag.
189 (luna-define-method elmo-folder-msgdb-create ((folder elmo-flag-folder)
193 (let ((dir (elmo-localdir-folder-directory-internal folder))
194 (new-msgdb (elmo-make-msgdb))
196 (len (length numbers)))
197 (message "Creating msgdb...")
199 (when (setq entity (elmo-localdir-msgdb-create-entity
200 new-msgdb dir (car numbers)))
201 (elmo-msgdb-append-entity new-msgdb entity
202 (list (elmo-flag-folder-flag-internal
204 (when (> len elmo-display-progress-threshold)
206 (elmo-display-progress
207 'elmo-flag-folder-msgdb-create "Creating msgdb..."
209 (setq numbers (cdr numbers)))
210 (message "Creating msgdb...done")
213 (luna-define-method elmo-folder-append-messages ((folder elmo-flag-folder)
216 &optional same-number)
217 (dolist (number numbers)
218 (elmo-global-flag-set (elmo-flag-folder-flag-internal folder)
219 src-folder number (elmo-message-field
223 (elmo-folder-set-flag src-folder
225 (elmo-flag-folder-flag-internal folder))
228 (luna-define-method elmo-folder-append-buffer ((folder elmo-flag-folder)
229 &optional flag number)
230 (error "Cannot append to the flag folder"))
232 (luna-define-method elmo-folder-unset-flag :before ((folder elmo-flag-folder)
236 (when (eq flag (elmo-flag-folder-flag-internal folder))
237 (error "Cannot unset flag `%s' in this folder." flag)))
241 (defmacro elmo-flag-get-folder (flag)
242 "Get the flag folder structure for FLAG."
243 `(when (memq ,flag elmo-global-flags)
244 (elmo-make-folder (concat "'flag/" (symbol-name ,flag)))))
246 (defun elmo-flag-folder-referrer (folder number)
247 "Return a list of referrer message information.
248 Each element is a cons cell like following:
250 FNAME is the name of the folder which the message is contained.
251 NUMBER is the number of the message."
252 (when (eq (elmo-folder-type-internal folder) 'flag)
253 (car (elmo-get-hash-val (concat "#" (number-to-string number))
254 (elmo-flag-folder-minfo-hash-internal
258 (defun elmo-global-flag-p (flag)
259 "Return non-nil when FLAG is global."
260 (memq flag elmo-global-flags))
262 (defun elmo-global-flags (fname number)
263 "Return a list of global flags for the message.
264 FNAME is the name string of the folder.
265 NUMBER is the number of the message."
266 (let ((flag-list elmo-global-flags)
269 (setq folder (elmo-flag-get-folder (car flag-list)))
270 (when (elmo-get-hash-val
271 (concat (number-to-string number) ":" fname)
272 (elmo-flag-folder-minfo-hash-internal folder))
273 (setq matches (cons (elmo-flag-folder-flag-internal folder)
275 (setq flag-list (cdr flag-list)))
278 (defun elmo-folder-list-global-flag-messages (folder flag)
279 "List messages which have global flag.
280 FOLDER is the elmo folder structure.
281 FLAG is the symbol of the flag."
282 (when (elmo-global-flag-p flag)
283 (let ((flag-folder (elmo-flag-get-folder flag))
285 (dolist (elem (elmo-flag-folder-minfo-internal flag-folder))
286 (if (setq number (elmo-message-number folder (nth 1 elem)))
287 (setq result (cons number result))))
291 ;; minfo is a list of following cell.
292 ;; ((((FNAME . NUMBER)...(FNAME . NUMBER)) MESSAGE-ID NUMBER-IN-FLAG-FOLDER)
293 ;; minfo-index is the hash table of above with following indice;
294 (defun elmo-global-flags-set (flags folder number message-id)
295 "Set global flags to the message.
296 FLAGS is a list of symbol of the flag.
297 FOLDER is the elmo folder structure.
298 NUMBER is the message number."
300 (elmo-global-flag-set flag folder number message-id)))
302 (defun elmo-local-flag-p (flag)
303 "Return non-nil when flag is not appropriate for global flag."
304 (memq flag elmo-local-flags))
306 (defsubst elmo-global-flag-set-internal (flag folder number message-id)
307 (when (elmo-local-flag-p flag)
308 (error "Cannot treat `%s' as global flag." flag))
310 (let ((flag-folder (elmo-flag-get-folder flag))
311 cache new-file new-number elem)
312 (if (setq elem (elmo-get-hash-val
314 (elmo-flag-folder-minfo-hash-internal
316 ;; Same ID already exists.
317 (when (and folder number
318 (not (member (cons (elmo-folder-name-internal folder)
319 number) (car elem))))
321 (cons (cons (elmo-folder-name-internal folder)
323 (setq new-number (nth 2 elem))
324 (elmo-set-hash-val (concat (number-to-string number)
325 ":" (elmo-folder-name-internal
328 (elmo-flag-folder-minfo-hash-internal
330 ;; Append new element.
331 (elmo-flag-folder-set-max-number-internal
333 (+ (or (elmo-flag-folder-max-number-internal flag-folder)
334 ;; This is the first time.
335 (car (elmo-folder-status flag-folder)))
341 (elmo-flag-folder-max-number-internal flag-folder)))
342 (elmo-localdir-folder-directory-internal flag-folder)))
344 (setq cache (and message-id (elmo-file-cache-get message-id)))
345 (if (and cache (eq (elmo-file-cache-status cache) 'entire))
346 (elmo-copy-file (elmo-file-cache-path cache)
348 (when (and folder number)
349 (elmo-message-fetch folder number
350 (elmo-make-fetch-strategy 'entire))
351 (write-region-as-binary (point-min) (point-max) new-file nil
353 (elmo-flag-folder-set-minfo-internal
357 (when (and folder number)
358 (list (cons (elmo-folder-name-internal folder)
362 (elmo-flag-folder-minfo-internal flag-folder)))
363 (when (and folder number)
364 (elmo-set-hash-val (concat (number-to-string number)
365 ":" (elmo-folder-name-internal
368 (elmo-flag-folder-minfo-hash-internal
370 (elmo-set-hash-val message-id elem
371 (elmo-flag-folder-minfo-hash-internal
373 (elmo-set-hash-val (concat "#" (number-to-string new-number)) elem
374 (elmo-flag-folder-minfo-hash-internal
376 (elmo-folder-commit flag-folder)
379 (defun elmo-global-flag-set (flag folder number message-id)
380 "Set global flag to the message.
381 FLAG is a symbol of the flag.
382 FOLDER is the elmo folder structure.
383 NUMBER is the message number.
384 MESSAGE-ID is the message-id of the message."
385 (when (elmo-global-flag-p flag)
386 (elmo-global-flag-set-internal flag folder number message-id)))
388 (defun elmo-global-flag-detach (flag folder number &optional delete-if-none)
389 "Detach the message from the global flag.
390 FOLDER is the folder structure.
391 NUMBERS is the message number.
392 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
393 the message is not flagged in any folder.
394 If DELETE-IF-NONE is a symbol `always',
395 delete message without flagged in other folder."
396 (unless (and (eq (elmo-folder-type-internal folder) 'flag)
397 (eq (elmo-flag-folder-flag-internal folder) flag))
398 (let ((flag-folder (elmo-flag-get-folder flag))
401 (setq key (concat (number-to-string number) ":"
402 (elmo-folder-name-internal folder))
403 elem (elmo-get-hash-val
405 (elmo-flag-folder-minfo-hash-internal flag-folder)))
407 (setcar elem (delete (cons (elmo-folder-name-internal folder)
409 (elmo-clear-hash-val key (elmo-flag-folder-minfo-hash-internal
411 ;; Does not have any referrer, remove.
412 (when (and delete-if-none
413 (or (eq delete-if-none 'always)
415 (elmo-flag-folder-delete-message flag-folder (nth 2 elem)
417 (elmo-localdir-delete-message flag-folder (nth 2 elem))
418 (elmo-folder-commit flag-folder)))))))
420 (defun elmo-global-flag-detach-messages (folder numbers &optional
422 "Detach all messages specified from all global flags.
423 FOLDER is the folder structure.
424 NUMBERS is the message number list.
425 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
426 the message is not flagged in any folder."
427 (unless (eq (elmo-folder-type-internal folder) 'flag)
428 (dolist (flag elmo-global-flags)
429 (dolist (number numbers)
430 (elmo-global-flag-detach flag folder number delete-if-none)))))
432 (defun elmo-get-global-flags (&optional flags ignore-preserved)
434 Return value is a subset of optional argument FLAGS.
435 If FLAGS is `t', all global flags becomes candidates.
436 If optional IGNORE-PRESERVED is non-nil, preserved flags
437 \(answered, cached, new, unread\) are not included."
438 (let ((result (copy-sequence (if (eq flags t)
439 (setq flags elmo-global-flags)
442 (unless (elmo-global-flag-p (car flags))
443 (setq result (delq (car flags) result)))
444 (setq flags (cdr flags)))
445 (when ignore-preserved
446 (dolist (flag elmo-preserved-flags)
447 (setq result (delq flag result))))
450 ;;; To migrate from global mark folder
451 (defvar elmo-global-mark-filename "global-mark"
452 "Obsolete variable. (Just for migration)")
454 (defun elmo-global-mark-migrate ()
455 "Migrate from 'mark to 'flag. For automatic migration."
456 (when (and (file-exists-p (expand-file-name elmo-global-mark-filename
457 elmo-msgdb-directory))
458 (elmo-global-flag-p 'important)
459 (not (file-exists-p (elmo-folder-expand-msgdb-path
460 (elmo-flag-get-folder 'important)))))
461 (elmo-global-mark-upgrade)))
463 (defun elmo-global-mark-upgrade ()
464 "Upgrade old `global-mark' structure."
466 (when (file-exists-p (expand-file-name
467 elmo-global-mark-filename elmo-msgdb-directory))
468 (message "Upgrading flag structure...")
469 (when (elmo-global-flag-p 'important)
473 elmo-global-mark-filename elmo-msgdb-directory)))
474 (folder (elmo-flag-get-folder 'important))
476 (dolist (elem global-marks)
477 (setq file-cache (elmo-file-cache-get (car elem)))
478 (when (eq (elmo-file-cache-status file-cache) 'entire)
479 (elmo-global-flag-set 'important nil nil (car elem))))))
480 (message "Upgrading flag structure...done")))
482 (luna-define-method elmo-folder-delete :around ((folder elmo-flag-folder))
483 (let ((flag (elmo-flag-folder-flag-internal folder)))
484 (when (luna-call-next-method)
485 (setq elmo-global-flags (delq flag elmo-global-flags))
489 (product-provide (provide 'elmo-flag) (require 'elmo-version))
491 ;;; elmo-flag.el ends here