[evolution-data-server] IMAPX: Support the QUOTA extension (RFC 2087).



commit 95d730c78c4748469c50163c2d59e65fb0e1227a
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sat Dec 8 14:22:41 2012 -0500

    IMAPX: Support the QUOTA extension (RFC 2087).

 camel/camel-imapx-folder.c |  325 ++++++++++++++++++++++++++++++--------------
 camel/camel-imapx-folder.h |    7 +
 camel/camel-imapx-server.c |  225 ++++++++++++++++++++++++++++++-
 camel/camel-imapx-server.h |    5 +
 camel/camel-imapx-store.c  |   74 ++++++++++-
 camel/camel-imapx-store.h  |   18 ++-
 camel/camel-imapx-utils.c  |  182 +++++++++++++++++++++++++
 camel/camel-imapx-utils.h  |   16 ++
 8 files changed, 741 insertions(+), 111 deletions(-)
---
diff --git a/camel/camel-imapx-folder.c b/camel/camel-imapx-folder.c
index b9bc966..6a9522f 100644
--- a/camel/camel-imapx-folder.c
+++ b/camel/camel-imapx-folder.c
@@ -38,10 +38,20 @@
 
 #define d(...) camel_imapx_debug(debug, '?', __VA_ARGS__)
 
+#define CAMEL_IMAPX_FOLDER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), CAMEL_TYPE_IMAPX_FOLDER, CamelIMAPXFolderPrivate))
+
+struct _CamelIMAPXFolderPrivate {
+	GMutex property_lock;
+	gchar **quota_root_names;
+};
+
 /* The custom property ID is a CamelArg artifact.
  * It still identifies the property in state files. */
 enum {
 	PROP_0,
+	PROP_QUOTA_ROOT_NAMES,
 	PROP_APPLY_FILTERS = 0x2501
 };
 
@@ -49,105 +59,6 @@ G_DEFINE_TYPE (CamelIMAPXFolder, camel_imapx_folder, CAMEL_TYPE_OFFLINE_FOLDER)
 
 static gboolean imapx_folder_get_apply_filters (CamelIMAPXFolder *folder);
 
