661 lines
14 KiB
C
661 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)
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <libedataserver/libedataserver.h>
|
|
|
|
#include "mail-mt.h"
|
|
|
|
/*#define MALLOC_CHECK*/
|
|
#define d(x)
|
|
|
|
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 MailMsgCreateActivityFunc create_activity = NULL;
|
|
static MailMsgSubmitActivityFunc submit_activity = NULL;
|
|
static MailMsgFreeActivityFunc free_activity = NULL;
|
|
static MailMsgCompleteActivityFunc complete_activity = NULL;
|
|
static MailMsgAlertErrorFunc alert_error = NULL;
|
|
static MailMsgCancelActivityFunc cancel_activity = NULL;
|
|
static MailMsgGetAlertSinkFunc get_alert_sink = NULL;
|
|
|
|
void
|
|
mail_msg_register_activities (MailMsgCreateActivityFunc acreate,
|
|
MailMsgSubmitActivityFunc asubmit,
|
|
MailMsgFreeActivityFunc freeact,
|
|
MailMsgCompleteActivityFunc comp_act,
|
|
MailMsgCancelActivityFunc cancel_act,
|
|
MailMsgAlertErrorFunc ealert,
|
|
MailMsgGetAlertSinkFunc ealertsink)
|
|
{
|
|
/* XXX This is an utter hack to keep EActivity out
|
|
* of EDS and still let Evolution do EActivity. */
|
|
create_activity = acreate;
|
|
submit_activity = asubmit;
|
|
free_activity = freeact;
|
|
complete_activity = comp_act;
|
|
cancel_activity = cancel_act;
|
|
alert_error = ealert;
|
|
get_alert_sink = ealertsink;
|
|
}
|
|
|
|
EAlertSink *
|
|
mail_msg_get_alert_sink ()
|
|
{
|
|
if (get_alert_sink)
|
|
return get_alert_sink ();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
mail_msg_cancelled (CamelOperation *operation,
|
|
gpointer user_data)
|
|
{
|
|
mail_msg_cancel (GPOINTER_TO_UINT (user_data));
|
|
}
|
|
|
|
static gboolean
|
|
mail_msg_submit (CamelOperation *cancellable)
|
|
{
|
|
|
|
if (submit_activity)
|
|
submit_activity ((GCancellable *) cancellable);
|
|
return FALSE;
|
|
}
|
|
|
|
gpointer
|
|
mail_msg_new (MailMsgInfo *info)
|
|
{
|
|
MailMsg *msg;
|
|
|
|
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->cancellable = camel_operation_new ();
|
|
|
|
if (create_activity)
|
|
create_activity (msg->cancellable);
|
|
|
|
g_signal_connect (
|
|
msg->cancellable, "cancelled",
|
|
G_CALLBACK (mail_msg_cancelled),
|
|
GINT_TO_POINTER (msg->seq));
|
|
|
|
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 (free_activity)
|
|
free_activity (mail_msg->cancellable);
|
|
|
|
if (mail_msg->cancellable != NULL)
|
|
g_object_unref (mail_msg->cancellable);
|
|
|
|
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_inc (&mail_msg->ref_count);
|
|
|
|
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_dec_and_test (&mail_msg->ref_count)) {
|
|
|
|
#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.
|
|
* Prioritize ahead of GTK+ redraws. */
|
|
g_idle_add_full (
|
|
G_PRIORITY_HIGH_IDLE,
|
|
(GSourceFunc) mail_msg_free, mail_msg, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
mail_msg_check_error (gpointer msg)
|
|
{
|
|
MailMsg *m = msg;
|
|
|
|
#ifdef MALLOC_CHECK
|
|
checkmem (m);
|
|
checkmem (m->cancel);
|
|
checkmem (m->priv);
|
|
#endif
|
|
|
|
if (m->error == NULL)
|
|
return;
|
|
|
|
if (complete_activity)
|
|
complete_activity (m->cancellable);
|
|
|
|
if (g_error_matches (m->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
|
if (cancel_activity)
|
|
cancel_activity (m->cancellable);
|
|
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 in the source code this time. */
|
|
if (g_error_matches (
|
|
m->error, CAMEL_FOLDER_ERROR,
|
|
CAMEL_FOLDER_ERROR_INVALID_UID))
|
|
return;
|
|
|
|
/* FIXME: Submit an error on the dbus */
|
|
if (alert_error) {
|
|
gchar *what;
|
|
|
|
if (m->info->desc && (what = m->info->desc (m))) {
|
|
alert_error (m->cancellable, what, m->error->message);
|
|
g_free (what);
|
|
} else
|
|
alert_error (m->cancellable, NULL, m->error->message);
|
|
}
|
|
}
|
|
|
|
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 = msg->cancellable;
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
cancellable = NULL;
|
|
else
|
|
g_object_ref (cancellable);
|
|
}
|
|
|
|
g_mutex_unlock (&mail_msg_lock);
|
|
|
|
if (cancellable != NULL) {
|
|
g_cancellable_cancel (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_all ();
|
|
|
|
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 = msg->cancellable;
|
|
|
|
g_idle_add_full (
|
|
G_PRIORITY_DEFAULT,
|
|
(GSourceFunc) mail_msg_submit,
|
|
g_object_ref (msg->cancellable),
|
|
(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 = msg->cancellable;
|
|
|
|
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->cancellable),
|
|
(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)
|
|
/* Prioritize ahead of GTK+ redraws. */
|
|
idle_source_id = g_idle_add_full (
|
|
G_PRIORITY_HIGH_IDLE,
|
|
(GSourceFunc) mail_msg_idle_cb, NULL, NULL);
|
|
G_UNLOCK (idle_source_id);
|
|
}
|
|
|
|
void
|
|
mail_msg_init (void)
|
|
{
|
|
g_mutex_init (&mail_msg_lock);
|
|
g_cond_init (&mail_msg_cond);
|
|
|
|
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)
|
|
/* Prioritize ahead of GTK+ redraws. */
|
|
idle_source_id = g_idle_add_full (
|
|
G_PRIORITY_HIGH_IDLE,
|
|
(GSourceFunc) mail_msg_idle_cb, NULL, 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;
|
|
}
|
|
|
|
va_end (ap);
|
|
|
|
if (g_cancellable_is_cancelled (cancellable)) {
|
|
if (cancel_activity)
|
|
cancel_activity (cancellable);
|
|
} else {
|
|
if (complete_activity)
|
|
complete_activity (cancellable);
|
|
}
|
|
|
|
if (m->done != NULL)
|
|
e_flag_set (m->done);
|
|
}
|
|
|
|
static void
|
|
do_free (struct _call_msg *msg)
|
|
{
|
|
va_end (msg->ap);
|
|
}
|
|
|
|
static MailMsgInfo mail_call_info = {
|
|
sizeof (struct _call_msg),
|
|
(MailMsgDescFunc) NULL,
|
|
(MailMsgExecFunc) do_call,
|
|
(MailMsgDoneFunc) NULL,
|
|
(MailMsgFreeFunc) do_free
|
|
};
|
|
|
|
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 = m->base.cancellable;
|
|
|
|
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;
|
|
}
|
|
|