[evolution-data-server] IMAPX: Support non-virtual Junk/Trash folders.



commit 1fd3da8927177ed0517abeaf3c7a29611d64546f
Author: Matthew Barnes <mbarnes redhat com>
Date:   Mon Dec 17 11:14:44 2012 -0500

    IMAPX: Support non-virtual Junk/Trash folders.
    
    Some webmail clients designate specific IMAP folders as Junk and Trash,
    such that when a message is deleted or determined to be spam, a copy of
    the message is appended to the designated folder, the original message
    is flagged as DELETED and the folder containing the original message is
    immediately expunged.
    
    This is significantly more expensive than simply flagging the message as
    DELETED or JUNK.  But users that access their mail by both Evolution and
    webmail frequently complain of seeing what appear to be duplicate Junk
    and Trash folders in Evolution [1].
    
    This commit allows IMAPX stores to be configured to mimic these webmail
    clients and hide their Junk and Trash virtual folders.  IMAPX will mimic
    the "copy-and-expunge" webmail client behavior each time it's commanded
    to synchronize message flags with the server.
    
    [1] So much so that it earned its own page in the user manual:
    http://library.gnome.org/users/evolution/stable/mail-two-trash-folders.html.en

 camel/camel-imapx-folder.c              |  342 +++++++++++++++++++++++++++++-
 camel/camel-imapx-folder.h              |    6 +
 camel/camel-imapx-server.c              |   78 ++++++--
 camel/camel-imapx-settings.c            |  354 +++++++++++++++++++++++++++++++
 camel/camel-imapx-settings.h            |   24 ++
 camel/camel-imapx-store.c               |   36 +++
 docs/reference/camel/camel-sections.txt |   12 +
 7 files changed, 831 insertions(+), 21 deletions(-)
---
diff --git a/camel/camel-imapx-folder.c b/camel/camel-imapx-folder.c
index 09760ab..6220be4 100644
--- a/camel/camel-imapx-folder.c
+++ b/camel/camel-imapx-folder.c
@@ -45,6 +45,10 @@
 struct _CamelIMAPXFolderPrivate {
 	GMutex property_lock;
 	gchar **quota_root_names;
+
+	GMutex move_to_hash_table_lock;
+	GHashTable *move_to_real_junk_uids;
+	GHashTable *move_to_real_trash_uids;
 };
 
 /* The custom property ID is a CamelArg artifact.
@@ -59,6 +63,86 @@ G_DEFINE_TYPE (CamelIMAPXFolder, camel_imapx_folder, CAMEL_TYPE_OFFLINE_FOLDER)
 
 static gboolean imapx_folder_get_apply_filters (CamelIMAPXFolder *folder);
 
+static void
+imapx_folder_claim_move_to_real_junk_uids (CamelIMAPXFolder *folder,
+                                           GPtrArray *out_uids_to_copy)
+{
+	CamelFolderSummary *summary;
+	GList *keys;
+
+	summary = CAMEL_FOLDER (folder)->summary;
+
+	g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+	keys = g_hash_table_get_keys (folder->priv->move_to_real_junk_uids);
+	g_list_foreach (keys, (GFunc) camel_pstring_strdup, NULL);
+	g_hash_table_remove_all (folder->priv->move_to_real_junk_uids);
+
+	g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+
+	while (keys != NULL) {
+		CamelMessageInfo *info;
+		CamelMessageFlags flags = 0;
+		const gchar *uid = keys->data;
+
+		/* Recheck the message flags before adding to the array.
+		 * Skip the UID if it's not still flagged as junk. */
+
+		info = camel_folder_summary_get (summary, uid);
+		if (info != NULL) {
+			flags = camel_message_info_flags (info);
+			camel_message_info_free (info);
+		}
+
+		if (flags & CAMEL_MESSAGE_JUNK)
+			g_ptr_array_add (out_uids_to_copy, (gpointer) uid);
+		else
+			camel_pstring_free (uid);
+
+		keys = g_list_delete_link (keys, keys);
+	}
+}
+
+static void
+imapx_folder_claim_move_to_real_trash_uids (CamelIMAPXFolder *folder,
+                                            GPtrArray *out_uids_to_copy)
+{
+	CamelFolderSummary *summary;
+	GList *keys;
+
+	summary = CAMEL_FOLDER (folder)->summary;
+
+	g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+	keys = g_hash_table_get_keys (folder->priv->move_to_real_trash_uids);
+	g_list_foreach (keys, (GFunc) camel_pstring_strdup, NULL);
+	g_hash_table_remove_all (folder->priv->move_to_real_trash_uids);
+
+	g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+
+	while (keys != NULL) {
+		CamelMessageInfo *info;
+		CamelMessageFlags flags = 0;
+		const gchar *uid = keys->data;
+
+		/* Recheck the message flags before adding to the array.
+		 * Skip the UID if it's not still flagged as deleted. */
+
+		info = camel_folder_summary_get (summary, uid);
+		if (info != NULL) {
+			flags = camel_message_info_flags (info);
+			camel_message_info_free (info);
+		}
+
+		if (flags & CAMEL_MESSAGE_DELETED)
+			g_ptr_array_add (out_uids_to_copy, (gpointer) uid);
+		else
+			camel_pstring_free (uid);
+
+		keys = g_list_delete_link (keys, keys);
+	}
+}
+
 static gboolean
 imapx_folder_get_apply_filters (CamelIMAPXFolder *folder)
 {
@@ -172,6 +256,10 @@ imapx_folder_finalize (GObject *object)
 	g_mutex_clear (&folder->priv->property_lock);
 	g_strfreev (folder->priv->quota_root_names);
 
+	g_mutex_clear (&folder->priv->move_to_hash_table_lock);
+	g_hash_table_destroy (folder->priv->move_to_real_junk_uids);
+	g_hash_table_destroy (folder->priv->move_to_real_trash_uids);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_imapx_folder_parent_class)->finalize (object);
 }