-CamelFolder *
-camel_imapx_folder_new (CamelStore *store,
-                        const gchar *folder_dir,
-                        const gchar *folder_name,
-                        GError **error)
-{
-	CamelFolder *folder;
-	CamelService *service;
-	CamelSettings *settings;
-	CamelIMAPXFolder *ifolder;
-	const gchar *short_name;
-	gchar *state_file;
-	gboolean filter_all;
-	gboolean filter_inbox;
-	gboolean filter_junk;
-	gboolean filter_junk_inbox;
-
-	d ("opening imap folder '%s'\n", folder_dir);
-
-	service = CAMEL_SERVICE (store);
-
-	settings = camel_service_ref_settings (service);
-
-	g_object_get (
-		settings,
-		"filter-all", &filter_all,
-		"filter-inbox", &filter_inbox,
-		"filter-junk", &filter_junk,
-		"filter-junk-inbox", &filter_junk_inbox,
-		NULL);
-
-	g_object_unref (settings);
-
-	short_name = strrchr (folder_name, '/');
-	if (short_name)
-		short_name++;
-	else
-		short_name = folder_name;
-
-	folder = g_object_new (
-		CAMEL_TYPE_IMAPX_FOLDER,
-		"display-name", short_name,
-		"full_name", folder_name,
-		"parent-store", store, NULL);
-	ifolder = (CamelIMAPXFolder *) folder;
-
-	((CamelIMAPXFolder *) folder)->raw_name = g_strdup (folder_name);
-
-	folder->summary = camel_imapx_summary_new (folder);
-	if (!folder->summary) {
-		g_set_error (
-			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
-			_("Could not create folder summary for %s"),
-			short_name);
-		return NULL;
-	}
-
-	ifolder->cache = camel_data_cache_new (folder_dir, error);
-	if (!ifolder->cache) {
-		g_prefix_error (
-			error, _("Could not create cache for %s: "),
-			short_name);
-		return NULL;
-	}
-
-	state_file = g_build_filename (folder_dir, "cmeta", NULL);
-	camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
-	g_free (state_file);
-	camel_object_state_read (CAMEL_OBJECT (folder));
-
-	ifolder->search = camel_folder_search_new ();
-	g_mutex_init (&ifolder->search_lock);
-	g_mutex_init (&ifolder->stream_lock);
-	ifolder->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
-	ifolder->exists_on_server = 0;
-	ifolder->unread_on_server = 0;
-	ifolder->modseq_on_server = 0;
-	ifolder->uidnext_on_server = 0;
-
-	if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
-		if (filter_inbox || filter_all)
-			folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
-		if (filter_junk)
-			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
-	} else {
-		if (filter_junk && !filter_junk_inbox)
-			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
-
-		if (filter_all || imapx_folder_get_apply_filters (ifolder))
-			folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
-	}
-
-	camel_store_summary_connect_folder_summary (
-		(CamelStoreSummary *) ((CamelIMAPXStore *) store)->summary,
-		folder_name, folder->summary);
-
-	return folder;
-}
-
 static gboolean
 imapx_folder_get_apply_filters (CamelIMAPXFolder *folder)
 {
@@ -184,6 +95,12 @@ imapx_folder_set_property (GObject *object,
 				CAMEL_IMAPX_FOLDER (object),
 				g_value_get_boolean (value));
 			return;
+
+		case PROP_QUOTA_ROOT_NAMES:
+			camel_imapx_folder_set_quota_root_names (
+				CAMEL_IMAPX_FOLDER (object),
+				g_value_get_boxed (value));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -198,7 +115,15 @@ imapx_folder_get_property (GObject *object,
 	switch (property_id) {
 		case PROP_APPLY_FILTERS:
 			g_value_set_boolean (
-				value, imapx_folder_get_apply_filters (
+				value,
+				imapx_folder_get_apply_filters (
+				CAMEL_IMAPX_FOLDER (object)));
+			return;
+
+		case PROP_QUOTA_ROOT_NAMES:
+			g_value_take_boxed (
+				value,
+				camel_imapx_folder_dup_quota_root_names (
 				CAMEL_IMAPX_FOLDER (object)));
 			return;
 	}
@@ -244,6 +169,8 @@ imapx_folder_finalize (GObject *object)
 	g_mutex_clear (&folder->search_lock);
 	g_mutex_clear (&folder->stream_lock);
 
+	g_mutex_clear (&folder->priv->property_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_imapx_folder_parent_class)->finalize (object);
 }
@@ -582,6 +509,54 @@ imapx_get_message_sync (CamelFolder *folder,
 	return msg;
 }
 
+static CamelFolderQuotaInfo *
+imapx_get_quota_info_sync (CamelFolder *folder,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+	CamelStore *parent_store;
+	CamelIMAPXServer *server;
+	CamelFolderQuotaInfo *quota_info = NULL;
+	const gchar *folder_name;
+	gchar **quota_root_names;
+	gboolean success = FALSE;
+
+	folder_name = camel_folder_get_full_name (folder);
+	parent_store = camel_folder_get_parent_store (folder);
+
+	server = camel_imapx_store_get_server (
+		CAMEL_IMAPX_STORE (parent_store),
+		folder_name, cancellable, error);
+
+	if (server != NULL) {
+		success = camel_imapx_server_update_quota_info (
+			server, folder_name, cancellable, error);
+		g_object_unref (server);
+	}
+
+	if (!success)
+		return NULL;
+
+	quota_root_names = camel_imapx_folder_dup_quota_root_names (
+		CAMEL_IMAPX_FOLDER (folder));
+
+	/* XXX Just return info for the first quota root name, I guess. */
+	if (quota_root_names != NULL && quota_root_names[0] != NULL)
+		quota_info = camel_imapx_store_dup_quota_info (
+			CAMEL_IMAPX_STORE (parent_store),
+			quota_root_names[0]);
+
+	g_strfreev (quota_root_names);
+
+	if (quota_info == NULL)
+		g_set_error (
+			error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+			_("No quota information available for folder '%s'"),
+			folder_name);
+
+	return quota_info;
+}
+
 static gboolean
 imapx_purge_message_cache_sync (CamelFolder *folder,
                                 gchar *start_uid,
@@ -763,6 +738,8 @@ camel_imapx_folder_class_init (CamelIMAPXFolderClass *class)
 	GObjectClass *object_class;
 	CamelFolderClass *folder_class;
 
+	g_type_class_add_private (class, sizeof (CamelIMAPXFolderPrivate));
+
 	object_class = G_OBJECT_CLASS (class);
 	object_class->set_property = imapx_folder_set_property;
 	object_class->get_property = imapx_folder_get_property;
@@ -780,6 +757,7 @@ camel_imapx_folder_class_init (CamelIMAPXFolderClass *class)
 	folder_class->expunge_sync = imapx_expunge_sync;
 	folder_class->fetch_messages_sync = imapx_fetch_messages_sync;
 	folder_class->get_message_sync = imapx_get_message_sync;
+	folder_class->get_quota_info_sync = imapx_get_quota_info_sync;
 	folder_class->purge_message_cache_sync = imapx_purge_message_cache_sync;
 	folder_class->refresh_info_sync = imapx_refresh_info_sync;
 	folder_class->synchronize_sync = imapx_synchronize_sync;
@@ -796,6 +774,17 @@ camel_imapx_folder_class_init (CamelIMAPXFolderClass *class)
 			FALSE,
 			G_PARAM_READWRITE |
 			CAMEL_PARAM_PERSISTENT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_QUOTA_ROOT_NAMES,
+		g_param_spec_boxed (
+			"quota-root-names",
+			"Quota Root Names",
+			"Quota root names for this folder",
+			G_TYPE_STRV,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -803,6 +792,8 @@ camel_imapx_folder_init (CamelIMAPXFolder *imapx_folder)
 {
 	CamelFolder *folder = CAMEL_FOLDER (imapx_folder);
 
+	imapx_folder->priv = CAMEL_IMAPX_FOLDER_GET_PRIVATE (imapx_folder);
+
 	folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
 
 	folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
@@ -810,5 +801,139 @@ camel_imapx_folder_init (CamelIMAPXFolder *imapx_folder)
 		CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_USER;
 
 	camel_folder_set_lock_async (folder, TRUE);
+
+	g_mutex_init (&imapx_folder->priv->property_lock);
+}
+
+CamelFolder *
+camel_imapx_folder_new (CamelStore *store,
+                        const gchar *folder_dir,
+                        const gchar *folder_name,
+                        GError **error)
+{
+	CamelFolder *folder;
+	CamelService *service;
+	CamelSettings *settings;
+	CamelIMAPXFolder *ifolder;
+	const gchar *short_name;
+	gchar *state_file;
+	gboolean filter_all;
+	gboolean filter_inbox;
+	gboolean filter_junk;
+	gboolean filter_junk_inbox;
+
+	d ("opening imap folder '%s'\n", folder_dir);
+
+	service = CAMEL_SERVICE (store);
+
+	settings = camel_service_ref_settings (service);
+
+	g_object_get (
+		settings,
+		"filter-all", &filter_all,
+		"filter-inbox", &filter_inbox,
+		"filter-junk", &filter_junk,
+		"filter-junk-inbox", &filter_junk_inbox,
+		NULL);
+
+	g_object_unref (settings);
+
+	short_name = strrchr (folder_name, '/');
+	if (short_name)
+		short_name++;
+	else
+		short_name = folder_name;
+
+	folder = g_object_new (
+		CAMEL_TYPE_IMAPX_FOLDER,
+		"display-name", short_name,
+		"full_name", folder_name,
+		"parent-store", store, NULL);
+	ifolder = (CamelIMAPXFolder *) folder;
+
+	((CamelIMAPXFolder *) folder)->raw_name = g_strdup (folder_name);
+
+	folder->summary = camel_imapx_summary_new (folder);
+	if (!folder->summary) {
+		g_set_error (
+			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+			_("Could not create folder summary for %s"),
+			short_name);
+		return NULL;
+	}
+
+	ifolder->cache = camel_data_cache_new (folder_dir, error);
+	if (!ifolder->cache) {
+		g_prefix_error (
+			error, _("Could not create cache for %s: "),
+			short_name);
+		return NULL;
+	}
+
+	state_file = g_build_filename (folder_dir, "cmeta", NULL);
+	camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
+	g_free (state_file);
+	camel_object_state_read (CAMEL_OBJECT (folder));
+
+	ifolder->search = camel_folder_search_new ();
+	g_mutex_init (&ifolder->search_lock);
+	g_mutex_init (&ifolder->stream_lock);
+	ifolder->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
+	ifolder->exists_on_server = 0;
+	ifolder->unread_on_server = 0;
+	ifolder->modseq_on_server = 0;
+	ifolder->uidnext_on_server = 0;
+
+	if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
+		if (filter_inbox || filter_all)
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+		if (filter_junk)
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+	} else {
+		if (filter_junk && !filter_junk_inbox)
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+
+		if (filter_all || imapx_folder_get_apply_filters (ifolder))
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+	}
+
+	camel_store_summary_connect_folder_summary (
+		(CamelStoreSummary *) ((CamelIMAPXStore *) store)->summary,
+		folder_name, folder->summary);
+
+	return folder;
+}
+
+gchar **
+camel_imapx_folder_dup_quota_root_names (CamelIMAPXFolder *folder)
+{
+	gchar **duplicate;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), NULL);
+
+	g_mutex_lock (&folder->priv->property_lock);
+
+	duplicate = g_strdupv (folder->priv->quota_root_names);
+
+	g_mutex_unlock (&folder->priv->property_lock);
+
+	return duplicate;
+}
+
+void
+camel_imapx_folder_set_quota_root_names (CamelIMAPXFolder *folder,
+                                         const gchar **quota_root_names)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+
+	g_mutex_lock (&folder->priv->property_lock);
+
+	g_strfreev (folder->priv->quota_root_names);
+	folder->priv->quota_root_names =
+		g_strdupv ((gchar **) quota_root_names);
+
+	g_mutex_unlock (&folder->priv->property_lock);
+
+	g_object_notify (G_OBJECT (folder), "quota-root-names");
 }
 
