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,
70 static ConnectionInfo *
71 connection_info_new (CamelIMAPXServer *is)
73 ConnectionInfo *cinfo;
74 GHashTable *folder_names;
76 folder_names = g_hash_table_new_full (
77 (GHashFunc) g_str_hash,
78 (GEqualFunc) g_str_equal,
79 (GDestroyNotify) g_free,
80 (GDestroyNotify) NULL);
82 cinfo = g_slice_new0 (ConnectionInfo);
83 cinfo->lock = g_mutex_new ();
84 cinfo->is = g_object_ref (is);
85 cinfo->folder_names = folder_names;
91 static ConnectionInfo *
92 connection_info_ref (ConnectionInfo *cinfo)
94 g_return_val_if_fail (cinfo != NULL, NULL);
95 g_return_val_if_fail (cinfo->ref_count > 0, NULL);
97 g_atomic_int_inc (&cinfo->ref_count);
103 connection_info_unref (ConnectionInfo *cinfo)
105 g_return_if_fail (cinfo != NULL);
106 g_return_if_fail (cinfo->ref_count > 0);
108 if (g_atomic_int_dec_and_test (&cinfo->ref_count)) {
109 camel_imapx_server_connect (cinfo->is, NULL, NULL);
111 g_mutex_free (cinfo->lock);
112 g_object_unref (cinfo->is);
113 g_hash_table_destroy (cinfo->folder_names);
114 g_free (cinfo->selected_folder);
116 g_slice_free (ConnectionInfo, cinfo);
121 connection_info_is_available (ConnectionInfo *cinfo)
125 g_return_val_if_fail (cinfo != NULL, FALSE);
127 g_mutex_lock (cinfo->lock);
129 /* Available means it's not tracking any folder names. */
130 available = (g_hash_table_size (cinfo->folder_names) == 0);
132 g_mutex_unlock (cinfo->lock);
138 connection_info_has_folder_name (ConnectionInfo *cinfo,
139 const gchar *folder_name)
143 g_return_val_if_fail (cinfo != NULL, FALSE);
145 if (folder_name == NULL)
148 g_mutex_lock (cinfo->lock);
150 value = g_hash_table_lookup (cinfo->folder_names, folder_name);
152 g_mutex_unlock (cinfo->lock);
154 return (value != NULL);
158 connection_info_insert_folder_name (ConnectionInfo *cinfo,
159 const gchar *folder_name)
161 g_return_if_fail (cinfo != NULL);
162 g_return_if_fail (folder_name != NULL);
164 g_mutex_lock (cinfo->lock);
166 g_hash_table_insert (
168 g_strdup (folder_name),
169 GINT_TO_POINTER (1));
171 g_mutex_unlock (cinfo->lock);
175 connection_info_remove_folder_name (ConnectionInfo *cinfo,
176 const gchar *folder_name)
178 g_return_if_fail (cinfo != NULL);
179 g_return_if_fail (folder_name != NULL);
181 g_mutex_lock (cinfo->lock);
183 g_hash_table_remove (cinfo->folder_names, folder_name);
185 g_mutex_unlock (cinfo->lock);
189 connection_info_dup_selected_folder (ConnectionInfo *cinfo)
191 gchar *selected_folder;
193 g_return_val_if_fail (cinfo != NULL, NULL);
195 g_mutex_lock (cinfo->lock);
197 selected_folder = g_strdup (cinfo->selected_folder);
199 g_mutex_unlock (cinfo->lock);
201 return selected_folder;
205 connection_info_set_selected_folder (ConnectionInfo *cinfo,
206 const gchar *selected_folder)
208 g_return_if_fail (cinfo != NULL);
210 g_mutex_lock (cinfo->lock);
212 g_free (cinfo->selected_folder);
213 cinfo->selected_folder = g_strdup (selected_folder);
215 g_mutex_unlock (cinfo->lock);
219 imapx_conn_manager_list_info (CamelIMAPXConnManager *con_man)
223 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
225 CON_READ_LOCK (con_man);
227 list = g_list_copy (con_man->priv->connections);
228 g_list_foreach (list, (GFunc) connection_info_ref, NULL);
230 CON_READ_UNLOCK (con_man);
235 static ConnectionInfo *
236 imapx_conn_manager_lookup_info (CamelIMAPXConnManager *con_man,
237 CamelIMAPXServer *is)
239 ConnectionInfo *cinfo = NULL;
242 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
243 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
245 CON_READ_LOCK (con_man);
247 list = con_man->priv->connections;
249 for (link = list; link != NULL; link = g_list_next (link)) {
250 ConnectionInfo *candidate = link->data;
252 if (candidate->is == is) {
253 cinfo = connection_info_ref (candidate);
258 CON_READ_UNLOCK (con_man);
264 imapx_conn_manager_remove_info (CamelIMAPXConnManager *con_man,
265 ConnectionInfo *cinfo)
268 gboolean removed = FALSE;
270 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), FALSE);
271 g_return_val_if_fail (cinfo != NULL, FALSE);
273 CON_WRITE_LOCK (con_man);
275 list = con_man->priv->connections;
276 link = g_list_find (list, cinfo);
279 list = g_list_remove_link (list, link);
280 connection_info_unref (cinfo);
284 con_man->priv->connections = list;
286 CON_WRITE_UNLOCK (con_man);
292 imapx_conn_manager_set_store (CamelIMAPXConnManager *con_man,
295 g_return_if_fail (CAMEL_IS_STORE (store));
296 g_return_if_fail (con_man->priv->store == NULL);
298 con_man->priv->store = store;
300 g_object_add_weak_pointer (
301 G_OBJECT (store), &con_man->priv->store);
305 imapx_conn_manager_set_property (GObject *object,
310 switch (property_id) {
312 imapx_conn_manager_set_store (
313 CAMEL_IMAPX_CONN_MANAGER (object),
314 g_value_get_object (value));
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
322 imapx_conn_manager_get_property (GObject *object,
327 switch (property_id) {
331 camel_imapx_conn_manager_get_store (
332 CAMEL_IMAPX_CONN_MANAGER (object)));
336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
340 imapx_conn_manager_dispose (GObject *object)
342 CamelIMAPXConnManagerPrivate *priv;
344 priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
348 (GDestroyNotify) connection_info_unref);
349 priv->connections = NULL;
351 if (priv->store != NULL) {
352 g_object_remove_weak_pointer (
353 G_OBJECT (priv->store), &priv->store);
357 /* Chain up to parent's dispose() method. */
358 G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->dispose (object);
362 imapx_conn_manager_finalize (GObject *object)
364 CamelIMAPXConnManagerPrivate *priv;
366 priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
368 g_static_rw_lock_free (&priv->rw_lock);
370 /* Chain up to parent's finalize() method. */
371 G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->finalize (object);
375 camel_imapx_conn_manager_class_init (CamelIMAPXConnManagerClass *class)
377 GObjectClass *object_class;
379 g_type_class_add_private (class, sizeof (CamelIMAPXConnManagerPrivate));
381 object_class = G_OBJECT_CLASS (class);
382 object_class->set_property = imapx_conn_manager_set_property;
383 object_class->get_property = imapx_conn_manager_get_property;
384 object_class->dispose = imapx_conn_manager_dispose;
385 object_class->finalize = imapx_conn_manager_finalize;
387 g_object_class_install_property (
390 g_param_spec_object (
393 "The CamelStore to which we belong",
396 G_PARAM_CONSTRUCT_ONLY |
397 G_PARAM_STATIC_STRINGS));
401 camel_imapx_conn_manager_init (CamelIMAPXConnManager *con_man)
403 con_man->priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (con_man);
405 g_static_rw_lock_init (&con_man->priv->rw_lock);
408 /* Static functions go here */
410 /* TODO destroy unused connections in a time-out loop */
412 imapx_conn_shutdown (CamelIMAPXServer *is,
413 CamelIMAPXConnManager *con_man)
415 ConnectionInfo *cinfo;
417 /* Returns a new ConnectionInfo reference. */
418 cinfo = imapx_conn_manager_lookup_info (con_man, is);
421 imapx_conn_manager_remove_info (con_man, cinfo);
422 connection_info_unref (cinfo);
427 imapx_conn_update_select (CamelIMAPXServer *is,
428 const gchar *selected_folder,
429 CamelIMAPXConnManager *con_man)
431 ConnectionInfo *cinfo;
432 gchar *old_selected_folder;
434 /* Returns a new ConnectionInfo reference. */
435 cinfo = imapx_conn_manager_lookup_info (con_man, is);
440 old_selected_folder = connection_info_dup_selected_folder (cinfo);
442 if (old_selected_folder != NULL) {
443 IMAPXJobQueueInfo *jinfo;
445 jinfo = camel_imapx_server_get_job_queue_info (is);
446 if (!g_hash_table_lookup (jinfo->folders, old_selected_folder)) {
447 connection_info_remove_folder_name (cinfo, old_selected_folder);
448 c(is->tagprefix, "Removed folder %s from connection folder list - select changed \n", old_selected_folder);
450 camel_imapx_destroy_job_queue_info (jinfo);
452 g_free (old_selected_folder);
455 connection_info_set_selected_folder (cinfo, selected_folder);
457 connection_info_unref (cinfo);
460 /* This should find a connection if the slots are full, returns NULL if there are slots available for a new connection for a folder */
461 static CamelIMAPXServer *
462 imapx_find_connection_unlocked (CamelIMAPXConnManager *con_man,
463 const gchar *folder_name)
465 CamelService *service;
466 CamelSettings *settings;
467 CamelIMAPXServer *is = NULL;
468 ConnectionInfo *cinfo = NULL;
470 guint concurrent_connections;
471 guint min_jobs = G_MAXUINT;
473 /* Caller must be holding CON_WRITE_LOCK. */
475 service = CAMEL_SERVICE (con_man->priv->store);
476 settings = camel_service_get_settings (service);
478 concurrent_connections =
479 camel_imapx_settings_get_concurrent_connections (
480 CAMEL_IMAPX_SETTINGS (settings));
482 /* XXX Have a dedicated connection for INBOX ? */
484 list = con_man->priv->connections;
486 /* If a folder was not given, find the least-busy connection. */
487 if (folder_name == NULL)
490 /* First try to find a connection already handling this folder. */
491 for (link = list; link != NULL; link = g_list_next (link)) {
492 ConnectionInfo *candidate = link->data;
494 if (connection_info_has_folder_name (candidate, folder_name)) {
495 cinfo = connection_info_ref (candidate);
500 /* Next try to find a connection not handling any folders. */
501 for (link = list; link != NULL; link = g_list_next (link)) {
502 ConnectionInfo *candidate = link->data;
504 if (connection_info_is_available (candidate)) {
505 cinfo = connection_info_ref (candidate);
511 /* Pick the connection with the least number of jobs in progress. */
512 for (link = list; link != NULL; link = g_list_next (link)) {
513 ConnectionInfo *candidate = link->data;
514 IMAPXJobQueueInfo *jinfo = NULL;
516 jinfo = camel_imapx_server_get_job_queue_info (candidate->is);
519 cinfo = connection_info_ref (candidate);
520 min_jobs = jinfo->queue_len;
522 } else if (jinfo->queue_len < min_jobs) {
523 connection_info_unref (cinfo);
524 cinfo = connection_info_ref (candidate);
525 min_jobs = jinfo->queue_len;
528 camel_imapx_destroy_job_queue_info (jinfo);
532 if (cinfo != NULL && folder_name != NULL)
533 connection_info_insert_folder_name (cinfo, folder_name);
536 is = g_object_ref (cinfo->is);
537 connection_info_unref (cinfo);
540 if (camel_debug_flag (conman))
541 g_assert (!(concurrent_connections == g_list_length (con_man->priv->connections) && is == NULL));
546 static CamelIMAPXServer *
547 imapx_create_new_connection_unlocked (CamelIMAPXConnManager *con_man,
548 const gchar *folder_name,
549 GCancellable *cancellable,
552 CamelIMAPXServer *is = NULL;
553 CamelIMAPXStore *imapx_store;
554 CamelStore *store = con_man->priv->store;
555 CamelService *service;
556 ConnectionInfo *cinfo = NULL;
559 /* Caller must be holding CON_WRITE_LOCK. */
561 service = CAMEL_SERVICE (store);
563 imapx_store = CAMEL_IMAPX_STORE (store);
565 camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
567 /* Check if we got cancelled while we were waiting. */
568 if (g_cancellable_set_error_if_cancelled (cancellable, error))
571 is = camel_imapx_server_new (store);
573 /* XXX As part of the connect operation the CamelIMAPXServer will
574 * have to call camel_session_authenticate_sync(), but it has
575 * no way to pass itself through in that call so the service
576 * knows which CamelIMAPXServer is trying to authenticate.
578 * IMAPX is the only provider that does multiple connections
579 * like this, so I didn't want to pollute the CamelSession and
580 * CamelService authentication APIs with an extra argument.
581 * Instead we do this little hack so the service knows which
582 * CamelIMAPXServer to act on in its authenticate_sync() method.
584 * Because we're holding the CAMEL_SERVICE_REC_CONNECT_LOCK
585 * we should not have multiple IMAPX connections trying to
586 * authenticate at once, so this should be thread-safe.
588 imapx_store->authenticating_server = g_object_ref (is);
589 success = camel_imapx_server_connect (is, cancellable, error);
590 g_object_unref (imapx_store->authenticating_server);
591 imapx_store->authenticating_server = NULL;
601 G_CALLBACK (imapx_conn_shutdown), con_man);
603 is, "select_changed",
604 G_CALLBACK (imapx_conn_update_select), con_man);
606 cinfo = connection_info_new (is);
608 if (folder_name != NULL)
609 connection_info_insert_folder_name (cinfo, folder_name);
611 /* Takes ownership of the ConnectionInfo. */
612 con_man->priv->connections = g_list_prepend (
613 con_man->priv->connections, cinfo);
615 c(is->tagprefix, "Created new connection for %s and total connections %d \n", folder_name, g_list_length (con_man->priv->connections));
618 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
623 /****************************/
625 CamelIMAPXConnManager *
626 camel_imapx_conn_manager_new (CamelStore *store)
628 g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
630 return g_object_new (
631 CAMEL_TYPE_IMAPX_CONN_MANAGER, "store", store, NULL);
635 camel_imapx_conn_manager_get_store (CamelIMAPXConnManager *con_man)
637 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
639 return CAMEL_STORE (con_man->priv->store);
643 camel_imapx_conn_manager_get_connection (CamelIMAPXConnManager *con_man,
644 const gchar *folder_name,
645 GCancellable *cancellable,
648 CamelIMAPXServer *is = NULL;
650 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
652 /* Hold the writer lock while we requisition a CamelIMAPXServer
653 * to prevent other threads from adding or removing connections. */
654 CON_WRITE_LOCK (con_man);
656 /* Check if we got cancelled while waiting for the lock. */
657 if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
658 is = imapx_find_connection_unlocked (con_man, folder_name);
660 is = imapx_create_new_connection_unlocked (
661 con_man, folder_name, cancellable, error);
664 CON_WRITE_UNLOCK (con_man);
670 camel_imapx_conn_manager_get_connections (CamelIMAPXConnManager *con_man)
674 g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
676 list = imapx_conn_manager_list_info (con_man);
678 /* Swap ConnectionInfo for CamelIMAPXServer in each link. */
679 for (link = list; link != NULL; link = g_list_next (link)) {
680 ConnectionInfo *cinfo = link->data;
681 link->data = g_object_ref (cinfo->is);
682 connection_info_unref (cinfo);
688 /* Used for handling operations that fails to execute and that needs to removed from folder list */
690 camel_imapx_conn_manager_update_con_info (CamelIMAPXConnManager *con_man,
691 CamelIMAPXServer *is,
692 const gchar *folder_name)
694 ConnectionInfo *cinfo;
695 IMAPXJobQueueInfo *jinfo;
697 g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
699 /* Returns a new ConnectionInfo reference. */
700 cinfo = imapx_conn_manager_lookup_info (con_man, is);
705 jinfo = camel_imapx_server_get_job_queue_info (cinfo->is);
706 if (!g_hash_table_lookup (jinfo->folders, folder_name)) {
707 connection_info_remove_folder_name (cinfo, folder_name);
708 c(is->tagprefix, "Removed folder %s from connection folder list - op done \n", folder_name);
710 camel_imapx_destroy_job_queue_info (jinfo);
712 connection_info_unref (cinfo);
716 camel_imapx_conn_manager_close_connections (CamelIMAPXConnManager *con_man)
718 g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
720 CON_WRITE_LOCK (con_man);
723 con_man->priv->connections,
724 (GDestroyNotify) connection_info_unref);
725 con_man->priv->connections = NULL;
727 CON_WRITE_UNLOCK (con_man);