* wl-summary.el (wl-summary-get-original-buffer): New function.
[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-messages (folder)
294   (let* ((path (elmo-msgdb-expand-path folder))
295          (number-alist (elmo-msgdb-number-load path))
296          (number-list (mapcar 'car number-alist))
297          (append-list (elmo-dop-append-list-load folder))
298          (append-num (length append-list))
299          alreadies
300          killed
301          max-num
302          (i 0))
303     (setq killed (elmo-dop-list-deleted folder number-alist))
304     (while append-list
305       (if (rassoc (car append-list) number-alist)
306           (setq alreadies (append alreadies
307                                   (list (car append-list)))))
308       (setq append-list (cdr append-list)))
309     (setq append-num (- append-num (length alreadies)))
310     (setq max-num
311           (or (nth (max (- (length number-list) 1) 0)
312                    number-list) 0))
313     (while (< i append-num)
314       (setq number-list
315             (append number-list
316                     (list (+ max-num i 1))))
317       (setq i (+ 1 i)))
318     (elmo-living-messages number-list killed)))
319
320 (defun elmo-dop-count-appended (folder)
321   (length (elmo-dop-append-list-load folder)))
322
323 (defun elmo-dop-call-func-on-msgs (folder func-name msgs msgdb)
324   (let ((append-list (elmo-dop-append-list-load folder))
325         (number-alist (elmo-msgdb-get-number-alist msgdb))
326         matched)
327     (if (eq (elmo-folder-get-type folder) 'imap4)
328         (progn
329 ;;;       (while append-list
330 ;;;         (if (setq matched (car (rassoc (car append-list) number-alist)))
331 ;;;             (setq msgs (delete matched msgs)))
332 ;;;         (setq append-list (cdr append-list)))
333           (if msgs
334               (elmo-dop-queue-append folder func-name msgs)))
335       ;; maildir... XXX hard coding.....
336       (if (not (featurep 'elmo-maildir))
337           (require 'maildir))
338       (funcall (intern (format "elmo-maildir-%s" func-name))
339                (elmo-folder-get-spec folder)
340                msgs msgdb))))
341
342 (defun elmo-dop-folder-status (folder)
343   (let* ((number-alist (elmo-msgdb-number-load
344                         (elmo-folder-msgdb-path folder)))
345          (number-list (mapcar 'car number-alist))
346          (append-list (elmo-dop-append-list-load folder))
347          (append-num (length append-list))
348          alreadies
349          (i 0)
350          max-num)
351     (while append-list
352       (if (rassoc (car append-list) number-alist)
353           (setq alreadies (append alreadies
354                                   (list (car append-list)))))
355       (setq append-list (cdr append-list)))
356     (setq max-num
357           (or (nth (max (- (length number-list) 1) 0) number-list)
358               0))
359     (cons (- (+ max-num append-num) (length alreadies))
360           (- (+ (length number-list) append-num) (length alreadies)))))
361
362 (defun elmo-dop-max-of-folder (folder)
363   (if (eq (elmo-folder-get-type folder) 'imap4)
364       (if elmo-enable-disconnected-operation
365           (let* ((number-alist (elmo-msgdb-number-load
366                                 (elmo-msgdb-expand-path folder)))
367                  (number-list (mapcar 'car number-alist))
368                  (append-list (elmo-dop-append-list-load folder))
369                  (append-num (length append-list))
370                  alreadies
371                  (i 0)
372                  max-num)
373             (while append-list
374               (if (rassoc (car append-list) number-alist)
375                   (setq alreadies (append alreadies
376                                           (list (car append-list)))))
377               (setq append-list (cdr append-list)))
378             (setq max-num
379                   (or (nth (max (- (length number-list) 1) 0) number-list)
380                       0))
381             (cons (- (+ max-num append-num) (length alreadies))
382                   (- (+ (length number-list) append-num) (length alreadies))))
383         (error "Unplugged"))
384     ;; not imap4 folder.
385     (elmo-call-func folder "max-of-folder")))
386
387 (defun elmo-dop-save-pending-messages (folder)
388   (message (format "Saving queued message in %s..." elmo-lost+found-folder))
389   (let* ((append-list (elmo-dop-append-list-load folder))
390          file-string)
391     (while append-list
392       (when (setq file-string (elmo-get-file-string  ; message string
393                                (elmo-cache-get-path
394                                 (car append-list))))
395         (elmo-append-msg elmo-lost+found-folder file-string)
396         (elmo-dop-unlock-message (car append-list)))
397       (setq append-list (cdr append-list))
398       (elmo-dop-append-list-save folder nil)))
399   (message (format "Saving queued message in %s...done"
400                    elmo-lost+found-folder)))
401
402 (defun elmo-dop-flush-pending-append-operations (folder &optional appends resume)
403   (message "Appending queued messages...")
404   (let* ((append-list (or appends
405                           (elmo-dop-append-list-load folder)))
406          (appendings append-list)
407          (i 0)
408          (num (length append-list))
409          failure file-string)
410     (when resume
411       ;; Resume msgdb changed by elmo-dop-msgdb-create.
412       (let* ((resumed-list (elmo-dop-append-list-load folder t))
413              (number-alist (elmo-msgdb-number-load
414                             (elmo-msgdb-expand-path folder)))
415              (appendings append-list)
416              pair dels)
417         (while appendings
418           (if (setq pair (rassoc (car appendings) number-alist))
419               (setq resumed-list (append resumed-list
420                                          (list (car appendings)))))
421           (setq appendings (cdr appendings)))
422         (elmo-dop-append-list-save folder resumed-list t)))
423     (while appendings
424       (let* ((seen-list (elmo-msgdb-seen-load
425                          (elmo-msgdb-expand-path folder))))
426         (setq failure nil)
427         (setq file-string (elmo-get-file-string  ; message string
428                            (elmo-cache-get-path
429                             (car appendings))))
430         (when file-string
431           (condition-case ()
432               (elmo-append-msg folder file-string (car appendings) nil
433                                (not (member (car appendings) seen-list)))
434             (quit  (setq failure t))
435             (error (setq failure t)))
436           (setq i (+ 1 i))
437           (message (format "Appending queued messages...%d" i))
438           (if failure
439               (elmo-append-msg elmo-lost+found-folder
440                                file-string (car appendings) nil
441                                (not (member (car appendings) seen-list)))))
442         (elmo-dop-unlock-message (car appendings))
443         (setq appendings (cdr appendings))))
444     ;; All pending append operation is flushed.
445     (elmo-dop-append-list-save folder nil)
446     (elmo-commit folder)
447     (unless resume
448       ;; delete '(folder "append-operations") in elmo-dop-queue.
449       (let (elmo-dop-queue)
450         (elmo-dop-queue-load)
451         (setq elmo-dop-queue (delete (list folder "append-operations" nil)
452                                      elmo-dop-queue))
453         (elmo-dop-queue-save))))
454   (message "Appending queued messages...done"))
455
456 (defun elmo-dop-folder-exists-p (folder)
457   (or (file-exists-p (elmo-msgdb-expand-path folder))
458       (if (and elmo-enable-disconnected-operation
459                (eq (elmo-folder-get-type folder) 'imap4))
460           (file-exists-p (elmo-msgdb-expand-path folder))
461         (elmo-call-func folder "folder-exists-p"))))
462
463 (defun elmo-dop-create-folder (folder)
464   (if (eq (elmo-folder-get-type folder) 'imap4)
465       (if elmo-enable-disconnected-operation
466           (elmo-dop-queue-append folder "create-folder-maybe" nil)
467         (error "Unplugged"))
468     (elmo-call-func folder "create-folder")))
469
470 (defun elmo-dop-delete-folder (folder)
471   (error "Unplugged"))
472
473 (defun elmo-dop-rename-folder (old-folder new-folder)
474   (error "Unplugged"))
475
476 (defun elmo-dop-append-msg (folder string message-id &optional msg)
477   (if elmo-enable-disconnected-operation
478       (if message-id
479           (progn
480             (unless (elmo-cache-exists-p message-id)
481               (elmo-set-work-buf
482                (insert string)
483                (elmo-cache-save message-id nil folder msg (current-buffer))))
484             (let ((append-list (elmo-dop-append-list-load folder))
485                   (number-alist (elmo-msgdb-number-load
486                                  (elmo-msgdb-expand-path folder))))
487               (when (and ; not in current folder.
488                      (not (rassoc message-id number-alist))
489                      (not (member message-id append-list)))
490                 (setq append-list
491                       (append append-list (list message-id)))
492                 (elmo-dop-lock-message message-id)
493                 (elmo-dop-append-list-save folder append-list)
494                 (elmo-dop-queue-append folder "append-operations" nil))
495               t))
496         nil)
497     (error "Unplugged")))
498
499 (defalias 'elmo-dop-msgdb-create 'elmo-dop-msgdb-create-as-numlist)
500
501 (defun elmo-dop-msgdb-create-as-numlist (folder numlist new-mark already-mark
502                                                 seen-mark important-mark
503                                                 seen-list)
504   (if (or (eq (elmo-folder-get-type folder) 'imap4)
505           (eq (elmo-folder-get-type folder) 'nntp))
506       (if elmo-enable-disconnected-operation
507           (let* ((num-alist (elmo-msgdb-number-load
508                              (elmo-msgdb-expand-path folder)))
509                  (number-list (mapcar 'car num-alist))
510                  (ov (elmo-msgdb-overview-load
511                       (elmo-msgdb-expand-path folder)))
512                  (append-list (elmo-dop-append-list-load folder))
513                  (num (length numlist))
514                  (i 0)
515                  overview number-alist mark-alist msgid ov-entity
516                  max-num percent seen gmark)
517             (setq max-num
518                   (or (nth (max (- (length number-list) 1) 0) number-list)
519                       0))
520             (while numlist
521               (if (setq msgid
522                         (nth (+ (length append-list)
523                                 (- (car numlist) max-num 1 num))
524                              append-list))
525                   (progn
526                     (setq overview
527                           (elmo-msgdb-append-element
528                            overview
529                            (elmo-localdir-msgdb-create-overview-entity-from-file
530                             (car numlist)
531                             (elmo-cache-get-path msgid))))
532                     (setq number-alist
533                           (elmo-msgdb-number-add number-alist
534                                                  (car numlist) msgid))
535                     (setq seen (member msgid seen-list))
536                     (if (setq gmark
537                               (or (elmo-msgdb-global-mark-get msgid)
538                                   (if (elmo-cache-exists-p
539                                        msgid
540                                        folder
541                                        (car number-alist))
542                                       (if seen
543                                           nil
544                                         already-mark)
545                                     (if seen
546                                         seen-mark)
547                                     new-mark)))
548                         (setq mark-alist
549                               (elmo-msgdb-mark-append
550                                mark-alist (car numlist) gmark))))
551                 
552                 (when (setq ov-entity (assoc
553                                        (cdr (assq (car numlist) num-alist))
554                                        ov))
555                   (setq overview
556                         (elmo-msgdb-append-element
557                          overview ov-entity))
558                   (setq number-alist
559                         (elmo-msgdb-number-add number-alist
560                                                (car numlist)
561                                                (car ov-entity)))
562                   (setq seen (member ov-entity seen-list))
563                   (if (setq gmark
564                             (or (elmo-msgdb-global-mark-get (car ov-entity))
565                                 (if (elmo-cache-exists-p
566                                      msgid
567                                      folder
568                                      (car ov-entity))
569                                     (if seen
570                                         nil
571                                       already-mark)
572                                   (if seen
573                                       seen-mark)
574                                   new-mark)))
575                       (setq mark-alist
576                             (elmo-msgdb-mark-append
577                              mark-alist (car numlist) gmark)))))
578               (when (> num elmo-display-progress-threshold)
579                 (setq i (1+ i))
580                 (setq percent (/ (* i 100) num))
581                 (elmo-display-progress
582                  'elmo-dop-msgdb-create-as-numlist "Creating msgdb..."
583                  percent))
584               (setq numlist (cdr numlist)))
585             (list overview number-alist mark-alist))
586         (error "Unplugged"))
587     ;; not imap4 folder...
588     (elmo-call-func folder "msgdb-create" numlist new-mark already-mark
589                     seen-mark important-mark seen-list)))
590
591 (require 'product)
592 (product-provide (provide 'elmo-dop) (require 'elmo-version))
593
594 ;;; elmo-dop.el ends here