diff --git a/camel/camel-imapx-folder.h b/camel/camel-imapx-folder.h
index 0b9c0d2..3ce36e7 100644
--- a/camel/camel-imapx-folder.h
+++ b/camel/camel-imapx-folder.h
@@ -56,9 +56,11 @@ G_BEGIN_DECLS
 
 typedef struct _CamelIMAPXFolder CamelIMAPXFolder;
 typedef struct _CamelIMAPXFolderClass CamelIMAPXFolderClass;
+typedef struct _CamelIMAPXFolderPrivate CamelIMAPXFolderPrivate;
 
 struct _CamelIMAPXFolder {
 	CamelOfflineFolder parent;
+	CamelIMAPXFolderPrivate *priv;
 
 	gchar *raw_name;
 	CamelDataCache *cache;
@@ -91,6 +93,11 @@ CamelFolder *	camel_imapx_folder_new		(CamelStore *parent,
 gchar *		imapx_get_filename		(CamelFolder *folder,
 						 const gchar *uid,
 						 GError **error);
+gchar **	camel_imapx_folder_dup_quota_root_names
+						(CamelIMAPXFolder *folder);
+void		camel_imapx_folder_set_quota_root_names
+						(CamelIMAPXFolder *folder,
+						 const gchar **quota_root_names);
 
 G_END_DECLS
 
diff --git a/camel/camel-imapx-server.c b/camel/camel-imapx-server.c
index b000211..5d641a1 100644
--- a/camel/camel-imapx-server.c
+++ b/camel/camel-imapx-server.c
@@ -86,6 +86,7 @@ typedef struct _ManageSubscriptionsData ManageSubscriptionsData;
 typedef struct _RenameFolderData RenameFolderData;
 typedef struct _CreateFolderData CreateFolderData;
 typedef struct _DeleteFolderData DeleteFolderData;
+typedef struct _QuotaData QuotaData;
 
 struct _GetMessageData {
 	/* in: uid requested */
@@ -165,6 +166,10 @@ struct _DeleteFolderData {
 	gchar *folder_name;
 };
 
+struct _QuotaData {
+	gchar *folder_name;
+};
+
 /* untagged response handling */
 
 /* May need to turn this into separate,
@@ -239,6 +244,14 @@ static gboolean	imapx_untagged_preauth		(CamelIMAPXServer *is,
 						 CamelIMAPXStream *stream,
 						 GCancellable *cancellable,
 						 GError **error);
+static gboolean	imapx_untagged_quota		(CamelIMAPXServer *is,
+						 CamelIMAPXStream *stream,
+						 GCancellable *cancellable,
+						 GError **error);
+static gboolean	imapx_untagged_quotaroot	(CamelIMAPXServer *is,
+						 CamelIMAPXStream *stream,
+						 GCancellable *cancellable,
+						 GError **error);
 static gboolean	imapx_untagged_recent		(CamelIMAPXServer *is,
 						 CamelIMAPXStream *stream,
 						 GCancellable *cancellable,
@@ -266,6 +279,8 @@ enum {
 	IMAPX_UNTAGGED_ID_NO,
 	IMAPX_UNTAGGED_ID_OK,
 	IMAPX_UNTAGGED_ID_PREAUTH,
+	IMAPX_UNTAGGED_ID_QUOTA,
+	IMAPX_UNTAGGED_ID_QUOTAROOT,
 	IMAPX_UNTAGGED_ID_RECENT,
 	IMAPX_UNTAGGED_ID_STATUS,
 	IMAPX_UNTAGGED_ID_VANISHED,
@@ -286,6 +301,8 @@ static const CamelIMAPXUntaggedRespHandlerDesc _untagged_descr[] = {
 	{CAMEL_IMAPX_UNTAGGED_NO, imapx_untagged_ok_no_bad, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_OK, imapx_untagged_ok_no_bad, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_PREAUTH, imapx_untagged_preauth, CAMEL_IMAPX_UNTAGGED_OK, TRUE /*overridden */ },
+	{CAMEL_IMAPX_UNTAGGED_QUOTA, imapx_untagged_quota, NULL, FALSE},
+	{CAMEL_IMAPX_UNTAGGED_QUOTAROOT, imapx_untagged_quotaroot, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_RECENT, imapx_untagged_recent, NULL, TRUE},
 	{CAMEL_IMAPX_UNTAGGED_STATUS, imapx_untagged_status, NULL, TRUE},
 	{CAMEL_IMAPX_UNTAGGED_VANISHED, imapx_untagged_vanished, NULL, TRUE},
@@ -372,6 +389,7 @@ enum {
 	IMAPX_JOB_DELETE_FOLDER = 1 << 12,
 	IMAPX_JOB_RENAME_FOLDER = 1 << 13,
 	IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
+	IMAPX_JOB_UPDATE_QUOTA_INFO = 1 << 15
 };
 
 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
@@ -391,7 +409,8 @@ enum {
 	IMAPX_PRIIORITY_COPY_MESSAGE = -60,
 	IMAPX_PRIORITY_LIST = -80,
 	IMAPX_PRIORITY_IDLE = -100,
-	IMAPX_PRIORITY_SYNC_MESSAGE = -120
+	IMAPX_PRIORITY_SYNC_MESSAGE = -120,
+	IMAPX_PRIORITY_UPDATE_QUOTA_INFO = -80
 };
 
 struct _imapx_flag_change {
@@ -670,6 +689,14 @@ delete_folder_data_free (DeleteFolderData *data)
 	g_slice_free (DeleteFolderData, data);
 }
 
+static void
+quota_data_free (QuotaData *data)
+{
+	g_free (data->folder_name);
+
+	g_slice_free (QuotaData, data);
+}
+
 /*
   this creates a uid (or sequence number) set directly into a command,
   if total is set, then we break it up into total uids. (i.e. command time)
@@ -1915,6 +1942,101 @@ imapx_untagged_list (CamelIMAPXServer *is,
 }
 
 static gboolean
+imapx_untagged_quota (CamelIMAPXServer *is,
+                      CamelIMAPXStream *stream,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+	gchar *quota_root_name = NULL;
+	CamelFolderQuotaInfo *quota_info = NULL;
+	gboolean success;
+
+	success = camel_imapx_parse_quota (
+		stream, cancellable, &quota_root_name, &quota_info, error);
+
+	/* Sanity check */
+	g_return_val_if_fail (
+		(success && (quota_root_name != NULL)) ||
+		(!success && (quota_root_name == NULL)), FALSE);
+
+	if (success) {
+		CamelIMAPXStore *store;
+
+		store = camel_imapx_server_ref_store (is);
+		camel_imapx_store_set_quota_info (
+			store, quota_root_name, quota_info);
+		g_object_unref (store);
+
+		g_free (quota_root_name);
+		camel_folder_quota_info_free (quota_info);
+	}
+
+	return success;
+}
+
+static gboolean
+imapx_untagged_quotaroot (CamelIMAPXServer *is,
+                          CamelIMAPXStream *stream,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+	CamelIMAPXStore *store;
+	CamelIMAPXStoreNamespace *ns;
+	CamelFolder *folder = NULL;
+	gchar *mailbox_name = NULL;
+	gchar **quota_root_names = NULL;
+	gboolean success;
+	GError *local_error = NULL;
+
+	success = camel_imapx_parse_quotaroot (
+		stream, cancellable, &mailbox_name, &quota_root_names, error);
+
+	/* Sanity check */
+	g_return_val_if_fail (
+		(success && (mailbox_name != NULL)) ||
+		(!success && (mailbox_name == NULL)), FALSE);
+
+	if (!success)
+		return FALSE;
+
+	store = camel_imapx_server_ref_store (is);
+
+	ns = camel_imapx_store_summary_namespace_find_full (
+		store->summary, mailbox_name);
+	if (ns != NULL) {
+		gchar *folder_path;
+
+		folder_path = camel_imapx_store_summary_full_to_path (
+			store->summary, mailbox_name, ns->sep);
+		if (folder_path != NULL) {
+			folder = camel_store_get_folder_sync (
+				CAMEL_STORE (store), folder_path, 0,
+				cancellable, &local_error);
+			g_free (folder_path);
+		}
+	}
+
+	if (folder != NULL) {
+		camel_imapx_folder_set_quota_root_names (
+			CAMEL_IMAPX_FOLDER (folder),
+			(const gchar **) quota_root_names);
+		g_object_unref (folder);
+	}
+
+	if (local_error != NULL) {
+		g_warning (
+			"%s: Failed to get folder '%s': %s",
+			G_STRFUNC, mailbox_name, local_error->message);
+		g_error_free (local_error);
+	}
+
+	g_free (mailbox_name);
+	g_strfreev (quota_root_names);
+
+	return TRUE;
+}
+
+static gboolean
 imapx_untagged_recent (CamelIMAPXServer *is,
                        CamelIMAPXStream *stream,
                        GCancellable *cancellable,
@@ -5813,6 +5935,69 @@ imapx_job_rename_folder_start (CamelIMAPXJob *job,
 /* ********************************************************************** */
 
 static gboolean
+imapx_command_update_quota_info_done (CamelIMAPXServer *is,
+                                      CamelIMAPXCommand *ic,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+	CamelIMAPXJob *job;
+	gboolean success = TRUE;
+
+	job = camel_imapx_command_get_job (ic);
+	g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
+
+	if (camel_imapx_command_set_error_if_failed (ic, error)) {
+		g_prefix_error (
+			error, "%s: ",
+			_("Error retrieving quota information"));
+		success = FALSE;
+	}
+
+	imapx_unregister_job (is, job);
+	camel_imapx_command_unref (ic);
+
+	return success;
+}
+
+static gboolean
+imapx_job_update_quota_info_start (CamelIMAPXJob *job,
+                                   CamelIMAPXServer *is,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+	CamelIMAPXCommand *ic;
+	CamelIMAPXStore *store;
+	QuotaData *data;
+	gchar *encoded_folder_name;
+	gboolean success;
+
+	data = camel_imapx_job_get_data (job);
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	store = camel_imapx_server_ref_store (is);
+
+	encoded_folder_name =
+		imapx_encode_folder_name (store, data->folder_name);
+
+	ic = camel_imapx_command_new (
+		is, "GETQUOTAROOT", NULL,
+		"GETQUOTAROOT %s", encoded_folder_name);
+	ic->pri = job->pri;
+	camel_imapx_command_set_job (ic, job);
+	ic->complete = imapx_command_update_quota_info_done;
+
+	success = imapx_command_queue (is, ic, cancellable, error);
+
+	g_free (encoded_folder_name);
+
+	g_object_unref (store);
+
+	return success;
+}
+
+/* ********************************************************************** */
+
+static gboolean
 imapx_command_noop_done (CamelIMAPXServer *is,
                          CamelIMAPXCommand *ic,
                          GCancellable *cancellable,
@@ -7552,6 +7737,44 @@ camel_imapx_server_rename_folder (CamelIMAPXServer *is,
 	return success;
 }
 
+gboolean
+camel_imapx_server_update_quota_info (CamelIMAPXServer *is,
+                                      const gchar *folder_name,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+	CamelIMAPXJob *job;
+	QuotaData *data;
+	gboolean success;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+	g_return_val_if_fail (folder_name != NULL, FALSE);
+
+	if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_QUOTA) == 0) {
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+			_("IMAP server does not support quotas"));
+		return FALSE;
+	}
+
+	data = g_slice_new0 (QuotaData);
+	data->folder_name = g_strdup (folder_name);
+
+	job = camel_imapx_job_new (cancellable);
+	job->type = IMAPX_JOB_UPDATE_QUOTA_INFO;
+	job->start = imapx_job_update_quota_info_start;
+	job->pri = IMAPX_PRIORITY_UPDATE_QUOTA_INFO;
+
+	camel_imapx_job_set_data (
+		job, data, (GDestroyNotify) quota_data_free);
+
+	success = imapx_submit_job (is, job, error);
+
+	camel_imapx_job_unref (job);
+
+	return success;
+}
+
 IMAPXJobQueueInfo *
 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
 {
diff --git a/camel/camel-imapx-server.h b/camel/camel-imapx-server.h
index 02cc44a..15cdfce 100644
--- a/camel/camel-imapx-server.h
+++ b/camel/camel-imapx-server.h
@@ -272,6 +272,11 @@ gboolean	camel_imapx_server_rename_folder
 						 const gchar *new_name,
 						 GCancellable *cancellable,
 						 GError **error);
+gboolean	camel_imapx_server_update_quota_info
+						(CamelIMAPXServer *is,
+						 const gchar *folder_name,
+						 GCancellable *cancellable,
+						 GError **error);
 struct _IMAPXJobQueueInfo *
 		camel_imapx_server_get_job_queue_info
 						(CamelIMAPXServer *is);
diff --git a/camel/camel-imapx-store.c b/camel/camel-imapx-store.c
index 792e61c..bfa95c3 100644
--- a/camel/camel-imapx-store.c
+++ b/camel/camel-imapx-store.c
@@ -52,6 +52,15 @@
 
 #define FINFO_REFRESH_INTERVAL 60
 
+#define CAMEL_IMAPX_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), CAMEL_TYPE_IMAPX_STORE, CamelIMAPXStorePrivate))
+
+struct _CamelIMAPXStorePrivate {
+	GHashTable *quota_info;
+	GMutex quota_info_lock;
+};
+
 static GInitableIface *parent_initable_interface;
 
 /* Forward Declarations */
@@ -130,6 +139,9 @@ imapx_store_finalize (GObject *object)
 
 	g_mutex_clear (&imapx_store->get_finfo_lock);
 
+	g_hash_table_destroy (imapx_store->priv->quota_info);
+	g_mutex_clear (&imapx_store->priv->quota_info_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_imapx_store_parent_class)->finalize (object);
 }
