1 /*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-conn-manager.h
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6 * Authors: Chenthill Palanisamy <pchenthill@novell.com>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "camel-imapx-conn-manager.h"
23 #include "camel-imapx-settings.h"
24 #include "camel-imapx-store.h"
25 #include "camel-imapx-utils.h"
27 #define c(...) camel_imapx_debug(conman, __VA_ARGS__)
29 #define CON_READ_LOCK(x) \
30 (g_static_rw_lock_reader_lock (&(x)->priv->rw_lock))
31 #define CON_READ_UNLOCK(x) \
32 (g_static_rw_lock_reader_unlock (&(x)->priv->rw_lock))
33 #define CON_WRITE_LOCK(x) \
34 (g_static_rw_lock_writer_lock (&(x)->priv->rw_lock))
35 #define CON_WRITE_UNLOCK(x) \
36 (g_static_rw_lock_writer_unlock (&(x)->priv->rw_lock))
38 #define CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE(obj) \
39 (G_TYPE_INSTANCE_GET_PRIVATE \
40 ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManagerPrivate))
42 typedef struct _ConnectionInfo ConnectionInfo;
44 struct _CamelIMAPXConnManagerPrivate {
45 /* XXX Might be easier for this to be a hash table,
46 * with CamelIMAPXServer pointers as the keys. */
48 gpointer store; /* weak pointer */
49 GStaticRWLock rw_lock;
52 struct _ConnectionInfo {
55 GHashTable *folder_names;
56 gchar *selected_folder;
57 volatile gint ref_count;
66 CamelIMAPXConnManager,
67 camel_imapx_conn_manager,
71 imapx_conn_shutdown (CamelIMAPXServer *is, CamelIMAPXConnManager *con_man);
74 imapx_conn_update_select (CamelIMAPXServer *is,
75 const gchar *selected_folder,
76 CamelIMAPXConnManager *con_man);
78 static ConnectionInfo *
79 connection_info_new (CamelIMAPXServer *is)
81 ConnectionInfo *cinfo;
82 GHashTable *folder_names;
84 folder_names = g_hash_table_new_full (
85 (GHashFunc) g_str_hash,
86 (GEqualFunc) g_str_equal,
87 (GDestroyNotify) g_free,
88 (GDestroyNotify) NULL);
90 cinfo = g_slice_new0 (ConnectionInfo);
91 cinfo->lock = g_mutex_new ();
92 cinfo->is = g_object_ref (is);
93 cinfo->folder_names = folder_names;
99 static ConnectionInfo *
100 connection_info_ref (ConnectionInfo *cinfo)
102 g_return_val_if_fail (cinfo != NULL, NULL);
103 g_return_val_if_fail (cinfo->ref_count > 0, NULL);
105 g_atomic_int_inc (&cinfo->ref_count);
111 connection_info_unref (ConnectionInfo *cinfo)
113 g_return_if_fail (cinfo != NULL);
114 g_return_if_fail (cinfo->ref_count > 0);
116 if (g_atomic_int_dec_and_test (&cinfo->ref_count)) {
117 camel_imapx_server_connect (cinfo->is, NULL, NULL);
118 g_mutex_free (cinfo->lock);
119 g_object_unref (cinfo->is);
120 g_hash_table_destroy (cinfo->folder_names);
121 g_free (cinfo->selected_folder);
123 g_slice_free (ConnectionInfo, cinfo);
128 connection_info_cancel_and_unref (ConnectionInfo *cinfo)
130 g_return_if_fail (cinfo != NULL);
131 g_return_if_fail (cinfo->ref_count > 0);
133 g_signal_handlers_disconnect_matched (cinfo->is, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, imapx_conn_shutdown, NULL);
134 g_signal_handlers_disconnect_matched (cinfo->is, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, imapx_conn_update_select, NULL);
135 g_cancellable_cancel (cinfo->is->cancellable);
136 connection_info_unref (cinfo);
140 connection_info_is_available (ConnectionInfo *cinfo)
144 g_return_val_if_fail (cinfo != NULL, FALSE);
146 g_mutex_lock (cinfo->lock);
148 /* Available means it's not tracking any folder names. */
149 available = (g_hash_table_size (cinfo->folder_names) == 0);
151 g_mutex_unlock (cinfo->lock);
157 connection_info_has_folder_name (ConnectionInfo *cinfo,
158 const gchar *folder_name)
162 g_return_val_if_fail (cinfo != NULL, FALSE);
164 if (folder_name == NULL)
167 g_mutex_lock (cinfo->lock);
169 value = g_hash_table_lookup (cinfo->folder_names, folder_name);
171 g_mutex_unlock (cinfo->lock);
173 return (value != NULL);
177 connection_info_insert_folder_name (ConnectionInfo *cinfo,
178 const gchar *folder_name)
180 g_return_if_fail (cinfo != NULL);
181 g_return_if_fail (folder_name != NULL);
183 g_mutex_lock (cinfo->lock);
185 g_hash_table_insert (
187 g_strdup (folder_name),
188 GINT_TO_POINTER (1));
190 g_mutex_unlock (cinfo->lock);
194 connection_info_remove_folder_name (ConnectionInfo *cinfo,
195 const gchar *folder_name)
197 g_return_if_fail (cinfo != NULL);
198 g_return_if_fail (folder_name != NULL);
200 g_mutex_lock (cinfo->lock);
202 g_hash_table_remove (cinfo->folder_names, folder_name);
204 g_mutex_unlock (cinfo->lock);
208 connection_info_dup_selected_folder (ConnectionInfo *cinfo)
210 gchar *selected_folder;
212 g_return_val_if_fail (cinfo != NULL, NULL);
214 g_mutex_lock (cinfo->lock);
216 selected_folder = g_strdup (cinfo->selected_folder);
218 g_mutex_unlock (cinfo->lock);
220 return selected_folder;
224 connection_info_set_selected_folder (ConnectionInfo *cinfo,
225 const gchar *selected_folder)
227 g_return_if_fail (cinfo != NULL);
229 g_mutex_lock (cinfo->lock);
231 g_free (cinfo->selected_folder);
232 cinfo->selected_folder = g_strdup (selected_folder);
234 g_mutex_unlock (cinfo->lock);
238 imapx_conn_manager_list_info (CamelIMAPXConnManager *con_man)
242 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
244 CON_READ_LOCK (con_man);
246 list = g_list_copy (con_man->priv->connections);
247 g_list_foreach (list, (GFunc) connection_info_ref, NULL);
249 CON_READ_UNLOCK (con_man);
254 static ConnectionInfo *
255 imapx_conn_manager_lookup_info (CamelIMAPXConnManager *con_man,
256 CamelIMAPXServer *is)
258 ConnectionInfo *cinfo = NULL;
261 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
262 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
264 CON_READ_LOCK (con_man);
266 list = con_man->priv->connections;
268 for (link = list; link != NULL; link = g_list_next (link)) {
269 ConnectionInfo *candidate = link->data;
271 if (candidate->is == is) {
272 cinfo = connection_info_ref (candidate);
277 CON_READ_UNLOCK (con_man);
283 imapx_conn_manager_remove_info (CamelIMAPXConnManager *con_man,
284 ConnectionInfo *cinfo)
287 gboolean removed = FALSE;
289 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), FALSE);
290 g_return_val_if_fail (cinfo != NULL, FALSE);
292 CON_WRITE_LOCK (con_man);
294 list = con_man->priv->connections;
295 link = g_list_find (list, cinfo);
298 list = g_list_delete_link (list, link);
299 connection_info_unref (cinfo);
303 con_man->priv->connections = list;
305 CON_WRITE_UNLOCK (con_man);
311 imapx_conn_manager_set_store (CamelIMAPXConnManager *con_man,
314 g_return_if_fail (CAMEL_IS_STORE (store));
315 g_return_if_fail (con_man->priv->store == NULL);
317 con_man->priv->store = store;
319 g_object_add_weak_pointer (
320 G_OBJECT (store), &con_man->priv->store);
324 imapx_conn_manager_set_property (GObject *object,
329 switch (property_id) {
331 imapx_conn_manager_set_store (
332 CAMEL_IMAPX_CONN_MANAGER (object),
333 g_value_get_object (value));
337 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
341 imapx_conn_manager_get_property (GObject *object,
346 switch (property_id) {
350 camel_imapx_conn_manager_get_store (
351 CAMEL_IMAPX_CONN_MANAGER (object)));
355 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
359 imapx_conn_manager_dispose (GObject *object)
361 CamelIMAPXConnManagerPrivate *priv;
363 priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
367 (GDestroyNotify) connection_info_unref);
368 priv->connections = NULL;
370 if (priv->store != NULL) {
371 g_object_remove_weak_pointer (
372 G_OBJECT (priv->store), &priv->store);
376 /* Chain up to parent's dispose() method. */
377 G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->dispose (object);
381 imapx_conn_manager_finalize (GObject *object)
383 CamelIMAPXConnManagerPrivate *priv;
385 priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
387 g_static_rw_lock_free (&priv->rw_lock);
389 /* Chain up to parent's finalize() method. */
390 G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->finalize (object);
394 camel_imapx_conn_manager_class_init (CamelIMAPXConnManagerClass *class)
396 GObjectClass *object_class;
398 g_type_class_add_private (class, sizeof (CamelIMAPXConnManagerPrivate));
400 object_class = G_OBJECT_CLASS (class);
401 object_class->set_property = imapx_conn_manager_set_property;
402 object_class->get_property = imapx_conn_manager_get_property;
403 object_class->dispose = imapx_conn_manager_dispose;
404 object_class->finalize = imapx_conn_manager_finalize;
406 g_object_class_install_property (
409 g_param_spec_object (
412 "The CamelStore to which we belong",
415 G_PARAM_CONSTRUCT_ONLY |
416 G_PARAM_STATIC_STRINGS));
420 camel_imapx_conn_manager_init (CamelIMAPXConnManager *con_man)
422 con_man->priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (con_man);
424 g_static_rw_lock_init (&con_man->priv->rw_lock);
427 /* Static functions go here */
429 /* TODO destroy unused connections in a time-out loop */
431 imapx_conn_shutdown (CamelIMAPXServer *is,
432 CamelIMAPXConnManager *con_man)
434 ConnectionInfo *cinfo;
436 /* Returns a new ConnectionInfo reference. */
437 cinfo = imapx_conn_manager_lookup_info (con_man, is);
440 imapx_conn_manager_remove_info (con_man, cinfo);
441 connection_info_unref (cinfo);
446 imapx_conn_update_select (CamelIMAPXServer *is,
447 const gchar *selected_folder,
448 CamelIMAPXConnManager *con_man)
450 ConnectionInfo *cinfo;
451 gchar *old_selected_folder;
453 /* Returns a new ConnectionInfo reference. */
454 cinfo = imapx_conn_manager_lookup_info (con_man, is);
459 old_selected_folder = connection_info_dup_selected_folder (cinfo);
461 if (old_selected_folder != NULL) {
462 IMAPXJobQueueInfo *jinfo;
464 jinfo = camel_imapx_server_get_job_queue_info (is);
465 if (!g_hash_table_lookup (jinfo->folders, old_selected_folder)) {
466 connection_info_remove_folder_name (cinfo, old_selected_folder);
467 c(is->tagprefix, "Removed folder %s from connection folder list - select changed \n", old_selected_folder);
469 camel_imapx_destroy_job_queue_info (jinfo);
471 g_free (old_selected_folder);
474 connection_info_set_selected_folder (cinfo, selected_folder);
476 connection_info_unref (cinfo);
479 /* This should find a connection if the slots are full, returns NULL if there are slots available for a new connection for a folder */
480 static CamelIMAPXServer *
481 imapx_find_connection_unlocked (CamelIMAPXConnManager *con_man,
482 const gchar *folder_name)
484 CamelService *service;
485 CamelSettings *settings;
486 CamelIMAPXServer *is = NULL;
487 ConnectionInfo *cinfo = NULL;
489 guint concurrent_connections;
490 guint min_jobs = G_MAXUINT;
492 /* Caller must be holding CON_WRITE_LOCK. */
494 service = CAMEL_SERVICE (con_man->priv->store);
496 settings = camel_service_ref_settings (service);
498 concurrent_connections =
499 camel_imapx_settings_get_concurrent_connections (
500 CAMEL_IMAPX_SETTINGS (settings));
502 g_object_unref (settings);
504 /* XXX Have a dedicated connection for INBOX ? */
506 list = con_man->priv->connections;
508 /* If a folder was not given, find the least-busy connection. */
509 if (folder_name == NULL)
512 /* First try to find a connection already handling this folder. */
513 for (link = list; link != NULL; link = g_list_next (link)) {
514 ConnectionInfo *candidate = link->data;
516 if (connection_info_has_folder_name (candidate, folder_name)) {
517 cinfo = connection_info_ref (candidate);
522 /* Next try to find a connection not handling any folders. */
523 for (link = list; link != NULL; link = g_list_next (link)) {
524 ConnectionInfo *candidate = link->data;
526 if (connection_info_is_available (candidate)) {
527 cinfo = connection_info_ref (candidate);
533 /* Pick the connection with the least number of jobs in progress. */
534 for (link = list; link != NULL; link = g_list_next (link)) {
535 ConnectionInfo *candidate = link->data;
536 IMAPXJobQueueInfo *jinfo = NULL;
538 jinfo = camel_imapx_server_get_job_queue_info (candidate->is);
541 cinfo = connection_info_ref (candidate);
542 min_jobs = jinfo->queue_len;
544 } else if (jinfo->queue_len < min_jobs) {
545 connection_info_unref (cinfo);
546 cinfo = connection_info_ref (candidate);
547 min_jobs = jinfo->queue_len;
550 camel_imapx_destroy_job_queue_info (jinfo);
554 if (cinfo != NULL && folder_name != NULL)
555 connection_info_insert_folder_name (cinfo, folder_name);
558 is = g_object_ref (cinfo->is);
559 connection_info_unref (cinfo);
562 if (camel_debug_flag (conman))
563 g_assert (!(concurrent_connections == g_list_length (con_man->priv->connections) && is == NULL));
568 static CamelIMAPXServer *
569 imapx_create_new_connection_unlocked (CamelIMAPXConnManager *con_man,
570 const gchar *folder_name,
571 GCancellable *cancellable,
574 CamelIMAPXServer *is = NULL;
575 CamelIMAPXStore *imapx_store;
576 CamelStore *store = con_man->priv->store;
577 ConnectionInfo *cinfo = NULL;
580 /* Caller must be holding CON_WRITE_LOCK. */
582 imapx_store = CAMEL_IMAPX_STORE (store);
584 /* Check if we got cancelled while we were waiting. */
585 if (g_cancellable_set_error_if_cancelled (cancellable, error))
588 is = camel_imapx_server_new (store);
590 /* XXX As part of the connect operation the CamelIMAPXServer will
591 * have to call camel_session_authenticate_sync(), but it has
592 * no way to pass itself through in that call so the service
593 * knows which CamelIMAPXServer is trying to authenticate.
595 * IMAPX is the only provider that does multiple connections
596 * like this, so I didn't want to pollute the CamelSession and
597 * CamelService authentication APIs with an extra argument.
598 * Instead we do this little hack so the service knows which
599 * CamelIMAPXServer to act on in its authenticate_sync() method.
601 * Because we're holding the CAMEL_SERVICE_REC_CONNECT_LOCK
602 * we should not have multiple IMAPX connections trying to
603 * authenticate at once, so this should be thread-safe.
605 imapx_store->authenticating_server = g_object_ref (is);
606 success = camel_imapx_server_connect (is, cancellable, error);
607 g_object_unref (imapx_store->authenticating_server);
608 imapx_store->authenticating_server = NULL;
617 G_CALLBACK (imapx_conn_shutdown), con_man);
619 is, "select_changed",
620 G_CALLBACK (imapx_conn_update_select), con_man);
622 cinfo = connection_info_new (is);
624 if (folder_name != NULL)
625 connection_info_insert_folder_name (cinfo, folder_name);
627 /* Takes ownership of the ConnectionInfo. */
628 con_man->priv->connections = g_list_prepend (
629 con_man->priv->connections, cinfo);
631 c(is->tagprefix, "Created new connection for %s and total connections %d \n", folder_name, g_list_length (con_man->priv->connections));
636 /****************************/
638 CamelIMAPXConnManager *
639 camel_imapx_conn_manager_new (CamelStore *store)
641 g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
643 return g_object_new (
644 CAMEL_TYPE_IMAPX_CONN_MANAGER, "store", store, NULL);
648 camel_imapx_conn_manager_get_store (CamelIMAPXConnManager *con_man)
650 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
652 return CAMEL_STORE (con_man->priv->store);
656 camel_imapx_conn_manager_get_connection (CamelIMAPXConnManager *con_man,
657 const gchar *folder_name,
658 GCancellable *cancellable,
661 CamelIMAPXServer *is = NULL;
663 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
665 /* Hold the writer lock while we requisition a CamelIMAPXServer
666 * to prevent other threads from adding or removing connections. */
667 CON_WRITE_LOCK (con_man);
669 /* Check if we got cancelled while waiting for the lock. */
670 if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
671 is = imapx_find_connection_unlocked (con_man, folder_name);
673 is = imapx_create_new_connection_unlocked (
674 con_man, folder_name, cancellable, error);
677 CON_WRITE_UNLOCK (con_man);
683 camel_imapx_conn_manager_get_connections (CamelIMAPXConnManager *con_man)
687 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
689 list = imapx_conn_manager_list_info (con_man);
691 /* Swap ConnectionInfo for CamelIMAPXServer in each link. */
692 for (link = list; link != NULL; link = g_list_next (link)) {
693 ConnectionInfo *cinfo = link->data;
694 link->data = g_object_ref (cinfo->is);
695 connection_info_unref (cinfo);
701 /* Used for handling operations that fails to execute and that needs to removed from folder list */
703 camel_imapx_conn_manager_update_con_info (CamelIMAPXConnManager *con_man,
704 CamelIMAPXServer *is,
705 const gchar *folder_name)
707 ConnectionInfo *cinfo;
708 IMAPXJobQueueInfo *jinfo;
710 g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
712 /* Returns a new ConnectionInfo reference. */
713 cinfo = imapx_conn_manager_lookup_info (con_man, is);
718 jinfo = camel_imapx_server_get_job_queue_info (cinfo->is);
719 if (!g_hash_table_lookup (jinfo->folders, folder_name)) {
720 connection_info_remove_folder_name (cinfo, folder_name);
721 c(is->tagprefix, "Removed folder %s from connection folder list - op done \n", folder_name);
723 camel_imapx_destroy_job_queue_info (jinfo);
725 connection_info_unref (cinfo);
729 camel_imapx_conn_manager_close_connections (CamelIMAPXConnManager *con_man)
731 g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
733 CON_WRITE_LOCK (con_man);
736 con_man->priv->connections,
737 (GDestroyNotify) connection_info_cancel_and_unref);
738 con_man->priv->connections = NULL;
740 CON_WRITE_UNLOCK (con_man);