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.
5 *
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.
10 *
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/>.
13 *
14 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
15 */
16
17 #include "evolution-config.h"
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <errno.h>
23
24 #include <gtk/gtk.h>
25
26 #include <libedataserver/libedataserver.h>
27
28 #include "mail-mt.h"
29
30 /*#define MALLOC_CHECK*/
31 #define d(x)
32
33 static guint mail_msg_seq; /* sequence number of each message */
34
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;
39
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;
47
48 void
mail_msg_register_activities(MailMsgCreateActivityFunc acreate,MailMsgSubmitActivityFunc asubmit,MailMsgFreeActivityFunc freeact,MailMsgCompleteActivityFunc comp_act,MailMsgCancelActivityFunc cancel_act,MailMsgAlertErrorFunc ealert,MailMsgGetAlertSinkFunc ealertsink)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)
56 {
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;
66 }
67
68 EAlertSink *
mail_msg_get_alert_sink()69 mail_msg_get_alert_sink ()
70 {
71 if (get_alert_sink)
72 return get_alert_sink ();
73
74 return NULL;
75 }
76
77 static void
mail_msg_cancelled(CamelOperation * operation,gpointer user_data)78 mail_msg_cancelled (CamelOperation *operation,
79 gpointer user_data)
80 {
81 mail_msg_cancel (GPOINTER_TO_UINT (user_data));
82 }
83
84 static gboolean
mail_msg_submit(CamelOperation * cancellable)85 mail_msg_submit (CamelOperation *cancellable)
86 {
87
88 if (submit_activity)
89 submit_activity ((GCancellable *) cancellable);
90 return FALSE;
91 }
92
93 gpointer
mail_msg_new_with_cancellable(MailMsgInfo * info,GCancellable * cancellable)94 mail_msg_new_with_cancellable (MailMsgInfo *info,
95 GCancellable *cancellable)
96 {
97 MailMsg *msg;
98
99 g_mutex_lock (&mail_msg_lock);
100
101 msg = g_slice_alloc0 (info->size);
102 msg->info = info;
103 msg->ref_count = 1;
104 msg->seq = mail_msg_seq++;
105
106 if (cancellable)
107 msg->cancellable = g_object_ref (cancellable);
108 else
109 msg->cancellable = camel_operation_new ();
110
111 if (create_activity)
112 create_activity (msg->cancellable);
113
114 g_signal_connect (
115 msg->cancellable, "cancelled",
116 G_CALLBACK (mail_msg_cancelled),
117 GINT_TO_POINTER (msg->seq));
118
119 g_hash_table_insert (
120 mail_msg_active_table, GINT_TO_POINTER (msg->seq), msg);
121
122 d (printf ("New message %p\n", msg));
123
124 g_mutex_unlock (&mail_msg_lock);
125
126 return msg;
127 }
128
129 gpointer
mail_msg_new(MailMsgInfo * info)130 mail_msg_new (MailMsgInfo *info)
131 {
132 return mail_msg_new_with_cancellable (info, NULL);
133 }
134
135 #ifdef MALLOC_CHECK
136 #include <mcheck.h>
137
138 static void
checkmem(gpointer p)139 checkmem (gpointer p)
140 {
141 if (p) {
142 gint status = mprobe (p);
143
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 ();
154 }
155 }
156 }
157 #endif
158
159 static gboolean
mail_msg_free(MailMsg * mail_msg)160 mail_msg_free (MailMsg *mail_msg)
161 {
162 /* This is an idle callback. */
163
164 if (free_activity)
165 free_activity (mail_msg->cancellable);
166
167 if (mail_msg->cancellable != NULL)
168 g_object_unref (mail_msg->cancellable);
169
170 if (mail_msg->error != NULL)
171 g_error_free (mail_msg->error);
172
173 g_slice_free1 (mail_msg->info->size, mail_msg);
174
175 return FALSE;
176 }
177
178 gpointer
mail_msg_ref(gpointer msg)179 mail_msg_ref (gpointer msg)
180 {
181 MailMsg *mail_msg = msg;
182
183 g_return_val_if_fail (mail_msg != NULL, msg);
184 g_return_val_if_fail (mail_msg->ref_count > 0, msg);
185
186 g_atomic_int_inc (&mail_msg->ref_count);
187
188 return msg;
189 }
190
191 void
mail_msg_unref(gpointer msg)192 mail_msg_unref (gpointer msg)
193 {
194 MailMsg *mail_msg = msg;
195
196 g_return_if_fail (mail_msg != NULL);
197 g_return_if_fail (mail_msg->ref_count > 0);
198
199 if (g_atomic_int_dec_and_test (&mail_msg->ref_count)) {
200
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));
207
208 if (mail_msg->info->free)
209 mail_msg->info->free (mail_msg);
210
211 g_mutex_lock (&mail_msg_lock);
212
213 g_hash_table_remove (
214 mail_msg_active_table,
215 GINT_TO_POINTER (mail_msg->seq));
216 g_cond_broadcast (&mail_msg_cond);
217
218 g_mutex_unlock (&mail_msg_lock);
219
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);
226 }
227 }
228
229 void
mail_msg_check_error(gpointer msg)230 mail_msg_check_error (gpointer msg)
231 {
232 MailMsg *m = msg;
233
234 #ifdef MALLOC_CHECK
235 checkmem (m);
236 checkmem (m->cancel);
237 checkmem (m->priv);
238 #endif
239
240 if (m->error == NULL)
241 return;
242
243 if (complete_activity)
244 complete_activity (m->cancellable);
245
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;
250 }
251
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;
260
261 /* FIXME: Submit an error on the dbus */
262 if (alert_error) {
263 gchar *what;
264
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);
270 }
271 }
272
273 void
mail_msg_cancel(guint msgid)274 mail_msg_cancel (guint msgid)
275 {
276 MailMsg *msg;
277 GCancellable *cancellable = NULL;
278
279 g_mutex_lock (&mail_msg_lock);
280
281 msg = g_hash_table_lookup (
282 mail_msg_active_table, GINT_TO_POINTER (msgid));
283
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);
292 }
293
294 g_mutex_unlock (&mail_msg_lock);
295
296 if (cancellable != NULL) {
297 g_cancellable_cancel (cancellable);
298 g_object_unref (cancellable);
299 }
300 }
301
302 gboolean
mail_msg_active(void)303 mail_msg_active (void)
304 {
305 gboolean active;
306
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);
310
311 return active;
312 }
313
314 /* **************************************** */
315
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;
321
322 static gboolean
mail_msg_idle_cb(void)323 mail_msg_idle_cb (void)
324 {
325 MailMsg *msg;
326
327 g_return_val_if_fail (main_loop_queue != NULL, FALSE);
328 g_return_val_if_fail (msg_reply_queue != NULL, FALSE);
329
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;
336
337 cancellable = msg->cancellable;
338
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);
349 }
350
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);
357 }
358 return FALSE;
359 }
360
361 static void
mail_msg_proxy(MailMsg * msg)362 mail_msg_proxy (MailMsg *msg)
363 {
364 GCancellable *cancellable;
365
366 cancellable = msg->cancellable;
367
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);
372 }
373
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);
379
380 if (msg->info->exec != NULL)
381 msg->info->exec (msg, cancellable, &msg->error);
382
383 if (msg->info->desc != NULL)
384 camel_operation_pop_message (cancellable);
385
386 g_async_queue_push (msg_reply_queue, msg);
387
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);
395 }
396
397 void
mail_msg_init(void)398 mail_msg_init (void)
399 {
400 g_mutex_init (&mail_msg_lock);
401 g_cond_init (&mail_msg_cond);
402
403 main_loop_queue = g_async_queue_new ();
404 msg_reply_queue = g_async_queue_new ();
405
406 mail_msg_active_table = g_hash_table_new (NULL, NULL);
407 main_thread = g_thread_self ();
408 }
409
410 static gint
mail_msg_compare(const MailMsg * msg1,const MailMsg * msg2)411 mail_msg_compare (const MailMsg *msg1,
412 const MailMsg *msg2)
413 {
414 gint priority1 = msg1->priority;
415 gint priority2 = msg2->priority;
416
417 if (priority1 == priority2)
418 return 0;
419
420 return (priority1 < priority2) ? 1 : -1;
421 }
422
423 static gpointer
create_thread_pool(gpointer data)424 create_thread_pool (gpointer data)
425 {
426 GThreadPool *thread_pool;
427 gint max_threads = GPOINTER_TO_INT (data);
428
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);
434
435 return thread_pool;
436 }
437
438 void
mail_msg_main_loop_push(gpointer msg)439 mail_msg_main_loop_push (gpointer msg)
440 {
441 g_async_queue_push_sorted (
442 main_loop_queue, msg,
443 (GCompareDataFunc) mail_msg_compare, NULL);
444
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);
452 }
453
454 void
mail_msg_unordered_push(gpointer msg)455 mail_msg_unordered_push (gpointer msg)
456 {
457 static GOnce once = G_ONCE_INIT;
458
459 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (10));
460
461 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
462 }
463
464 void
mail_msg_fast_ordered_push(gpointer msg)465 mail_msg_fast_ordered_push (gpointer msg)
466 {
467 static GOnce once = G_ONCE_INIT;
468
469 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
470
471 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
472 }
473
474 void
mail_msg_slow_ordered_push(gpointer msg)475 mail_msg_slow_ordered_push (gpointer msg)
476 {
477 static GOnce once = G_ONCE_INIT;
478
479 g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
480
481 g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
482 }
483
484 gboolean
mail_in_main_thread(void)485 mail_in_main_thread (void)
486 {
487 return (g_thread_self () == main_thread);
488 }
489
490 /* ********************************************************************** */
491
492 struct _call_msg {
493 MailMsg base;
494
495 mail_call_t type;
496 MailMainFunc func;
497 gpointer ret;
498 va_list ap;
499 EFlag *done;
500 };
501
502 static void
do_call(struct _call_msg * m,GCancellable * cancellable,GError ** error)503 do_call (struct _call_msg *m,
504 GCancellable *cancellable,
505 GError **error)
506 {
507 gpointer p1, *p2, *p3, *p4, *p5;
508 gint i1;
509 va_list ap;
510
511 G_VA_COPY (ap, m->ap);
512
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;
553 }
554
555 va_end (ap);
556
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);
563 }
564
565 if (m->done != NULL)
566 e_flag_set (m->done);
567 }
568
569 static void
do_free(struct _call_msg * msg)570 do_free (struct _call_msg *msg)
571 {
572 va_end (msg->ap);
573 }
574
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
581 };
582
583 gpointer
mail_call_main(mail_call_t type,MailMainFunc func,...)584 mail_call_main (mail_call_t type,
585 MailMainFunc func,
586 ...)
587 {
588 GCancellable *cancellable;
589 struct _call_msg *m;
590 gpointer ret;
591 va_list ap;
592
593 va_start (ap, func);
594
595 m = mail_msg_new (&mail_call_info);
596 m->type = type;
597 m->func = func;
598 G_VA_COPY (m->ap, ap);
599
600 cancellable = m->base.cancellable;
601
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);
610 }
611
612 va_end (ap);
613
614 ret = m->ret;
615 mail_msg_unref (m);
616
617 /* the m->ap is freed on the message end, at do_free() above */
618 /* coverity[missing_va_end] */
619 return ret;
620 }
621
622