@@ -1770,6 +1782,8 @@ camel_imapx_store_class_init (CamelIMAPXStoreClass *class)
 	CamelServiceClass *service_class;
 	CamelStoreClass *store_class;
 
+	g_type_class_add_private (class, sizeof (CamelIMAPXStorePrivate));
+
 	object_class = G_OBJECT_CLASS (class);
 	object_class->dispose = imapx_store_dispose;
 	object_class->finalize = imapx_store_finalize;
@@ -1821,13 +1835,63 @@ camel_subscribable_init (CamelSubscribableInterface *interface)
 }
 
 static void
-camel_imapx_store_init (CamelIMAPXStore *istore)
+camel_imapx_store_init (CamelIMAPXStore *store)
 {
-	g_mutex_init (&istore->get_finfo_lock);
-	istore->last_refresh_time = time (NULL) - (FINFO_REFRESH_INTERVAL + 10);
-	istore->dir_sep = '/';
-	istore->con_man = camel_imapx_conn_manager_new (CAMEL_STORE (istore));
+	store->priv = CAMEL_IMAPX_STORE_GET_PRIVATE (store);
+
+	g_mutex_init (&store->get_finfo_lock);
+	store->last_refresh_time = time (NULL) - (FINFO_REFRESH_INTERVAL + 10);
+	store->dir_sep = '/';
+	store->con_man = camel_imapx_conn_manager_new (CAMEL_STORE (store));
+
+	store->priv->quota_info = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) camel_folder_quota_info_free);
+	g_mutex_init (&store->priv->quota_info_lock);
 
 	imapx_utils_init ();
 }
 
