Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-operation.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Michael Zucchi <NotZed@ximian.com>
4  *
5  *  Copyright 2003 Ximian, Inc. (www.ximian.com)
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <pthread.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <sys/time.h>
31
32 #ifdef HAVE_NSS
33 #include <nspr.h>
34 #endif
35
36 #include <glib.h>
37
38 #include <libedataserver/e-msgport.h>
39
40 #include "camel-operation.h"
41
42 #define d(x)
43
44 /* ********************************************************************** */
45
46 struct _status_stack {
47         guint32 flags;
48         char *msg;
49         int pc;                         /* last pc reported */
50         unsigned int stamp;             /* last stamp reported */
51 };
52
53 struct _CamelOperation {
54         struct _CamelOperation *next;
55         struct _CamelOperation *prev;
56
57         pthread_t id;           /* id of running thread */
58         guint32 flags;          /* cancelled ? */
59         int blocked;            /* cancellation blocked depth */
60         int refcount;
61
62         CamelOperationStatusFunc status;
63         void *status_data;
64         unsigned int status_update;
65
66         /* stack of status messages (struct _status_stack *) */
67         GSList *status_stack;
68         struct _status_stack *lastreport;
69
70         EMsgPort *cancel_port;
71         int cancel_fd;
72 #ifdef HAVE_NSS
73         PRFileDesc *cancel_prfd;
74 #endif
75 };
76
77 #define CAMEL_OPERATION_CANCELLED (1<<0)
78 #define CAMEL_OPERATION_TRANSIENT (1<<1)
79
80 /* Delay before a transient operation has any effect on the status */
81 #define CAMEL_OPERATION_TRANSIENT_DELAY (5)
82
83 static pthread_mutex_t operation_lock = PTHREAD_MUTEX_INITIALIZER;
84 #define LOCK() pthread_mutex_lock(&operation_lock)
85 #define UNLOCK() pthread_mutex_unlock(&operation_lock)
86
87
88 static unsigned int stamp (void);
89 static EDList operation_list = E_DLIST_INITIALISER(operation_list);
90 static pthread_key_t operation_key;
91 static pthread_once_t operation_once = PTHREAD_ONCE_INIT;
92
93 typedef struct _CamelOperationMsg {
94         EMsg msg;
95 } CamelOperationMsg ;
96
97 static void
98 co_createspecific(void)
99 {
100         pthread_key_create(&operation_key, NULL);
101 }
102
103 static CamelOperation *
104 co_getcc(void)
105 {
106         pthread_once(&operation_once, co_createspecific);
107
108         return (CamelOperation *)pthread_getspecific(operation_key);
109 }
110
111 /**
112  * camel_operation_new:
113  * @status: Callback for receiving status messages.  This will always
114  * be called with an internal lock held.
115  * @status_data: User data.
116  * 
117  * Create a new camel operation handle.  Camel operation handles can
118  * be used in a multithreaded application (or a single operation
119  * handle can be used in a non threaded appliation) to cancel running
120  * operations and to obtain notification messages of the internal
121  * status of messages.
122  * 
123  * Return value: A new operation handle.
124  **/
125 CamelOperation *
126 camel_operation_new (CamelOperationStatusFunc status, void *status_data)
127 {
128         CamelOperation *cc;
129
130         cc = g_malloc0(sizeof(*cc));
131
132         cc->flags = 0;
133         cc->blocked = 0;
134         cc->refcount = 1;
135         cc->status = status;
136         cc->status_data = status_data;
137         cc->cancel_port = e_msgport_new();
138         cc->cancel_fd = -1;
139
140         LOCK();
141         e_dlist_addtail(&operation_list, (EDListNode *)cc);
142         UNLOCK();
143         
144         return cc;
145 }
146
147 /**
148  * camel_operation_mute:
149  * @cc: 
150  * 
151  * mutes a camel operation permanently.  from this point on you will never
152  * receive operation updates, even if more are sent.
153  **/
154 void
155 camel_operation_mute(CamelOperation *cc)
156 {
157         LOCK();
158         cc->status = NULL;
159         cc->status_data = NULL;
160         UNLOCK();
161 }
162
163 /**
164  * camel_operation_registered:
165  *
166  * Returns the registered operation, or %NULL if none registered.
167  **/
168 CamelOperation *
169 camel_operation_registered (void)
170 {
171         CamelOperation *cc = co_getcc();
172
173         if (cc)
174                 camel_operation_ref(cc);
175
176         return cc;
177 }
178
179 /**
180  * camel_operation_ref:
181  * @cc: operation context
182  * 
183  * Add a reference to the CamelOperation @cc.
184  **/
185 void
186 camel_operation_ref (CamelOperation *cc)
187 {
188         g_assert(cc->refcount > 0);
189
190         LOCK();
191         cc->refcount++;
192         UNLOCK();
193 }
194
195 /**
196  * camel_operation_unref:
197  * @cc: operation context
198  * 
199  * Unref and potentially free @cc.
200  **/
201 void
202 camel_operation_unref (CamelOperation *cc)
203 {
204         GSList *n;
205
206         g_assert(cc->refcount > 0);
207
208         LOCK();
209         if (cc->refcount == 1) {
210                 CamelOperationMsg *msg;
211                 
212                 e_dlist_remove((EDListNode *)cc);
213
214                 while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
215                         g_free(msg);
216
217                 e_msgport_destroy(cc->cancel_port);
218                 
219                 n = cc->status_stack;
220                 while (n) {
221                         g_warning("Camel operation status stack non empty: %s", (char *)n->data);
222                         g_free(n->data);
223                         n = n->next;
224                 }
225                 g_slist_free(cc->status_stack);
226
227                 g_free(cc);
228         } else {
229                 cc->refcount--;
230         }
231         UNLOCK();
232 }
233
234 /**
235  * camel_operation_cancel_block:
236  * @cc: operation context
237  * 
238  * Block cancellation for this operation.  If @cc is NULL, then the
239  * current thread is blocked.
240  **/
241 void
242 camel_operation_cancel_block (CamelOperation *cc)
243 {
244         if (cc == NULL)
245                 cc = co_getcc();
246
247         if (cc) {
248                 LOCK();
249                 cc->blocked++;
250                 UNLOCK();
251         }
252 }
253
254 /**
255  * camel_operation_cancel_unblock:
256  * @cc: operation context
257  * 
258  * Unblock cancellation, when the unblock count reaches the block
259  * count, then this operation can be cancelled.  If @cc is NULL, then
260  * the current thread is unblocked.
261  **/
262 void
263 camel_operation_cancel_unblock (CamelOperation *cc)
264 {
265         if (cc == NULL)
266                 cc = co_getcc();
267
268         if (cc) {
269                 LOCK();
270                 cc->blocked--;
271                 UNLOCK();
272         }
273 }
274
275 /**
276  * camel_operation_cancel:
277  * @cc: operation context
278  * 
279  * Cancel a given operation.  If @cc is NULL then all outstanding
280  * operations are cancelled.
281  **/
282 void
283 camel_operation_cancel (CamelOperation *cc)
284 {
285         CamelOperationMsg *msg;
286         
287         LOCK();
288
289         if (cc == NULL) {
290                 CamelOperation *cn;
291
292                 cc = (CamelOperation *)operation_list.head;
293                 cn = cc->next;
294                 while (cn) {
295                         cc->flags |= CAMEL_OPERATION_CANCELLED;
296                         msg = g_malloc0(sizeof(*msg));
297                         e_msgport_put(cc->cancel_port, (EMsg *)msg);
298                         cc = cn;
299                         cn = cn->next;
300                 }
301         } else if ((cc->flags & CAMEL_OPERATION_CANCELLED) == 0) {
302                 d(printf("cancelling thread %d\n", cc->id));
303
304                 cc->flags |= CAMEL_OPERATION_CANCELLED;
305                 msg = g_malloc0(sizeof(*msg));
306                 e_msgport_put(cc->cancel_port, (EMsg *)msg);
307         }
308
309         UNLOCK();
310 }
311
312 /**
313  * camel_operation_uncancel:
314  * @cc: operation context
315  * 
316  * Uncancel a cancelled operation.  If @cc is NULL then the current
317  * operation is uncancelled.
318  *
319  * This is useful, if e.g. you need to do some cleaning up where a
320  * cancellation lying around in the same thread will abort any
321  * processing.
322  **/
323 void
324 camel_operation_uncancel(CamelOperation *cc)
325 {
326         if (cc == NULL)
327                 cc = co_getcc();
328
329         if (cc) {
330                 CamelOperationMsg *msg;
331
332                 LOCK();
333                 while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
334                         g_free(msg);
335
336                 cc->flags &= ~CAMEL_OPERATION_CANCELLED;
337                 UNLOCK();
338         }
339 }
340
341 /**
342  * camel_operation_register:
343  * @cc: operation context
344  * 
345  * Register a thread or the main thread for cancellation through @cc.
346  * If @cc is NULL, then a new cancellation is created for this thread.
347  *
348  * All calls to operation_register() should save their value and call
349  * operation_register again with that, to automatically stack
350  * registrations.
351  *
352  * Return Value: Returns the previously registered operatoin.
353  *
354  **/
355 CamelOperation *
356 camel_operation_register (CamelOperation *cc)
357 {
358         CamelOperation *oldcc = co_getcc();
359
360         pthread_setspecific(operation_key, cc);
361
362         return oldcc;
363 }
364
365 /**
366  * camel_operation_unregister:
367  * @cc: operation context
368  * 
369  * Unregister the current thread for all cancellations.
370  **/
371 void
372 camel_operation_unregister (CamelOperation *cc)
373 {
374         pthread_once(&operation_once, co_createspecific);
375         pthread_setspecific(operation_key, NULL);
376 }
377
378 /**
379  * camel_operation_cancel_check:
380  * @cc: operation context
381  * 
382  * Check if cancellation has been applied to @cc.  If @cc is NULL,
383  * then the CamelOperation registered for the current thread is used.
384  * 
385  * Return value: TRUE if the operation has been cancelled.
386  **/
387 gboolean
388 camel_operation_cancel_check (CamelOperation *cc)
389 {
390         CamelOperationMsg *msg;
391         int cancelled;
392
393         d(printf("checking for cancel in thread %d\n", pthread_self()));
394
395         if (cc == NULL)
396                 cc = co_getcc();
397
398         LOCK();
399
400         if (cc == NULL || cc->blocked > 0) {
401                 d(printf("ahah!  cancellation is blocked\n"));
402                 cancelled = FALSE;
403         } else if (cc->flags & CAMEL_OPERATION_CANCELLED) {
404                 d(printf("previously cancelled\n"));
405                 cancelled = TRUE;
406         } else if ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) {
407                 d(printf("Got cancellation message\n"));
408                 do {
409                         g_free(msg);
410                 } while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)));
411                 cc->flags |= CAMEL_OPERATION_CANCELLED;
412                 cancelled = TRUE;
413         } else
414                 cancelled = FALSE;
415
416         UNLOCK();
417
418         return cancelled;
419 }
420
421 /**
422  * camel_operation_cancel_fd:
423  * @cc: operation context
424  * 
425  * Retrieve a file descriptor that can be waited on (select, or poll)
426  * for read, to asynchronously detect cancellation.
427  * 
428  * Return value: The fd, or -1 if cancellation is not available
429  * (blocked, or has not been registered for this thread).
430  **/
431 int
432 camel_operation_cancel_fd (CamelOperation *cc)
433 {
434         if (cc == NULL)
435                 cc = co_getcc();
436
437         if (cc == NULL || cc->blocked)
438                 return -1;
439
440         LOCK();
441
442         if (cc->cancel_fd == -1)
443                 cc->cancel_fd = e_msgport_fd(cc->cancel_port);
444
445         UNLOCK();
446
447         return cc->cancel_fd;
448 }
449
450 #ifdef HAVE_NSS
451 /**
452  * camel_operation_cancel_prfd:
453  * @cc: operation context
454  * 
455  * Retrieve a file descriptor that can be waited on (select, or poll)
456  * for read, to asynchronously detect cancellation.
457  * 
458  * Return value: The fd, or NULL if cancellation is not available
459  * (blocked, or has not been registered for this thread).
460  **/
461 PRFileDesc *
462 camel_operation_cancel_prfd (CamelOperation *cc)
463 {
464         if (cc == NULL)
465                 cc = co_getcc();
466
467         if (cc == NULL || cc->blocked)
468                 return NULL;
469
470         LOCK();
471
472         if (cc->cancel_prfd == NULL)
473                 cc->cancel_prfd = e_msgport_prfd(cc->cancel_port);
474
475         UNLOCK();
476
477         return cc->cancel_prfd;
478 }
479 #endif /* HAVE_NSS */
480
481 /**
482  * camel_operation_start:
483  * @cc: operation context
484  * @what: action being performed (printf-style format string)
485  * @Varargs: varargs
486  * 
487  * Report the start of an operation.  All start operations should have
488  * similar end operations.
489  **/
490 void
491 camel_operation_start (CamelOperation *cc, char *what, ...)
492 {
493         va_list ap;
494         char *msg;
495         struct _status_stack *s;
496
497         if (cc == NULL)
498                 cc = co_getcc();
499
500         if (cc == NULL)
501                 return;
502
503         LOCK();
504
505         if (cc->status == NULL) {
506                 UNLOCK();
507                 return;
508         }
509
510         va_start(ap, what);
511         msg = g_strdup_vprintf(what, ap);
512         va_end(ap);
513         cc->status_update = 0;
514         s = g_malloc0(sizeof(*s));
515         s->msg = msg;
516         s->flags = 0;
517         cc->lastreport = s;
518         cc->status_stack = g_slist_prepend(cc->status_stack, s);
519
520         UNLOCK();
521
522         cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);
523
524         d(printf("start '%s'\n", msg, pc));
525 }
526
527 /**
528  * camel_operation_start_transient:
529  * @cc: operation context
530  * @what: printf-style format string describing the action being performed
531  * @Varargs: varargs
532  * 
533  * Start a transient event.  We only update this to the display if it
534  * takes very long to process, and if we do, we then go back to the
535  * previous state when finished.
536  **/
537 void
538 camel_operation_start_transient (CamelOperation *cc, char *what, ...)
539 {
540         va_list ap;
541         char *msg;
542         struct _status_stack *s;
543
544         if (cc == NULL)
545                 cc = co_getcc();
546
547         if (cc == NULL || cc->status == NULL)
548                 return;
549
550         LOCK();
551
552         va_start(ap, what);
553         msg = g_strdup_vprintf(what, ap);
554         va_end(ap);
555         cc->status_update = 0;
556         s = g_malloc0(sizeof(*s));
557         s->msg = msg;
558         s->flags = CAMEL_OPERATION_TRANSIENT;
559         s->stamp = stamp();
560         cc->status_stack = g_slist_prepend(cc->status_stack, s);
561         d(printf("start '%s'\n", msg, pc));
562
563         UNLOCK();
564
565         /* we dont report it yet */
566         /*cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);*/
567 }
568
569 static unsigned int stamp(void)
570 {
571         GTimeVal tv;
572
573         g_get_current_time(&tv);
574         /* update 4 times/second */
575         return (tv.tv_sec * 4) + tv.tv_usec / (1000000/4);
576 }
577
578 /**
579  * camel_operation_progress:
580  * @cc: Operation to report to.
581  * @pc: Percent complete, 0 to 100.
582  * 
583  * Report progress on the current operation.  If @cc is NULL, then the
584  * currently registered operation is used.  @pc reports the current
585  * percentage of completion, which should be in the range of 0 to 100.
586  *
587  * If the total percentage is not know, then use
588  * camel_operation_progress_count().
589  **/
590 void
591 camel_operation_progress (CamelOperation *cc, int pc)
592 {
593         unsigned int now;
594         struct _status_stack *s;
595         char *msg = NULL;
596
597         if (cc == NULL)
598                 cc = co_getcc();
599
600         if (cc == NULL)
601                 return;
602
603         LOCK();
604
605         if (cc->status == NULL || cc->status_stack == NULL) {
606                 UNLOCK();
607                 return;
608         }
609
610         s = cc->status_stack->data;
611         s->pc = pc;
612
613         /* Transient messages dont start updating till 4 seconds after
614            they started, then they update every second */
615         now = stamp();
616         if (cc->status_update == now) {
617                 cc = NULL;
618         } else if (s->flags & CAMEL_OPERATION_TRANSIENT) {
619                 if (s->stamp + CAMEL_OPERATION_TRANSIENT_DELAY > now) {
620                         cc = NULL;
621                 } else {
622                         cc->status_update = now;
623                         cc->lastreport = s;
624                         msg = g_strdup(s->msg);
625                 }
626         } else {
627                 s->stamp = cc->status_update = now;
628                 cc->lastreport = s;
629                 msg = g_strdup(s->msg);
630         }
631
632         UNLOCK();
633
634         if (cc) {
635                 cc->status(cc, msg, pc, cc->status_data);
636                 g_free(msg);
637         }
638 }
639
640 /**
641  * camel_operation_progress_count:
642  * @cc: operation context
643  * @sofar:
644  *
645  **/
646 void
647 camel_operation_progress_count (CamelOperation *cc, int sofar)
648 {
649         camel_operation_progress(cc, sofar);
650 }
651
652 /**
653  * camel_operation_end:
654  * @cc: operation context
655  * @what: Format string.
656  * @Varargs: varargs 
657  * 
658  * Report the end of an operation.  If @cc is NULL, then the currently
659  * registered operation is notified.
660  **/
661 void
662 camel_operation_end (CamelOperation *cc)
663 {
664         struct _status_stack *s, *p;
665         unsigned int now;
666         char *msg = NULL;
667         int pc = 0;
668
669         if (cc == NULL)
670                 cc = co_getcc();
671
672         if (cc == NULL)
673                 return;
674
675         LOCK();
676
677         if (cc->status == NULL || cc->status_stack == NULL) {
678                 UNLOCK();
679                 return;
680         }
681
682         /* so what we do here is this.  If the operation that just
683          * ended was transient, see if we have any other transient
684          * messages that haven't been updated yet above us, otherwise,
685          * re-update as a non-transient at the last reported pc */
686         now = stamp();
687         s = cc->status_stack->data;
688         if (s->flags & CAMEL_OPERATION_TRANSIENT) {
689                 if (cc->lastreport == s) {
690                         GSList *l = cc->status_stack->next;
691                         while (l) {
692                                 p = l->data;
693                                 if (p->flags & CAMEL_OPERATION_TRANSIENT) {
694                                         if (p->stamp + CAMEL_OPERATION_TRANSIENT_DELAY < now) {
695                                                 msg = g_strdup(p->msg);
696                                                 pc = p->pc;
697                                                 cc->lastreport = p;
698                                                 break;
699                                         }
700                                 } else {
701                                         msg = g_strdup(p->msg);
702                                         pc = p->pc;
703                                         cc->lastreport = p;
704                                         break;
705                                 }
706                                 l = l->next;
707                         }
708                 }
709                 g_free(s->msg);
710         } else {
711                 msg = s->msg;
712                 pc = CAMEL_OPERATION_END;
713                 cc->lastreport = s;
714         }
715         g_free(s);
716         cc->status_stack = g_slist_remove_link(cc->status_stack, cc->status_stack);
717
718         UNLOCK();
719
720         if (msg) {
721                 cc->status(cc, msg, pc, cc->status_data);
722                 g_free(msg);
723         }
724 }