Files
evolution/mail/mail-mt.c
Matthew Barnes ba8f1f78f4 Add e_activity_handle_cancellation().
Convenience function for use in GAsyncReadyCallback functions.

This acknowledges the cancellation, so that the activity's description
changes from "(cancelling)" to "(cancelled)" and the description appears
crossed out in the UI for a moment before disappearing.
2011-05-11 12:59:29 -04:00

639 lines
14 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <libedataserver/e-flag.h>
#include <e-util/e-alert-sink.h>
#include <shell/e-shell-view.h>
#include "mail-mt.h"
/*#define MALLOC_CHECK*/
#define d(x)
/* XXX This is a dirty hack on a dirty hack. We really need
* to rework or get rid of the functions that use this. */
const gchar *shell_builtin_backend = "mail";
static guint mail_msg_seq; /* sequence number of each message */
/* Table of active messages. Must hold mail_msg_lock to access. */
static GHashTable *mail_msg_active_table;
static GMutex *mail_msg_lock;
static GCond *mail_msg_cond;
static void
mail_msg_cancelled (CamelOperation *operation,
gpointer user_data)
{
mail_msg_cancel (GPOINTER_TO_UINT (user_data));
}
static gboolean
mail_msg_submit (EActivity *activity)
{
EShell *shell;
EShellBackend *shell_backend;
shell = e_shell_get_default ();
shell_backend = e_shell_get_backend_by_name (
shell, shell_builtin_backend);
e_shell_backend_add_activity (shell_backend, activity);
return FALSE;
}
gpointer
mail_msg_new (MailMsgInfo *info)
{
MailMsg *msg;
GCancellable *cancellable;
g_mutex_lock (mail_msg_lock);
msg = g_slice_alloc0 (info->size);
msg->info = info;
msg->ref_count = 1;
msg->seq = mail_msg_seq++;
msg->activity = e_activity_new ();
cancellable = camel_operation_new ();
e_activity_set_percent (msg->activity, 0.0);
e_activity_set_cancellable (msg->activity, cancellable);
g_signal_connect (
cancellable, "cancelled",
G_CALLBACK (mail_msg_cancelled),
GINT_TO_POINTER (msg->seq));
g_object_unref (cancellable);
g_hash_table_insert (
mail_msg_active_table, GINT_TO_POINTER (msg->seq), msg);
d(printf("New message %p\n", msg));
g_mutex_unlock (mail_msg_lock);
return msg;
}
#ifdef MALLOC_CHECK
#include <mcheck.h>
static void
checkmem (gpointer p)
{
if (p) {
gint status = mprobe (p);
switch (status) {
case MCHECK_HEAD:
printf("Memory underrun at %p\n", p);
abort ();
case MCHECK_TAIL:
printf("Memory overrun at %p\n", p);
abort ();
case MCHECK_FREE:
printf("Double free %p\n", p);
abort ();
}
}
}
#endif
static gboolean
mail_msg_free (MailMsg *mail_msg)
{
/* This is an idle callback. */
if (mail_msg->activity != NULL)
g_object_unref (mail_msg->activity);
if (mail_msg->error != NULL)
g_error_free (mail_msg->error);
g_slice_free1 (mail_msg->info->size, mail_msg);
return FALSE;
}
gpointer
mail_msg_ref (gpointer msg)
{
MailMsg *mail_msg = msg;
g_return_val_if_fail (mail_msg != NULL, msg);
g_return_val_if_fail (mail_msg->ref_count > 0, msg);
g_atomic_int_add (&mail_msg->ref_count, 1);
return msg;
}
void
mail_msg_unref (gpointer msg)
{
MailMsg *mail_msg = msg;
g_return_if_fail (mail_msg != NULL);
g_return_if_fail (mail_msg->ref_count > 0);
if (g_atomic_int_exchange_and_add (&mail_msg->ref_count, -1) > 1)
return;
#ifdef MALLOC_CHECK
checkmem (mail_msg);
checkmem (mail_msg->cancel);
checkmem (mail_msg->priv);
#endif
d(printf("Free message %p\n", msg));
if (mail_msg->info->free)
mail_msg->info->free (mail_msg);
g_mutex_lock (mail_msg_lock);
g_hash_table_remove (
mail_msg_active_table,
GINT_TO_POINTER (mail_msg->seq));
g_cond_broadcast (mail_msg_cond);
g_mutex_unlock (mail_msg_lock);
/* Destroy the message from an idle callback
* so we know we're in the main loop thread. */
g_idle_add ((GSourceFunc) mail_msg_free, mail_msg);
}
void
mail_msg_check_error (gpointer msg)
{
EShell *shell;
EShellView *shell_view;
EShellWindow *shell_window = NULL;
EShellContent *shell_content;
MailMsg *m = msg;
gchar *what;
GList *list, *iter;
#ifdef MALLOC_CHECK
checkmem (m);
checkmem (m->cancel);
checkmem (m->priv);
#endif
if (e_activity_handle_cancellation (m->activity, m->error))
return;
e_activity_set_state (m->activity, E_ACTIVITY_COMPLETED);
if (m->error == NULL)
return;
/* XXX Hmm, no explanation of why this is needed. It looks like
* a lame hack and will be removed at some point, if only to
* reintroduce whatever issue made this necessary so we can
* document it the source code this time. */
if (g_error_matches (m->error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_UID))
return;
shell = e_shell_get_default ();
/* Find the most recently used EShellWindow. */
list = e_shell_get_watched_windows (shell);
for (iter = list; iter != NULL; iter = g_list_next (iter)) {
if (E_IS_SHELL_WINDOW (iter->data)) {
shell_window = E_SHELL_WINDOW (iter->data);
break;
}
}
/* If we can't find an EShellWindow then... well, screw it. */
if (shell_window == NULL)
return;
shell_view = e_shell_window_get_shell_view (
shell_window, shell_builtin_backend);
shell_content = e_shell_view_get_shell_content (shell_view);
if (m->info->desc && (what = m->info->desc (m))) {
e_alert_submit (
E_ALERT_SINK (shell_content),
"mail:async-error", what,
m->error->message, NULL);
g_free (what);
} else
e_alert_submit (
E_ALERT_SINK (shell_content),
"mail:async-error-nodescribe",
m->error->message, NULL);
}
void
mail_msg_cancel (guint msgid)
{
MailMsg *msg;
GCancellable *cancellable = NULL;
g_mutex_lock (mail_msg_lock);
msg = g_hash_table_lookup (
mail_msg_active_table, GINT_TO_POINTER (msgid));
/* Hold a reference to the GCancellable so it doesn't finalize
* itself on us between unlocking the mutex and cancelling. */
if (msg != NULL) {
cancellable = e_activity_get_cancellable (msg->activity);
if (g_cancellable_is_cancelled (cancellable))
cancellable = NULL;
else
g_object_ref (cancellable);
}
g_mutex_unlock (mail_msg_lock);
if (cancellable != NULL) {
camel_operation_cancel (CAMEL_OPERATION (cancellable));
g_object_unref (cancellable);
}
}
gboolean
mail_msg_active (void)
{
gboolean active;
g_mutex_lock (mail_msg_lock);
active = g_hash_table_size (mail_msg_active_table) > 0;
g_mutex_unlock (mail_msg_lock);
return active;
}
/* **************************************** */
static GHookList cancel_hook_list;
GHook *
mail_cancel_hook_add (GHookFunc func, gpointer data)
{
GHook *hook;
g_mutex_lock (mail_msg_lock);
if (!cancel_hook_list.is_setup)
g_hook_list_init (&cancel_hook_list, sizeof (GHook));
hook = g_hook_alloc (&cancel_hook_list);
hook->func = func;
hook->data = data;
g_hook_append (&cancel_hook_list, hook);
g_mutex_unlock (mail_msg_lock);
return hook;
}
void
mail_cancel_hook_remove (GHook *hook)
{
g_mutex_lock (mail_msg_lock);
g_return_if_fail (cancel_hook_list.is_setup);
g_hook_destroy_link (&cancel_hook_list, hook);
g_mutex_unlock (mail_msg_lock);
}
void
mail_cancel_all (void)
{
camel_operation_cancel (NULL);
g_mutex_lock (mail_msg_lock);
if (cancel_hook_list.is_setup)
g_hook_list_invoke (&cancel_hook_list, FALSE);
g_mutex_unlock (mail_msg_lock);
}
static guint idle_source_id = 0;
G_LOCK_DEFINE_STATIC (idle_source_id);
static GAsyncQueue *main_loop_queue = NULL;
static GAsyncQueue *msg_reply_queue = NULL;
static GThread *main_thread = NULL;
static gboolean
mail_msg_idle_cb (void)
{
MailMsg *msg;
g_return_val_if_fail (main_loop_queue != NULL, FALSE);
g_return_val_if_fail (msg_reply_queue != NULL, FALSE);
G_LOCK (idle_source_id);
idle_source_id = 0;
G_UNLOCK (idle_source_id);
/* check the main loop queue */
while ((msg = g_async_queue_try_pop (main_loop_queue)) != NULL) {
GCancellable *cancellable;
cancellable = e_activity_get_cancellable (msg->activity);
g_idle_add_full (
G_PRIORITY_DEFAULT,
(GSourceFunc) mail_msg_submit,
g_object_ref (msg->activity),
(GDestroyNotify) g_object_unref);
if (msg->info->exec != NULL)
msg->info->exec (msg, cancellable, &msg->error);
if (msg->info->done != NULL)
msg->info->done (msg);
mail_msg_unref (msg);
}
/* check the reply queue */
while ((msg = g_async_queue_try_pop (msg_reply_queue)) != NULL) {
if (msg->info->done != NULL)
msg->info->done (msg);
mail_msg_check_error (msg);
mail_msg_unref (msg);
}
return FALSE;
}
static void
mail_msg_proxy (MailMsg *msg)
{
GCancellable *cancellable;
cancellable = e_activity_get_cancellable (msg->activity);
if (msg->info->desc != NULL) {
gchar *text = msg->info->desc (msg);
camel_operation_push_message (cancellable, "%s", text);
g_free (text);
}
g_idle_add_full (
G_PRIORITY_DEFAULT,
(GSourceFunc) mail_msg_submit,
g_object_ref (msg->activity),
(GDestroyNotify) g_object_unref);
if (msg->info->exec != NULL)
msg->info->exec (msg, cancellable, &msg->error);
if (msg->info->desc != NULL)
camel_operation_pop_message (cancellable);
g_async_queue_push (msg_reply_queue, msg);
G_LOCK (idle_source_id);
if (idle_source_id == 0)
idle_source_id = g_idle_add (
(GSourceFunc) mail_msg_idle_cb, NULL);
G_UNLOCK (idle_source_id);
}
void
mail_msg_init (void)
{
mail_msg_lock = g_mutex_new ();
mail_msg_cond = g_cond_new ();
main_loop_queue = g_async_queue_new ();
msg_reply_queue = g_async_queue_new ();
mail_msg_active_table = g_hash_table_new (NULL, NULL);
main_thread = g_thread_self ();
}
static gint
mail_msg_compare (const MailMsg *msg1, const MailMsg *msg2)
{
gint priority1 = msg1->priority;
gint priority2 = msg2->priority;
if (priority1 == priority2)
return 0;
return (priority1 < priority2) ? 1 : -1;
}
static gpointer
create_thread_pool (gpointer data)
{
GThreadPool *thread_pool;
gint max_threads = GPOINTER_TO_INT (data);
/* once created, run forever */
thread_pool = g_thread_pool_new (
(GFunc) mail_msg_proxy, NULL, max_threads, FALSE, NULL);
g_thread_pool_set_sort_function (
thread_pool, (GCompareDataFunc) mail_msg_compare, NULL);
return thread_pool;
}
void
mail_msg_main_loop_push (gpointer msg)
{
g_async_queue_push_sorted (main_loop_queue, msg,
(GCompareDataFunc) mail_msg_compare, NULL);
G_LOCK (idle_source_id);
if (idle_source_id == 0)
idle_source_id = g_idle_add (
(GSourceFunc) mail_msg_idle_cb, NULL);
G_UNLOCK (idle_source_id);
}
void
mail_msg_unordered_push (gpointer msg)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (10));
g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}
void
mail_msg_fast_ordered_push (gpointer msg)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}
void
mail_msg_slow_ordered_push (gpointer msg)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}
gboolean
mail_in_main_thread (void)
{
return (g_thread_self () == main_thread);
}
/* ********************************************************************** */
struct _call_msg {
MailMsg base;
mail_call_t type;
MailMainFunc func;
gpointer ret;
va_list ap;
EFlag *done;
};
static void
do_call (struct _call_msg *m,
GCancellable *cancellable,
GError **error)
{
gpointer p1, *p2, *p3, *p4, *p5;
gint i1;
va_list ap;
G_VA_COPY (ap, m->ap);
switch (m->type) {
case MAIL_CALL_p_p:
p1 = va_arg (ap, gpointer );
m->ret = m->func (p1);
break;
case MAIL_CALL_p_pp:
p1 = va_arg (ap, gpointer );
p2 = va_arg (ap, gpointer );
m->ret = m->func (p1, p2);
break;
case MAIL_CALL_p_ppp:
p1 = va_arg (ap, gpointer );
p2 = va_arg (ap, gpointer );
p3 = va_arg (ap, gpointer );
m->ret = m->func (p1, p2, p3);
break;
case MAIL_CALL_p_pppp:
p1 = va_arg (ap, gpointer );
p2 = va_arg (ap, gpointer );
p3 = va_arg (ap, gpointer );
p4 = va_arg (ap, gpointer );
m->ret = m->func (p1, p2, p3, p4);
break;
case MAIL_CALL_p_ppppp:
p1 = va_arg (ap, gpointer );
p2 = va_arg (ap, gpointer );
p3 = va_arg (ap, gpointer );
p4 = va_arg (ap, gpointer );
p5 = va_arg (ap, gpointer );
m->ret = m->func (p1, p2, p3, p4, p5);
break;
case MAIL_CALL_p_ppippp:
p1 = va_arg (ap, gpointer );
p2 = va_arg (ap, gpointer );
i1 = va_arg (ap, gint);
p3 = va_arg (ap, gpointer );
p4 = va_arg (ap, gpointer );
p5 = va_arg (ap, gpointer );
m->ret = m->func (p1, p2, i1, p3, p4, p5);
break;
}
e_activity_set_state (
m->base.activity,
g_cancellable_is_cancelled (cancellable) ?
E_ACTIVITY_CANCELLED : E_ACTIVITY_COMPLETED);
if (m->done != NULL)
e_flag_set (m->done);
}
static MailMsgInfo mail_call_info = {
sizeof (struct _call_msg),
(MailMsgDescFunc) NULL,
(MailMsgExecFunc) do_call,
(MailMsgDoneFunc) NULL,
(MailMsgFreeFunc) NULL
};
gpointer
mail_call_main (mail_call_t type, MailMainFunc func, ...)
{
GCancellable *cancellable;
struct _call_msg *m;
gpointer ret;
va_list ap;
va_start (ap, func);
m = mail_msg_new (&mail_call_info);
m->type = type;
m->func = func;
G_VA_COPY (m->ap, ap);
cancellable = e_activity_get_cancellable (m->base.activity);
if (mail_in_main_thread ())
do_call (m, cancellable, &m->base.error);
else {
mail_msg_ref (m);
m->done = e_flag_new ();
mail_msg_main_loop_push (m);
e_flag_wait (m->done);
e_flag_free (m->done);
}
va_end (ap);
ret = m->ret;
mail_msg_unref (m);
return ret;
}
void
mail_mt_set_backend (gchar *backend)
{
shell_builtin_backend = backend;
}