Synch up with main trunk and so on.
[elisp/wanderlust.git] / elmo / elmo-dop.el
1 ;;; elmo-dop.el -- Modules for Disconnected Operations on ELMO.
2
3 ;; Copyright (C) 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 'elmo)
33 (require 'elmo-vars)
34 (require 'elmo-msgdb)
35 (require 'elmo-util)
36
37 ;; global variable.
38 (defvar elmo-dop-queue nil
39   "A list of (folder-name function-to-be-called argument-list).
40 Automatically loaded/saved.")
41
42 (defun elmo-dop-queue-append (folder function argument)
43   (let ((operation (list (elmo-folder-name-internal folder)
44                          function argument)))
45     (elmo-dop-queue-load)
46     (unless (member operation elmo-dop-queue) ;; don't append same operation
47       (setq elmo-dop-queue
48             (append elmo-dop-queue
49                     (list operation)))
50       (elmo-dop-queue-save))))
51
52 (defun elmo-dop-queue-flush (&optional force)
53   "Flush Disconnected operations.
54 If optional argument FORCE is non-nil, try flushing all operation queues
55 even an operation concerns the unplugged folder."
56   (elmo-dop-queue-load) ; load cache.
57   (elmo-dop-queue-merge)
58   (let ((queue elmo-dop-queue)
59         (count 0)
60         len)
61     (while queue
62       (if (or force (elmo-folder-plugged-p (elmo-make-folder (caar queue))))
63           (setq count (1+ count)))
64       (setq queue (cdr queue)))
65     (when (> count 0)
66       (if (elmo-y-or-n-p
67            (format "%d pending operation(s) exists.  Perform now? " count)
68            (not elmo-dop-flush-confirm) t)
69           (progn
70             (message "")
71             (sit-for 0)
72             (let ((queue elmo-dop-queue)
73                   (performed 0)
74                   (i 0)
75                   (num (length elmo-dop-queue))
76                   folder func failure)
77               (while queue
78                 ;; now perform pending processes.
79                 (setq failure nil)
80                 (setq i (+ 1 i))
81                 (message "Flushing queue....%d/%d." i num)
82                 (condition-case err
83                     (if (and (not force)
84                              (not (elmo-folder-plugged-p (nth 0 (car queue)))))
85                         (setq failure t)
86                       (setq folder (nth 0 (car queue))
87                             func (nth 1 (car queue)))
88                       (cond
89                        ((string= func "prefetch-msgs")
90                         (elmo-prefetch-msgs
91                          folder
92                          (nth 2 (car queue)))) ;argunemt
93                        ((string= func "append-operations")
94                         (elmo-dop-flush-pending-append-operations
95                          folder nil t))
96                        (t
97                         (elmo-call-func
98                          folder
99                          func
100                          (nth 2 (car queue)) ;argunemt
101                          ))))
102                   (quit  (setq failure t))
103                   (error (setq failure err)))
104                 (if failure
105                     ;; create-folder was failed.
106                     (when (and (string= func "create-folder-maybe")
107                                (elmo-y-or-n-p
108                                 (format
109                                  "Create folder %s failed.  Abort creating? "
110                                  folder)
111                                 (not elmo-dop-flush-confirm) t))
112                       (elmo-dop-save-pending-messages folder)
113                       (setq elmo-dop-queue (delq (car queue) elmo-dop-queue)))
114                   (setq elmo-dop-queue (delq (car queue) elmo-dop-queue))
115                   (setq performed (+ 1 performed)))
116                 (setq queue (cdr queue)))
117               (message "%d/%d operation(s) are performed successfully."
118                        performed num)
119               (sit-for 1) ; 
120               (elmo-dop-queue-save)))
121         (if (elmo-y-or-n-p "Clear all pending operations? "
122                            (not elmo-dop-flush-confirm) t)
123             (let ((queue elmo-dop-queue))
124               (while queue
125                 (if (string= (nth 1 (car queue)) "append-operations")
126                     (elmo-dop-append-list-save (nth 0 (car queue)) nil))
127                 (setq queue (cdr queue)))
128               (setq elmo-dop-queue nil)
129               (message "All pending operations are cleared.")
130               (elmo-dop-queue-save))
131           (message "")))
132       count)))
133
134 (defconst elmo-dop-merge-funcs
135   '("delete-msgids"
136     "prefetch-msgs"
137     "unmark-important"
138     "mark-as-important"
139     "mark-as-read"
140     "mark-as-unread"))
141
142 (defun elmo-dop-queue-merge ()
143   (let ((queue elmo-dop-queue)
144         new-queue match-queue que)
145     (while (setq que (car queue))
146       (if (and
147            (member (cadr que) elmo-dop-merge-funcs)
148            (setq match-queue
149                  (car (delete nil
150                               (mapcar '(lambda (new-queue)
151                                          (if (and
152                                               (string= (car que) (car new-queue))
153                                               (string= (cadr que) (cadr new-queue)))
154                                              new-queue))
155                                       new-queue)))))
156           (setcar (cddr match-queue)
157                   (append (nth 2 match-queue) (nth 2 que)))
158         (setq new-queue (append new-queue (list que))))
159       (setq queue (cdr queue)))
160     (setq elmo-dop-queue new-queue)))
161
162 (defun elmo-dop-queue-load ()
163   (save-excursion
164     (setq elmo-dop-queue
165           (elmo-object-load
166            (expand-file-name elmo-queue-filename
167                              elmo-msgdb-dir)))))
168
169 (defun elmo-dop-queue-save ()
170   (save-excursion
171     (elmo-object-save
172      (expand-file-name elmo-queue-filename
173                        elmo-msgdb-dir)
174      elmo-dop-queue)))
175
176 (defun elmo-dop-lock-message (message-id &optional lock-list)
177   (let ((locked (or lock-list
178                     (elmo-object-load
179                      (expand-file-name
180                       elmo-msgdb-lock-list-filename
181                       elmo-msgdb-dir)))))
182     (setq locked (cons message-id locked))
183     (elmo-object-save
184      (expand-file-name elmo-msgdb-lock-list-filename
185                        elmo-msgdb-dir)
186      locked)))
187
188 (defun elmo-dop-unlock-message (message-id &optional lock-list)
189   (let ((locked (or lock-list
190                     (elmo-object-load
191                      (expand-file-name elmo-msgdb-lock-list-filename
192                                        elmo-msgdb-dir)))))
193     (setq locked (delete message-id locked))
194     (elmo-object-save
195      (expand-file-name elmo-msgdb-lock-list-filename
196                        elmo-msgdb-dir)
197      locked)))
198
199 (defun elmo-dop-lock-list-load ()
200   (elmo-object-load
201    (expand-file-name elmo-msgdb-lock-list-filename
202                      elmo-msgdb-dir)))
203
204 (defun elmo-dop-lock-list-save (lock-list)
205   (elmo-object-save
206    (expand-file-name elmo-msgdb-lock-list-filename
207                      elmo-msgdb-dir)
208    lock-list))
209
210 (defun elmo-dop-append-list-load (folder &optional resume)
211   (elmo-object-load
212    (expand-file-name (if resume
213                          elmo-msgdb-resume-list-filename
214                        elmo-msgdb-append-list-filename)
215                      (elmo-folder-msgdb-path folder))))
216
217 (defun elmo-dop-append-list-save (folder append-list &optional resume)
218   (if append-list
219       (elmo-object-save
220        (expand-file-name (if resume
221                              elmo-msgdb-resume-list-filename
222                            elmo-msgdb-append-list-filename)
223                          (elmo-folder-msgdb-path folder))
224        append-list)
225     (condition-case ()
226         (delete-file (expand-file-name (if resume
227                                            elmo-msgdb-resume-list-filename
228                                          elmo-msgdb-append-list-filename)
229                                        (elmo-folder-msgdb-path folder)))
230       (error))))
231
232 (defun elmo-dop-deleting-numbers-to-msgids (alist numbers appended)
233   "returns (new-appended . deleting-msgids)."
234   (let (msgid deleting-msgids)
235     (while numbers
236       (setq msgid (cdr (assq (car numbers) alist)))
237       (if (member msgid appended)
238           (setq appended (delete msgid appended))
239         (setq deleting-msgids (append deleting-msgids (list msgid))))
240       (setq numbers (cdr numbers)))
241     (cons appended deleting-msgids)))
242
243 (defun elmo-dop-list-deleted (name number-alist)
244   "List message numbers to be deleted on folder with NAME from NUMBER-ALIST."
245   (elmo-dop-queue-load)
246   (let ((queue elmo-dop-queue)
247         numbers matches nalist)
248     (while queue
249       (if (and (string= (nth 0 (car queue)) name)
250                (string= (nth 1 (car queue)) "delete-msgids"))
251           (setq numbers
252                 (nconc numbers
253                        (delq nil (mapcar
254                                   (lambda (x)
255                                     (mapcar 'car
256                                             (elmo-string-rassoc-all
257                                              x number-alist)))
258                                   (nth 2 (car queue)))))))
259       (setq queue (cdr queue)))
260     (elmo-uniq-list (elmo-flatten numbers))))
261
262 (defun elmo-dop-delete-msgs (folder msgs msgdb)
263   (save-match-data
264     (let ((folder-numbers (elmo-make-folder-numbers-list folder msgs))
265           appended-deleting)
266       (while folder-numbers
267         (if (eq (elmo-folder-get-type (car (car folder-numbers)))
268                 'imap4)
269             (if elmo-enable-disconnected-operation
270                 (progn
271                   (setq appended-deleting
272                         (elmo-dop-deleting-numbers-to-msgids
273                          (elmo-msgdb-get-number-alist msgdb)
274                          msgs ; virtual number
275                          (elmo-dop-append-list-load folder)))
276                   (if (cdr appended-deleting)
277                       (elmo-dop-queue-append
278                        (car (car folder-numbers)) ; real folder
279                        "delete-msgids" ;; for secure removal.
280                        (cdr appended-deleting)))
281                   (elmo-dop-append-list-save folder (car appended-deleting)))
282               (error "Unplugged"))
283           ;; not imap4 folder...delete now!
284           (elmo-call-func (car (car folder-numbers)) "delete-msgs"
285                           (cdr (car folder-numbers))))
286         (setq folder-numbers (cdr folder-numbers))))
287     t))
288
289 (defun elmo-dop-prefetch-msgs (folder msgs)
290   (save-match-data
291     (elmo-dop-queue-append folder "prefetch-msgs" msgs)))
292
293 (defun elmo-dop-list-folder (folder &optional nohide)
294   (if (or (memq (elmo-folder-get-type folder)
295                 '(imap4 nntp pop3 filter pipe))
296           (and (elmo-multi-p folder) (not (elmo-folder-local-p folder))))
297       (if elmo-enable-disconnected-operation
298           (let* ((path (elmo-msgdb-expand-path folder))
299                  (number-alist (elmo-msgdb-number-load path))
300                  (number-list (mapcar 'car number-alist))
301                  (append-list (elmo-dop-append-list-load folder))
302                  (append-num (length append-list))
303                  (killed (and elmo-use-killed-list
304                               (elmo-msgdb-killed-list-load path)))
305                  alreadies
306                  max-num
307                  (i 0))
308             (setq killed (nconc (elmo-dop-list-deleted folder number-alist)
309                                 killed))
310             (while append-list
311               (if (rassoc (car append-list) number-alist)
312                   (setq alreadies (append alreadies
313                                           (list (car append-list)))))
314               (setq append-list (cdr append-list)))
315             (setq append-num (- append-num (length alreadies)))
316             (setq max-num
317                   (or (nth (max (- (length number-list) 1) 0)
318                            number-list) 0))
319             (while (< i append-num)
320               (setq number-list
321                     (append number-list
322                             (list (+ max-num i 1))))
323               (setq i (+ 1 i)))
324             (elmo-living-messages number-list killed))
325         (error "Unplugged"))
326     ;; not imap4 folder...list folder
327     (elmo-call-func folder "list-folder")))
328
329 (defun elmo-dop-count-appended (folder)
330   (length (elmo-dop-append-list-load folder)))
331
332 (defun elmo-dop-call-func-on-msgs (folder func-name msgs msgdb)
333   (let ((append-list (elmo-dop-append-list-load folder))
334         (number-alist (elmo-msgdb-get-number-alist msgdb))
335         matched)
336     (if (eq (elmo-folder-get-type folder) 'imap4)
337         (progn
338 ;;;       (while append-list
339 ;;;         (if (setq matched (car (rassoc (car append-list) number-alist)))
340 ;;;             (setq msgs (delete matched msgs)))
341 ;;;         (setq append-list (cdr append-list)))
342           (if msgs
343               (elmo-dop-queue-append folder func-name msgs)))
344       ;; maildir... XXX hard coding.....
345       (if (not (featurep 'elmo-maildir))
346           (require 'maildir))
347       (funcall (intern (format "elmo-maildir-%s" func-name))
348                (elmo-folder-get-spec folder)
349                msgs msgdb))))
350
351 (defun elmo-dop-folder-status (folder)
352   (let* ((number-alist (elmo-msgdb-number-load
353                         (elmo-folder-msgdb-path folder)))
354          (number-list (mapcar 'car number-alist))
355          (append-list (elmo-dop-append-list-load folder))
356          (append-num (length append-list))
357          alreadies
358          (i 0)
359          max-num)
360     (while append-list
361       (if (rassoc (car append-list) number-alist)
362           (setq alreadies (append alreadies
363                                   (list (car append-list)))))
364       (setq append-list (cdr append-list)))
365     (setq max-num
366           (or (nth (max (- (length number-list) 1) 0) number-list)
367               0))
368     (cons (- (+ max-num append-num) (length alreadies))
369           (- (+ (length number-list) append-num) (length alreadies)))))
370
371 (defun elmo-dop-max-of-folder (folder)
372   (if (eq (elmo-folder-get-type folder) 'imap4)
373       (if elmo-enable-disconnected-operation
374           (let* ((number-alist (elmo-msgdb-number-load
375                                 (elmo-msgdb-expand-path folder)))
376                  (number-list (mapcar 'car number-alist))
377                  (append-list (elmo-dop-append-list-load folder))
378                  (append-num (length append-list))
379                  alreadies
380                  (i 0)
381                  max-num)
382             (while append-list
383               (if (rassoc (car append-list) number-alist)
384                   (setq alreadies (append alreadies
385                                           (list (car append-list)))))
386               (setq append-list (cdr append-list)))
387             (setq max-num
388                   (or (nth (max (- (length number-list) 1) 0) number-list)
389                       0))
390             (cons (- (+ max-num append-num) (length alreadies))
391                   (- (+ (length number-list) append-num) (length alreadies))))
392         (error "Unplugged"))
393     ;; not imap4 folder.
394     (elmo-call-func folder "max-of-folder")))
395
396 (defun elmo-dop-save-pending-messages (folder)
397   (message (format "Saving queued message in %s..." elmo-lost+found-folder))
398   (let* ((append-list (elmo-dop-append-list-load folder))
399          file-string)
400     (while append-list
401       (when (setq file-string (elmo-get-file-string  ; message string
402                                (elmo-cache-get-path
403                                 (car append-list))))
404         (elmo-append-msg elmo-lost+found-folder file-string)
405         (elmo-dop-unlock-message (car append-list)))
406       (setq append-list (cdr append-list))
407       (elmo-dop-append-list-save folder nil)))
408   (message (format "Saving queued message in %s...done"
409                    elmo-lost+found-folder)))
410
411 (defun elmo-dop-flush-pending-append-operations (folder &optional appends resume)
412   (message "Appending queued messages...")
413   (let* ((append-list (or appends
414                           (elmo-dop-append-list-load folder)))
415          (appendings append-list)
416          (i 0)
417          (num (length append-list))
418          failure file-string)
419     (when resume
420       ;; Resume msgdb changed by elmo-dop-msgdb-create.
421       (let* ((resumed-list (elmo-dop-append-list-load folder t))
422              (number-alist (elmo-msgdb-number-load
423                             (elmo-msgdb-expand-path folder)))
424              (appendings append-list)
425              pair dels)
426         (while appendings
427           (if (setq pair (rassoc (car appendings) number-alist))
428               (setq resumed-list (append resumed-list
429                                          (list (car appendings)))))
430           (setq appendings (cdr appendings)))
431         (elmo-dop-append-list-save folder resumed-list t)))
432     (while appendings
433       (let* ((seen-list (elmo-msgdb-seen-load
434                          (elmo-msgdb-expand-path folder))))
435         (setq failure nil)
436         (setq file-string (elmo-get-file-string  ; message string
437                            (elmo-cache-get-path
438                             (car appendings))))
439         (when file-string
440           (condition-case ()
441               (elmo-append-msg folder file-string (car appendings) nil
442                                (not (member (car appendings) seen-list)))
443             (quit  (setq failure t))
444             (error (setq failure t)))
445           (setq i (+ 1 i))
446           (message (format "Appending queued messages...%d" i))
447           (if failure
448               (elmo-append-msg elmo-lost+found-folder
449                                file-string (car appendings) nil
450                                (not (member (car appendings) seen-list)))))
451         (elmo-dop-unlock-message (car appendings))
452         (setq appendings (cdr appendings))))
453     ;; All pending append operation is flushed.
454     (elmo-dop-append-list-save folder nil)
455     (elmo-commit folder)
456     (unless resume
457       ;; delete '(folder "append-operations") in elmo-dop-queue.
458       (let (elmo-dop-queue)
459         (elmo-dop-queue-load)
460         (setq elmo-dop-queue (delete (list folder "append-operations" nil)
461                                      elmo-dop-queue))
462         (elmo-dop-queue-save))))
463   (message "Appending queued messages...done"))
464
465 (defun elmo-dop-folder-exists-p (folder)
466   (or (file-exists-p (elmo-msgdb-expand-path folder))
467       (if (and elmo-enable-disconnected-operation
468                (eq (elmo-folder-get-type folder) 'imap4))
469           (file-exists-p (elmo-msgdb-expand-path folder))
470         (elmo-call-func folder "folder-exists-p"))))
471
472 (defun elmo-dop-create-folder (folder)
473   (if (eq (elmo-folder-get-type folder) 'imap4)
474       (if elmo-enable-disconnected-operation
475           (elmo-dop-queue-append folder "create-folder-maybe" nil)
476         (error "Unplugged"))
477     (elmo-call-func folder "create-folder")))
478
479 (defun elmo-dop-delete-folder (folder)
480   (error "Unplugged"))
481
482 (defun elmo-dop-rename-folder (old-folder new-folder)
483   (error "Unplugged"))
484
485 (defun elmo-dop-append-msg (folder string message-id &optional msg)
486   (if elmo-enable-disconnected-operation
487       (if message-id
488           (progn
489             (unless (elmo-cache-exists-p message-id)
490               (elmo-set-work-buf
491                (insert string)
492                (elmo-cache-save message-id nil folder msg (current-buffer))))
493             (let ((append-list (elmo-dop-append-list-load folder))
494                   (number-alist (elmo-msgdb-number-load
495                                  (elmo-msgdb-expand-path folder))))
496               (when (and ; not in current folder.
497                      (not (rassoc message-id number-alist))
498                      (not (member message-id append-list)))
499                 (setq append-list
500                       (append append-list (list message-id)))
501                 (elmo-dop-lock-message message-id)
502                 (elmo-dop-append-list-save folder append-list)
503                 (elmo-dop-queue-append folder "append-operations" nil))
504               t))
505         nil)
506     (error "Unplugged")))
507
508 (defalias 'elmo-dop-msgdb-create 'elmo-dop-msgdb-create-as-numlist)
509
510 (defun elmo-dop-msgdb-create-as-numlist (folder numlist new-mark already-mark
511                                                 seen-mark important-mark
512                                                 seen-list)
513   (if (or (eq (elmo-folder-get-type folder) 'imap4)
514           (eq (elmo-folder-get-type folder) 'nntp))
515       (if elmo-enable-disconnected-operation
516           (let* ((num-alist (elmo-msgdb-number-load
517                              (elmo-msgdb-expand-path folder)))
518                  (number-list (mapcar 'car num-alist))
519                  (ov (elmo-msgdb-overview-load
520                       (elmo-msgdb-expand-path folder)))
521                  (append-list (elmo-dop-append-list-load folder))
522                  (num (length numlist))
523                  (i 0)
524                  overview number-alist mark-alist msgid ov-entity
525                  max-num percent seen gmark)
526             (setq max-num
527                   (or (nth (max (- (length number-list) 1) 0) number-list)
528                       0))
529             (while numlist
530               (if (setq msgid
531                         (nth (+ (length append-list)
532                                 (- (car numlist) max-num 1 num))
533                              append-list))
534                   (progn
535                     (setq overview
536                           (elmo-msgdb-append-element
537                            overview
538                            (elmo-localdir-msgdb-create-overview-entity-from-file
539                             (car numlist)
540                             (elmo-cache-get-path msgid))))
541                     (setq number-alist
542                           (elmo-msgdb-number-add number-alist
543                                                  (car numlist) msgid))
544                     (setq seen (member msgid seen-list))
545                     (if (setq gmark
546                               (or (elmo-msgdb-global-mark-get msgid)
547                                   (if (elmo-cache-exists-p
548                                        msgid
549                                        folder
550                                        (car number-alist))
551                                       (if seen
552                                           nil
553                                         already-mark)
554                                     (if seen
555                                         seen-mark)
556                                     new-mark)))
557                         (setq mark-alist
558                               (elmo-msgdb-mark-append
559                                mark-alist (car numlist) gmark))))
560                 
561                 (when (setq ov-entity (assoc
562                                        (cdr (assq (car numlist) num-alist))
563                                        ov))
564                   (setq overview
565                         (elmo-msgdb-append-element
566                          overview ov-entity))
567                   (setq number-alist
568                         (elmo-msgdb-number-add number-alist
569                                                (car numlist)
570                                                (car ov-entity)))
571                   (setq seen (member ov-entity seen-list))
572                   (if (setq gmark
573                             (or (elmo-msgdb-global-mark-get (car ov-entity))
574                                 (if (elmo-cache-exists-p
575                                      msgid
576                                      folder
577                                      (car ov-entity))
578                                     (if seen
579                                         nil
580                                       already-mark)
581                                   (if seen
582                                       seen-mark)
583                                   new-mark)))
584                       (setq mark-alist
585                             (elmo-msgdb-mark-append
586                              mark-alist (car numlist) gmark)))))
587               (when (> num elmo-display-progress-threshold)
588                 (setq i (1+ i))
589                 (setq percent (/ (* i 100) num))
590                 (elmo-display-progress
591                  'elmo-dop-msgdb-create-as-numlist "Creating msgdb..."
592                  percent))
593               (setq numlist (cdr numlist)))
594             (list overview number-alist mark-alist))
595         (error "Unplugged"))
596     ;; not imap4 folder...
597     (elmo-call-func folder "msgdb-create" numlist new-mark already-mark
598                     seen-mark important-mark seen-list)))
599
600 (require 'product)
601 (product-provide (provide 'elmo-dop) (require 'elmo-version))
602
603 ;;; elmo-dop.el ends here