aboutsummaryrefslogtreecommitdiffstats
path: root/camel/camel-operation.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/camel-operation.c')
-rw-r--r--camel/camel-operation.c540
1 files changed, 540 insertions, 0 deletions
diff --git a/camel/camel-operation.c b/camel/camel-operation.c
new file mode 100644
index 0000000000..9d631fca7b
--- /dev/null
+++ b/camel/camel-operation.c
@@ -0,0 +1,540 @@
+
+#include "config.h"
+
+#include <stdio.h>
+#ifdef ENABLE_THREADS
+#include <pthread.h>
+#endif
+
+#include <glib.h>
+#include "camel-operation.h"
+#include "e-util/e-msgport.h"
+
+#define d(x)
+
+/* ********************************************************************** */
+
+struct _CamelOperation {
+ pthread_t id; /* id of running thread */
+ guint32 flags; /* cancelled ? */
+ int blocked; /* cancellation blocked depth */
+ int refcount;
+
+ CamelOperationStatusFunc status;
+ void *status_data;
+ time_t status_update;
+
+ /* stack of status messages (char *) */
+ GSList *status_stack;
+
+#ifdef ENABLE_THREADS
+ EMsgPort *cancel_port;
+ int cancel_fd;
+ pthread_mutex_t lock;
+#endif
+};
+
+#define CAMEL_OPERATION_CANCELLED (1<<0)
+
+#ifdef ENABLE_THREADS
+#define CAMEL_OPERATION_LOCK(cc) pthread_mutex_lock(&cc->lock)
+#define CAMEL_OPERATION_UNLOCK(cc) pthread_mutex_unlock(&cc->lock)
+#define CAMEL_ACTIVE_LOCK() pthread_mutex_lock(&operation_active_lock)
+#define CAMEL_ACTIVE_UNLOCK() pthread_mutex_unlock(&operation_active_lock)
+static pthread_mutex_t operation_active_lock = PTHREAD_MUTEX_INITIALIZER;
+#else
+#define CAMEL_OPERATION_LOCK(cc)
+#define CAMEL_OPERATION_UNLOCK(cc)
+#define CAMEL_ACTIVE_LOCK()
+#define CAMEL_ACTIVE_UNLOCK()
+#endif
+
+static GHashTable *operation_active;
+
+typedef struct _CamelOperationMsg {
+ EMsg msg;
+} CamelOperationMsg ;
+
+/**
+ * camel_operation_new:
+ * @status: Callback for receiving status messages.
+ * @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;
+#ifdef ENABLE_THREADS
+ cc->id = ~0;
+ cc->cancel_port = e_msgport_new();
+ cc->cancel_fd = e_msgport_fd(cc->cancel_port);
+ pthread_mutex_init(&cc->lock, NULL);
+#endif
+
+ return cc;
+}
+
+/**
+ * camel_operation_reset:
+ * @cc:
+ *
+ * Resets an operation cancel state and message.
+ **/
+void camel_operation_reset(CamelOperation *cc)
+{
+ GSList *n;
+
+#ifdef ENABLE_THREADS
+ CamelOperationMsg *msg;
+
+ while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
+ g_free(msg);
+#endif
+
+ n = cc->status_stack;
+ while (n) {
+ g_free(n->data);
+ n = n->next;
+ }
+ g_slist_free(cc->status_stack);
+ cc->status_stack = NULL;
+
+ cc->flags = 0;
+ cc->blocked = 0;
+}
+
+/**
+ * camel_operation_ref:
+ * @cc:
+ *
+ * Add a reference to the CamelOperation @cc.
+ **/
+void camel_operation_ref(CamelOperation *cc)
+{
+ CAMEL_OPERATION_LOCK(cc);
+ cc->refcount++;
+ CAMEL_OPERATION_UNLOCK(cc);
+}
+
+/**
+ * camel_operation_unref:
+ * @cc:
+ *
+ * Unref and potentially free @cc.
+ **/
+void camel_operation_unref(CamelOperation *cc)
+{
+ GSList *n;
+#ifdef ENABLE_THREADS
+ CamelOperationMsg *msg;
+
+ if (cc->refcount == 1) {
+ while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
+ g_free(msg);
+
+ e_msgport_destroy(cc->cancel_port);
+#endif
+ 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 {
+ CAMEL_OPERATION_LOCK(cc);
+ cc->refcount--;
+ CAMEL_OPERATION_UNLOCK(cc);
+ }
+}
+
+/**
+ * camel_operation_cancel_block:
+ * @cc:
+ *
+ * Block cancellation for this operation. If @cc is NULL, then the
+ * current thread is blocked.
+ **/
+void camel_operation_cancel_block(CamelOperation *cc)
+{
+ CAMEL_ACTIVE_LOCK();
+ if (operation_active == NULL)
+ operation_active = g_hash_table_new(NULL, NULL);
+
+ if (cc == NULL)
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+
+ if (cc) {
+ CAMEL_OPERATION_LOCK(cc);
+ cc->blocked++;
+ CAMEL_OPERATION_UNLOCK(cc);
+ }
+}
+
+/**
+ * camel_operation_cancel_unblock:
+ * @cc:
+ *
+ * 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)
+{
+ CAMEL_ACTIVE_LOCK();
+ if (operation_active == NULL)
+ operation_active = g_hash_table_new(NULL, NULL);
+
+ if (cc == NULL)
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+
+ if (cc) {
+ CAMEL_OPERATION_LOCK(cc);
+ cc->blocked--;
+ CAMEL_OPERATION_UNLOCK(cc);
+ }
+}
+
+static void
+cancel_thread(void *key, CamelOperation *cc, void *data)
+{
+ if (cc)
+ camel_operation_cancel(cc);
+}
+
+/**
+ * camel_operation_cancel:
+ * @cc:
+ *
+ * Cancel a given operation. If @cc is NULL then all outstanding
+ * operations are cancelled.
+ **/
+void camel_operation_cancel(CamelOperation *cc)
+{
+ CamelOperationMsg *msg;
+
+ if (cc == NULL) {
+ if (operation_active) {
+ CAMEL_ACTIVE_LOCK();
+ g_hash_table_foreach(operation_active, (GHFunc)cancel_thread, NULL);
+ CAMEL_ACTIVE_UNLOCK();
+ }
+ } else if ((cc->flags & CAMEL_OPERATION_CANCELLED) == 0) {
+ d(printf("cancelling thread %d\n", cc->id));
+
+ CAMEL_OPERATION_LOCK(cc);
+ msg = g_malloc0(sizeof(*msg));
+ e_msgport_put(cc->cancel_port, (EMsg *)msg);
+ cc->flags |= CAMEL_OPERATION_CANCELLED;
+ CAMEL_OPERATION_UNLOCK(cc);
+ }
+}
+
+/**
+ * camel_operation_register:
+ * @cc:
+ *
+ * Register a thread or the main thread for cancellation through @cc.
+ * If @cc is NULL, then a new cancellation is created for this thread,
+ * but may only be cancelled from the same thread.
+ *
+ * All calls to operation_register() should be matched with calls to
+ * operation_unregister(), or resources will be lost.
+ **/
+void camel_operation_register(CamelOperation *cc)
+{
+ pthread_t id = pthread_self();
+
+ CAMEL_ACTIVE_LOCK();
+
+ if (operation_active == NULL)
+ operation_active = g_hash_table_new(NULL, NULL);
+
+ if (cc == NULL) {
+ cc = g_hash_table_lookup(operation_active, (void *)id);
+ if (cc == NULL) {
+ cc = camel_operation_new(NULL, NULL);
+ }
+ }
+
+ cc->id = id;
+ g_hash_table_insert(operation_active, (void *)id, cc);
+
+ d(printf("registering thread %ld for cancellation\n", id));
+
+ CAMEL_ACTIVE_UNLOCK();
+
+ camel_operation_ref(cc);
+}
+
+/**
+ * camel_operation_unregister:
+ * @cc:
+ *
+ * Unregister a given operation from being cancelled. If @cc is NULL,
+ * then the current thread is used.
+ **/
+void camel_operation_unregister(CamelOperation *cc)
+{
+ CAMEL_ACTIVE_LOCK();
+
+ if (operation_active == NULL)
+ operation_active = g_hash_table_new(NULL, NULL);
+
+ if (cc == NULL) {
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ if (cc == NULL) {
+ g_warning("Trying to unregister a thread that was never registered for cancellation");
+ }
+ }
+
+ if (cc)
+ g_hash_table_remove(operation_active, (void *)cc->id);
+
+ CAMEL_ACTIVE_UNLOCK();
+
+ d({if (cc) printf("unregistering thread %d for cancellation\n", cc->id);});
+
+ if (cc)
+ camel_operation_unref(cc);
+}
+
+/**
+ * camel_operation_cancel_check:
+ * @cc:
+ *
+ * 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;
+
+ d(printf("checking for cancel in thread %d\n", pthread_self()));
+
+ if (cc == NULL) {
+ if (operation_active) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ }
+ if (cc == NULL)
+ return FALSE;
+ }
+
+ if (cc->blocked > 0) {
+ d(printf("ahah! cancellation is blocked\n"));
+ return FALSE;
+ }
+
+ if (cc->flags & CAMEL_OPERATION_CANCELLED) {
+ d(printf("previously cancelled\n"));
+ return TRUE;
+ }
+
+ msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port);
+ if (msg) {
+ d(printf("Got cancellation message\n"));
+ CAMEL_OPERATION_LOCK(cc);
+ cc->flags |= CAMEL_OPERATION_CANCELLED;
+ CAMEL_OPERATION_UNLOCK(cc);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * camel_operation_cancel_fd:
+ * @cc:
+ *
+ * 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) {
+ if (operation_active) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ }
+ if (cc == NULL)
+ return -1;
+ }
+ if (cc->blocked)
+ return -1;
+
+ return cc->cancel_fd;
+}
+
+/**
+ * camel_operation_start:
+ * @cc:
+ * @what:
+ * @:
+ *
+ * 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;
+
+ if (operation_active == NULL)
+ return;
+
+ if (cc == NULL) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ if (cc == NULL)
+ return;
+ }
+
+ if (cc->status == NULL)
+ return;
+
+ va_start(ap, what);
+ msg = g_strdup_vprintf(what, ap);
+ va_end(ap);
+ cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);
+ cc->status_update = 0;
+ cc->status_stack = g_slist_prepend(cc->status_stack, msg);
+ d(printf("start '%s'\n", msg, pc));
+}
+
+/**
+ * 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)
+{
+ char *msg;
+ time_t now;
+
+ if (operation_active == NULL)
+ return;
+
+ if (cc == NULL) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ if (cc == NULL)
+ return;
+ }
+
+ if (cc->status == NULL)
+ return;
+
+ if (cc->status_stack == NULL)
+ return;
+
+ now = time(0);
+ if (cc->status_update != now) {
+ msg =cc->status_stack->data;
+ cc->status(cc, msg, pc, cc->status_data);
+ d(printf("progress '%s' %d %%\n", msg, pc));
+ cc->status_update = now;
+ }
+}
+
+void camel_operation_progress_count(CamelOperation *cc, int sofar)
+{
+ char *msg;
+ time_t now;
+
+ if (operation_active == NULL)
+ return;
+
+ if (cc == NULL) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ if (cc == NULL)
+ return;
+ }
+
+ if (cc->status == NULL)
+ return;
+
+ if (cc->status_stack == NULL)
+ return;
+
+ /* FIXME: generate some meaningful pc value */
+ now = time(0);
+ if (cc->status_update != now) {
+ msg =cc->status_stack->data;
+ cc->status(cc, msg, sofar, cc->status_data);
+ d(printf("progress '%s' %d done\n", msg, sofar));
+ cc->status_update = now;
+ }
+}
+
+/**
+ * camel_operation_end:
+ * @cc:
+ * @what: Format string.
+ * @:
+ *
+ * Report the end of an operation. If @cc is NULL, then the currently
+ * registered operation is notified.
+ **/
+void camel_operation_end(CamelOperation *cc)
+{
+ char *msg;
+
+ if (operation_active == NULL)
+ return;
+
+ if (cc == NULL) {
+ CAMEL_ACTIVE_LOCK();
+ cc = g_hash_table_lookup(operation_active, (void *)pthread_self());
+ CAMEL_ACTIVE_UNLOCK();
+ if (cc == NULL)
+ return;
+ }
+
+ if (cc->status == NULL)
+ return;
+
+ if (cc->status_stack == NULL)
+ return;
+
+ msg = cc->status_stack->data;
+ cc->status(cc, msg, CAMEL_OPERATION_END, cc->status_data);
+ g_free(msg);
+ cc->status_stack = g_slist_remove_link(cc->status_stack, cc->status_stack);
+}