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