Enhance progress indicator of folder copy/move
[evolution.git] / src / libemail-engine / mail-mt.c
bloba612ff85211fbc85cc1740382103b1d1b68fd488
1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
14 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
17 #include "evolution-config.h"
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <errno.h>
24 #include <gtk/gtk.h>
26 #include <libedataserver/libedataserver.h>
28 #include "mail-mt.h"
30 /*#define MALLOC_CHECK*/
31 #define d(x)
33 static guint mail_msg_seq; /* sequence number of each message */
35 /* Table of active messages. Must hold mail_msg_lock to access. */
36 static GHashTable *mail_msg_active_table;
37 static GMutex mail_msg_lock;
38 static GCond mail_msg_cond;
40 static MailMsgCreateActivityFunc create_activity = NULL;
41 static MailMsgSubmitActivityFunc submit_activity = NULL;
42 static MailMsgFreeActivityFunc free_activity = NULL;
43 static MailMsgCompleteActivityFunc complete_activity = NULL;
44 static MailMsgAlertErrorFunc alert_error = NULL;
45 static MailMsgCancelActivityFunc cancel_activity = NULL;
46 static MailMsgGetAlertSinkFunc get_alert_sink = NULL;
48 void
49 mail_msg_register_activities (MailMsgCreateActivityFunc acreate,
50 MailMsgSubmitActivityFunc asubmit,
51 MailMsgFreeActivityFunc freeact,
52 MailMsgCompleteActivityFunc comp_act,
53 MailMsgCancelActivityFunc cancel_act,
54 MailMsgAlertErrorFunc ealert,
55 MailMsgGetAlertSinkFunc ealertsink)
57 /* XXX This is an utter hack to keep EActivity out
58 * of EDS and still let Evolution do EActivity. */
59 create_activity = acreate;
60 submit_activity = asubmit;
61 free_activity = freeact;
62 complete_activity = comp_act;
63 cancel_activity = cancel_act;
64 alert_error = ealert;
65 get_alert_sink = ealertsink;
68 EAlertSink *
69 mail_msg_get_alert_sink ()
71 if (get_alert_sink)
72 return get_alert_sink ();
74 return NULL;
77 static void
78 mail_msg_cancelled (CamelOperation *operation,
79 gpointer user_data)
81 mail_msg_cancel (GPOINTER_TO_UINT (user_data));
84 static gboolean
85 mail_msg_submit (CamelOperation *cancellable)
88 if (submit_activity)
89 submit_activity ((GCancellable *) cancellable);
90 return FALSE;
93 gpointer
94 mail_msg_new_with_cancellable (MailMsgInfo *info,
95 GCancellable *cancellable)
97 MailMsg *msg;
99 g_mutex_lock (&mail_msg_lock);
101 msg = g_slice_alloc0 (info->size);
102 msg->info = info;
103 msg->ref_count = 1;
104 msg->seq = mail_msg_seq++;
106 if (cancellable)
107 msg->cancellable = g_object_ref (cancellable);
108 else
109 msg->cancellable = camel_operation_new ();
111 if (create_activity)
112 create_activity (msg->cancellable);
114 g_signal_connect (
115 msg->cancellable, "cancelled",
116 G_CALLBACK (mail_msg_cancelled),
117 GINT_TO_POINTER (msg->seq));
119 g_hash_table_insert (
120 mail_msg_active_table, GINT_TO_POINTER (msg->seq), msg);
122 d (printf ("New message %p\n", msg));
124 g_mutex_unlock (&mail_msg_lock);
126 return msg;
129 gpointer
130 mail_msg_new (MailMsgInfo *info)
132 return mail_msg_new_with_cancellable (info, NULL);
135 #ifdef MALLOC_CHECK
136 #include <mcheck.h>
138 static void
139 checkmem (gpointer p)
141 if (p) {
142 gint status = mprobe (p);
144 switch (status) {
145 case MCHECK_HEAD:
146 printf ("Memory underrun at %p\n", p);
147 abort ();
148 case MCHECK_TAIL:
149 printf ("Memory overrun at %p\n", p);
150 abort ();
151 case MCHECK_FREE:
152 printf ("Double free %p\n", p);
153 abort ();
157 #endif
159 static gboolean
160 mail_msg_free (MailMsg *mail_msg)
162 /* This is an idle callback. */
164 if (free_activity)
165 free_activity (mail_msg->cancellable);
167 if (mail_msg->cancellable != NULL)
168 g_object_unref (mail_msg->cancellable);
170 if (mail_msg->error != NULL)
171 g_error_free (mail_msg->error);
173 g_slice_free1 (mail_msg->info->size, mail_msg);
175 return FALSE;
178 gpointer
179 mail_msg_ref (gpointer msg)
181 MailMsg *mail_msg = msg;
183 g_return_val_if_fail (mail_msg != NULL, msg);
184 g_return_val_if_fail (mail_msg->ref_count > 0, msg);
186 g_atomic_int_inc (&mail_msg->ref_count);
188 return msg;
191 void
192 mail_msg_unref (gpointer msg)
194 MailMsg *mail_msg = msg;
196 g_return_if_fail (mail_msg != NULL);
197 g_return_if_fail (mail_msg->ref_count > 0);
199 if (g_atomic_int_dec_and_test (&mail_msg->ref_count)) {
201 #ifdef MALLOC_CHECK
202 checkmem (mail_msg);
203 checkmem (mail_msg->cancel);
204 checkmem (mail_msg->priv);
205 #endif
206 d (printf ("Free message %p\n", msg));
208 if (mail_msg->info->free)
209 mail_msg->info->free (mail_msg);
211 g_mutex_lock (&mail_msg_lock);
213 g_hash_table_remove (
214 mail_msg_active_table,
215 GINT_TO_POINTER (mail_msg->seq));
216 g_cond_broadcast (&mail_msg_cond);
218 g_mutex_unlock (&mail_msg_lock);
220 /* Destroy the message from an idle callback
221 * so we know we're in the main loop thread.
222 * Prioritize ahead of GTK+ redraws. */
223 g_idle_add_full (
224 G_PRIORITY_HIGH_IDLE,
225 (GSourceFunc) mail_msg_free, mail_msg, NULL);
229 void
230 mail_msg_check_error (gpointer msg)
232 MailMsg *m = msg;
234 #ifdef MALLOC_CHECK
235 checkmem (m);
236 checkmem (m->cancel);
237 checkmem (m->priv);
238 #endif
240 if (m->error == NULL)
241 return;
243 if (complete_activity)
244 complete_activity (m->cancellable);
246 if (g_error_matches (m->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
247 if (cancel_activity)
248 cancel_activity (m->cancellable);
249 return;
252 /* XXX Hmm, no explanation of why this is needed. It looks like
253 * a lame hack and will be removed at some point, if only to
254 * reintroduce whatever issue made this necessary so we can
255 * document it in the source code this time. */
256 if (g_error_matches (
257 m->error, CAMEL_FOLDER_ERROR,
258 CAMEL_FOLDER_ERROR_INVALID_UID))
259 return;
261 /* FIXME: Submit an error on the dbus */
262 if (alert_error) {
263 gchar *what;
265 if (m->info->desc && (what = m->info->desc (m))) {
266 alert_error (m->cancellable, what, m->error->message);
267 g_free (what);
268 } else
269 alert_error (m->cancellable, NULL, m->error->message);
273 void
274 mail_msg_cancel (guint msgid)
276 MailMsg *msg;
277 GCancellable *cancellable = NULL;
279 g_mutex_lock (&mail_msg_lock);
281 msg = g_hash_table_lookup (
282 mail_msg_active_table, GINT_TO_POINTER (msgid));
284 /* Hold a reference to the GCancellable so it doesn't finalize
285 * itself on us between unlocking the mutex and cancelling. */
286 if (msg != NULL) {
287 cancellable = msg->cancellable;
288 if (g_cancellable_is_cancelled (cancellable))
289 cancellable = NULL;
290 else
291 g_object_ref (cancellable);
294 g_mutex_unlock (&mail_msg_lock);
296 if (cancellable != NULL) {
297 g_cancellable_cancel (cancellable);
298 g_object_unref (cancellable);
302 gboolean
303 mail_msg_active (void)
305 gboolean active;
307 g_mutex_lock (&mail_msg_lock);
308 active = g_hash_table_size (mail_msg_active_table) > 0;
309 g_mutex_unlock (&mail_msg_lock);
311 return active;
314 /* **************************************** */
316 static guint idle_source_id = 0;
317 G_LOCK_DEFINE_STATIC (idle_source_id);
318 static GAsyncQueue *main_loop_queue = NULL;
319 static GAsyncQueue *msg_reply_queue = NULL;
320 static GThread *main_thread = NULL;
322 static gboolean
323 mail_msg_idle_cb (void)
325 MailMsg *msg;
327 g_return_val_if_fail (main_loop_queue != NULL, FALSE);
328 g_return_val_if_fail (msg_reply_queue != NULL, FALSE);
330 G_LOCK (idle_source_id);
331 idle_source_id = 0;
332 G_UNLOCK (idle_source_id);
333 /* check the main loop queue */
334 while ((msg = g_async_queue_try_pop (main_loop_queue)) != NULL) {
335 GCancellable *cancellable;
337 cancellable = msg->cancellable;
339 g_idle_add_full (
340 G_PRIORITY_DEFAULT,
341 (GSourceFunc) mail_msg_submit,
342 g_object_ref (msg->cancellable),
343 (GDestroyNotify) g_object_unref);
344 if (msg->info->exec != NULL)
345 msg->info->exec (msg, cancellable, &msg->error);
346 if (msg->info->done != NULL)
347 msg->info->done (msg);
348 mail_msg_unref (msg);
351 /* check the reply queue */
352 while ((msg = g_async_queue_try_pop (msg_reply_queue)) != NULL) {
353 if (msg->info->done != NULL)
354 msg->info->done (msg);
355 mail_msg_check_error (msg);
356 mail_msg_unref (msg);
358 return FALSE;
361 static void
362 mail_msg_proxy (MailMsg *msg)
364 GCancellable *cancellable;
366 cancellable = msg->cancellable;
368 if (msg->info->desc != NULL) {
369 gchar *text = msg->info->desc (msg);
370 camel_operation_push_message (cancellable, "%s", text);
371 g_free (text);
374 g_idle_add_full (
375 G_PRIORITY_DEFAULT,
376 (GSourceFunc) mail_msg_submit,
377 g_object_ref (msg->cancellable),
378 (GDestroyNotify) g_object_unref);
380 if (msg->info->exec != NULL)
381 msg->info->exec (msg, cancellable, &msg->error);
383 if (msg->info->desc != NULL)
384 camel_operation_pop_message (cancellable);
386 g_async_queue_push (msg_reply_queue, msg);
388 G_LOCK (idle_source_id);
389 if (idle_source_id == 0)
390 /* Prioritize ahead of GTK+ redraws. */
391 idle_source_id = g_idle_add_full (
392 G_PRIORITY_HIGH_IDLE,
393 (GSourceFunc) mail_msg_idle_cb, NULL, NULL);
394 G_UNLOCK (idle_source_id);
397 void
398 mail_msg_init (void)
400 g_mutex_init (&mail_msg_lock);
401 g_cond_init (&mail_msg_cond);
403 main_loop_queue = g_async_queue_new ();
404 msg_reply_queue = g_async_queue_new ();
406 mail_msg_active_table = g_hash_table_new (NULL, NULL);
407 main_thread = g_thread_self ();
410 static gint
411 mail_msg_compare (const MailMsg *msg1,
412 const MailMsg *msg2)
414 gint priority1 = msg1->priority;
415 gint priority2 = msg2->priority;
417 if (priority1 == priority2)
418 return 0;
420 return (priority1 < priority2) ? 1 : -1;
423 static gpointer
424 create_thread_pool (gpointer data)
426 GThreadPool *thread_pool;
427 gint max_threads = GPOINTER_TO_INT (data);
429 /* once created, run forever */
430 thread_pool = g_thread_pool_new (
431 (GFunc) mail_msg_proxy, NULL, max_threads, FALSE, NULL);
432 g_thread_pool_set_sort_function (
433 thread_pool, (GCompareDataFunc) mail_msg_compare, NULL);
435 return thread_pool;
438 void
439 mail_msg_main_loop_push (gpointer msg)
441 g_async_queue_push_sorted (
442 main_loop_queue, msg,
443 (GCompareDataFunc) mail_msg_compare, NULL);
445 G_LOCK (idle_source_id);
446 if (idle_source_id == 0)
447 /* Prioritize ahead of GTK+ redraws. */
448 idle_source_id = g_idle_add_full (
449 G_PRIORITY_HIGH_IDLE,
450 (GSourceFunc) mail_msg_idle_cb, NULL, NULL);
451 G_UNLOCK (idle_source_id);
454 void
455 mail_msg_unordered_push (gpointer msg)
457 static GOnce once = G_ONCE_INIT;
459 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (10));
461 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
464 void
465 mail_msg_fast_ordered_push (gpointer msg)
467 static GOnce once = G_ONCE_INIT;
469 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
471 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
474 void
475 mail_msg_slow_ordered_push (gpointer msg)
477 static GOnce once = G_ONCE_INIT;
479 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
481 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
484 gboolean
485 mail_in_main_thread (void)
487 return (g_thread_self () == main_thread);
490 /* ********************************************************************** */
492 struct _call_msg {
493 MailMsg base;
495 mail_call_t type;
496 MailMainFunc func;
497 gpointer ret;
498 va_list ap;
499 EFlag *done;
502 static void
503 do_call (struct _call_msg *m,
504 GCancellable *cancellable,
505 GError **error)
507 gpointer p1, *p2, *p3, *p4, *p5;
508 gint i1;
509 va_list ap;
511 G_VA_COPY (ap, m->ap);
513 switch (m->type) {
514 case MAIL_CALL_p_p:
515 p1 = va_arg (ap, gpointer);
516 m->ret = m->func (p1);
517 break;
518 case MAIL_CALL_p_pp:
519 p1 = va_arg (ap, gpointer);
520 p2 = va_arg (ap, gpointer);
521 m->ret = m->func (p1, p2);
522 break;
523 case MAIL_CALL_p_ppp:
524 p1 = va_arg (ap, gpointer);
525 p2 = va_arg (ap, gpointer);
526 p3 = va_arg (ap, gpointer);
527 m->ret = m->func (p1, p2, p3);
528 break;
529 case MAIL_CALL_p_pppp:
530 p1 = va_arg (ap, gpointer);
531 p2 = va_arg (ap, gpointer);
532 p3 = va_arg (ap, gpointer);
533 p4 = va_arg (ap, gpointer);
534 m->ret = m->func (p1, p2, p3, p4);
535 break;
536 case MAIL_CALL_p_ppppp:
537 p1 = va_arg (ap, gpointer);
538 p2 = va_arg (ap, gpointer);
539 p3 = va_arg (ap, gpointer);
540 p4 = va_arg (ap, gpointer);
541 p5 = va_arg (ap, gpointer);
542 m->ret = m->func (p1, p2, p3, p4, p5);
543 break;
544 case MAIL_CALL_p_ppippp:
545 p1 = va_arg (ap, gpointer);
546 p2 = va_arg (ap, gpointer);
547 i1 = va_arg (ap, gint);
548 p3 = va_arg (ap, gpointer);
549 p4 = va_arg (ap, gpointer);
550 p5 = va_arg (ap, gpointer);
551 m->ret = m->func (p1, p2, i1, p3, p4, p5);
552 break;
555 va_end (ap);
557 if (g_cancellable_is_cancelled (cancellable)) {
558 if (cancel_activity)
559 cancel_activity (cancellable);
560 } else {
561 if (complete_activity)
562 complete_activity (cancellable);
565 if (m->done != NULL)
566 e_flag_set (m->done);
569 static void
570 do_free (struct _call_msg *msg)
572 va_end (msg->ap);
575 static MailMsgInfo mail_call_info = {
576 sizeof (struct _call_msg),
577 (MailMsgDescFunc) NULL,
578 (MailMsgExecFunc) do_call,
579 (MailMsgDoneFunc) NULL,
580 (MailMsgFreeFunc) do_free
583 gpointer
584 mail_call_main (mail_call_t type,
585 MailMainFunc func,
586 ...)
588 GCancellable *cancellable;
589 struct _call_msg *m;
590 gpointer ret;
591 va_list ap;
593 va_start (ap, func);
595 m = mail_msg_new (&mail_call_info);
596 m->type = type;
597 m->func = func;
598 G_VA_COPY (m->ap, ap);
600 cancellable = m->base.cancellable;
602 if (mail_in_main_thread ())
603 do_call (m, cancellable, &m->base.error);
604 else {
605 mail_msg_ref (m);
606 m->done = e_flag_new ();
607 mail_msg_main_loop_push (m);
608 e_flag_wait (m->done);
609 e_flag_free (m->done);
612 va_end (ap);
614 ret = m->ret;
615 mail_msg_unref (m);
617 /* the m->ap is freed on the message end, at do_free() above */
618 /* coverity[missing_va_end] */
619 return ret;