1 /*
2  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authors: Michael Zucchi <notzed@ximian.com>
17  */
18 
19 #include "evolution-data-server-config.h"
20 
21 #include <string.h>
22 
23 #include <glib/gi18n-lib.h>
24 #include <glib/gstdio.h>
25 
26 #include "camel-db.h"
27 #include "camel-session.h"
28 #include "camel-string-utils.h"
29 #include "camel-vee-folder.h"
30 #include "camel-vee-store.h"
31 
32 /* Translators: 'Unmatched' is a folder name under Search folders where are shown
33  * all messages not belonging into any other configured search folder */
34 #define PRETTY_UNMATCHED_FOLDER_NAME _("Unmatched")
35 
36 #define d(x)
37 
38 /* flags
39  * 1 = delete (0 = add)
40  * 2 = noselect
41 */
42 #define CHANGE_ADD (0)
43 #define CHANGE_DELETE (1)
44 #define CHANGE_NOSELECT (2)
45 
46 extern gint camel_application_is_exiting;
47 
48 /* The custom property ID is a CamelArg artifact.
49  * It still identifies the property in state files. */
50 enum {
51 	PROP_0,
52 	PROP_UNMATCHED_ENABLED = 0x2400
53 };
54 
55 struct _CamelVeeStorePrivate {
56 	CamelVeeDataCache *vee_data_cache;
57 	CamelVeeFolder *unmatched_folder;
58 	gboolean unmatched_enabled;
59 
60 	GMutex sf_counts_mutex;
61 	GHashTable *subfolder_usage_counts; /* CamelFolder * (subfolder) => gint of usages, for unmatched_folder */
62 
63 	GMutex vu_counts_mutex;
64 	GHashTable *vuid_usage_counts; /* gchar * (vuid) => gint of usages, those with 0 comes to unmatched_folder */
65 };
66 
G_DEFINE_TYPE_WITH_PRIVATE(CamelVeeStore,camel_vee_store,CAMEL_TYPE_STORE)67 G_DEFINE_TYPE_WITH_PRIVATE (CamelVeeStore, camel_vee_store, CAMEL_TYPE_STORE)
68 
69 static gint
70 vee_folder_cmp (gconstpointer ap,
71                 gconstpointer bp)
72 {
73 	const gchar *full_name_a;
74 	const gchar *full_name_b;
75 
76 	full_name_a = camel_folder_get_full_name (((CamelFolder **) ap)[0]);
77 	full_name_b = camel_folder_get_full_name (((CamelFolder **) bp)[0]);
78 
79 	return g_strcmp0 (full_name_a, full_name_b);
80 }
81 
82 static void
change_folder(CamelStore * store,const gchar * name,guint32 flags,gint count)83 change_folder (CamelStore *store,
84                const gchar *name,
85                guint32 flags,
86                gint count)
87 {
88 	CamelFolderInfo *fi;
89 	const gchar *tmp;
90 
91 	fi = camel_folder_info_new ();
92 	fi->full_name = g_strdup (name);
93 	tmp = strrchr (name, '/');
94 	if (tmp == NULL)
95 		tmp = name;
96 	else
97 		tmp++;
98 	fi->display_name = g_strdup (tmp);
99 	fi->unread = count;
100 	fi->flags = CAMEL_FOLDER_VIRTUAL;
101 	if (!(flags & CHANGE_DELETE))
102 		fi->flags |= CAMEL_FOLDER_NOCHILDREN;
103 	if (flags & CHANGE_NOSELECT)
104 		fi->flags |= CAMEL_FOLDER_NOSELECT;
105 	if (flags & CHANGE_DELETE)
106 		camel_store_folder_deleted (store, fi);
107 	else
108 		camel_store_folder_created (store, fi);
109 	camel_folder_info_free (fi);
110 }
111 
112 static void
vee_store_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)113 vee_store_set_property (GObject *object,
114                         guint property_id,
115                         const GValue *value,
116                         GParamSpec *pspec)
117 {
118 	switch (property_id) {
119 		case PROP_UNMATCHED_ENABLED:
120 			camel_vee_store_set_unmatched_enabled (
121 				CAMEL_VEE_STORE (object),
122 				g_value_get_boolean (value));
123 			return;
124 	}
125 
126 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
127 }
128 
129 static void
vee_store_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)130 vee_store_get_property (GObject *object,
131                         guint property_id,
132                         GValue *value,
133                         GParamSpec *pspec)
134 {
135 	switch (property_id) {
136 		case PROP_UNMATCHED_ENABLED:
137 			g_value_set_boolean (
138 				value,
139 				camel_vee_store_get_unmatched_enabled (
140 				CAMEL_VEE_STORE (object)));
141 			return;
142 	}
143 
144 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
145 }
146 
147 static void
vee_store_dispose(GObject * object)148 vee_store_dispose (GObject *object)
149 {
150 	CamelVeeStorePrivate *priv;
151 
152 	priv = CAMEL_VEE_STORE (object)->priv;
153 
154 	g_clear_object (&priv->vee_data_cache);
155 	g_clear_object (&priv->unmatched_folder);
156 
157 	/* Chain up to parent's method. */
158 	G_OBJECT_CLASS (camel_vee_store_parent_class)->dispose (object);
159 }
160 
161 static void
vee_store_finalize(GObject * object)162 vee_store_finalize (GObject *object)
163 {
164 	CamelVeeStorePrivate *priv;
165 
166 	priv = CAMEL_VEE_STORE (object)->priv;
167 
168 	g_hash_table_destroy (priv->subfolder_usage_counts);
169 	g_hash_table_destroy (priv->vuid_usage_counts);
170 	g_mutex_clear (&priv->sf_counts_mutex);
171 	g_mutex_clear (&priv->vu_counts_mutex);
172 
173 	/* Chain up to parent's finalize () method. */
174 	G_OBJECT_CLASS (camel_vee_store_parent_class)->finalize (object);
175 }
176 
177 static void
vee_store_constructed(GObject * object)178 vee_store_constructed (GObject *object)
179 {
180 	CamelVeeStore *vee_store;
181 
182 	vee_store = CAMEL_VEE_STORE (object);
183 
184 	/* Chain up to parent's constructed() method. */
185 	G_OBJECT_CLASS (camel_vee_store_parent_class)->constructed (object);
186 
187 	/* Set up unmatched folder */
188 	vee_store->priv->unmatched_folder = g_object_new (
189 		CAMEL_TYPE_VEE_FOLDER,
190 		"full-name", CAMEL_UNMATCHED_NAME,
191 		"display-name", PRETTY_UNMATCHED_FOLDER_NAME,
192 		"parent-store", vee_store, NULL);
193 	camel_vee_folder_construct (
194 		vee_store->priv->unmatched_folder, CAMEL_STORE_FOLDER_PRIVATE);
195 	vee_store->priv->subfolder_usage_counts = g_hash_table_new (g_direct_hash, g_direct_equal);
196 	vee_store->priv->vuid_usage_counts = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
197 	g_mutex_init (&vee_store->priv->sf_counts_mutex);
198 	g_mutex_init (&vee_store->priv->vu_counts_mutex);
199 }
200 
201 static gchar *
vee_store_get_name(CamelService * service,gboolean brief)202 vee_store_get_name (CamelService *service,
203                     gboolean brief)
204 {
205 	return g_strdup ("Virtual Folder Store");
206 }
207 
208 static CamelFolder *
vee_store_get_folder_sync(CamelStore * store,const gchar * folder_name,CamelStoreGetFolderFlags flags,GCancellable * cancellable,GError ** error)209 vee_store_get_folder_sync (CamelStore *store,
210                            const gchar *folder_name,
211                            CamelStoreGetFolderFlags flags,
212                            GCancellable *cancellable,
213                            GError **error)
214 {
215 	CamelVeeFolder *vf;
216 	CamelFolder *folder;
217 	gchar *name, *p;
218 	gsize name_len;
219 
220 	vf = (CamelVeeFolder *) camel_vee_folder_new (store, folder_name, flags);
221 	if (vf && ((camel_vee_folder_get_flags (vf) & CAMEL_STORE_FOLDER_PRIVATE) == 0)) {
222 		const gchar *full_name;
223 
224 		full_name = camel_folder_get_full_name (CAMEL_FOLDER (vf));
225 
226 		/* Check that parents exist, if not, create dummy ones */
227 		name_len = strlen (full_name) + 1;
228 		name = alloca (name_len);
229 		g_strlcpy (name, full_name, name_len);
230 		p = name;
231 		while ( (p = strchr (p, '/'))) {
232 			*p = 0;
233 
234 			folder = camel_object_bag_reserve (camel_store_get_folders_bag (store), name);
235 			if (folder == NULL) {
236 				/* create a dummy vFolder for this, makes get_folder_info simpler */
237 				folder = camel_vee_folder_new (store, name, flags);
238 				camel_object_bag_add (camel_store_get_folders_bag (store), name, folder);
239 				change_folder (store, name, CHANGE_ADD | CHANGE_NOSELECT, 0);
240 				/* FIXME: this sort of leaks folder, nobody owns a ref to it but us */
241 			} else {
242 				g_object_unref (folder);
243 			}
244 			*p++='/';
245 		}
246 
247 		change_folder (store, full_name, CHANGE_ADD, camel_folder_get_message_count ((CamelFolder *) vf));
248 	}
249 
250 	return (CamelFolder *) vf;
251 }
252 
253 static CamelFolderInfo *
vee_store_create_unmatched_fi(void)254 vee_store_create_unmatched_fi (void)
255 {
256 	CamelFolderInfo *info;
257 
258 	info = camel_folder_info_new ();
259 	info->full_name = g_strdup (CAMEL_UNMATCHED_NAME);
260 	info->display_name = g_strdup (PRETTY_UNMATCHED_FOLDER_NAME);
261 	info->unread = -1;
262 	info->flags =
263 		CAMEL_FOLDER_NOCHILDREN |
264 		CAMEL_FOLDER_NOINFERIORS |
265 		CAMEL_FOLDER_SYSTEM |
266 		CAMEL_FOLDER_VIRTUAL;
267 
268 	return info;
269 }
270 
271 static CamelFolderInfo *
vee_store_get_folder_info_sync(CamelStore * store,const gchar * top,CamelStoreGetFolderInfoFlags flags,GCancellable * cancellable,GError ** error)272 vee_store_get_folder_info_sync (CamelStore *store,
273                                 const gchar *top,
274                                 CamelStoreGetFolderInfoFlags flags,
275                                 GCancellable *cancellable,
276                                 GError **error)
277 {
278 	CamelFolderInfo *info, *res = NULL, *tail;
279 	GPtrArray *folders;
280 	GHashTable *infos_hash;
281 	gint i;
282 
283 	d (printf ("Get folder info '%s'\n", top ? top:"<null>"));
284 
285 	folders = camel_store_dup_opened_folders (store);
286 	if (!folders || !folders->pdata || !folders->len) {
287 		if (folders)
288 			g_ptr_array_free (folders, TRUE);
289 
290 		return NULL;
291 	}
292 
293 	qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), vee_folder_cmp);
294 
295 	infos_hash = g_hash_table_new (g_str_hash, g_str_equal);
296 	for (i = 0; i < folders->len; i++) {
297 		CamelVeeFolder *folder = folders->pdata[i];
298 		const gchar *full_name;
299 		const gchar *display_name;
300 		gint add = FALSE;
301 		gchar *pname, *tmp;
302 		CamelFolderInfo *pinfo;
303 
304 		full_name = camel_folder_get_full_name (CAMEL_FOLDER (folder));
305 		display_name = camel_folder_get_display_name (CAMEL_FOLDER (folder));
306 
307 		/* check we have to include this one */
308 		if (top) {
309 			gint namelen = strlen (full_name);
310 			gint toplen = strlen (top);
311 
312 			add = ((namelen == toplen
313 				&& strcmp (full_name, top) == 0)
314 			       || ((namelen > toplen)
315 				   && strncmp (full_name, top, toplen) == 0
316 				   && full_name[toplen] == '/'
317 				   && ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
318 				       || strchr (full_name + toplen + 1, '/') == NULL)));
319 		} else {
320 			add = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
321 				|| strchr (full_name, '/') == NULL;
322 		}
323 
324 		if (add) {
325 			gint32 unread;
326 
327 			unread = camel_folder_get_unread_message_count (
328 				CAMEL_FOLDER (folder));
329 
330 			info = camel_folder_info_new ();
331 			info->full_name = g_strdup (full_name);
332 			info->display_name = g_strdup (display_name);
333 			info->unread = unread;
334 			info->flags =
335 				CAMEL_FOLDER_NOCHILDREN |
336 				CAMEL_FOLDER_VIRTUAL;
337 			g_hash_table_insert (infos_hash, info->full_name, info);
338 
339 			if (res == NULL)
340 				res = info;
341 		} else {
342 			info = NULL;
343 		}
344 
345 		/* check for parent, if present, update flags and if adding, update parent linkage */
346 		pname = g_strdup (full_name);
347 		d (printf ("looking up parent of '%s'\n", pname));
348 		tmp = strrchr (pname, '/');
349 		if (tmp) {
350 			*tmp = 0;
351 			pinfo = g_hash_table_lookup (infos_hash, pname);
352 		} else
353 			pinfo = NULL;
354 
355 		if (pinfo) {
356 			pinfo->flags = (pinfo->flags & ~(CAMEL_FOLDER_CHILDREN | CAMEL_FOLDER_NOCHILDREN)) | CAMEL_FOLDER_CHILDREN;
357 			d (printf ("updating parent flags for children '%s' %08x\n", pinfo->full_name, pinfo->flags));
358 			tail = pinfo->child;
359 			if (tail == NULL)
360 				pinfo->child = info;
361 		} else if (info != res) {
362 			tail = res;
363 		} else {
364 			tail = NULL;
365 		}
366 
367 		if (info && tail) {
368 			while (tail->next)
369 				tail = tail->next;
370 			tail->next = info;
371 			info->parent = pinfo;
372 		}
373 
374 		g_free (pname);
375 	}
376 	g_ptr_array_foreach (folders, (GFunc) g_object_unref, NULL);
377 	g_ptr_array_free (folders, TRUE);
378 	g_hash_table_destroy (infos_hash);
379 
380 	/* and add UNMATCHED, if scanning from top/etc and it's enabled */
381 	if (camel_vee_store_get_unmatched_enabled (CAMEL_VEE_STORE (store)) &&
382 	    (top == NULL || top[0] == 0 || strncmp (top, CAMEL_UNMATCHED_NAME, strlen (CAMEL_UNMATCHED_NAME)) == 0)) {
383 		info = vee_store_create_unmatched_fi ();
384 
385 		if (res == NULL)
386 			res = info;
387 		else {
388 			tail = res;
389 			while (tail->next)
390 				tail = tail->next;
391 			tail->next = info;
392 		}
393 	}
394 
395 	return res;
396 }
397 
398 static CamelFolder *
vee_store_get_junk_folder_sync(CamelStore * store,GCancellable * cancellable,GError ** error)399 vee_store_get_junk_folder_sync (CamelStore *store,
400                                 GCancellable *cancellable,
401                                 GError **error)
402 {
403 	return NULL;
404 }
405 
406 static CamelFolder *
vee_store_get_trash_folder_sync(CamelStore * store,GCancellable * cancellable,GError ** error)407 vee_store_get_trash_folder_sync (CamelStore *store,
408                                  GCancellable *cancellable,
409                                  GError **error)
410 {
411 	return NULL;
412 }
413 
414 static gboolean
vee_store_delete_folder_sync(CamelStore * store,const gchar * folder_name,GCancellable * cancellable,GError ** error)415 vee_store_delete_folder_sync (CamelStore *store,
416                               const gchar *folder_name,
417                               GCancellable *cancellable,
418                               GError **error)
419 {
420 	CamelFolder *folder;
421 
422 	if (strcmp (folder_name, CAMEL_UNMATCHED_NAME) == 0) {
423 		g_set_error (
424 			error, CAMEL_STORE_ERROR,
425 			CAMEL_STORE_ERROR_NO_FOLDER,
426 			_("Cannot delete folder: %s: Invalid operation"),
427 			folder_name);
428 		return FALSE;
429 	}
430 
431 	folder = camel_object_bag_get (camel_store_get_folders_bag (store), folder_name);
432 	if (folder) {
433 		CamelObject *object = CAMEL_OBJECT (folder);
434 		const gchar *state_filename;
435 
436 		state_filename = camel_object_get_state_filename (object);
437 		if (state_filename != NULL) {
438 			g_unlink (state_filename);
439 			camel_object_set_state_filename (object, NULL);
440 		}
441 
442 		if ((camel_vee_folder_get_flags (CAMEL_VEE_FOLDER (folder)) & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
443 			/* what about now-empty parents?  ignore? */
444 			change_folder (store, folder_name, CHANGE_DELETE, -1);
445 		}
446 
447 		g_object_unref (folder);
448 	} else {
449 		g_set_error (
450 			error, CAMEL_STORE_ERROR,
451 			CAMEL_STORE_ERROR_NO_FOLDER,
452 			_("Cannot delete folder: %s: No such folder"),
453 			folder_name);
454 		return FALSE;
455 	}
456 
457 	return TRUE;
458 }
459 
460 static gboolean
vee_store_rename_folder_sync(CamelStore * store,const gchar * old,const gchar * new,GCancellable * cancellable,GError ** error)461 vee_store_rename_folder_sync (CamelStore *store,
462                               const gchar *old,
463                               const gchar *new,
464                               GCancellable *cancellable,
465                               GError **error)
466 {
467 	CamelFolder *folder, *oldfolder;
468 	gchar *p, *name;
469 	gsize name_len;
470 
471 	d (printf ("vee rename folder '%s' '%s'\n", old, new));
472 
473 	if (strcmp (old, CAMEL_UNMATCHED_NAME) == 0) {
474 		g_set_error (
475 			error, CAMEL_STORE_ERROR,
476 			CAMEL_STORE_ERROR_NO_FOLDER,
477 			_("Cannot rename folder: %s: Invalid operation"), old);
478 		return FALSE;
479 	}
480 
481 	/* See if it exists, for vfolders, all folders are in the folders hash */
482 	oldfolder = camel_object_bag_get (camel_store_get_folders_bag (store), old);
483 	if (oldfolder == NULL) {
484 		g_set_error (
485 			error, CAMEL_STORE_ERROR,
486 			CAMEL_STORE_ERROR_NO_FOLDER,
487 			_("Cannot rename folder: %s: No such folder"), old);
488 		return FALSE;
489 	}
490 
491 	/* Check that new parents exist, if not, create dummy ones */
492 	name_len = strlen (new) + 1;
493 	name = alloca (name_len);
494 	g_strlcpy (name, new, name_len);
495 	p = name;
496 	while ( (p = strchr (p, '/'))) {
497 		*p = 0;
498 
499 		folder = camel_object_bag_reserve (camel_store_get_folders_bag (store), name);
500 		if (folder == NULL) {
501 			/* create a dummy vFolder for this, makes get_folder_info simpler */
502 			folder = camel_vee_folder_new (store, name, camel_vee_folder_get_flags (CAMEL_VEE_FOLDER (oldfolder)));
503 			camel_object_bag_add (camel_store_get_folders_bag (store), name, folder);
504 			change_folder (store, name, CHANGE_ADD | CHANGE_NOSELECT, 0);
505 			/* FIXME: this sort of leaks folder, nobody owns a ref to it but us */
506 		} else {
507 			g_object_unref (folder);
508 		}
509 		*p++='/';
510 	}
511 
512 	g_object_unref (oldfolder);
513 
514 	return TRUE;
515 }
516 
517 static gboolean
vee_store_get_can_auto_save_changes(CamelStore * store)518 vee_store_get_can_auto_save_changes (CamelStore *store)
519 {
520 	/* Let only the real folder auto-save the changes */
521 	return FALSE;
522 }
523 
524 static void
camel_vee_store_class_init(CamelVeeStoreClass * class)525 camel_vee_store_class_init (CamelVeeStoreClass *class)
526 {
527 	GObjectClass *object_class;
528 	CamelServiceClass *service_class;
529 	CamelStoreClass *store_class;
530 
531 	object_class = G_OBJECT_CLASS (class);
532 	object_class->set_property = vee_store_set_property;
533 	object_class->get_property = vee_store_get_property;
534 	object_class->dispose = vee_store_dispose;
535 	object_class->finalize = vee_store_finalize;
536 	object_class->constructed = vee_store_constructed;
537 
538 	service_class = CAMEL_SERVICE_CLASS (class);
539 	service_class->get_name = vee_store_get_name;
540 
541 	store_class = CAMEL_STORE_CLASS (class);
542 	store_class->get_folder_sync = vee_store_get_folder_sync;
543 	store_class->get_folder_info_sync = vee_store_get_folder_info_sync;
544 	store_class->get_junk_folder_sync = vee_store_get_junk_folder_sync;
545 	store_class->get_trash_folder_sync = vee_store_get_trash_folder_sync;
546 	store_class->delete_folder_sync = vee_store_delete_folder_sync;
547 	store_class->rename_folder_sync = vee_store_rename_folder_sync;
548 	store_class->get_can_auto_save_changes = vee_store_get_can_auto_save_changes;
549 
550 	g_object_class_install_property (
551 		object_class,
552 		PROP_UNMATCHED_ENABLED,
553 		g_param_spec_boolean (
554 			"unmatched-enabled",
555 			"Unmatched Enabled",
556 			_("Enable _Unmatched folder"),
557 			TRUE,
558 			G_PARAM_READWRITE |
559 			G_PARAM_EXPLICIT_NOTIFY));
560 }
561 
562 static void
camel_vee_store_init(CamelVeeStore * vee_store)563 camel_vee_store_init (CamelVeeStore *vee_store)
564 {
565 	CamelStore *store = CAMEL_STORE (vee_store);
566 
567 	vee_store->priv = camel_vee_store_get_instance_private (vee_store);
568 	vee_store->priv->vee_data_cache = camel_vee_data_cache_new ();
569 	vee_store->priv->unmatched_enabled = TRUE;
570 
571 	/* we dont want a vtrash/vjunk on this one */
572 	camel_store_set_flags (store, camel_store_get_flags (store) & ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK));
573 }
574 
575 /**
576  * camel_vee_store_new:
577  *
578  * Create a new #CamelVeeStore object.
579  *
580  * Returns: (transfer full): new #CamelVeeStore object
581  **/
582 CamelVeeStore *
camel_vee_store_new(void)583 camel_vee_store_new (void)
584 {
585 	return g_object_new (CAMEL_TYPE_VEE_STORE, NULL);
586 }
587 
588 /**
589  * camel_vee_store_get_vee_data_cache:
590  * @vstore: a #CamelVeeStore
591  *
592  * Returns: (type CamelVeeFolder) (transfer none): the associated #CamelVeeDataCache
593  *
594  * Since: 3.6
595  **/
596 CamelVeeDataCache *
camel_vee_store_get_vee_data_cache(CamelVeeStore * vstore)597 camel_vee_store_get_vee_data_cache (CamelVeeStore *vstore)
598 {
599 	g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL);
600 
601 	return vstore->priv->vee_data_cache;
602 }
603 
604 /**
605  * camel_vee_store_get_unmatched_folder:
606  * @vstore: a #CamelVeeStore
607  *
608  * Returns: (transfer none): the Unmatched folder instance, or %NULL,
609  *    when it's disabled.
610  *
611  * Since: 3.6
612  **/
613 CamelVeeFolder *
camel_vee_store_get_unmatched_folder(CamelVeeStore * vstore)614 camel_vee_store_get_unmatched_folder (CamelVeeStore *vstore)
615 {
616 	g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL);
617 
618 	if (!camel_vee_store_get_unmatched_enabled (vstore))
619 		return NULL;
620 
621 	return vstore->priv->unmatched_folder;
622 }
623 
624 /**
625  * camel_vee_store_get_unmatched_enabled:
626  * @vstore: a #CamelVeeStore
627  *
628  * Returns: whether Unmatched folder processing is enabled
629  *
630  * Since: 3.6
631  **/
632 gboolean
camel_vee_store_get_unmatched_enabled(CamelVeeStore * vstore)633 camel_vee_store_get_unmatched_enabled (CamelVeeStore *vstore)
634 {
635 	g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), FALSE);
636 
637 	return vstore->priv->unmatched_enabled;
638 }
639 
640 /**
641  * camel_vee_store_set_unmatched_enabled:
642  * @vstore: a #CamelVeeStore
643  * @is_enabled: value to set
644  *
645  * Sets whether the Unmatched folder processing is enabled.
646  *
647  * Since: 3.6
648  **/
649 void
camel_vee_store_set_unmatched_enabled(CamelVeeStore * vstore,gboolean is_enabled)650 camel_vee_store_set_unmatched_enabled (CamelVeeStore *vstore,
651                                        gboolean is_enabled)
652 {
653 	CamelFolderInfo *fi_unmatched;
654 
655 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
656 
657 	if (vstore->priv->unmatched_enabled == is_enabled)
658 		return;
659 
660 	vstore->priv->unmatched_enabled = is_enabled;
661 	g_object_notify (G_OBJECT (vstore), "unmatched-enabled");
662 
663 	fi_unmatched = vee_store_create_unmatched_fi ();
664 
665 	if (is_enabled) {
666 		camel_store_folder_created (CAMEL_STORE (vstore), fi_unmatched);
667 		camel_vee_store_rebuild_unmatched_folder (vstore, NULL, NULL);
668 	} else {
669 		camel_store_folder_deleted (CAMEL_STORE (vstore), fi_unmatched);
670 	}
671 
672 	camel_folder_info_free (fi_unmatched);
673 }
674 
675 struct AddToUnmatchedData {
676 	CamelVeeFolder *unmatched_folder;
677 	CamelFolderChangeInfo *changes;
678 	gboolean unmatched_enabled;
679 	GHashTable *vuid_usage_counts;
680 };
681 
682 static void
add_to_unmatched_folder_cb(CamelVeeMessageInfoData * mi_data,CamelFolder * subfolder,gpointer user_data)683 add_to_unmatched_folder_cb (CamelVeeMessageInfoData *mi_data,
684                             CamelFolder *subfolder,
685                             gpointer user_data)
686 {
687 	struct AddToUnmatchedData *atud = user_data;
688 	const gchar *vuid;
689 
690 	g_return_if_fail (atud != NULL);
691 
692 	vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
693 	g_hash_table_insert (
694 		atud->vuid_usage_counts,
695 		(gpointer) camel_pstring_strdup (vuid),
696 		GINT_TO_POINTER (0));
697 
698 	if (atud->unmatched_enabled)
699 		camel_vee_folder_add_vuid (atud->unmatched_folder, mi_data, atud->changes);
700 }
701 
702 /**
703  * camel_vee_store_note_subfolder_used:
704  * @vstore: a #CamelVeeStore
705  * @subfolder: a #CamelFolder
706  * @used_by: (type CamelVeeFolder): a #CamelVeeFolder
707  *
708  * Notes that the @subfolder is used by @used_by folder, which
709  * is used to determine what folders will be included in
710  * the Unmatched folders.
711  *
712  * Since: 3.6
713  **/
714 void
camel_vee_store_note_subfolder_used(CamelVeeStore * vstore,CamelFolder * subfolder,CamelVeeFolder * used_by)715 camel_vee_store_note_subfolder_used (CamelVeeStore *vstore,
716                                      CamelFolder *subfolder,
717                                      CamelVeeFolder *used_by)
718 {
719 	gint counts;
720 
721 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
722 	g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
723 	g_return_if_fail (CAMEL_IS_VEE_FOLDER (used_by));
724 
725 	/* only real folders can be part of the unmatched folder */
726 	if (CAMEL_IS_VEE_FOLDER (subfolder) ||
727 	    used_by == vstore->priv->unmatched_folder ||
728 	    camel_application_is_exiting)
729 		return;
730 
731 	g_mutex_lock (&vstore->priv->sf_counts_mutex);
732 
733 	counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder));
734 	counts++;
735 	g_hash_table_insert (
736 		vstore->priv->subfolder_usage_counts,
737 		subfolder, GINT_TO_POINTER (counts));
738 
739 	if (counts == 1) {
740 		struct AddToUnmatchedData atud;
741 		CamelFolder *unmatched_folder;
742 
743 		camel_vee_data_cache_add_subfolder (vstore->priv->vee_data_cache, subfolder);
744 
745 		g_mutex_lock (&vstore->priv->vu_counts_mutex);
746 
747 		/* all messages from the folder are unmatched at the beginning */
748 		atud.unmatched_folder = vstore->priv->unmatched_folder;
749 		atud.changes = camel_folder_change_info_new ();
750 		atud.unmatched_enabled = camel_vee_store_get_unmatched_enabled (vstore);
751 		atud.vuid_usage_counts = vstore->priv->vuid_usage_counts;
752 
753 		if (atud.unmatched_enabled)
754 			camel_vee_folder_add_folder (vstore->priv->unmatched_folder, subfolder, NULL);
755 
756 		unmatched_folder = CAMEL_FOLDER (atud.unmatched_folder);
757 
758 		camel_folder_freeze (unmatched_folder);
759 
760 		camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder,
761 			add_to_unmatched_folder_cb, &atud);
762 
763 		camel_folder_thaw (unmatched_folder);
764 		g_mutex_unlock (&vstore->priv->vu_counts_mutex);
765 
766 		if (camel_folder_change_info_changed (atud.changes))
767 			camel_folder_changed (unmatched_folder, atud.changes);
768 		camel_folder_change_info_free (atud.changes);
769 	}
770 
771 	g_mutex_unlock (&vstore->priv->sf_counts_mutex);
772 }
773 
774 static void
remove_vuid_count_record_cb(CamelVeeMessageInfoData * mi_data,CamelFolder * subfolder,gpointer user_data)775 remove_vuid_count_record_cb (CamelVeeMessageInfoData *mi_data,
776                              CamelFolder *subfolder,
777                              gpointer user_data)
778 {
779 	GHashTable *vuid_usage_counts = user_data;
780 
781 	g_return_if_fail (mi_data != NULL);
782 	g_return_if_fail (user_data != NULL);
783 
784 	g_hash_table_remove (vuid_usage_counts, camel_vee_message_info_data_get_vee_message_uid (mi_data));
785 }
786 
787 /**
788  * camel_vee_store_note_subfolder_unused:
789  * @vstore: a #CamelVeeStore
790  * @subfolder: a #CamelFolder
791  * @unused_by: (type CamelVeeFolder): a #CamelVeeFolder
792  *
793  * This is a counter part of camel_vee_store_note_subfolder_used(). Once
794  * the @subfolder is claimed to be not used by all folders its message infos
795  * are removed from the Unmatched folder.
796  *
797  * Since: 3.6
798  **/
799 void
camel_vee_store_note_subfolder_unused(CamelVeeStore * vstore,CamelFolder * subfolder,CamelVeeFolder * unused_by)800 camel_vee_store_note_subfolder_unused (CamelVeeStore *vstore,
801                                        CamelFolder *subfolder,
802                                        CamelVeeFolder *unused_by)
803 {
804 	gint counts;
805 
806 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
807 	g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
808 	g_return_if_fail (CAMEL_IS_VEE_FOLDER (unused_by));
809 
810 	/* only real folders can be part of the unmatched folder */
811 	if (CAMEL_IS_VEE_FOLDER (subfolder) ||
812 	    unused_by == vstore->priv->unmatched_folder ||
813 	    camel_application_is_exiting)
814 		return;
815 
816 	g_mutex_lock (&vstore->priv->sf_counts_mutex);
817 
818 	counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder));
819 	g_return_if_fail (counts > 0);
820 
821 	counts--;
822 	if (counts == 0) {
823 		g_hash_table_remove (vstore->priv->subfolder_usage_counts, subfolder);
824 		if (camel_vee_store_get_unmatched_enabled (vstore))
825 			camel_vee_folder_remove_folder (vstore->priv->unmatched_folder, subfolder, NULL);
826 
827 		g_mutex_lock (&vstore->priv->vu_counts_mutex);
828 		camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder,
829 			remove_vuid_count_record_cb, vstore->priv->vuid_usage_counts);
830 		g_mutex_unlock (&vstore->priv->vu_counts_mutex);
831 
832 		camel_vee_data_cache_remove_subfolder (vstore->priv->vee_data_cache, subfolder);
833 	} else {
834 		g_hash_table_insert (
835 			vstore->priv->subfolder_usage_counts,
836 			subfolder, GINT_TO_POINTER (counts));
837 	}
838 
839 	g_mutex_unlock (&vstore->priv->sf_counts_mutex);
840 }
841 
842 /**
843  * camel_vee_store_note_vuid_used:
844  * @vstore: a #CamelVeeStore
845  * @mi_data: a #CamelVeeMessageInfoData
846  * @used_by: (type CamelVeeFolder): a #CamelVeeFolder
847  *
848  * Notes the @mi_data is used by the @used_by virtual folder, which
849  * removes it from the Unmatched folder, if not used anywhere else.
850  *
851  * Since: 3.6
852  **/
853 void
camel_vee_store_note_vuid_used(CamelVeeStore * vstore,CamelVeeMessageInfoData * mi_data,CamelVeeFolder * used_by)854 camel_vee_store_note_vuid_used (CamelVeeStore *vstore,
855                                 CamelVeeMessageInfoData *mi_data,
856                                 CamelVeeFolder *used_by)
857 {
858 	gint counts;
859 	const gchar *vuid;
860 	CamelFolder *subfolder;
861 	CamelVeeSubfolderData *sf_data;
862 
863 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
864 	g_return_if_fail (used_by != NULL);
865 	g_return_if_fail (mi_data != NULL);
866 
867 	/* these notifications are ignored from Unmatched folder */
868 	if (used_by == vstore->priv->unmatched_folder ||
869 	    camel_application_is_exiting)
870 		return;
871 
872 	/* unmatched folder holds only real folders */
873 	sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
874 	subfolder = camel_vee_subfolder_data_get_folder (sf_data);
875 	if (CAMEL_IS_VEE_FOLDER (subfolder))
876 		return;
877 
878 	g_mutex_lock (&vstore->priv->vu_counts_mutex);
879 
880 	vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
881 
882 	counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid));
883 	counts++;
884 	g_hash_table_insert (
885 		vstore->priv->vuid_usage_counts,
886 		(gpointer) camel_pstring_strdup (vuid),
887 		GINT_TO_POINTER (counts));
888 
889 	if (counts == 1 && camel_vee_store_get_unmatched_enabled (vstore)) {
890 		CamelFolderChangeInfo *changes;
891 
892 		changes = camel_folder_change_info_new ();
893 
894 		camel_vee_folder_remove_vuid (vstore->priv->unmatched_folder, mi_data, changes);
895 
896 		if (camel_folder_change_info_changed (changes))
897 			camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes);
898 		camel_folder_change_info_free (changes);
899 	}
900 
901 	g_mutex_unlock (&vstore->priv->vu_counts_mutex);
902 }
903 
904 static gboolean
vee_store_folder_contains_uid(CamelFolder * folder,const gchar * message_uid)905 vee_store_folder_contains_uid (CamelFolder *folder,
906 			       const gchar *message_uid)
907 {
908 	CamelFolderSummary *summary;
909 	gboolean contains;
910 
911 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
912 	g_return_val_if_fail (message_uid != NULL, FALSE);
913 
914 	summary = camel_folder_get_folder_summary (folder);
915 	if (!summary) {
916 		CamelMessageInfo *nfo;
917 
918 		nfo = camel_folder_get_message_info (folder, message_uid);
919 		contains = nfo != NULL;
920 
921 		g_clear_object (&nfo);
922 	} else {
923 		contains = camel_folder_summary_check_uid (summary, message_uid);
924 	}
925 
926 	return contains;
927 }
928 
929 /**
930  * camel_vee_store_note_vuid_unused:
931  * @vstore: a #CamelVeeStore
932  * @mi_data: a #CamelVeeMessageInfoData
933  * @unused_by: (type CamelVeeFolder): a #CamelVeeFolder
934  *
935  * A counter part of camel_vee_store_note_vuid_used(). Once the @unused_by
936  * claims the @mi_data is not used by it anymore, and neither any other
937  * virtual folder is using it, then the Unmatched folder will have it added.
938  *
939  * Since: 3.6
940  **/
941 void
camel_vee_store_note_vuid_unused(CamelVeeStore * vstore,CamelVeeMessageInfoData * mi_data,CamelVeeFolder * unused_by)942 camel_vee_store_note_vuid_unused (CamelVeeStore *vstore,
943                                   CamelVeeMessageInfoData *mi_data,
944                                   CamelVeeFolder *unused_by)
945 {
946 	gint counts;
947 	const gchar *vuid;
948 	CamelFolder *subfolder;
949 	CamelVeeSubfolderData *sf_data;
950 
951 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
952 	g_return_if_fail (unused_by != NULL);
953 	g_return_if_fail (mi_data != NULL);
954 
955 	/* these notifications are ignored from Unmatched folder */
956 	if (unused_by == vstore->priv->unmatched_folder ||
957 	    camel_application_is_exiting)
958 		return;
959 
960 	/* unmatched folder holds only real folders */
961 	sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
962 	subfolder = camel_vee_subfolder_data_get_folder (sf_data);
963 	if (CAMEL_IS_VEE_FOLDER (subfolder))
964 		return;
965 
966 	g_mutex_lock (&vstore->priv->vu_counts_mutex);
967 
968 	vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
969 
970 	counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid));
971 	counts--;
972 	if (counts < 0) {
973 		g_mutex_unlock (&vstore->priv->vu_counts_mutex);
974 		g_return_if_fail (counts >= 0);
975 		return;
976 	}
977 
978 	g_hash_table_insert (
979 		vstore->priv->vuid_usage_counts,
980 		(gpointer) camel_pstring_strdup (vuid),
981 		GINT_TO_POINTER (counts));
982 
983 	if (counts == 0 &&
984 	    camel_vee_store_get_unmatched_enabled (vstore) &&
985 	    vee_store_folder_contains_uid (subfolder, camel_vee_message_info_data_get_orig_message_uid (mi_data))) {
986 		CamelFolderChangeInfo *changes;
987 
988 		changes = camel_folder_change_info_new ();
989 
990 		camel_vee_folder_add_vuid (vstore->priv->unmatched_folder, mi_data, changes);
991 
992 		if (camel_folder_change_info_changed (changes))
993 			camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes);
994 		camel_folder_change_info_free (changes);
995 	}
996 
997 	g_mutex_unlock (&vstore->priv->vu_counts_mutex);
998 }
999 
1000 struct RebuildUnmatchedData {
1001 	CamelVeeDataCache *data_cache;
1002 	CamelVeeFolder *unmatched_folder;
1003 	CamelFolderChangeInfo *changes;
1004 	GCancellable *cancellable;
1005 };
1006 
1007 static void
rebuild_unmatched_folder_cb(gpointer key,gpointer value,gpointer user_data)1008 rebuild_unmatched_folder_cb (gpointer key,
1009                              gpointer value,
1010                              gpointer user_data)
1011 {
1012 	const gchar *vuid = key;
1013 	gint counts = GPOINTER_TO_INT (value);
1014 	struct RebuildUnmatchedData *rud = user_data;
1015 	CamelVeeSubfolderData *si_data;
1016 	CamelVeeMessageInfoData *mi_data;
1017 
1018 	g_return_if_fail (vuid != NULL);
1019 	g_return_if_fail (rud != NULL);
1020 
1021 	if (counts != 0 || g_cancellable_is_cancelled (rud->cancellable))
1022 		return;
1023 
1024 	mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (rud->data_cache, vuid);
1025 	if (!mi_data)
1026 		return;
1027 
1028 	si_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
1029 
1030 	camel_vee_folder_add_folder (rud->unmatched_folder, camel_vee_subfolder_data_get_folder (si_data), NULL);
1031 	camel_vee_folder_add_vuid (rud->unmatched_folder, mi_data, rud->changes);
1032 
1033 	g_object_unref (mi_data);
1034 }
1035 
1036 static void
vee_store_rebuild_unmatched_folder(CamelSession * session,GCancellable * cancellable,CamelVeeStore * vstore,GError ** error)1037 vee_store_rebuild_unmatched_folder (CamelSession *session,
1038                                     GCancellable *cancellable,
1039                                     CamelVeeStore *vstore,
1040                                     GError **error)
1041 {
1042 	struct RebuildUnmatchedData rud;
1043 	CamelVeeFolder *vunmatched;
1044 	CamelFolder *unmatched_folder;
1045 	CamelFolderChangeInfo *changes;
1046 
1047 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
1048 
1049 	vunmatched = camel_vee_store_get_unmatched_folder (vstore);
1050 	/* someone could disable it meanwhile */
1051 	if (!vunmatched)
1052 		return;
1053 
1054 	unmatched_folder = CAMEL_FOLDER (vunmatched);
1055 	g_return_if_fail (unmatched_folder != NULL);
1056 
1057 	camel_folder_freeze (unmatched_folder);
1058 
1059 	/* start from scratch, with empty folder */
1060 	camel_vee_folder_set_folders (vunmatched, NULL, cancellable);
1061 
1062 	changes = camel_folder_change_info_new ();
1063 
1064 	rud.data_cache = vstore->priv->vee_data_cache;
1065 	rud.unmatched_folder = vunmatched;
1066 	rud.changes = changes;
1067 	rud.cancellable = cancellable;
1068 
1069 	g_hash_table_foreach (vstore->priv->vuid_usage_counts, rebuild_unmatched_folder_cb, &rud);
1070 
1071 	camel_folder_thaw (unmatched_folder);
1072 
1073 	if (camel_folder_change_info_changed (changes))
1074 		camel_folder_changed (unmatched_folder, changes);
1075 	camel_folder_change_info_free (changes);
1076 
1077 	/* Just to mute CHECKED_RETURN warning */
1078 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
1079 		return;
1080 }
1081 
1082 /**
1083  * camel_vee_store_rebuild_unmatched_folder:
1084  * @vstore: a #CamelVeeStore
1085  * @cancellable: optional #GCancellable object, or %NULL
1086  * @error: return location for a #GError, or %NULL
1087  *
1088  * Let's the @vstore know to rebuild the Unmatched folder. This is done
1089  * as a separate job, when the @cancellable is %NULL, otherwise it's run
1090  * synchronously.
1091  *
1092  * Since: 3.6
1093  **/
1094 void
camel_vee_store_rebuild_unmatched_folder(CamelVeeStore * vstore,GCancellable * cancellable,GError ** error)1095 camel_vee_store_rebuild_unmatched_folder (CamelVeeStore *vstore,
1096                                           GCancellable *cancellable,
1097                                           GError **error)
1098 {
1099 	g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
1100 
1101 	/* this operation requires cancellable, thus if called
1102 	 * without it then run in a dedicated thread */
1103 	if (!cancellable) {
1104 		CamelService *service;
1105 		CamelSession *session;
1106 
1107 		service = CAMEL_SERVICE (vstore);
1108 		session = camel_service_ref_session (service);
1109 
1110 		if (session) {
1111 			camel_session_submit_job (
1112 				session, _("Updating Unmatched search folder"), (CamelSessionCallback)
1113 				vee_store_rebuild_unmatched_folder,
1114 				g_object_ref (vstore),
1115 				g_object_unref);
1116 
1117 			g_object_unref (session);
1118 		}
1119 	} else {
1120 		vee_store_rebuild_unmatched_folder (NULL, cancellable, vstore, error);
1121 	}
1122 }
1123