+CamelFolderQuotaInfo *
+camel_imapx_store_dup_quota_info (CamelIMAPXStore *store,
+                                  const gchar *quota_root_name)
+{
+	CamelFolderQuotaInfo *info;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
+	g_return_val_if_fail (quota_root_name != NULL, NULL);
+
+	g_mutex_lock (&store->priv->quota_info_lock);
+
+	info = g_hash_table_lookup (
+		store->priv->quota_info, quota_root_name);
+
+	/* camel_folder_quota_info_clone() handles NULL gracefully. */
+	info = camel_folder_quota_info_clone (info);
+
+	g_mutex_unlock (&store->priv->quota_info_lock);
+
+	return info;
+}
+
+void
+camel_imapx_store_set_quota_info (CamelIMAPXStore *store,
+                                  const gchar *quota_root_name,
+                                  const CamelFolderQuotaInfo *info)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
+	g_return_if_fail (quota_root_name != NULL);
+
+	g_mutex_lock (&store->priv->quota_info_lock);
+
+	/* camel_folder_quota_info_clone() handles NULL gracefully. */
+	g_hash_table_insert (
+		store->priv->quota_info,
+		g_strdup (quota_root_name),
+		camel_folder_quota_info_clone (info));
+
+	g_mutex_unlock (&store->priv->quota_info_lock);
+}
+
diff --git a/camel/camel-imapx-store.h b/camel/camel-imapx-store.h
index dd7fc90..3eac2e0 100644
--- a/camel/camel-imapx-store.h
+++ b/camel/camel-imapx-store.h
@@ -92,12 +92,20 @@ struct _CamelIMAPXStoreClass {
 GType		camel_imapx_store_get_type	(void);
 CamelIMAPXServer *
 		camel_imapx_store_get_server	(CamelIMAPXStore *store,
-						const gchar *folder_name,
-						GCancellable *cancellable,
-						GError **error);
+						 const gchar *folder_name,
+						 GCancellable *cancellable,
+						 GError **error);
 void		camel_imapx_store_op_done	(CamelIMAPXStore *istore,
-						CamelIMAPXServer *server,
-						const gchar *folder_name);
+						 CamelIMAPXServer *server,
+						 const gchar *folder_name);
+CamelFolderQuotaInfo *
+		camel_imapx_store_dup_quota_info
+						(CamelIMAPXStore *store,
+						 const gchar *quota_root_name);
+void		camel_imapx_store_set_quota_info
+						(CamelIMAPXStore *store,
+						 const gchar *quota_root_name,
+						 const CamelFolderQuotaInfo *info);
 
 G_END_DECLS
 