@@ -627,6 +715,148 @@ imapx_refresh_info_sync (CamelFolder *folder,
 	return success;
 }
 
+/* Helper for imapx_synchronize_sync() */
+static gboolean
+imapx_move_to_real_junk (CamelIMAPXServer *server,
+                         CamelFolder *folder,
+                         GCancellable *cancellable,
+                         gboolean *out_need_to_expunge,
+                         GError **error)
+{
+	CamelIMAPXSettings *settings;
+	GPtrArray *uids_to_copy;
+	gchar *real_junk_path = NULL;
+	gboolean success = TRUE;
+
+	*out_need_to_expunge = FALSE;
+
+	uids_to_copy = g_ptr_array_new_with_free_func (
+		(GDestroyNotify) camel_pstring_free);
+
+	settings = camel_imapx_server_ref_settings (server);
+	if (camel_imapx_settings_get_use_real_junk_path (settings)) {
+		real_junk_path =
+			camel_imapx_settings_dup_real_junk_path (settings);
+		imapx_folder_claim_move_to_real_junk_uids (
+			CAMEL_IMAPX_FOLDER (folder), uids_to_copy);
+	}
+	g_object_unref (settings);
+
+	if (uids_to_copy->len > 0) {
+		CamelFolder *destination = NULL;
+		CamelIMAPXStore *store;
+
+		store = camel_imapx_server_ref_store (server);
+
+		if (real_junk_path != NULL) {
+			destination = camel_store_get_folder_sync (
+				CAMEL_STORE (store),
+				real_junk_path, 0,
+				cancellable, error);
+		} else {
+			g_set_error (
+				error, CAMEL_FOLDER_ERROR,
+				CAMEL_FOLDER_ERROR_INVALID_PATH,
+				_("No destination folder specified"));
+		}
+
+		if (destination != NULL) {
+			success = camel_imapx_server_copy_message (
+				server, folder, destination,
+				uids_to_copy, TRUE,
+				cancellable, error);
+		} else {
+			success = FALSE;
+		}
+
+		*out_need_to_expunge = success;
+
+		if (!success) {
+			g_prefix_error (
+				error, "%s: ",
+				_("Unable to move junk messages"));
+		}
+
+		g_object_unref (store);
+	}
+
+	g_ptr_array_unref (uids_to_copy);
+	g_free (real_junk_path);
+
+	return success;
+}
+
+/* Helper for imapx_synchronize_sync() */
+static gboolean
+imapx_move_to_real_trash (CamelIMAPXServer *server,
+                          CamelFolder *folder,
+                          GCancellable *cancellable,
+                          gboolean *out_need_to_expunge,
+                          GError **error)
+{
+	CamelIMAPXSettings *settings;
+	GPtrArray *uids_to_copy;
+	gchar *real_trash_path = NULL;
+	gboolean success = TRUE;
+
+	*out_need_to_expunge = FALSE;
+
+	uids_to_copy = g_ptr_array_new_with_free_func (
+		(GDestroyNotify) camel_pstring_free);
+
+	settings = camel_imapx_server_ref_settings (server);
+	if (camel_imapx_settings_get_use_real_trash_path (settings)) {
+		real_trash_path =
+			camel_imapx_settings_dup_real_trash_path (settings);
+		imapx_folder_claim_move_to_real_trash_uids (
+			CAMEL_IMAPX_FOLDER (folder), uids_to_copy);
+	}
+	g_object_unref (settings);
+
+	if (uids_to_copy->len > 0) {
+		CamelFolder *destination = NULL;
+		CamelIMAPXStore *store;
+
+		store = camel_imapx_server_ref_store (server);
+
+		if (real_trash_path != NULL) {
+			destination = camel_store_get_folder_sync (
+				CAMEL_STORE (store),
+				real_trash_path, 0,
+				cancellable, error);
+		} else {
+			g_set_error (
+				error, CAMEL_FOLDER_ERROR,
+				CAMEL_FOLDER_ERROR_INVALID_PATH,
+				_("No destination folder specified"));
+		}
+
+		if (destination != NULL) {
+			success = camel_imapx_server_copy_message (
+				server, folder, destination,
+				uids_to_copy, TRUE,
+				cancellable, error);
+		} else {
+			success = FALSE;
+		}
+
+		*out_need_to_expunge = success;
+
+		if (!success) {
+			g_prefix_error (
+				error, "%s: ",
+				_("Unable to move deleted messages"));
+		}
+
+		g_object_unref (store);
+	}
+
+	g_ptr_array_unref (uids_to_copy);
+	g_free (real_trash_path);
+
+	return success;
+}
+
 static gboolean
 imapx_synchronize_sync (CamelFolder *folder,
                         gboolean expunge,
@@ -654,9 +884,25 @@ imapx_synchronize_sync (CamelFolder *folder,
 	server = camel_imapx_store_get_server (
 		istore, folder_name, cancellable, error);
 	if (server != NULL) {
+		gboolean need_to_expunge;
+
 		success = camel_imapx_server_sync_changes (
 			server, folder, cancellable, error);
 
+		if (success) {
+			success = imapx_move_to_real_junk (
+				server, folder, cancellable,
+				&need_to_expunge, error);
+			expunge |= need_to_expunge;
+		}
+
+		if (success) {
+			success = imapx_move_to_real_trash (
+				server, folder, cancellable,
+				&need_to_expunge, error);
+			expunge |= need_to_expunge;
+		}
+
 		/* Sync twice - make sure deleted flags are written out,
 		 * then sync again incase expunge changed anything */
 
@@ -834,18 +1080,40 @@ static void
 camel_imapx_folder_init (CamelIMAPXFolder *imapx_folder)
 {
 	CamelFolder *folder = CAMEL_FOLDER (imapx_folder);
+	GHashTable *move_to_real_junk_uids;
+	GHashTable *move_to_real_trash_uids;
+
+	move_to_real_junk_uids = g_hash_table_new_full (
+		(GHashFunc) g_direct_hash,
+		(GEqualFunc) g_direct_equal,
+		(GDestroyNotify) camel_pstring_free,
+		(GDestroyNotify) NULL);
+
+	move_to_real_trash_uids = g_hash_table_new_full (
+		(GHashFunc) g_direct_hash,
+		(GEqualFunc) g_direct_equal,
+		(GDestroyNotify) camel_pstring_free,
+		(GDestroyNotify) NULL);
 
 	imapx_folder->priv = CAMEL_IMAPX_FOLDER_GET_PRIVATE (imapx_folder);
 
 	folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
 
-	folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
-		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT |
-		CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_USER;
+	folder->permanent_flags =
+		CAMEL_MESSAGE_ANSWERED |
+		CAMEL_MESSAGE_DELETED |
+		CAMEL_MESSAGE_DRAFT |
+		CAMEL_MESSAGE_FLAGGED |
+		CAMEL_MESSAGE_SEEN |
+		CAMEL_MESSAGE_USER;
 
 	camel_folder_set_lock_async (folder, TRUE);
 
 	g_mutex_init (&imapx_folder->priv->property_lock);
+
+	g_mutex_init (&imapx_folder->priv->move_to_hash_table_lock);
+	imapx_folder->priv->move_to_real_junk_uids = move_to_real_junk_uids;
+	imapx_folder->priv->move_to_real_trash_uids = move_to_real_trash_uids;
 }
 
 CamelFolder *
@@ -980,3 +1248,71 @@ camel_imapx_folder_set_quota_root_names (CamelIMAPXFolder *folder,
 	g_object_notify (G_OBJECT (folder), "quota-root-names");
 }
 
+/**
+ * camel_imapx_folder_maybe_move_to_real_junk:
+ * @folder: a #CamelIMAPXFolder
+ * @message_uid: a message UID
+ *
+ * Adds @message_uid to a pool of messages that may need to be moved to a
+ * real junk folder the next time @folder is explicitly synchronized by way
+ * of camel_folder_synchronize() or camel_folder_synchronize_sync().
+ *
+ * The message flags for @message_uid are double checked before moving the
+ * message to make sure it's still flagged as junk; hence the "maybe" part
+ * of the function name.
+ *
+ * This only applies when using a real folder to track junk messages,
+ * as specified by the #CamelIMAPXSettings:use-real-junk-path setting.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_folder_maybe_move_to_real_junk (CamelIMAPXFolder *folder,
+                                            const gchar *message_uid)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+	g_return_if_fail (message_uid != NULL);
+
+	g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+	g_hash_table_add (
+		folder->priv->move_to_real_junk_uids,
+		(gpointer) camel_pstring_strdup (message_uid));
+
+	g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+}
+
+/**
+ * camel_imapx_folder_maybe_move_to_real_trash:
+ * @folder: a #CamelIMAPXFolder
+ * @message_uid: a message UID
+ *
+ * Adds @message_uid to a pool of messages that may need to be moved to a
+ * real trash folder the next time @folder is explicitly synchronized by way
+ * of camel_folder_synchronize() or camel_folder_synchronize_sync().
+ *
+ * The message flags for @message_uid are double checked before moving the
+ * message to make sure it's still flagged as deleted; hence the "maybe" part
+ * of the function name.
+ *
+ * This only applies when using a real folder to track deleted messages,
+ * as specified by the #CamelIMAPXSettings:use-real-trash-path setting.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_folder_maybe_move_to_real_trash (CamelIMAPXFolder *folder,
+                                             const gchar *message_uid)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+	g_return_if_fail (message_uid != NULL);
+
+	g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+	g_hash_table_add (
+		folder->priv->move_to_real_trash_uids,
+		(gpointer) camel_pstring_strdup (message_uid));
+
+	g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+}
+
diff --git a/camel/camel-imapx-folder.h b/camel/camel-imapx-folder.h
index 3ce36e7..ccc495b 100644
--- a/camel/camel-imapx-folder.h
+++ b/camel/camel-imapx-folder.h
@@ -98,6 +98,12 @@ gchar **	camel_imapx_folder_dup_quota_root_names
 void		camel_imapx_folder_set_quota_root_names
 						(CamelIMAPXFolder *folder,
 						 const gchar **quota_root_names);
+void		camel_imapx_folder_maybe_move_to_real_junk
+						(CamelIMAPXFolder *folder,
+						 const gchar *message_uid);
+void		camel_imapx_folder_maybe_move_to_real_trash
+						(CamelIMAPXFolder *folder,
+						 const gchar *message_uid);
 
 G_END_DECLS
 
diff --git a/camel/camel-imapx-server.c b/camel/camel-imapx-server.c
index 5d641a1..5e63267 100644
--- a/camel/camel-imapx-server.c
+++ b/camel/camel-imapx-server.c
@@ -7238,11 +7238,15 @@ imapx_server_sync_changes (CamelIMAPXServer *is,
                            GError **error)
 {
 	guint i, on_orset, off_orset;
-	GPtrArray *uids;
+	GPtrArray *changed_uids;
 	GArray *on_user = NULL, *off_user = NULL;
 	CamelIMAPXMessageInfo *info;
 	CamelIMAPXJob *job;
+	CamelIMAPXSettings *settings;
 	SyncChangesData *data;
+	gboolean use_real_junk_path;
+	gboolean use_real_trash_path;
+	gboolean nothing_to_do;
 	gboolean registered;
 	gboolean success = TRUE;
 
@@ -7250,28 +7254,41 @@ imapx_server_sync_changes (CamelIMAPXServer *is,
 	 * turned off and a mask of all flags which have been turned
 	 * on. If either of these aren't 0, then we have work to do,
 	 * and we fire off a job to do it.
- *
+	 *
 	 * User flags are a bit more tricky, we rely on the user
 	 * flags being sorted, and then we create a bunch of lists;
 	 * one for each flag being turned off, including each
 	 * info being turned off, and one for each flag being turned on.
-	*/
-	uids = camel_folder_summary_get_changed (folder->summary);
+	 */
+	changed_uids = camel_folder_summary_get_changed (folder->summary);
 
-	if (uids->len == 0) {
-		camel_folder_free_uids (folder, uids);
+	if (changed_uids->len == 0) {
+		camel_folder_free_uids (folder, changed_uids);
 		return TRUE;
 	}
 
+	settings = camel_imapx_server_ref_settings (is);
+	use_real_junk_path =
+		camel_imapx_settings_get_use_real_junk_path (settings);
+	use_real_trash_path =
+		camel_imapx_settings_get_use_real_trash_path (settings);
+	g_object_unref (settings);
+
 	off_orset = on_orset = 0;
-	for (i = 0; i < uids->len; i++) {
+	for (i = 0; i < changed_uids->len; i++) {
 		guint32 flags, sflags;
 		CamelFlag *uflags, *suflags;
+		const gchar *uid;
+		gboolean move_to_real_junk;
+		gboolean move_to_real_trash;
 		guint j = 0;
 
-		info = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary, uids->pdata[i]);
+		uid = g_ptr_array_index (changed_uids, i);
 
-		if (!info)
+		info = (CamelIMAPXMessageInfo *)
+			camel_folder_summary_get (folder->summary, uid);
+
+		if (info == NULL)
 			continue;
 
 		if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
@@ -7279,14 +7296,35 @@ imapx_server_sync_changes (CamelIMAPXServer *is,
 			continue;
 		}
 
-		flags = ((CamelMessageInfoBase *) info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
+		flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
 		sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
+
+		move_to_real_junk =
+			use_real_junk_path &&
+			(flags & CAMEL_MESSAGE_JUNK);
+
+		move_to_real_trash =
+			use_real_trash_path &&
+			(flags & CAMEL_MESSAGE_DELETED);
+
+		if (move_to_real_junk)
+			camel_imapx_folder_maybe_move_to_real_junk (
+				CAMEL_IMAPX_FOLDER (folder), uid);
+
+		if (move_to_real_trash) {
+			/* Remove the DELETED flag so the message
+			 * appears normally in the Trash folder. */
+			flags &= ~CAMEL_MESSAGE_DELETED;
+			camel_imapx_folder_maybe_move_to_real_trash (
+				CAMEL_IMAPX_FOLDER (folder), uid);
+		}
+
 		if (flags != sflags) {
-			off_orset |= ( flags ^ sflags ) & ~flags;
+			off_orset |= (flags ^ sflags) & ~flags;
 			on_orset |= (flags ^ sflags) & flags;
 		}
 
-		uflags = ((CamelMessageInfoBase *) info)->user_flags;
+		uflags = info->info.user_flags;
 		suflags = info->server_user_flags;
 		while (uflags || suflags) {
 			gint res;
@@ -7344,11 +7382,16 @@ imapx_server_sync_changes (CamelIMAPXServer *is,
 		camel_message_info_free (info);
 	}
 
-	if ((on_orset | off_orset) == 0 && on_user == NULL && off_user == NULL) {
+	nothing_to_do =
+		(on_orset == 0) &&
+		(off_orset == 0) &&
+		(on_user == NULL) &&
+		(off_user == NULL);
+
+	if (nothing_to_do) {
 		imapx_sync_free_user (on_user);
 		imapx_sync_free_user (off_user);
-		camel_folder_free_uids (folder, uids);
-
+		camel_folder_free_uids (folder, changed_uids);
 		return TRUE;
 	}
 
@@ -7364,14 +7407,13 @@ imapx_server_sync_changes (CamelIMAPXServer *is,
 
 		imapx_sync_free_user (on_user);
 		imapx_sync_free_user (off_user);
-		camel_folder_free_uids (folder, uids);
-
+		camel_folder_free_uids (folder, changed_uids);
 		return TRUE;
 	}
 
 	data = g_slice_new0 (SyncChangesData);
 	data->folder = g_object_ref (folder);
-	data->changed_uids = uids;  /* takes ownership */
+	data->changed_uids = changed_uids;  /* takes ownership */
 	data->on_set = on_orset;
 	data->off_set = off_orset;
 	data->on_user = on_user;  /* takes ownership */
diff --git a/camel/camel-imapx-settings.c b/camel/camel-imapx-settings.c
index 99d8a88..c01f13e 100644
--- a/camel/camel-imapx-settings.c
+++ b/camel/camel-imapx-settings.c
@@ -28,6 +28,8 @@
 struct _CamelIMAPXSettingsPrivate {
 	GMutex property_lock;
 	gchar *namespace;
+	gchar *real_junk_path;
+	gchar *real_trash_path;
 	gchar *shell_command;
 
 	guint batch_fetch_count;
@@ -42,6 +44,8 @@ struct _CamelIMAPXSettingsPrivate {
 	gboolean use_mobile_mode;
 	gboolean use_namespace;
 	gboolean use_qresync;
+	gboolean use_real_junk_path;
+	gboolean use_real_trash_path;
 	gboolean use_shell_command;
 	gboolean use_subscriptions;
 
@@ -63,12 +67,16 @@ enum {
 	PROP_MOBILE_MODE,
 	PROP_NAMESPACE,
 	PROP_PORT,
+	PROP_REAL_JUNK_PATH,
+	PROP_REAL_TRASH_PATH,
 	PROP_SECURITY_METHOD,
 	PROP_SHELL_COMMAND,
 	PROP_USER,
 	PROP_USE_IDLE,
 	PROP_USE_NAMESPACE,
 	PROP_USE_QRESYNC,
+	PROP_USE_REAL_JUNK_PATH,
+	PROP_USE_REAL_TRASH_PATH,
 	PROP_USE_SHELL_COMMAND,
 	PROP_USE_SUBSCRIPTIONS
 };
@@ -165,6 +173,18 @@ imapx_settings_set_property (GObject *object,
 				g_value_get_uint (value));
 			return;
 
+		case PROP_REAL_JUNK_PATH:
+			camel_imapx_settings_set_real_junk_path (
+				CAMEL_IMAPX_SETTINGS (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_REAL_TRASH_PATH:
+			camel_imapx_settings_set_real_trash_path (
+				CAMEL_IMAPX_SETTINGS (object),
+				g_value_get_string (value));
+			return;
+
 		case PROP_SECURITY_METHOD:
 			camel_network_settings_set_security_method (
 				CAMEL_NETWORK_SETTINGS (object),
@@ -201,6 +221,18 @@ imapx_settings_set_property (GObject *object,
 				g_value_get_boolean (value));
 			return;
 
+		case PROP_USE_REAL_JUNK_PATH:
+			camel_imapx_settings_set_use_real_junk_path (
+				CAMEL_IMAPX_SETTINGS (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_USE_REAL_TRASH_PATH:
+			camel_imapx_settings_set_use_real_trash_path (
+				CAMEL_IMAPX_SETTINGS (object),
+				g_value_get_boolean (value));
+			return;
+
 		case PROP_USE_SHELL_COMMAND:
 			camel_imapx_settings_set_use_shell_command (
 				CAMEL_IMAPX_SETTINGS (object),
@@ -315,6 +347,20 @@ imapx_settings_get_property (GObject *object,
 				CAMEL_NETWORK_SETTINGS (object)));
 			return;
 
+		case PROP_REAL_JUNK_PATH:
+			g_value_take_string (
+				value,
+				camel_imapx_settings_dup_real_junk_path (
+				CAMEL_IMAPX_SETTINGS (object)));
+			return;
+
+		case PROP_REAL_TRASH_PATH:
+			g_value_take_string (
+				value,
+				camel_imapx_settings_dup_real_trash_path (
+				CAMEL_IMAPX_SETTINGS (object)));
+			return;
+
 		case PROP_SECURITY_METHOD:
 			g_value_set_enum (
 				value,
@@ -357,6 +403,20 @@ imapx_settings_get_property (GObject *object,
 				CAMEL_IMAPX_SETTINGS (object)));
 			return;
 
+		case PROP_USE_REAL_JUNK_PATH:
+			g_value_set_boolean (
+				value,
+				camel_imapx_settings_get_use_real_junk_path (
+				CAMEL_IMAPX_SETTINGS (object)));
+			return;
+
+		case PROP_USE_REAL_TRASH_PATH:
+			g_value_set_boolean (
+				value,
+				camel_imapx_settings_get_use_real_trash_path (
+				CAMEL_IMAPX_SETTINGS (object)));
+			return;
+
 		case PROP_USE_SHELL_COMMAND:
 			g_value_set_boolean (
 				value,
@@ -546,6 +606,30 @@ camel_imapx_settings_class_init (CamelIMAPXSettingsClass *class)
 		PROP_PORT,
 		"port");
 
+	g_object_class_install_property (
+		object_class,
+		PROP_REAL_JUNK_PATH,
+		g_param_spec_string (
+			"real-junk-path",
+			"Real Junk Path",
+			"Path for a non-virtual Junk folder",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REAL_TRASH_PATH,
+		g_param_spec_string (
+			"real-trash-path",
+			"Real Trash Path",
+			"Path for a non-virtual Trash folder",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
 	/* Inherited from CamelNetworkSettings. */
 	g_object_class_override_property (
 		object_class,
@@ -608,6 +692,30 @@ camel_imapx_settings_class_init (CamelIMAPXSettingsClass *class)
 
 	g_object_class_install_property (
 		object_class,
+		PROP_USE_REAL_JUNK_PATH,
+		g_param_spec_boolean (
+			"use-real-junk-path",
+			"Use Real Junk Path",
+			"Whether to use a non-virtual Junk folder",
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_USE_REAL_TRASH_PATH,
+		g_param_spec_boolean (
+			"use-real-trash-path",
+			"Use Real Trash Path",
+			"Whether to use a non-virtual Trash folder",
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
 		PROP_USE_SHELL_COMMAND,
 		g_param_spec_boolean (
 			"use-shell-command",
@@ -1118,6 +1226,166 @@ camel_imapx_settings_set_namespace (CamelIMAPXSettings *settings,
 }
 
 /**
+ * camel_imapx_settings_get_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the path to a real, non-virtual Junk folder to be used instead
+ * of Camel's standard virtual Junk folder.
+ *
+ * Returns: path to a real junk folder
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_imapx_settings_get_real_junk_path (CamelIMAPXSettings *settings)
+{
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+	return settings->priv->real_junk_path;
+}
+
+/**
+ * camel_imapx_settings_dup_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_real_junk_path().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXSettings:real-junk-path
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_imapx_settings_dup_real_junk_path (CamelIMAPXSettings *settings)
+{
+	const gchar *protected;
+	gchar *duplicate;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+	g_mutex_lock (&settings->priv->property_lock);
+
+	protected = camel_imapx_settings_get_real_junk_path (settings);
+	duplicate = g_strdup (protected);
+
+	g_mutex_unlock (&settings->priv->property_lock);
+
+	return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ * @real_junk_path: path to a real Junk folder, or %NULL
+ *
+ * Sets the path to a real, non-virtual Junk folder to be used instead of
+ * Camel's standard virtual Junk folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_real_junk_path (CamelIMAPXSettings *settings,
+                                         const gchar *real_junk_path)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+	/* An empty string is equivalent to NULL. */
+	if (real_junk_path != NULL && *real_junk_path == '\0')
+		real_junk_path = NULL;
+
+	g_mutex_lock (&settings->priv->property_lock);
+
+	g_free (settings->priv->real_junk_path);
+	settings->priv->real_junk_path = g_strdup (real_junk_path);
+
+	g_mutex_unlock (&settings->priv->property_lock);
+
+	g_object_notify (G_OBJECT (settings), "real-junk-path");
+}
+
+/**
+ * camel_imapx_settings_get_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the path to a real, non-virtual Trash folder to be used instead
+ * of Camel's standard virtual Trash folder.
+ *
+ * Returns: path to a real Trash folder
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_imapx_settings_get_real_trash_path (CamelIMAPXSettings *settings)
+{
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+	return settings->priv->real_trash_path;
+}
+
+/**
+ * camel_imapx_settings_dup_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_real_trash_path().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXsettings:real-trash-path
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_imapx_settings_dup_real_trash_path (CamelIMAPXSettings *settings)
+{
+	const gchar *protected;
+	gchar *duplicate;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+	g_mutex_lock (&settings->priv->property_lock);
+
+	protected = camel_imapx_settings_get_real_trash_path (settings);
+	duplicate = g_strdup (protected);
+
+	g_mutex_unlock (&settings->priv->property_lock);
+
+	return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ * @real_trash_path: path to a real Trash folder, or %NULL
+ *
+ * Sets the path to a real, non-virtual Trash folder to be used instead of
+ * Camel's standard virtual Trash folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_real_trash_path (CamelIMAPXSettings *settings,
+                                          const gchar *real_trash_path)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+	/* An empty string is equivalent to NULL. */
+	if (real_trash_path != NULL && *real_trash_path == '\0')
+		real_trash_path = NULL;
+
+	g_mutex_lock (&settings->priv->property_lock);
+
+	g_free (settings->priv->real_trash_path);
+	settings->priv->real_trash_path = g_strdup (real_trash_path);
+
+	g_mutex_unlock (&settings->priv->property_lock);
+
+	g_object_notify (G_OBJECT (settings), "real-trash-path");
+}
+
+/**
  * camel_imapx_settings_get_shell_command:
  * @settings: a #CamelIMAPXSettings
  *
@@ -1346,6 +1614,92 @@ camel_imapx_settings_set_use_qresync (CamelIMAPXSettings *settings,
 }
 
 /**
+ * camel_imapx_settings_get_use_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a real, non-virtual Junk folder instead of Camel's
+ * standard virtual Junk folder.
+ *
+ * Returns: whether to use a real Junk folder
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_imapx_settings_get_use_real_junk_path (CamelIMAPXSettings *settings)
+{
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+	return settings->priv->use_real_junk_path;
+}
+
+/**
+ * camel_imapx_settings_set_use_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ * @use_real_junk_path: whether to use a real Junk folder
+ *
+ * Sets whether to use a real, non-virtual Junk folder instead of Camel's
+ * standard virtual Junk folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_use_real_junk_path (CamelIMAPXSettings *settings,
+                                             gboolean use_real_junk_path)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+	if (settings->priv->use_real_junk_path == use_real_junk_path)
+		return;
+
+	settings->priv->use_real_junk_path = use_real_junk_path;
+
+	g_object_notify (G_OBJECT (settings), "use-real-junk-path");
+}
+
+/**
+ * camel_imapx_settings_get_use_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a real, non-virtual Trash folder instead of Camel's
+ * standard virtual Trash folder.
+ *
+ * Returns: whether to use a real Trash folder
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_imapx_settings_get_use_real_trash_path (CamelIMAPXSettings *settings)
+{
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+	return settings->priv->use_real_trash_path;
+}
+
+/**
+ * camel_imapx_settings_set_use_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ * @use_real_trash_path: whether to use a real Trash folder
+ *
+ * Sets whether to use a real, non-virtual Trash folder instead of Camel's
+ * standard virtual Trash folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_use_real_trash_path (CamelIMAPXSettings *settings,
+                                              gboolean use_real_trash_path)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+	if (settings->priv->use_real_trash_path == use_real_trash_path)
+		return;
+
+	settings->priv->use_real_trash_path = use_real_trash_path;
+
+	g_object_notify (G_OBJECT (settings), "use-real-trash-path");
+}
+
+/**
  * camel_imapx_settings_get_use_shell_command:
  * @settings: a #CamelIMAPXSettings
  *
diff --git a/camel/camel-imapx-settings.h b/camel/camel-imapx-settings.h
index 5821819..635f525 100644
--- a/camel/camel-imapx-settings.h
+++ b/camel/camel-imapx-settings.h
@@ -112,6 +112,20 @@ gchar *		camel_imapx_settings_dup_namespace
 void		camel_imapx_settings_set_namespace
 						(CamelIMAPXSettings *settings,
 						 const gchar *namespace_);
+const gchar *	camel_imapx_settings_get_real_junk_path
+						(CamelIMAPXSettings *settings);
+gchar *		camel_imapx_settings_dup_real_junk_path
+						(CamelIMAPXSettings *settings);
+void		camel_imapx_settings_set_real_junk_path
+						(CamelIMAPXSettings *settings,
+						 const gchar *real_junk_path);
+const gchar *	camel_imapx_settings_get_real_trash_path
+						(CamelIMAPXSettings *settings);
+gchar *		camel_imapx_settings_dup_real_trash_path
+						(CamelIMAPXSettings *settings);
+void		camel_imapx_settings_set_real_trash_path
+						(CamelIMAPXSettings *settings,
+						 const gchar *real_trash_path);
 const gchar *	camel_imapx_settings_get_shell_command
 						(CamelIMAPXSettings *settings);
 gchar *		camel_imapx_settings_dup_shell_command
@@ -134,6 +148,16 @@ gboolean	camel_imapx_settings_get_use_qresync
 void		camel_imapx_settings_set_use_qresync
 						(CamelIMAPXSettings *settings,
 						 gboolean use_qresync);
+gboolean	camel_imapx_settings_get_use_real_junk_path
+						(CamelIMAPXSettings *settings);
+void		camel_imapx_settings_set_use_real_junk_path
+						(CamelIMAPXSettings *settings,
+						 gboolean use_real_junk_path);
+gboolean	camel_imapx_settings_get_use_real_trash_path
+						(CamelIMAPXSettings *settings);
+void		camel_imapx_settings_set_use_real_trash_path
+						(CamelIMAPXSettings *settings,
+						 gboolean use_real_trash_path);
 gboolean	camel_imapx_settings_get_use_shell_command
 						(CamelIMAPXSettings *settings);
 void		camel_imapx_settings_set_use_shell_command
diff --git a/camel/camel-imapx-store.c b/camel/camel-imapx-store.c
index bfa95c3..405ff48 100644
--- a/camel/camel-imapx-store.c
+++ b/camel/camel-imapx-store.c
@@ -105,6 +105,38 @@ imapx_name_equal (gconstpointer a,
 }
 
 static void
+imapx_store_update_store_flags (CamelStore *store)
+{
+	CamelService *service;
+	CamelSettings *settings;
+	CamelIMAPXSettings *imapx_settings;
+
+	/* XXX This only responds to the service's entire settings object
+	 *     being replaced, not when individual settings change.  When
+	 *     individual settings change, a restart is required for them
+	 *     to take effect. */
+
+	service = CAMEL_SERVICE (store);
+	settings = camel_service_ref_settings (service);
+	imapx_settings = CAMEL_IMAPX_SETTINGS (settings);
+
+	if (camel_imapx_settings_get_use_real_junk_path (imapx_settings)) {
+		store->flags &= ~CAMEL_STORE_VJUNK;
+		store->flags |= CAMEL_STORE_REAL_JUNK_FOLDER;
+	} else {
+		store->flags |= CAMEL_STORE_VJUNK;
+		store->flags &= ~CAMEL_STORE_REAL_JUNK_FOLDER;
+	}
+
+	if (camel_imapx_settings_get_use_real_trash_path (imapx_settings))
+		store->flags &= ~CAMEL_STORE_VTRASH;
+	else
+		store->flags |= CAMEL_STORE_VTRASH;
+
+	g_object_unref (settings);
+}
+
+static void
 imapx_store_dispose (GObject *object)
 {
 	CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (object);
@@ -1852,6 +1884,10 @@ camel_imapx_store_init (CamelIMAPXStore *store)
 	g_mutex_init (&store->priv->quota_info_lock);
 
 	imapx_utils_init ();
+
+	g_signal_connect (
+		store, "notify::settings",
+		G_CALLBACK (imapx_store_update_store_flags), NULL);
 }
 
 CamelFolderQuotaInfo *
diff --git a/docs/reference/camel/camel-sections.txt b/docs/reference/camel/camel-sections.txt
index 73332b3..46813f0 100644
--- a/docs/reference/camel/camel-sections.txt
+++ b/docs/reference/camel/camel-sections.txt
@@ -796,6 +796,8 @@ CamelIMAPXFolder
 camel_imapx_folder_new
 camel_imapx_folder_dup_quota_root_names
 camel_imapx_folder_set_quota_root_names
+camel_imapx_folder_maybe_move_to_real_junk
+camel_imapx_folder_maybe_move_to_real_trash
 <SUBSECTION Standard>
 CAMEL_IMAPX_FOLDER
 CAMEL_IS_IMAPX_FOLDER
@@ -901,6 +903,12 @@ camel_imapx_settings_set_mobile_mode
 camel_imapx_settings_get_namespace
 camel_imapx_settings_dup_namespace
 camel_imapx_settings_set_namespace
+camel_imapx_settings_get_real_junk_path
+camel_imapx_settings_dup_real_junk_path
+camel_imapx_settings_set_real_junk_path
+camel_imapx_settings_get_real_trash_path
+camel_imapx_settings_dup_real_trash_path
+camel_imapx_settings_set_real_trash_path
 camel_imapx_settings_get_shell_command
 camel_imapx_settings_dup_shell_command
 camel_imapx_settings_set_shell_command
@@ -910,6 +918,10 @@ camel_imapx_settings_get_use_namespace
 camel_imapx_settings_set_use_namespace
 camel_imapx_settings_get_use_qresync
 camel_imapx_settings_set_use_qresync
+camel_imapx_settings_get_use_real_junk_path
+camel_imapx_settings_set_use_real_junk_path
+camel_imapx_settings_get_use_real_trash_path
+camel_imapx_settings_set_use_real_trash_path
 camel_imapx_settings_get_use_shell_command
 camel_imapx_settings_set_use_shell_command
 camel_imapx_settings_get_use_subscriptions



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