Merged beta branch.
[elisp/wanderlust.git] / elmo / elmo-localdir.el
1 ;;; elmo-localdir.el -- Localdir Interface for ELMO.
2
3 ;; Copyright 1998,1999,2000 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
29 ;;; Code:
30 ;; 
31
32 (require 'emu)
33 (require 'std11)
34
35 (eval-when-compile
36   (require 'elmo-cache))
37 (require 'elmo-msgdb)
38
39 (defsubst elmo-localdir-get-folder-directory (spec)
40   (if (file-name-absolute-p (nth 1 spec))
41       (nth 1 spec) ; already full path.
42     (expand-file-name (nth 1 spec)
43                       (cond ((eq (car spec) 'localnews)
44                              elmo-localnews-folder-path)
45                             (t
46                              elmo-localdir-folder-path)))))
47
48 (defun elmo-localdir-msgdb-expand-path (spec)
49   (let ((fld-name (nth 1 spec)))
50     (expand-file-name fld-name
51                       (expand-file-name "localdir"
52                                         elmo-msgdb-dir))))
53
54 (defun elmo-localdir-number-to-filename (spec dir number &optional loc-alist)
55   (expand-file-name (int-to-string number) dir))
56
57 (if (boundp 'nemacs-version)
58     (defsubst elmo-localdir-insert-header (file)
59       "Insert the header of the article (Does not work on nemacs)."
60       (as-binary-input-file
61        (insert-file-contents file)))
62   (defsubst elmo-localdir-insert-header (file)
63     "Insert the header of the article."
64     (let ((beg 0)
65           insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
66           insert-file-contents-post-hook
67           format-alist)
68       (when (file-exists-p file)
69         ;; Read until header separator is found.
70         (while (and (eq elmo-localdir-header-chop-length
71                         (nth 1
72                              (as-binary-input-file
73                               (insert-file-contents
74                                file nil beg
75                                (incf beg elmo-localdir-header-chop-length)))))
76                     (prog1 (not (search-forward "\n\n" nil t))
77                       (goto-char (point-max)))))))))
78
79
80 (defsubst elmo-localdir-msgdb-create-overview-entity-from-file (number file)
81   (save-excursion
82     (let ((tmp-buffer (get-buffer-create " *ELMO LocalDir Temp*"))
83           insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
84           insert-file-contents-post-hook header-end
85           (attrib (file-attributes file))
86           ret-val size mtime)
87       (set-buffer tmp-buffer)
88       (erase-buffer)
89       (if (not (file-exists-p file))
90           ()
91         (setq size (nth 7 attrib))
92         (setq mtime (timezone-make-date-arpa-standard
93                      (current-time-string (nth 5 attrib)) (current-time-zone)))
94         ;; insert header from file.
95         (catch 'done
96           (condition-case nil
97               (elmo-localdir-insert-header file)
98             (error (throw 'done nil)))
99           (goto-char (point-min))
100           (setq header-end
101                 (if (re-search-forward "\\(^--.*$\\)\\|\\(\n\n\\)" nil t)
102                     (point)
103                   (point-max)))
104           (narrow-to-region (point-min) header-end)
105           (setq ret-val (elmo-msgdb-create-overview-from-buffer number size mtime))
106           (kill-buffer tmp-buffer))
107         ret-val
108         ))))
109
110 (defun elmo-localdir-msgdb-create-entity (dir number)
111   (elmo-localdir-msgdb-create-overview-entity-from-file
112    number (expand-file-name (int-to-string number) dir)))
113
114 (defun elmo-localdir-msgdb-create-as-numlist (spec numlist new-mark
115                                                    already-mark seen-mark
116                                                    important-mark seen-list)
117   (when numlist
118     (let ((dir (elmo-localdir-get-folder-directory spec))
119           overview number-alist mark-alist entity message-id
120           num seen gmark
121           (i 0)
122           (len (length numlist)))
123       (message "Creating msgdb...")
124       (while numlist
125         (setq entity
126               (elmo-localdir-msgdb-create-entity
127                dir (car numlist)))
128         (if (null entity)
129             ()
130           (setq num (elmo-msgdb-overview-entity-get-number entity))
131           (setq overview
132                 (elmo-msgdb-append-element
133                  overview entity))
134           (setq message-id (elmo-msgdb-overview-entity-get-id entity))
135           (setq number-alist
136                 (elmo-msgdb-number-add number-alist
137                                        num
138                                        message-id))
139           (setq seen (member message-id seen-list))
140           (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
141                               (if (elmo-cache-exists-p message-id) ; XXX
142                                   (if seen
143                                       nil
144                                     already-mark)
145                                 (if seen
146                                     nil ;;seen-mark
147                                   new-mark))))
148               (setq mark-alist
149                     (elmo-msgdb-mark-append
150                      mark-alist
151                      num
152                      gmark))))
153         (when (> len elmo-display-progress-threshold)
154           (setq i (1+ i))
155           (elmo-display-progress
156            'elmo-localdir-msgdb-create-as-numlist "Creating msgdb..."
157            (/ (* i 100) len)))
158         (setq numlist (cdr numlist)))
159       (message "Creating msgdb...done.")
160       (list overview number-alist mark-alist))))
161
162 (defalias 'elmo-localdir-msgdb-create 'elmo-localdir-msgdb-create-as-numlist)
163
164 (defvar elmo-localdir-list-folders-spec-string "+")
165 (defvar elmo-localdir-list-folders-filter-regexp "^\\(\\.\\.?\\|[0-9]+\\)$")
166
167 (defun elmo-localdir-list-folders (spec &optional hierarchy)
168   (let ((folder (concat elmo-localdir-list-folders-spec-string (nth 1 spec))))
169     (elmo-localdir-list-folders-subr folder hierarchy)))
170
171 (defun elmo-localdir-list-folders-subr (folder &optional hierarchy)
172   (let ((case-fold-search t)
173         (w32-get-true-file-link-count t) ; for Meadow
174         folders curdir dirent relpath abspath attr
175         subprefix subfolder)
176     (condition-case ()
177         (progn
178           (setq curdir
179                 (expand-file-name (nth 1 (elmo-folder-get-spec folder))
180                                   elmo-localdir-folder-path))
181           (if (string-match "^[+=$.]$" folder) ; localdir, archive, localnews
182               (setq subprefix folder)
183             (setq subprefix (concat folder elmo-path-sep))
184             ;; include parent
185             (setq folders (list folder)))
186           (setq dirent (directory-files curdir))
187           (catch 'done
188            (while dirent
189             (setq relpath (car dirent))
190             (setq dirent (cdr dirent))
191             (setq abspath (expand-file-name relpath curdir))
192             (and
193              (not (string-match
194                    elmo-localdir-list-folders-filter-regexp
195                    relpath))
196              (eq (nth 0 (setq attr (file-attributes abspath))) t)
197              (if (eq hierarchy 'check)
198                  (throw 'done (nconc folders t))
199                t)
200              (setq subfolder (concat subprefix relpath))
201              (setq folders (nconc folders
202                                   (if (and hierarchy
203                                            (if elmo-have-link-count
204                                                (< 2 (nth 1 attr))
205                                              (cdr
206                                               (elmo-localdir-list-folders-subr
207                                                subfolder 'check))))
208                                       (list (list subfolder))
209                                     (list subfolder))))
210              (or
211               hierarchy
212               (and elmo-have-link-count (>= 2 (nth 1 attr)))
213               (setq folders
214                     (nconc folders (cdr (elmo-localdir-list-folders-subr
215                                          subfolder hierarchy))))))))
216           folders)
217       (file-error folders))))
218
219 (defsubst elmo-localdir-list-folder-subr (spec &optional nonsort)
220   (let* ((dir (elmo-localdir-get-folder-directory spec))
221          (flist (mapcar 'string-to-int
222                         (directory-files dir nil "^[0-9]+$" t)))
223          (killed (and elmo-use-killed-list
224                       (elmo-msgdb-killed-list-load
225                        (elmo-msgdb-expand-path spec))))
226          numbers)
227     (if nonsort
228         (cons (or (elmo-max-of-list flist) 0)
229               (if killed
230                   (- (length flist) (length killed))
231                 (length flist)))
232       (setq numbers (sort flist '<))
233       (elmo-living-messages numbers killed))))
234
235 (defun elmo-localdir-append-msg (spec string &optional msg no-see)
236   (let ((dir (elmo-localdir-get-folder-directory spec))
237         (tmp-buffer (get-buffer-create " *ELMO Temp buffer*"))
238         (next-num (or msg
239                       (1+ (car (elmo-localdir-max-of-folder spec)))))
240         filename)
241     (save-excursion
242       (set-buffer tmp-buffer)
243       (erase-buffer)
244       (setq filename (expand-file-name (int-to-string
245                                         next-num)
246                                        dir))
247       (unwind-protect
248           (if (file-writable-p filename)
249               (progn
250                 (insert string)
251                 (as-binary-output-file
252                  (write-region (point-min) (point-max) filename nil 'no-msg))
253                 t)
254             nil
255             )
256         (kill-buffer tmp-buffer)))))
257
258 (defun elmo-localdir-delete-msg (spec number)
259   (let (file
260         (dir (elmo-localdir-get-folder-directory spec))
261         (number (int-to-string number)))
262     (setq file (expand-file-name number dir))
263     (if (and (string-match "[0-9]+" number) ; for safety.
264              (file-exists-p file)
265              (file-writable-p file)
266              (not (file-directory-p file)))
267         (progn (delete-file file)
268                t))))
269
270 (defun elmo-localdir-read-msg (spec number outbuf &optional set-mark)
271   (save-excursion
272     (let* ((number (int-to-string number))
273            (dir (elmo-localdir-get-folder-directory spec))
274            (file (expand-file-name number dir)))
275       (set-buffer outbuf)
276       (erase-buffer)
277       (when (file-exists-p file)
278         (as-binary-input-file (insert-file-contents file))
279         (elmo-delete-cr-get-content-type)))))
280
281 (defun elmo-localdir-delete-msgs (spec msgs)
282   (mapcar '(lambda (msg) (elmo-localdir-delete-msg spec msg))
283           msgs))
284
285 (defun elmo-localdir-list-folder (spec); called by elmo-localdir-search()
286   (elmo-localdir-list-folder-subr spec))
287
288 (defun elmo-localdir-max-of-folder (spec)
289   (elmo-localdir-list-folder-subr spec t))
290
291 (defun elmo-localdir-check-validity (spec validity-file)
292   (let* ((dir (elmo-localdir-get-folder-directory spec))
293          (cur-val (nth 5 (file-attributes dir)))
294          (file-val (read
295                     (or (elmo-get-file-string validity-file)
296                         "nil"))))
297     (cond
298      ((or (null cur-val) (null file-val)) nil)
299      ((> (car cur-val) (car file-val)) nil)
300      ((= (car cur-val) (car file-val))
301       (if (> (cadr cur-val) (cadr file-val)) nil t)) ; t if same
302      (t t))))
303
304 (defun elmo-localdir-sync-validity (spec validity-file)
305   (save-excursion
306     (let* ((dir (elmo-localdir-get-folder-directory spec))
307            (tmp-buffer (get-buffer-create " *ELMO TMP*"))
308            (number-file (expand-file-name elmo-msgdb-number-filename dir)))
309       (set-buffer tmp-buffer)
310       (erase-buffer)
311       (prin1 (nth 5 (file-attributes dir)) tmp-buffer)
312       (princ "\n" tmp-buffer)
313       (if (file-writable-p validity-file)
314           (write-region (point-min) (point-max)
315                         validity-file nil 'no-msg)
316         (message (format "%s is not writable." number-file)))
317       (kill-buffer tmp-buffer))))
318
319 (defun elmo-localdir-folder-exists-p (spec)
320   (file-directory-p (elmo-localdir-get-folder-directory spec)))
321
322 (defun elmo-localdir-folder-creatable-p (spec)
323   t)
324
325 (defun elmo-localdir-create-folder (spec)
326   (save-excursion
327     (let ((dir (elmo-localdir-get-folder-directory spec)))
328       (if (file-directory-p dir)
329           ()
330         (if (file-exists-p dir)
331             (error "Create folder failed")
332           (elmo-make-directory dir))
333         t
334         ))))
335
336 (defun elmo-localdir-delete-folder (spec)
337   (let* ((dir (elmo-localdir-get-folder-directory spec)))
338     (if (not (file-directory-p dir))
339         (error "no such directory: %s" dir)
340       (elmo-delete-directory dir t)
341       t)))
342
343 (defun elmo-localdir-rename-folder (old-spec new-spec)
344   (let* ((old (elmo-localdir-get-folder-directory old-spec))
345          (new (elmo-localdir-get-folder-directory new-spec))
346          (new-dir (directory-file-name (file-name-directory new))))
347     (if (not (file-directory-p old))
348         (error "no such directory: %s" old)
349       (if (file-exists-p new)
350           (error "already exists directory: %s" new)
351         (if (not (file-exists-p new-dir))
352             (elmo-make-directory new-dir))
353         (rename-file old new)
354         t))))
355
356 (defsubst elmo-localdir-field-condition-match (spec condition
357                                                     number number-list)
358   (elmo-file-field-condition-match
359    (expand-file-name (int-to-string number)
360                      (elmo-localdir-get-folder-directory spec))
361    condition
362    number number-list))
363
364 (defun elmo-localdir-search (spec condition &optional from-msgs)
365   (let* ((msgs (or from-msgs (elmo-localdir-list-folder spec)))
366          (num (length msgs))
367          (i 0)
368          number-list case-fold-search ret-val)
369     (setq number-list msgs)
370     (while msgs
371       (if (elmo-localdir-field-condition-match spec condition
372                                                (car msgs) number-list)
373           (setq ret-val (cons (car msgs) ret-val)))
374       (when (> num elmo-display-progress-threshold)
375         (setq i (1+ i))
376         (elmo-display-progress
377          'elmo-localdir-search "Searching..."
378          (/ (* i 100) num)))
379       (setq msgs (cdr msgs)))
380     (nreverse ret-val)))
381
382 ;;; (localdir, maildir, localnews) -> localdir
383 (defun elmo-localdir-copy-msgs (dst-spec msgs src-spec
384                                          &optional loc-alist same-number)
385   (let ((dst-dir
386          (elmo-localdir-get-folder-directory dst-spec))
387         (next-num (1+ (car (elmo-localdir-max-of-folder dst-spec)))))
388     (while msgs
389       (elmo-copy-file
390        ;; src file
391        (elmo-call-func src-spec "get-msg-filename" (car msgs) loc-alist)
392        ;; dst file
393        (expand-file-name (int-to-string
394                           (if same-number (car msgs) next-num))
395                          dst-dir))
396       (if (and (setq msgs (cdr msgs))
397                (not same-number))
398           (setq next-num
399                 (if (and (eq (car dst-spec) 'localdir)
400                          (elmo-localdir-locked-p))
401                     ;; MDA is running.
402                     (1+ (car (elmo-localdir-max-of-folder dst-spec)))
403                   (1+ next-num)))))
404     t))
405
406 (defun elmo-localdir-pack-number (spec msgdb arg)
407   (let ((dir (elmo-localdir-get-folder-directory spec))
408         (onum-alist (elmo-msgdb-get-number-alist msgdb))
409         (omark-alist (elmo-msgdb-get-mark-alist msgdb))
410         (new-number 1)                  ; first ordinal position in localdir
411         flist onum mark new-mark-alist total)
412     (setq flist
413           (if elmo-pack-number-check-strict
414               (elmo-call-func spec "list-folder") ; allow localnews
415             (mapcar 'car onum-alist)))
416     (setq total (length flist))
417     (while flist
418       (when (> total elmo-display-progress-threshold)
419         (elmo-display-progress
420          'elmo-localdir-pack-number "Packing..."
421          (/ (* new-number 100) total)))
422       (setq onum (car flist))
423       (when (not (eq onum new-number))          ; why \=() is wrong..
424         (elmo-bind-directory
425          dir
426          ;; xxx  nfs,hardlink
427          (rename-file (int-to-string onum) (int-to-string new-number) t))
428         ;; update overview
429         (elmo-msgdb-overview-entity-set-number
430          (elmo-msgdb-overview-get-entity onum msgdb)
431          new-number)
432         ;; update number-alist
433         (setcar (assq onum onum-alist) new-number))
434       ;; update mark-alist
435       (when (setq mark (cadr (assq onum omark-alist)))
436         (setq new-mark-alist
437               (elmo-msgdb-mark-append
438                new-mark-alist
439                new-number mark)))
440       (setq new-number (1+ new-number))
441       (setq flist (cdr flist)))
442     (message "Packing...done.")
443     (list (elmo-msgdb-get-overview msgdb)
444           onum-alist
445           new-mark-alist
446           (elmo-msgdb-get-location msgdb)
447           ;; remake hash table
448           (elmo-msgdb-make-overview-hashtb (elmo-msgdb-get-overview msgdb)))))
449
450 (defun elmo-localdir-use-cache-p (spec number)
451   nil)
452
453 (defun elmo-localdir-local-file-p (spec number)
454   t)
455
456 (defun elmo-localdir-get-msg-filename (spec number &optional loc-alist)
457   (expand-file-name
458    (int-to-string number)
459    (elmo-localdir-get-folder-directory spec)))
460
461 (defun elmo-localdir-locked-p ()
462   (if elmo-localdir-lockfile-list
463       (let ((lock elmo-localdir-lockfile-list))
464         (catch 'found
465           (while lock
466             (if (file-exists-p (car lock))
467                 (throw 'found t))
468             (setq lock (cdr lock)))))))
469
470 (defalias 'elmo-localdir-sync-number-alist
471   'elmo-generic-sync-number-alist)
472 (defalias 'elmo-localdir-list-folder-unread
473   'elmo-generic-list-folder-unread)
474 (defalias 'elmo-localdir-list-folder-important
475   'elmo-generic-list-folder-important)
476 (defalias 'elmo-localdir-commit 'elmo-generic-commit)
477 (defalias 'elmo-localdir-folder-diff 'elmo-generic-folder-diff)
478
479 (require 'product)
480 (product-provide (provide 'elmo-localdir) (require 'elmo-version))
481
482 ;;; elmo-localdir.el ends here