diff --git a/camel/camel-imapx-utils.c b/camel/camel-imapx-utils.c
index d653679..1d72d3f 100644
--- a/camel/camel-imapx-utils.c
+++ b/camel/camel-imapx-utils.c
@@ -371,6 +371,7 @@ struct {
 	{ "QRESYNC", IMAPX_CAPABILITY_QRESYNC },
 	{ "LIST-EXTENDED", IMAPX_CAPABILITY_LIST_EXTENDED },
 	{ "LIST-STATUS", IMAPX_CAPABILITY_LIST_STATUS },
+	{ "QUOTA", IMAPX_CAPABILITY_QUOTA }
 };
 
 static GMutex capa_htable_lock;         /* capabilities lookup table lock */
@@ -1991,6 +1992,187 @@ imapx_free_list (struct _list_info *linfo)
 	}
 }
 
+gboolean
+camel_imapx_parse_quota (CamelIMAPXStream *is,
+                         GCancellable *cancellable,
+                         gchar **out_quota_root_name,
+                         CamelFolderQuotaInfo **out_quota_info,
+                         GError **error)
+{
+	GQueue queue = G_QUEUE_INIT;
+	CamelFolderQuotaInfo *info;
+	CamelFolderQuotaInfo *next;
+	gint tok;
+	guint len;
+	guchar *token;
+	gchar *quota_root_name = NULL;
+	gchar *resource_name = NULL;
+	guint64 resource_usage;
+	guint64 resource_limit;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), FALSE);
+	g_return_val_if_fail (out_quota_root_name != NULL, FALSE);
+	g_return_val_if_fail (out_quota_info != NULL, FALSE);
+
+	/* quota_response  ::= "QUOTA" SP astring SP quota_list
+	 * quota_list      ::= "(" #quota_resource ")"
+	 * quota_resource  ::= atom SP number SP number */
+
+	tok = camel_imapx_stream_astring (is, &token, cancellable, error);
+	if (tok != 0)
+		goto fail;
+	quota_root_name = g_strdup ((gchar *) token);
+
+	tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
+	switch (tok) {
+		case IMAPX_TOK_ERROR:
+		case IMAPX_TOK_PROTOCOL:
+			goto fail;
+		case '(':
+			break;
+		default:
+			g_set_error (
+				error, CAMEL_IMAPX_ERROR, 1,
+				"quota_response: expecting '('");
+			goto fail;
+	}
+
+quota_resource:
+
+	tok = camel_imapx_stream_atom (is, &token, &len, cancellable, error);
+	if (tok != 0)
+		goto fail;
+	resource_name = g_strdup ((gchar *) token);
+
+	resource_usage = camel_imapx_stream_number (
+		is, cancellable, &local_error);
+	if (local_error != NULL) {
+		g_propagate_error (error, local_error);
+		goto fail;
+	}
+
+	resource_limit = camel_imapx_stream_number (
+		is, cancellable, &local_error);
+	if (local_error != NULL) {
+		g_propagate_error (error, local_error);
+		goto fail;
+	}
+
+	info = camel_folder_quota_info_new (
+		resource_name, resource_usage, resource_limit);
+	g_queue_push_tail (&queue, info);
+
+	g_free (resource_name);
+	resource_name = NULL;
+
+	tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
+	switch (tok) {
+		case IMAPX_TOK_ERROR:
+		case IMAPX_TOK_PROTOCOL:
+			goto fail;
+		case ')':
+			break;
+		default:
+			camel_imapx_stream_ungettoken (is, tok, token, len);
+			goto quota_resource;
+	}
+
+	/* Eat the newline. */
+	if (camel_imapx_stream_skip (is, cancellable, error) != 0)
+		goto fail;
+
+	/* String together all the CamelFolderQuotaInfo structs. */
+
+	info = next = NULL;
+
+	while (!g_queue_is_empty (&queue)) {
+		info = g_queue_pop_tail (&queue);
+		info->next = next;
+		next = info;
+	}
+
+	*out_quota_root_name = quota_root_name;
+	*out_quota_info = info;
+
+	return TRUE;
+
+fail:
+	g_free (quota_root_name);
+	g_free (resource_name);
+
+	while (!g_queue_is_empty (&queue)) {
+		info = g_queue_pop_head (&queue);
+		camel_folder_quota_info_free (info);
+	}
+
+	return FALSE;
+}
+
+gboolean
+camel_imapx_parse_quotaroot (CamelIMAPXStream *is,
+                             GCancellable *cancellable,
+                             gchar **out_mailbox_name,
+                             gchar ***out_quota_root_names,
+                             GError **error)
+{
+	GQueue queue = G_QUEUE_INIT;
+	gint tok;
+	guint len;
+	guchar *token;
+	gchar *mailbox_name = NULL;
+	gchar **quota_root_names = NULL;
+	gint ii = 0;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), FALSE);
+	g_return_val_if_fail (out_mailbox_name != NULL, FALSE);
+	g_return_val_if_fail (out_quota_root_names != NULL, FALSE);
+
+	/* quotaroot_response ::= "QUOTAROOT" SP astring *(SP astring) */
+
+	tok = camel_imapx_stream_astring (is, &token, cancellable, error);
+	if (tok != 0)
+		goto fail;
+	mailbox_name = camel_utf7_utf8 ((gchar *) token);
+
+	while (TRUE) {
+		/* Peek at the next token, and break
+		 * out of the loop if we get a newline. */
+		tok = camel_imapx_stream_token (
+			is, &token, &len, cancellable, error);
+		if (tok == '\n')
+			break;
+		if (tok == IMAPX_TOK_ERROR || tok == IMAPX_TOK_PROTOCOL)
+			goto fail;
+		camel_imapx_stream_ungettoken (is, tok, token, len);
+
+		tok = camel_imapx_stream_astring (
+			is, &token, cancellable, error);
+		if (tok == 0)
+			g_queue_push_tail (
+				&queue, g_strdup ((gchar *) token));
+		else
+			goto fail;
+	}
+
+	quota_root_names = g_new0 (gchar *, queue.length + 1);
+	while (!g_queue_is_empty (&queue))
+		quota_root_names[ii++] = g_queue_pop_head (&queue);
+
+	*out_mailbox_name = mailbox_name;
+	*out_quota_root_names = quota_root_names;
+
+	return TRUE;
+
+fail:
+	g_free (mailbox_name);
+
+	while (!g_queue_is_empty (&queue))
+		g_free (g_queue_pop_head (&queue));
+
+	return FALSE;
+}
+
 /* ********************************************************************** */
 
 /*
diff --git a/camel/camel-imapx-utils.h b/camel/camel-imapx-utils.h
index e4abaa6..ed08313 100644
--- a/camel/camel-imapx-utils.h
+++ b/camel/camel-imapx-utils.h
@@ -90,6 +90,8 @@ typedef enum _camel_imapx_id_t {
 #define CAMEL_IMAPX_UNTAGGED_NO         "NO"
 #define CAMEL_IMAPX_UNTAGGED_OK         "OK"
 #define CAMEL_IMAPX_UNTAGGED_PREAUTH    "PREAUTH"
+#define CAMEL_IMAPX_UNTAGGED_QUOTA      "QUOTA"
+#define CAMEL_IMAPX_UNTAGGED_QUOTAROOT  "QUOTAROOT"
 #define CAMEL_IMAPX_UNTAGGED_RECENT     "RECENT"
 #define CAMEL_IMAPX_UNTAGGED_STATUS     "STATUS"
 #define CAMEL_IMAPX_UNTAGGED_VANISHED   "VANISHED"
@@ -144,6 +146,7 @@ enum {
 	IMAPX_CAPABILITY_QRESYNC		= (1 << 9),
 	IMAPX_CAPABILITY_LIST_STATUS		= (1 << 10),
 	IMAPX_CAPABILITY_LIST_EXTENDED		= (1 << 11),
+	IMAPX_CAPABILITY_QUOTA			= (1 << 12)
 };
 
 struct _capability_info {
@@ -303,6 +306,19 @@ gchar *		imapx_list_get_path		(struct _list_info *linfo);
 void		imapx_free_list			(struct _list_info *linfo);
 
 /* ********************************************************************** */
+
+gboolean	camel_imapx_parse_quota		(struct _CamelIMAPXStream *is,
+						 GCancellable *cancellable,
+						 gchar **out_quota_root_name,
+						 CamelFolderQuotaInfo **out_quota_info,
+						 GError **error);
+gboolean	camel_imapx_parse_quotaroot	(struct _CamelIMAPXStream *is,
+						 GCancellable *cancellable,
+						 gchar **out_mailbox_name,
+						 gchar ***out_quota_root_names,
+						 GError **error);
+
+/* ********************************************************************** */
 typedef struct _IMAPXJobQueueInfo {
 	guint queue_len;
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]