* elmo-localdir.el (elmo-folder-append-messages): Don't refer
[elisp/wanderlust.git] / elmo / elmo-localdir.el
1 ;;; elmo-localdir.el --- Localdir Interface for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 1998,1999,2000 Masahiro MURATA <muse@ba2.so-net.ne.jp>
5 ;; Copyright (C) 1999,2000      Kenichi OKADA  <okada@opaopa.org>
6
7 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
8 ;;      Masahiro MURATA <muse@ba2.so-net.ne.jp>
9 ;;      Kenichi OKADA <okada@opaopa.org>
10 ;; Keywords: mail, net news
11
12 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
13
14 ;; This program is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; any later version.
18 ;;
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23 ;;
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28 ;;
29
30 ;;; Commentary:
31 ;;
32
33 ;;; Code:
34 ;;
35 (eval-when-compile (require 'cl))
36
37 (require 'elmo-msgdb)
38 (require 'elmo)
39
40 (defcustom elmo-localdir-folder-path "~/Mail"
41   "*Local mail directory (MH format) path."
42   :type 'directory
43   :group 'elmo)
44
45 (defvar elmo-localdir-lockfile-list nil)
46
47 ;;; ELMO Local directory folder
48 (eval-and-compile
49   (luna-define-class elmo-localdir-folder (elmo-folder)
50                      (dir-name directory))
51   (luna-define-internal-accessors 'elmo-localdir-folder))
52
53 ;;; elmo-localdir specific methods.
54 (luna-define-generic elmo-localdir-folder-path (folder)
55   "Return local directory path of the FOLDER.")
56
57 (luna-define-generic elmo-localdir-folder-name (folder name)
58   "Return directory NAME for FOLDER.")
59
60 (luna-define-method elmo-localdir-folder-path ((folder elmo-localdir-folder))
61   elmo-localdir-folder-path)
62
63 (luna-define-method elmo-localdir-folder-name ((folder elmo-localdir-folder)
64                                                name)
65   name)
66
67 (luna-define-method elmo-folder-initialize ((folder
68                                              elmo-localdir-folder)
69                                             name)
70   (elmo-localdir-folder-set-dir-name-internal folder name)
71   (if (file-name-absolute-p name)
72       (elmo-localdir-folder-set-directory-internal
73        folder
74        (expand-file-name name))
75     (elmo-localdir-folder-set-directory-internal
76      folder
77      (expand-file-name
78       (elmo-localdir-folder-name folder name)
79       (elmo-localdir-folder-path folder))))
80   folder)
81
82 ;; open, check, commit, and close are generic.
83
84 (luna-define-method elmo-folder-exists-p ((folder elmo-localdir-folder))
85   (file-directory-p (elmo-localdir-folder-directory-internal folder)))
86
87 (luna-define-method elmo-folder-expand-msgdb-path ((folder
88                                                     elmo-localdir-folder))
89   (expand-file-name
90    (mapconcat
91     'identity
92     (mapcar
93      'elmo-replace-string-as-filename
94      (split-string
95       (let ((dir-name (elmo-localdir-folder-dir-name-internal folder)))
96         (if (file-name-absolute-p dir-name)
97             (expand-file-name dir-name)
98           dir-name))
99       "/"))
100     "/")
101    (expand-file-name ;;"localdir"
102     (symbol-name (elmo-folder-type-internal folder))
103     elmo-msgdb-directory)))
104
105 (luna-define-method elmo-message-file-name ((folder
106                                              elmo-localdir-folder)
107                                             number)
108   (expand-file-name (int-to-string number)
109                     (elmo-localdir-folder-directory-internal folder)))
110
111 (luna-define-method elmo-folder-message-file-number-p ((folder
112                                                         elmo-localdir-folder))
113   t)
114
115 (luna-define-method elmo-folder-message-file-directory ((folder
116                                                          elmo-localdir-folder))
117   (elmo-localdir-folder-directory-internal folder))
118
119 (luna-define-method elmo-folder-message-make-temp-file-p
120   ((folder elmo-localdir-folder))
121   t)
122
123 (luna-define-method elmo-folder-message-make-temp-files ((folder
124                                                           elmo-localdir-folder)
125                                                          numbers
126                                                          &optional
127                                                          start-number)
128   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
129         (cur-number (or start-number 0)))
130     (dolist (number numbers)
131       (elmo-copy-file
132        (expand-file-name
133         (int-to-string number)
134         (elmo-localdir-folder-directory-internal folder))
135        (expand-file-name
136         (int-to-string (if start-number cur-number number))
137         temp-dir))
138       (incf cur-number))
139     temp-dir))
140
141 (defun elmo-localdir-msgdb-create-entity (dir number)
142   (elmo-msgdb-create-overview-entity-from-file
143    number (expand-file-name (int-to-string number) dir)))
144
145 (luna-define-method elmo-folder-msgdb-create ((folder elmo-localdir-folder)
146                                               numbers
147                                               flag-table)
148   (when numbers
149     (let ((dir (elmo-localdir-folder-directory-internal folder))
150           overview number-alist mark-alist entity message-id
151           num gmark
152           (i 0)
153           (len (length numbers)))
154       (message "Creating msgdb...")
155       (while numbers
156         (setq entity
157               (elmo-localdir-msgdb-create-entity
158                dir (car numbers)))
159         (if (null entity)
160             ()
161           (setq num (elmo-msgdb-overview-entity-get-number entity))
162           (setq overview
163                 (elmo-msgdb-append-element
164                  overview entity))
165           (setq message-id (elmo-msgdb-overview-entity-get-id entity))
166           (setq number-alist
167                 (elmo-msgdb-number-add number-alist
168                                        num
169                                        message-id))
170           (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
171                               (unless (eq 'read (elmo-flag-table-get 
172                                                  flag-table message-id))
173                                 (elmo-msgdb-mark
174                                  (elmo-flag-table-get flag-table message-id)
175                                  (elmo-file-cache-status
176                                   (elmo-file-cache-get message-id))
177                                  'new))))
178               (setq mark-alist
179                     (elmo-msgdb-mark-append
180                      mark-alist
181                      num
182                      gmark))))
183         (when (> len elmo-display-progress-threshold)
184           (setq i (1+ i))
185           (elmo-display-progress
186            'elmo-localdir-msgdb-create-as-numbers "Creating msgdb..."
187            (/ (* i 100) len)))
188         (setq numbers (cdr numbers)))
189       (message "Creating msgdb...done")
190       (list overview number-alist mark-alist))))
191
192 (luna-define-method elmo-folder-list-subfolders ((folder elmo-localdir-folder)
193                                                  &optional one-level)
194   (elmo-mapcar-list-of-list
195    (lambda (x) (concat (elmo-folder-prefix-internal folder) x))
196    (elmo-list-subdirectories
197     (elmo-localdir-folder-path folder)
198     (or (elmo-localdir-folder-dir-name-internal folder) "")
199     one-level)))
200
201 (defsubst elmo-localdir-list-subr (folder &optional nonsort)
202   (let ((flist (mapcar 'string-to-int
203                        (directory-files
204                         (elmo-localdir-folder-directory-internal folder)
205                         nil "^[0-9]+$" t)))
206         (killed (elmo-msgdb-killed-list-load (elmo-folder-msgdb-path folder))))
207     (if nonsort
208         (cons (or (elmo-max-of-list flist) 0)
209               (if killed
210                   (- (length flist)
211                      (elmo-msgdb-killed-list-length killed))
212                 (length flist)))
213       (sort flist '<))))
214
215 (luna-define-method elmo-folder-append-buffer ((folder elmo-localdir-folder)
216                                                unread
217                                                &optional number)
218   (let ((filename (elmo-message-file-name
219                    folder
220                    (or number
221                        (1+ (car (elmo-folder-status folder)))))))
222     (when (file-writable-p filename)
223       (write-region-as-binary
224        (point-min) (point-max) filename nil 'no-msg)
225       t)))
226
227 (luna-define-method elmo-folder-append-messages :around
228   ((folder elmo-localdir-folder)
229    src-folder numbers &optional same-number)
230   (if (elmo-folder-message-file-p src-folder)
231       (let ((src-msgdb-exists (not (zerop (elmo-folder-length src-folder))))
232             (dir (elmo-localdir-folder-directory-internal folder))
233             (table (elmo-flag-table-load (elmo-folder-msgdb-path folder)))
234             (succeeds numbers)
235             (next-num (1+ (car (elmo-folder-status folder))))
236             mark flag id)
237         (while numbers
238           (setq mark (and src-msgdb-exists
239                           (elmo-message-mark src-folder (car numbers)))
240                 flag (cond
241                       ((null mark) 'read)
242                       ((member mark (elmo-msgdb-answered-marks))
243                        'answered)
244                       ;;
245                       ((not (member mark (elmo-msgdb-unread-marks)))
246                        'read)))
247           (elmo-copy-file
248            (elmo-message-file-name src-folder (car numbers))
249            (expand-file-name
250             (int-to-string
251              (if same-number (car numbers) next-num))
252             dir))
253           ;; save flag-table only when src folder's msgdb is loaded.
254           (when (setq id (and src-msgdb-exists
255                               (elmo-message-field src-folder (car numbers)
256                                                   'message-id)))
257             (elmo-flag-table-set table id flag))
258           (elmo-progress-notify 'elmo-folder-move-messages)
259           (if (and (setq numbers (cdr numbers))
260                    (not same-number))
261               (setq next-num
262                     (if (elmo-localdir-locked-p)
263                         ;; MDA is running.
264                         (1+ (car (elmo-folder-status folder)))
265                       (1+ next-num)))))
266         (when (elmo-folder-persistent-p folder)
267           (elmo-flag-table-save (elmo-folder-msgdb-path folder) table))
268         succeeds)
269     (luna-call-next-method)))
270
271 (luna-define-method elmo-folder-delete-messages ((folder elmo-localdir-folder)
272                                                  numbers)
273   (dolist (number numbers)
274     (elmo-localdir-delete-message folder number))
275   t)
276
277 (defun elmo-localdir-delete-message (folder number)
278   "Delete message in the FOLDER with NUMBER."
279   (let ((filename (elmo-message-file-name folder number)))
280     (when (and (string-match "[0-9]+" filename) ; for safety.
281                (file-exists-p filename)
282                (file-writable-p filename)
283                (not (file-directory-p filename)))
284       (delete-file filename)
285       t)))
286
287 (luna-define-method elmo-message-fetch-internal ((folder elmo-localdir-folder)
288                                                  number strategy
289                                                  &optional section unread)
290   (when (file-exists-p (elmo-message-file-name folder number))
291     (insert-file-contents-as-binary
292      (elmo-message-file-name folder number))))
293
294 (luna-define-method elmo-folder-list-messages-internal
295   ((folder elmo-localdir-folder) &optional nohide)
296   (elmo-localdir-list-subr folder))
297
298 (luna-define-method elmo-folder-status ((folder elmo-localdir-folder))
299   (elmo-localdir-list-subr folder t))
300
301 (luna-define-method elmo-folder-creatable-p ((folder elmo-localdir-folder))
302   t)
303
304 (luna-define-method elmo-folder-writable-p ((folder elmo-localdir-folder))
305   t)
306
307 (luna-define-method elmo-folder-create ((folder elmo-localdir-folder))
308   (let ((dir (elmo-localdir-folder-directory-internal folder)))
309     (if (file-directory-p dir)
310         ()
311       (if (file-exists-p dir)
312           (error "Create folder failed")
313         (elmo-make-directory dir))
314       t)))
315
316 (luna-define-method elmo-folder-delete ((folder elmo-localdir-folder))
317   (let ((msgs (and (elmo-folder-exists-p folder)
318                    (elmo-folder-list-messages folder))))
319     (when (yes-or-no-p (format "%sDelete msgdb and substance of \"%s\"? "
320                                (if (> (length msgs) 0)
321                                    (format "%d msg(s) exists. " (length msgs))
322                                  "")
323                                (elmo-folder-name-internal folder)))
324       (let ((dir (elmo-localdir-folder-directory-internal folder)))
325         (if (not (file-directory-p dir))
326             (error "No such directory: %s" dir)
327           (elmo-delete-match-files dir "[0-9]+" t)))
328       (elmo-msgdb-delete-path folder)
329       t)))
330
331 (luna-define-method elmo-folder-rename-internal ((folder elmo-localdir-folder)
332                                                  new-folder)
333   (let* ((old (elmo-localdir-folder-directory-internal folder))
334          (new (elmo-localdir-folder-directory-internal new-folder))
335          (new-dir (directory-file-name (file-name-directory new))))
336     (unless (file-directory-p old)
337       (error "No such directory: %s" old))
338     (when (file-exists-p new)
339       (error "Already exists directory: %s" new))
340     (unless (file-directory-p new-dir)
341       (elmo-make-directory new-dir))
342     (rename-file old new)
343     t))
344
345 (defsubst elmo-localdir-field-condition-match (folder condition
346                                                       number number-list)
347   (elmo-file-field-condition-match
348    (expand-file-name (int-to-string number)
349                      (elmo-localdir-folder-directory-internal folder))
350    condition number number-list))
351
352 (luna-define-method elmo-folder-pack-numbers ((folder elmo-localdir-folder))
353   (let* ((dir (elmo-localdir-folder-directory-internal folder))
354          (msgdb (elmo-folder-msgdb folder))
355          (onum-alist (elmo-msgdb-get-number-alist msgdb))
356          (omark-alist (elmo-msgdb-get-mark-alist msgdb))
357          (new-number 1)                 ; first ordinal position in localdir
358          flist onum mark new-mark-alist total)
359     (setq flist
360           (if elmo-pack-number-check-strict
361               (elmo-folder-list-messages folder) ; allow localnews
362             (mapcar 'car onum-alist)))
363     (setq total (length flist))
364     (while flist
365       (when (> total elmo-display-progress-threshold)
366         (elmo-display-progress
367          'elmo-folder-pack-numbers "Packing..."
368          (/ (* new-number 100) total)))
369       (setq onum (car flist))
370       (when (not (eq onum new-number))          ; why \=() is wrong..
371         (elmo-bind-directory
372          dir
373          ;; xxx  nfs,hardlink
374          (rename-file (int-to-string onum) (int-to-string new-number) t))
375         ;; update overview
376         (elmo-msgdb-overview-entity-set-number
377          (elmo-msgdb-overview-get-entity onum msgdb)
378          new-number)
379         ;; update number-alist
380         (and (assq onum onum-alist)
381              (setcar (assq onum onum-alist) new-number)))
382       ;; update mark-alist
383       (when (setq mark (cadr (assq onum omark-alist)))
384         (setq new-mark-alist
385               (elmo-msgdb-mark-append
386                new-mark-alist
387                new-number mark)))
388       (setq new-number (1+ new-number))
389       (setq flist (cdr flist)))
390     (message "Packing...done")
391     (elmo-folder-set-msgdb-internal
392      folder
393      (elmo-make-msgdb
394       (elmo-msgdb-get-overview msgdb)
395       onum-alist
396       new-mark-alist))))
397
398 (luna-define-method elmo-folder-message-file-p ((folder elmo-localdir-folder))
399   t)
400
401 (luna-define-method elmo-message-file-name ((folder elmo-localdir-folder)
402                                             number)
403   (expand-file-name
404    (int-to-string number)
405    (elmo-localdir-folder-directory-internal folder)))
406
407 (defun elmo-localdir-locked-p ()
408   (if elmo-localdir-lockfile-list
409       (let ((lock elmo-localdir-lockfile-list))
410         (catch 'found
411           (while lock
412             (if (file-exists-p (car lock))
413                 (throw 'found t))
414             (setq lock (cdr lock)))))))
415
416 (require 'product)
417 (product-provide (provide 'elmo-localdir) (require 'elmo-version))
418
419 ;;; elmo-localdir.el ends here