1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Michael Zucchi <NotZed@ximian.com>
5 * Copyright 2003 Ximian, Inc. (www.ximian.com)
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.
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.
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.
38 #include <libedataserver/e-msgport.h>
40 #include "camel-operation.h"
44 /* ********************************************************************** */
46 struct _status_stack {
49 int pc; /* last pc reported */
50 unsigned int stamp; /* last stamp reported */
53 struct _CamelOperation {
54 struct _CamelOperation *next;
55 struct _CamelOperation *prev;
57 pthread_t id; /* id of running thread */
58 guint32 flags; /* cancelled ? */
59 int blocked; /* cancellation blocked depth */
62 CamelOperationStatusFunc status;
64 unsigned int status_update;
66 /* stack of status messages (struct _status_stack *) */
68 struct _status_stack *lastreport;
70 EMsgPort *cancel_port;
73 PRFileDesc *cancel_prfd;
77 #define CAMEL_OPERATION_CANCELLED (1<<0)
78 #define CAMEL_OPERATION_TRANSIENT (1<<1)
80 /* Delay before a transient operation has any effect on the status */
81 #define CAMEL_OPERATION_TRANSIENT_DELAY (5)
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)
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;
93 typedef struct _CamelOperationMsg {
98 co_createspecific(void)
100 pthread_key_create(&operation_key, NULL);
103 static CamelOperation *
106 pthread_once(&operation_once, co_createspecific);
108 return (CamelOperation *)pthread_getspecific(operation_key);
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.
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.
123 * Return value: A new operation handle.
126 camel_operation_new (CamelOperationStatusFunc status, void *status_data)
130 cc = g_malloc0(sizeof(*cc));
136 cc->status_data = status_data;
137 cc->cancel_port = e_msgport_new();
141 e_dlist_addtail(&operation_list, (EDListNode *)cc);
148 * camel_operation_mute:
151 * mutes a camel operation permanently. from this point on you will never
152 * receive operation updates, even if more are sent.
155 camel_operation_mute(CamelOperation *cc)
159 cc->status_data = NULL;
164 * camel_operation_registered:
166 * Returns the registered operation, or %NULL if none registered.
169 camel_operation_registered (void)
171 CamelOperation *cc = co_getcc();
174 camel_operation_ref(cc);
180 * camel_operation_ref:
181 * @cc: operation context
183 * Add a reference to the CamelOperation @cc.
186 camel_operation_ref (CamelOperation *cc)
188 g_assert(cc->refcount > 0);
196 * camel_operation_unref:
197 * @cc: operation context
199 * Unref and potentially free @cc.
202 camel_operation_unref (CamelOperation *cc)
206 g_assert(cc->refcount > 0);
209 if (cc->refcount == 1) {
210 CamelOperationMsg *msg;
212 e_dlist_remove((EDListNode *)cc);
214 while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
217 e_msgport_destroy(cc->cancel_port);
219 n = cc->status_stack;
221 g_warning("Camel operation status stack non empty: %s", (char *)n->data);
225 g_slist_free(cc->status_stack);
235 * camel_operation_cancel_block:
236 * @cc: operation context
238 * Block cancellation for this operation. If @cc is NULL, then the
239 * current thread is blocked.
242 camel_operation_cancel_block (CamelOperation *cc)
255 * camel_operation_cancel_unblock:
256 * @cc: operation context
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.
263 camel_operation_cancel_unblock (CamelOperation *cc)
276 * camel_operation_cancel:
277 * @cc: operation context
279 * Cancel a given operation. If @cc is NULL then all outstanding
280 * operations are cancelled.
283 camel_operation_cancel (CamelOperation *cc)
285 CamelOperationMsg *msg;
292 cc = (CamelOperation *)operation_list.head;
295 cc->flags |= CAMEL_OPERATION_CANCELLED;
296 msg = g_malloc0(sizeof(*msg));
297 e_msgport_put(cc->cancel_port, (EMsg *)msg);
301 } else if ((cc->flags & CAMEL_OPERATION_CANCELLED) == 0) {
302 d(printf("cancelling thread %d\n", cc->id));
304 cc->flags |= CAMEL_OPERATION_CANCELLED;
305 msg = g_malloc0(sizeof(*msg));
306 e_msgport_put(cc->cancel_port, (EMsg *)msg);
313 * camel_operation_uncancel:
314 * @cc: operation context
316 * Uncancel a cancelled operation. If @cc is NULL then the current
317 * operation is uncancelled.
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
324 camel_operation_uncancel(CamelOperation *cc)
330 CamelOperationMsg *msg;
333 while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
336 cc->flags &= ~CAMEL_OPERATION_CANCELLED;
342 * camel_operation_register:
343 * @cc: operation context
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.
348 * All calls to operation_register() should save their value and call
349 * operation_register again with that, to automatically stack
352 * Return Value: Returns the previously registered operatoin.
356 camel_operation_register (CamelOperation *cc)
358 CamelOperation *oldcc = co_getcc();
360 pthread_setspecific(operation_key, cc);
366 * camel_operation_unregister:
367 * @cc: operation context
369 * Unregister the current thread for all cancellations.
372 camel_operation_unregister (CamelOperation *cc)
374 pthread_once(&operation_once, co_createspecific);
375 pthread_setspecific(operation_key, NULL);
379 * camel_operation_cancel_check:
380 * @cc: operation context
382 * Check if cancellation has been applied to @cc. If @cc is NULL,
383 * then the CamelOperation registered for the current thread is used.
385 * Return value: TRUE if the operation has been cancelled.
388 camel_operation_cancel_check (CamelOperation *cc)
390 CamelOperationMsg *msg;
393 d(printf("checking for cancel in thread %d\n", pthread_self()));
400 if (cc == NULL || cc->blocked > 0) {
401 d(printf("ahah! cancellation is blocked\n"));
403 } else if (cc->flags & CAMEL_OPERATION_CANCELLED) {
404 d(printf("previously cancelled\n"));
406 } else if ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) {
407 d(printf("Got cancellation message\n"));
410 } while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)));
411 cc->flags |= CAMEL_OPERATION_CANCELLED;
422 * camel_operation_cancel_fd:
423 * @cc: operation context
425 * Retrieve a file descriptor that can be waited on (select, or poll)
426 * for read, to asynchronously detect cancellation.
428 * Return value: The fd, or -1 if cancellation is not available
429 * (blocked, or has not been registered for this thread).
432 camel_operation_cancel_fd (CamelOperation *cc)
437 if (cc == NULL || cc->blocked)
442 if (cc->cancel_fd == -1)
443 cc->cancel_fd = e_msgport_fd(cc->cancel_port);
447 return cc->cancel_fd;
452 * camel_operation_cancel_prfd:
453 * @cc: operation context
455 * Retrieve a file descriptor that can be waited on (select, or poll)
456 * for read, to asynchronously detect cancellation.
458 * Return value: The fd, or NULL if cancellation is not available
459 * (blocked, or has not been registered for this thread).
462 camel_operation_cancel_prfd (CamelOperation *cc)
467 if (cc == NULL || cc->blocked)
472 if (cc->cancel_prfd == NULL)
473 cc->cancel_prfd = e_msgport_prfd(cc->cancel_port);
477 return cc->cancel_prfd;
479 #endif /* HAVE_NSS */
482 * camel_operation_start:
483 * @cc: operation context
484 * @what: action being performed (printf-style format string)
487 * Report the start of an operation. All start operations should have
488 * similar end operations.
491 camel_operation_start (CamelOperation *cc, char *what, ...)
495 struct _status_stack *s;
505 if (cc->status == NULL) {
511 msg = g_strdup_vprintf(what, ap);
513 cc->status_update = 0;
514 s = g_malloc0(sizeof(*s));
518 cc->status_stack = g_slist_prepend(cc->status_stack, s);
522 cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);
524 d(printf("start '%s'\n", msg, pc));
528 * camel_operation_start_transient:
529 * @cc: operation context
530 * @what: printf-style format string describing the action being performed
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.
538 camel_operation_start_transient (CamelOperation *cc, char *what, ...)
542 struct _status_stack *s;
547 if (cc == NULL || cc->status == NULL)
553 msg = g_strdup_vprintf(what, ap);
555 cc->status_update = 0;
556 s = g_malloc0(sizeof(*s));
558 s->flags = CAMEL_OPERATION_TRANSIENT;
560 cc->status_stack = g_slist_prepend(cc->status_stack, s);
561 d(printf("start '%s'\n", msg, pc));
565 /* we dont report it yet */
566 /*cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);*/
569 static unsigned int stamp(void)
573 g_get_current_time(&tv);
574 /* update 4 times/second */
575 return (tv.tv_sec * 4) + tv.tv_usec / (1000000/4);
579 * camel_operation_progress:
580 * @cc: Operation to report to.
581 * @pc: Percent complete, 0 to 100.
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.
587 * If the total percentage is not know, then use
588 * camel_operation_progress_count().
591 camel_operation_progress (CamelOperation *cc, int pc)
594 struct _status_stack *s;
605 if (cc->status == NULL || cc->status_stack == NULL) {
610 s = cc->status_stack->data;
613 /* Transient messages dont start updating till 4 seconds after
614 they started, then they update every second */
616 if (cc->status_update == now) {
618 } else if (s->flags & CAMEL_OPERATION_TRANSIENT) {
619 if (s->stamp + CAMEL_OPERATION_TRANSIENT_DELAY > now) {
622 cc->status_update = now;
624 msg = g_strdup(s->msg);
627 s->stamp = cc->status_update = now;
629 msg = g_strdup(s->msg);
635 cc->status(cc, msg, pc, cc->status_data);
641 * camel_operation_progress_count:
642 * @cc: operation context
647 camel_operation_progress_count (CamelOperation *cc, int sofar)
649 camel_operation_progress(cc, sofar);
653 * camel_operation_end:
654 * @cc: operation context
655 * @what: Format string.
658 * Report the end of an operation. If @cc is NULL, then the currently
659 * registered operation is notified.
662 camel_operation_end (CamelOperation *cc)
664 struct _status_stack *s, *p;
677 if (cc->status == NULL || cc->status_stack == NULL) {
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 */
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;
693 if (p->flags & CAMEL_OPERATION_TRANSIENT) {
694 if (p->stamp + CAMEL_OPERATION_TRANSIENT_DELAY < now) {
695 msg = g_strdup(p->msg);
701 msg = g_strdup(p->msg);
712 pc = CAMEL_OPERATION_END;
716 cc->status_stack = g_slist_remove_link(cc->status_stack, cc->status_stack);
721 cc->status(cc, msg, pc, cc->status_data);