* elmo-imap4.el (elmo-imap4-send-command):
[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         folders curdir dirent relpath abspath attr
174         subprefix subfolder)
175     (condition-case ()
176         (progn
177           (setq curdir
178                 (expand-file-name (nth 1 (elmo-folder-get-spec folder))
179                                   elmo-localdir-folder-path))
180           (if (string-match "^[+=$.]$" folder) ; localdir, archive, localnews
181               (setq subprefix folder)
182             (setq subprefix (concat folder elmo-path-sep))
183             ;; include parent
184             (setq folders (list folder)))
185           (setq dirent (directory-files curdir))
186           (catch 'done
187            (while dirent
188             (setq relpath (car dirent))
189             (setq dirent (cdr dirent))
190             (setq abspath (expand-file-name relpath curdir))
191             (and
192              (not (string-match
193                    elmo-localdir-list-folders-filter-regexp
194                    relpath))
195              (eq (nth 0 (setq attr (file-attributes abspath))) t)
196              (if (eq hierarchy 'check)
197                  (throw 'done (nconc folders t))
198                t)
199              (setq subfolder (concat subprefix relpath))
200              (setq folders (nconc folders
201                                   (if (and hierarchy
202                                            (if elmo-have-link-count
203                                                (< 2 (nth 1 attr))
204                                              (cdr
205                                               (elmo-localdir-list-folders-subr
206                                                subfolder 'check))))
207                                       (list (list subfolder))
208                                     (list subfolder))))
209              (or
210               hierarchy
211               (and elmo-have-link-count (>= 2 (nth 1 attr)))
212               (setq folders
213                     (nconc folders (cdr (elmo-localdir-list-folders-subr
214                                          subfolder hierarchy))))))))
215           folders)
216       (file-error folders))))
217
218 (defsubst elmo-localdir-list-folder-subr (spec &optional nonsort)
219   (let* ((dir (elmo-localdir-get-folder-directory spec))
220          (flist (mapcar 'string-to-int
221                         (directory-files dir nil "^[0-9]+$" t)))
222          (killed (and elmo-use-killed-list
223                       (elmo-msgdb-killed-list-load
224                        (elmo-msgdb-expand-path nil spec))))
225          numbers)
226     (setq numbers
227           (if nonsort
228               (cons (or (elmo-max-of-list flist) 0) (length flist))
229             (sort flist '<)))
230     (if killed
231         (delq nil
232               (mapcar (lambda (number)
233                         (unless (memq number killed) number))
234                       numbers))
235       numbers)))
236
237 (defun elmo-localdir-append-msg (spec string &optional msg no-see)
238   (let ((dir (elmo-localdir-get-folder-directory spec))
239         (tmp-buffer (get-buffer-create " *ELMO Temp buffer*"))
240         (next-num (or msg
241                       (1+ (car (elmo-localdir-list-folder-subr spec t)))))
242         filename)
243     (save-excursion
244       (set-buffer tmp-buffer)
245       (erase-buffer)
246       (setq filename (expand-file-name (int-to-string
247                                         next-num)
248                                        dir))
249       (unwind-protect
250           (if (file-writable-p filename)
251               (progn
252                 (insert string)
253                 (as-binary-output-file
254                  (write-region (point-min) (point-max) filename nil 'no-msg))
255                 t)
256             nil
257             )
258         (kill-buffer tmp-buffer)))))
259
260 (defun elmo-localdir-delete-msg (spec number)
261   (let (file
262         (dir (elmo-localdir-get-folder-directory spec))
263         (number (int-to-string number)))
264     (setq file (expand-file-name number dir))
265     (if (and (string-match "[0-9]+" number) ; for safety.
266              (file-exists-p file)
267              (file-writable-p file)
268              (not (file-directory-p file)))
269         (progn (delete-file file)
270                t))))
271
272 (defun elmo-localdir-read-msg (spec number outbuf &optional set-mark)
273   (save-excursion
274     (let* ((number (int-to-string number))
275            (dir (elmo-localdir-get-folder-directory spec))
276            (file (expand-file-name number dir)))
277       (set-buffer outbuf)
278       (erase-buffer)
279       (when (file-exists-p file)
280         (as-binary-input-file (insert-file-contents file))
281         (elmo-delete-cr-get-content-type)))))
282
283 (defun elmo-localdir-delete-msgs (spec msgs)
284   (mapcar '(lambda (msg) (elmo-localdir-delete-msg spec msg))
285           msgs))
286
287 (defun elmo-localdir-list-folder (spec); called by elmo-localdir-search()
288   (elmo-localdir-list-folder-subr spec))
289
290 (defun elmo-localdir-max-of-folder (spec)
291   (elmo-localdir-list-folder-subr spec t))
292
293 (defun elmo-localdir-check-validity (spec validity-file)
294   (let* ((dir (elmo-localdir-get-folder-directory spec))
295          (cur-val (nth 5 (file-attributes dir)))
296          (file-val (read
297                     (or (elmo-get-file-string validity-file)
298                         "nil"))))
299     (cond
300      ((or (null cur-val) (null file-val)) nil)
301      ((> (car cur-val) (car file-val)) nil)
302      ((= (car cur-val) (car file-val))
303       (if (> (cadr cur-val) (cadr file-val)) nil t)) ; t if same
304      (t t))))
305
306 (defun elmo-localdir-sync-validity (spec validity-file)
307   (save-excursion
308     (let* ((dir (elmo-localdir-get-folder-directory spec))
309            (tmp-buffer (get-buffer-create " *ELMO TMP*"))
310            (number-file (expand-file-name elmo-msgdb-number-filename dir)))
311       (set-buffer tmp-buffer)
312       (erase-buffer)
313       (prin1 (nth 5 (file-attributes dir)) tmp-buffer)
314       (princ "\n" tmp-buffer)
315       (if (file-writable-p validity-file)
316           (write-region (point-min) (point-max)
317                         validity-file nil 'no-msg)
318         (message (format "%s is not writable." number-file)))
319       (kill-buffer tmp-buffer))))
320
321 (defun elmo-localdir-folder-exists-p (spec)
322   (file-directory-p (elmo-localdir-get-folder-directory spec)))
323
324 (defun elmo-localdir-folder-creatable-p (spec)
325   t)
326
327 (defun elmo-localdir-create-folder (spec)
328   (save-excursion
329     (let ((dir (elmo-localdir-get-folder-directory spec)))
330       (if (file-directory-p dir)
331           ()
332         (if (file-exists-p dir)
333             (error "Create folder failed")
334           (elmo-make-directory dir))
335         t
336         ))))
337
338 (defun elmo-localdir-delete-folder (spec)
339   (let* ((dir (elmo-localdir-get-folder-directory spec)))
340     (if (not (file-directory-p dir))
341         (error "no such directory: %s" dir)
342       (elmo-delete-directory dir t)
343       t)))
344
345 (defun elmo-localdir-rename-folder (old-spec new-spec)
346   (let* ((old (elmo-localdir-get-folder-directory old-spec))
347          (new (elmo-localdir-get-folder-directory new-spec))
348          (new-dir (directory-file-name (file-name-directory new))))
349     (if (not (file-directory-p old))
350         (error "no such directory: %s" old)
351       (if (file-exists-p new)
352           (error "already exists directory: %s" new)
353         (if (not (file-exists-p new-dir))
354             (elmo-make-directory new-dir))
355         (rename-file old new)
356         t))))
357
358 (defsubst elmo-localdir-field-condition-match (spec number condition)
359   (elmo-file-field-condition-match
360    (expand-file-name (int-to-string number)
361                      (elmo-localdir-get-folder-directory spec))
362    condition))
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) case-fold-search ret-val)
368     (while msgs
369       (if (elmo-localdir-field-condition-match spec (car msgs)
370                                                condition)
371           (setq ret-val (cons (car msgs) ret-val)))
372       (when (> num elmo-display-progress-threshold)
373         (setq i (1+ i))
374         (elmo-display-progress
375          'elmo-localdir-search "Searching..."
376          (/ (* i 100) num)))
377       (setq msgs (cdr msgs)))
378     (nreverse ret-val)))
379
380 ;;; (localdir, maildir, localnews) -> localdir
381 (defun elmo-localdir-copy-msgs (dst-spec msgs src-spec
382                                          &optional loc-alist same-number)
383   (let ((dst-dir
384          (elmo-localdir-get-folder-directory dst-spec))
385         (next-num (1+ (car (elmo-localdir-list-folder-subr dst-spec t)))))
386     (while msgs
387       (elmo-copy-file
388        ;; src file
389        (elmo-call-func src-spec "get-msg-filename" (car msgs) loc-alist)
390        ;; dst file
391        (expand-file-name (int-to-string
392                           (if same-number (car msgs) next-num))
393                          dst-dir))
394       (if (and (setq msgs (cdr msgs))
395                (not same-number))
396           (setq next-num
397                 (if (and (eq (car dst-spec) 'localdir)
398                          (elmo-localdir-locked-p))
399                     ;; MDA is running.
400                     (1+ (car (elmo-localdir-list-folder-subr dst-spec t)))
401                   (1+ next-num)))))
402     t))
403
404 (defun elmo-localdir-pack-number (spec msgdb arg)
405   (let ((dir (elmo-localdir-get-folder-directory spec))
406         (onum-alist (elmo-msgdb-get-number-alist msgdb))
407         (omark-alist (elmo-msgdb-get-mark-alist msgdb))
408         (new-number 1)                  ; first ordinal position in localdir
409         flist onum mark new-mark-alist total)
410     (setq flist
411           (if elmo-pack-number-check-strict
412               (elmo-call-func spec "list-folder") ; allow localnews
413             (mapcar 'car onum-alist)))
414     (setq total (length flist))
415     (while flist
416       (when (> total elmo-display-progress-threshold)
417         (elmo-display-progress
418          'elmo-localdir-pack-number "Packing..."
419          (/ (* new-number 100) total)))
420       (setq onum (car flist))
421       (when (not (eq onum new-number))          ; why \=() is wrong..
422         (elmo-bind-directory
423          dir
424          ;; xxx  nfs,hardlink
425          (rename-file (int-to-string onum) (int-to-string new-number) t))
426         ;; update overview
427         (elmo-msgdb-overview-entity-set-number
428          (elmo-msgdb-overview-get-entity onum msgdb)
429          new-number)
430         ;; update number-alist
431         (setcar (assq onum onum-alist) new-number))
432       ;; update mark-alist
433       (when (setq mark (cadr (assq onum omark-alist)))
434         (setq new-mark-alist
435               (elmo-msgdb-mark-append
436                new-mark-alist
437                new-number mark)))
438       (setq new-number (1+ new-number))
439       (setq flist (cdr flist)))
440     (message "Packing...done.")
441     (list (elmo-msgdb-get-overview msgdb)
442           onum-alist
443           new-mark-alist
444           (elmo-msgdb-get-location msgdb)
445           ;; remake hash table
446           (elmo-msgdb-make-overview-hashtb (elmo-msgdb-get-overview msgdb)))))
447
448 (defun elmo-localdir-use-cache-p (spec number)
449   nil)
450
451 (defun elmo-localdir-local-file-p (spec number)
452   t)
453
454 (defun elmo-localdir-get-msg-filename (spec number &optional loc-alist)
455   (expand-file-name
456    (int-to-string number)
457    (elmo-localdir-get-folder-directory spec)))
458
459 (defun elmo-localdir-locked-p ()
460   (if elmo-localdir-lockfile-list
461       (let ((lock elmo-localdir-lockfile-list))
462         (catch 'found
463           (while lock
464             (if (file-exists-p (car lock))
465                 (throw 'found t))
466             (setq lock (cdr lock)))))))
467
468 (defalias 'elmo-localdir-sync-number-alist
469   'elmo-generic-sync-number-alist)
470 (defalias 'elmo-localdir-list-folder-unread
471   'elmo-generic-list-folder-unread)
472 (defalias 'elmo-localdir-list-folder-important
473   'elmo-generic-list-folder-important)
474 (defalias 'elmo-localdir-commit 'elmo-generic-commit)
475
476 (provide 'elmo-localdir)
477
478 ;;; elmo-localdir.el ends here