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-flag-list '(important)
34 "A list of flag symbol which is managed globally by the flag folder."
35 :type '(repeat symbol)
38 (defvar elmo-global-flag-folder-alist nil
39 "Internal variable to hold global-flag-folder structures.")
42 (luna-define-class elmo-flag-folder (elmo-localdir-folder)
43 (flag minfo minfo-hash))
44 (luna-define-internal-accessors 'elmo-flag-folder))
46 (luna-define-method elmo-folder-initialize ((folder
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 (elmo-folder-set-name-internal
54 (concat (elmo-folder-name-internal folder) "/" name)))
55 (or (cdr (assq (intern name) elmo-global-flag-folder-alist))
56 (let ((flag (intern name))
58 (elmo-flag-folder-set-flag-internal folder flag)
59 (unless (elmo-global-flag-p flag)
60 (setq elmo-global-flag-list
61 (nconc elmo-global-flag-list (list flag))))
62 ;; must be AFTER set flag slot.
63 (setq msgdb-path (elmo-folder-msgdb-path folder))
64 (unless (file-directory-p msgdb-path)
65 (elmo-make-directory msgdb-path))
66 (elmo-localdir-folder-set-dir-name-internal
69 (elmo-localdir-folder-set-directory-internal
72 (if (file-exists-p (expand-file-name ".minfo" msgdb-path))
73 (elmo-flag-folder-set-minfo-internal
75 (elmo-object-load (expand-file-name ".minfo" msgdb-path))))
76 (elmo-flag-folder-set-minfo-hash-internal
78 (elmo-make-hash (length (elmo-flag-folder-minfo-internal folder))))
79 (dolist (elem (elmo-flag-folder-minfo-internal folder))
80 (elmo-set-hash-val (nth 1 elem) elem
81 (elmo-flag-folder-minfo-hash-internal folder))
82 (elmo-set-hash-val (concat "#" (number-to-string (nth 2 elem)))
84 (elmo-flag-folder-minfo-hash-internal folder))
85 (dolist (pair (car elem))
86 (elmo-set-hash-val (concat (number-to-string (cdr pair))
89 (elmo-flag-folder-minfo-hash-internal folder))))
90 (setq elmo-global-flag-folder-alist
91 (cons (cons flag folder) elmo-global-flag-folder-alist))
94 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-flag-folder))
95 (expand-file-name (concat "flag/"
97 (elmo-flag-folder-flag-internal folder)))
98 elmo-msgdb-directory))
100 (luna-define-method elmo-folder-commit :after ((folder
103 (expand-file-name ".minfo" (elmo-folder-msgdb-path folder))
104 (elmo-flag-folder-minfo-internal folder)))
106 (luna-define-method elmo-folder-list-subfolders ((folder elmo-flag-folder)
108 (let ((dir (expand-file-name "flag" elmo-msgdb-directory)))
109 (mapcar (lambda (flag)
111 (elmo-folder-prefix-internal folder)
112 (symbol-name (elmo-folder-type-internal folder))
117 (mapcar 'intern (delete ".." (delete "." (directory-files dir))))
118 elmo-global-flag-list)))))
120 (defun elmo-flag-folder-delete-message (folder number
121 &optional keep-referrer)
122 (let* ((elem (elmo-get-hash-val (concat "#" (number-to-string number))
123 (elmo-flag-folder-minfo-hash-internal
126 (dolist (pair (car elem))
127 (when (and (car pair) (cdr pair))
128 (elmo-clear-hash-val (concat (number-to-string (cdr pair)) ":"
130 (elmo-flag-folder-minfo-hash-internal
132 (unless keep-referrer
133 (setq target-folder (elmo-make-folder (car pair)))
134 (elmo-folder-open target-folder 'load-msgdb)
135 ;; Unset the flag of the original folder.
136 ;; (XXX Should the message-id checked?)
137 (elmo-message-unset-flag target-folder (cdr pair)
138 (elmo-flag-folder-flag-internal folder))
139 (elmo-folder-close target-folder))))
140 (elmo-clear-hash-val (concat "#" (number-to-string number))
141 (elmo-flag-folder-minfo-hash-internal
143 (elmo-clear-hash-val (nth 1 elem) (elmo-flag-folder-minfo-hash-internal
145 (elmo-flag-folder-set-minfo-internal
147 (delq elem (elmo-flag-folder-minfo-internal folder))))
150 (luna-define-method elmo-folder-delete-messages ((folder
153 (dolist (number numbers)
154 (elmo-flag-folder-delete-message folder number)
155 (elmo-localdir-delete-message folder number))
156 (elmo-folder-commit folder)
159 ;; Same as localdir except that the flag is always the flag.
160 (luna-define-method elmo-folder-msgdb-create ((folder elmo-flag-folder)
164 (let ((dir (elmo-localdir-folder-directory-internal folder))
165 (new-msgdb (elmo-make-msgdb))
167 (len (length numbers)))
168 (message "Creating msgdb...")
170 (when (setq entity (elmo-localdir-msgdb-create-entity
171 new-msgdb dir (car numbers)))
172 (elmo-msgdb-append-entity new-msgdb entity
173 (list (elmo-flag-folder-flag-internal
175 (when (> len elmo-display-progress-threshold)
177 (elmo-display-progress
178 'elmo-flag-folder-msgdb-create "Creating msgdb..."
180 (setq numbers (cdr numbers)))
181 (message "Creating msgdb...done")
184 (luna-define-method elmo-folder-append-messages ((folder elmo-flag-folder)
187 &optional same-number)
188 (dolist (number numbers)
189 (elmo-global-flag-set (elmo-flag-folder-flag-internal folder)
190 src-folder number (elmo-message-field
194 (elmo-folder-set-flag src-folder
196 (elmo-flag-folder-flag-internal folder))
199 (luna-define-method elmo-folder-append-buffer ((folder elmo-flag-folder)
200 &optional flag number)
201 (error "Cannot append to the flag folder"))
205 (defmacro elmo-flag-get-folder (flag)
206 "Get the flag folder structure for FLAG."
207 `(when (memq ,flag elmo-global-flag-list)
208 (elmo-make-folder (concat "'flag/" (symbol-name ,flag)))))
210 (defun elmo-flag-folder-referrer (folder number)
211 "Return a list of referrer message information.
212 Each element is a cons cell like following:
214 FNAME is the name of the folder which the message is contained.
215 NUMBER is the number of the message."
216 (when (eq (elmo-folder-type-internal folder) 'flag)
217 (car (elmo-get-hash-val (concat "#" (number-to-string number))
218 (elmo-flag-folder-minfo-hash-internal
222 (defun elmo-global-flag-p (flag)
223 "Return non-nil when FLAG is global."
224 (memq flag elmo-global-flag-list))
226 (defun elmo-global-flags (fname number)
227 "Return a list of global flags for the message.
228 FNAME is the name string of the folder.
229 NUMBER is the number of the message."
230 (let ((flag-list elmo-global-flag-list)
233 (setq folder (elmo-flag-get-folder (car flag-list)))
234 (when (elmo-get-hash-val
235 (concat (number-to-string number) ":" fname)
236 (elmo-flag-folder-minfo-hash-internal folder))
237 (setq matches (cons (elmo-flag-folder-flag-internal folder)
239 (setq flag-list (cdr flag-list)))
242 (defun elmo-folder-list-global-flag-messages (folder flag)
243 "List messages which have global flag.
244 FOLDER is the elmo folder structure.
245 FLAG is the symbol of the flag."
246 (when (elmo-global-flag-p flag)
247 (let ((flag-folder (elmo-flag-get-folder flag))
249 (dolist (elem (elmo-flag-folder-minfo-internal flag-folder))
250 (if (setq entity (elmo-message-entity folder (nth 1 elem)))
251 (setq result (cons (elmo-message-entity-number entity)
256 ;; minfo is a list of following cell.
257 ;; ((((FNAME . NUMBER)...(FNAME . NUMBER)) MESSAGE-ID NUMBER-IN-FLAG-FOLDER)
258 ;; minfo-index is the hash table of above with following indice;
259 (defun elmo-global-flags-set (flags folder number message-id)
260 "Set global flags to the message.
261 FLAGS is a list of symbol of the flag.
262 FOLDER is the elmo folder structure.
263 NUMBER is the message number."
265 (elmo-global-flag-set flag folder number message-id)))
267 (defsubst elmo-global-flag-set-internal (flag folder number message-id)
269 (let ((flag-folder (elmo-flag-get-folder flag))
270 cache new-file new-number elem)
271 (if (setq elem (elmo-get-hash-val
273 (elmo-flag-folder-minfo-hash-internal
275 ;; Same ID already exists.
276 (when (and folder number
277 (not (member (cons (elmo-folder-name-internal folder)
278 number) (car elem))))
280 (cons (cons (elmo-folder-name-internal folder)
282 (setq new-number (nth 2 elem))
283 (elmo-set-hash-val (concat (number-to-string number)
284 ":" (elmo-folder-name-internal
287 (elmo-flag-folder-minfo-hash-internal
289 ;; Append new element.
293 (setq new-number (1+ (car (elmo-folder-status flag-folder)))))
294 (elmo-localdir-folder-directory-internal flag-folder)))
296 (setq cache (and message-id (elmo-file-cache-get message-id)))
297 (if (and cache (eq (elmo-file-cache-status cache) 'entire))
298 (elmo-copy-file (elmo-file-cache-path cache)
300 (when (and folder number)
301 (elmo-message-fetch folder number (elmo-make-fetch-strategy
303 nil (current-buffer))
304 (write-region-as-binary (point-min) (point-max) new-file nil
306 (elmo-flag-folder-set-minfo-internal
310 (when (and folder number)
311 (list (cons (elmo-folder-name-internal folder)
315 (elmo-flag-folder-minfo-internal flag-folder)))
316 (when (and folder number)
317 (elmo-set-hash-val (concat (number-to-string number)
318 ":" (elmo-folder-name-internal
321 (elmo-flag-folder-minfo-hash-internal
323 (elmo-set-hash-val message-id elem
324 (elmo-flag-folder-minfo-hash-internal
326 (elmo-set-hash-val (concat "#" (number-to-string new-number)) elem
327 (elmo-flag-folder-minfo-hash-internal
329 (elmo-folder-commit flag-folder)
332 (defun elmo-global-flag-set (flag folder number message-id)
333 "Set global flag to the message.
334 FLAG is a symbol of the flag.
335 FOLDER is the elmo folder structure.
336 NUMBER is the message number.
337 MESSAGE-ID is the message-id of the message."
338 (when (elmo-global-flag-p flag)
339 (elmo-global-flag-set-internal flag folder number message-id)))
341 (defun elmo-global-flag-detach (flag folder number &optional delete-if-none)
342 "Detach the message from the global flag.
343 FOLDER is the folder structure.
344 NUMBERS is the message number.
345 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
346 the message is not flagged in any folder.
347 If DELETE-IF-NONE is a symbol `always',
348 delete message without flagged in other folder."
349 (unless (eq (elmo-folder-type-internal folder) 'flag)
350 (let ((flag-folder (elmo-flag-get-folder flag))
353 (setq key (concat (number-to-string number) ":"
354 (elmo-folder-name-internal folder))
355 elem (elmo-get-hash-val
357 (elmo-flag-folder-minfo-hash-internal flag-folder)))
359 (setcar elem (delete (cons (elmo-folder-name-internal folder)
361 (elmo-clear-hash-val key (elmo-flag-folder-minfo-hash-internal
363 ;; Does not have any referrer, remove.
364 (when (and delete-if-none
365 (or (eq delete-if-none 'always)
367 (elmo-flag-folder-delete-message flag-folder (nth 2 elem)
369 (elmo-localdir-delete-message flag-folder (nth 2 elem))
370 (elmo-folder-commit flag-folder)))))))
372 (defun elmo-global-flag-detach-messages (folder numbers &optional
374 "Detach all messages specified from all global flags.
375 FOLDER is the folder structure.
376 NUMBERS is the message number list.
377 If optional DELETE-IF-NONE is non-nil, delete message from flag folder when
378 the message is not flagged in any folder."
379 (unless (eq (elmo-folder-type-internal folder) 'flag)
380 (dolist (flag elmo-global-flag-list)
381 (dolist (number numbers)
382 (elmo-global-flag-detach flag folder number delete-if-none)))))
384 (defun elmo-get-global-flags (&optional flags ignore-preserved)
386 Return value is a subset of optional argument FLAGS.
387 If FLAGS is `t', all global flags becomes candidates.
388 If optional IGNORE-PRESERVED is non-nil, preserved flags
389 \(answered, cached, new, unread\) are not included."
390 (let ((result (copy-sequence (if (eq flags t)
391 (setq flags elmo-global-flag-list)
394 (unless (elmo-global-flag-p (car flags))
395 (setq result (delq (car flags) result)))
396 (setq flags (cdr flags)))
397 (when ignore-preserved
398 (dolist (flag elmo-preserved-flags)
399 (setq result (delq flag result))))
402 ;;; To migrate from global mark folder
403 (defvar elmo-global-mark-filename "global-mark"
404 "Obsolete variable. (Just for migration)")
406 (defun elmo-global-mark-migrate ()
407 "Migrate from 'mark to 'flag. For automatic migration."
408 (when (and (file-exists-p (expand-file-name elmo-global-mark-filename
409 elmo-msgdb-directory))
410 (elmo-global-flag-p 'important)
411 (not (file-exists-p (elmo-folder-expand-msgdb-path
412 (elmo-flag-get-folder 'important)))))
413 (elmo-global-mark-upgrade)))
415 (defun elmo-global-mark-upgrade ()
416 "Upgrade old `global-mark' structure."
418 (when (file-exists-p (expand-file-name
419 elmo-global-mark-filename elmo-msgdb-directory))
420 (message "Upgrading flag structure...")
421 (when (elmo-global-flag-p 'important)
425 elmo-global-mark-filename elmo-msgdb-directory)))
426 (folder (elmo-flag-get-folder 'important))
428 (dolist (elem global-marks)
429 (setq file-cache (elmo-file-cache-get (car elem)))
430 (when (eq (elmo-file-cache-status file-cache) 'entire)
431 (elmo-global-flag-set 'important nil nil (car elem))))))
432 (message "Upgrading flag structure...done")))
435 (product-provide (provide 'elmo-flag) (require 'elmo-version))
437 ;;; elmo-flag.el ends here