diff options
Diffstat (limited to 'camel/camel-operation.c')
-rw-r--r-- | camel/camel-operation.c | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/camel/camel-operation.c b/camel/camel-operation.c new file mode 100644 index 0000000000..406d576597 --- /dev/null +++ b/camel/camel-operation.c @@ -0,0 +1,730 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <NotZed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include <stdio.h> +#include <sys/time.h> +#include <unistd.h> +#include <pthread.h> +#ifdef HAVE_NSS +#include <nspr.h> +#endif + +#include "camel-operation.h" +#include "e-util/e-msgport.h" + +#define d(x) + +/* ********************************************************************** */ + +struct _status_stack { + guint32 flags; + char *msg; + int pc; /* last pc reported */ + unsigned int stamp; /* last stamp reported */ +}; + +struct _CamelOperation { + struct _CamelOperation *next; + struct _CamelOperation *prev; + + pthread_t id; /* id of running thread */ + guint32 flags; /* cancelled ? */ + int blocked; /* cancellation blocked depth */ + int refcount; + + CamelOperationStatusFunc status; + void *status_data; + unsigned int status_update; + + /* stack of status messages (struct _status_stack *) */ + GSList *status_stack; + struct _status_stack *lastreport; + + EMsgPort *cancel_port; + int cancel_fd; +#ifdef HAVE_NSS + PRFileDesc *cancel_prfd; +#endif +}; + +#define CAMEL_OPERATION_CANCELLED (1<<0) +#define CAMEL_OPERATION_TRANSIENT (1<<1) + +/* Delay before a transient operation has any effect on the status */ +#define CAMEL_OPERATION_TRANSIENT_DELAY (5) + +static pthread_mutex_t operation_lock = PTHREAD_MUTEX_INITIALIZER; +#define LOCK() pthread_mutex_lock(&operation_lock) +#define UNLOCK() pthread_mutex_unlock(&operation_lock) + + +static unsigned int stamp (void); +static EDList operation_list = E_DLIST_INITIALISER(operation_list); +static pthread_key_t operation_key; + +typedef struct _CamelOperationMsg { + EMsg msg; +} CamelOperationMsg ; + +/** + * camel_operation_init: + * @void: + * + * Init internal variables. Only call this once. + **/ +void +camel_operation_init(void) +{ + pthread_key_create(&operation_key, NULL); +} + +/** + * camel_operation_shutdown: + * + * Cleans up internal variables. + **/ +void +camel_operation_shutdown (void) +{ + pthread_key_delete (operation_key); +} + +/** + * camel_operation_new: + * @status: Callback for receiving status messages. This will always + * be called with an internal lock held. + * @status_data: User data. + * + * Create a new camel operation handle. Camel operation handles can + * be used in a multithreaded application (or a single operation + * handle can be used in a non threaded appliation) to cancel running + * operations and to obtain notification messages of the internal + * status of messages. + * + * Return value: A new operation handle. + **/ +CamelOperation * +camel_operation_new (CamelOperationStatusFunc status, void *status_data) +{ + CamelOperation *cc; + + cc = g_malloc0(sizeof(*cc)); + + cc->flags = 0; + cc->blocked = 0; + cc->refcount = 1; + cc->status = status; + cc->status_data = status_data; + cc->cancel_port = e_msgport_new(); + cc->cancel_fd = -1; + + LOCK(); + e_dlist_addtail(&operation_list, (EDListNode *)cc); + UNLOCK(); + + return cc; +} + +/** + * camel_operation_mute: + * @cc: + * + * mutes a camel operation permanently. from this point on you will never + * receive operation updates, even if more are sent. + **/ +void +camel_operation_mute(CamelOperation *cc) +{ + LOCK(); + cc->status = NULL; + cc->status_data = NULL; + UNLOCK(); +} + +/** + * camel_operation_registered: + * + * Returns the registered operation, or %NULL if none registered. + **/ +CamelOperation * +camel_operation_registered (void) +{ + CamelOperation *cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc) + camel_operation_ref(cc); + + return cc; +} + +/** + * camel_operation_ref: + * @cc: operation context + * + * Add a reference to the CamelOperation @cc. + **/ +void +camel_operation_ref (CamelOperation *cc) +{ + g_assert(cc->refcount > 0); + + LOCK(); + cc->refcount++; + UNLOCK(); +} + +/** + * camel_operation_unref: + * @cc: operation context + * + * Unref and potentially free @cc. + **/ +void +camel_operation_unref (CamelOperation *cc) +{ + GSList *n; + + g_assert(cc->refcount > 0); + + LOCK(); + if (cc->refcount == 1) { + CamelOperationMsg *msg; + + e_dlist_remove((EDListNode *)cc); + + while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) + g_free(msg); + + e_msgport_destroy(cc->cancel_port); + + n = cc->status_stack; + while (n) { + g_warning("Camel operation status stack non empty: %s", (char *)n->data); + g_free(n->data); + n = n->next; + } + g_slist_free(cc->status_stack); + + g_free(cc); + } else { + cc->refcount--; + } + UNLOCK(); +} + +/** + * camel_operation_cancel_block: + * @cc: operation context + * + * Block cancellation for this operation. If @cc is NULL, then the + * current thread is blocked. + **/ +void +camel_operation_cancel_block (CamelOperation *cc) +{ + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc) { + LOCK(); + cc->blocked++; + UNLOCK(); + } +} + +/** + * camel_operation_cancel_unblock: + * @cc: operation context + * + * Unblock cancellation, when the unblock count reaches the block + * count, then this operation can be cancelled. If @cc is NULL, then + * the current thread is unblocked. + **/ +void +camel_operation_cancel_unblock (CamelOperation *cc) +{ + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc) { + LOCK(); + cc->blocked--; + UNLOCK(); + } +} + +/** + * camel_operation_cancel: + * @cc: operation context + * + * Cancel a given operation. If @cc is NULL then all outstanding + * operations are cancelled. + **/ +void +camel_operation_cancel (CamelOperation *cc) +{ + CamelOperationMsg *msg; + + LOCK(); + + if (cc == NULL) { + CamelOperation *cn; + + cc = (CamelOperation *)operation_list.head; + cn = cc->next; + while (cn) { + cc->flags |= CAMEL_OPERATION_CANCELLED; + msg = g_malloc0(sizeof(*msg)); + e_msgport_put(cc->cancel_port, (EMsg *)msg); + cc = cn; + cn = cn->next; + } + } else if ((cc->flags & CAMEL_OPERATION_CANCELLED) == 0) { + d(printf("cancelling thread %d\n", cc->id)); + + cc->flags |= CAMEL_OPERATION_CANCELLED; + msg = g_malloc0(sizeof(*msg)); + e_msgport_put(cc->cancel_port, (EMsg *)msg); + } + + UNLOCK(); +} + +/** + * camel_operation_uncancel: + * @cc: operation context + * + * Uncancel a cancelled operation. If @cc is NULL then the current + * operation is uncancelled. + * + * This is useful, if e.g. you need to do some cleaning up where a + * cancellation lying around in the same thread will abort any + * processing. + **/ +void +camel_operation_uncancel(CamelOperation *cc) +{ + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc) { + CamelOperationMsg *msg; + + LOCK(); + while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) + g_free(msg); + + cc->flags &= ~CAMEL_OPERATION_CANCELLED; + UNLOCK(); + } +} + +/** + * camel_operation_register: + * @cc: operation context + * + * Register a thread or the main thread for cancellation through @cc. + * If @cc is NULL, then a new cancellation is created for this thread. + * + * All calls to operation_register() should save their value and call + * operation_register again with that, to automatically stack + * registrations. + * + * Return Value: Returns the previously registered operatoin. + * + **/ +CamelOperation * +camel_operation_register (CamelOperation *cc) +{ + CamelOperation *oldcc = pthread_getspecific(operation_key); + + pthread_setspecific(operation_key, cc); + + return oldcc; +} + +/** + * camel_operation_unregister: + * @cc: operation context + * + * Unregister the current thread for all cancellations. + **/ +void +camel_operation_unregister (CamelOperation *cc) +{ + pthread_setspecific(operation_key, NULL); +} + +/** + * camel_operation_cancel_check: + * @cc: operation context + * + * Check if cancellation has been applied to @cc. If @cc is NULL, + * then the CamelOperation registered for the current thread is used. + * + * Return value: TRUE if the operation has been cancelled. + **/ +gboolean +camel_operation_cancel_check (CamelOperation *cc) +{ + CamelOperationMsg *msg; + int cancelled; + + d(printf("checking for cancel in thread %d\n", pthread_self())); + + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + LOCK(); + + if (cc == NULL || cc->blocked > 0) { + d(printf("ahah! cancellation is blocked\n")); + cancelled = FALSE; + } else if (cc->flags & CAMEL_OPERATION_CANCELLED) { + d(printf("previously cancelled\n")); + cancelled = TRUE; + } else if ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) { + d(printf("Got cancellation message\n")); + do { + g_free(msg); + } while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))); + cc->flags |= CAMEL_OPERATION_CANCELLED; + cancelled = TRUE; + } else + cancelled = FALSE; + + UNLOCK(); + + return cancelled; +} + +/** + * camel_operation_cancel_fd: + * @cc: operation context + * + * Retrieve a file descriptor that can be waited on (select, or poll) + * for read, to asynchronously detect cancellation. + * + * Return value: The fd, or -1 if cancellation is not available + * (blocked, or has not been registered for this thread). + **/ +int +camel_operation_cancel_fd (CamelOperation *cc) +{ + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL || cc->blocked) + return -1; + + LOCK(); + + if (cc->cancel_fd == -1) + cc->cancel_fd = e_msgport_fd(cc->cancel_port); + + UNLOCK(); + + return cc->cancel_fd; +} + +#ifdef HAVE_NSS +/** + * camel_operation_cancel_prfd: + * @cc: operation context + * + * Retrieve a file descriptor that can be waited on (select, or poll) + * for read, to asynchronously detect cancellation. + * + * Return value: The fd, or NULL if cancellation is not available + * (blocked, or has not been registered for this thread). + **/ +PRFileDesc * +camel_operation_cancel_prfd (CamelOperation *cc) +{ + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL || cc->blocked) + return NULL; + + LOCK(); + + if (cc->cancel_prfd == NULL) + cc->cancel_prfd = e_msgport_prfd(cc->cancel_port); + + UNLOCK(); + + return cc->cancel_prfd; +} +#endif /* HAVE_NSS */ + +/** + * camel_operation_start: + * @cc: operation context + * @what: action being performed (printf-style format string) + * @Varargs: varargs + * + * Report the start of an operation. All start operations should have + * similar end operations. + **/ +void +camel_operation_start (CamelOperation *cc, char *what, ...) +{ + va_list ap; + char *msg; + struct _status_stack *s; + + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL) + return; + + LOCK(); + + if (cc->status == NULL) { + UNLOCK(); + return; + } + + va_start(ap, what); + msg = g_strdup_vprintf(what, ap); + va_end(ap); + cc->status_update = 0; + s = g_malloc0(sizeof(*s)); + s->msg = msg; + s->flags = 0; + cc->lastreport = s; + cc->status_stack = g_slist_prepend(cc->status_stack, s); + + UNLOCK(); + + cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data); + + d(printf("start '%s'\n", msg, pc)); +} + +/** + * camel_operation_start_transient: + * @cc: operation context + * @what: printf-style format string describing the action being performed + * @Varargs: varargs + * + * Start a transient event. We only update this to the display if it + * takes very long to process, and if we do, we then go back to the + * previous state when finished. + **/ +void +camel_operation_start_transient (CamelOperation *cc, char *what, ...) +{ + va_list ap; + char *msg; + struct _status_stack *s; + + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL || cc->status == NULL) + return; + + LOCK(); + + va_start(ap, what); + msg = g_strdup_vprintf(what, ap); + va_end(ap); + cc->status_update = 0; + s = g_malloc0(sizeof(*s)); + s->msg = msg; + s->flags = CAMEL_OPERATION_TRANSIENT; + s->stamp = stamp(); + cc->status_stack = g_slist_prepend(cc->status_stack, s); + d(printf("start '%s'\n", msg, pc)); + + UNLOCK(); + + /* we dont report it yet */ + /*cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);*/ +} + +static unsigned int stamp(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + /* update 4 times/second */ + return (tv.tv_sec * 4) + tv.tv_usec / (1000000/4); +} + +/** + * camel_operation_progress: + * @cc: Operation to report to. + * @pc: Percent complete, 0 to 100. + * + * Report progress on the current operation. If @cc is NULL, then the + * currently registered operation is used. @pc reports the current + * percentage of completion, which should be in the range of 0 to 100. + * + * If the total percentage is not know, then use + * camel_operation_progress_count(). + **/ +void +camel_operation_progress (CamelOperation *cc, int pc) +{ + unsigned int now; + struct _status_stack *s; + char *msg = NULL; + + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL) + return; + + LOCK(); + + if (cc->status == NULL || cc->status_stack == NULL) { + UNLOCK(); + return; + } + + s = cc->status_stack->data; + s->pc = pc; + + /* Transient messages dont start updating till 4 seconds after + they started, then they update every second */ + now = stamp(); + if (cc->status_update == now) { + cc = NULL; + } else if (s->flags & CAMEL_OPERATION_TRANSIENT) { + if (s->stamp + CAMEL_OPERATION_TRANSIENT_DELAY > now) { + cc = NULL; + } else { + cc->status_update = now; + cc->lastreport = s; + msg = g_strdup(s->msg); + } + } else { + s->stamp = cc->status_update = now; + cc->lastreport = s; + msg = g_strdup(s->msg); + } + + UNLOCK(); + + if (cc) { + cc->status(cc, msg, pc, cc->status_data); + g_free(msg); + } +} + +/** + * camel_operation_progress_count: + * @cc: operation context + * @sofar: + * + **/ +void +camel_operation_progress_count (CamelOperation *cc, int sofar) +{ + camel_operation_progress(cc, sofar); +} + +/** + * camel_operation_end: + * @cc: operation context + * @what: Format string. + * @Varargs: varargs + * + * Report the end of an operation. If @cc is NULL, then the currently + * registered operation is notified. + **/ +void +camel_operation_end (CamelOperation *cc) +{ + struct _status_stack *s, *p; + unsigned int now; + char *msg = NULL; + int pc = 0; + + if (cc == NULL) + cc = (CamelOperation *)pthread_getspecific(operation_key); + + if (cc == NULL) + return; + + LOCK(); + + if (cc->status == NULL || cc->status_stack == NULL) { + UNLOCK(); + return; + } + + /* so what we do here is this. If the operation that just + * ended was transient, see if we have any other transient + * messages that haven't been updated yet above us, otherwise, + * re-update as a non-transient at the last reported pc */ + now = stamp(); + s = cc->status_stack->data; + if (s->flags & CAMEL_OPERATION_TRANSIENT) { + if (cc->lastreport == s) { + GSList *l = cc->status_stack->next; + while (l) { + p = l->data; + if (p->flags & CAMEL_OPERATION_TRANSIENT) { + if (p->stamp + CAMEL_OPERATION_TRANSIENT_DELAY < now) { + msg = g_strdup(p->msg); + pc = p->pc; + cc->lastreport = p; + break; + } + } else { + msg = g_strdup(p->msg); + pc = p->pc; + cc->lastreport = p; + break; + } + l = l->next; + } + } + g_free(s->msg); + } else { + msg = s->msg; + pc = CAMEL_OPERATION_END; + cc->lastreport = s; + } + g_free(s); + cc->status_stack = g_slist_remove_link(cc->status_stack, cc->status_stack); + + UNLOCK(); + + if (msg) { + cc->status(cc, msg, pc, cc->status_data); + g_free(msg); + } +} |