No issues found
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Michael Zucchi <notzed@ximian.com>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <string.h>
28
29 #include <glib/gi18n.h>
30
31 #include "libevolution-utils/e-alert-dialog.h"
32
33 #include "libemail-utils/mail-mt.h"
34 #include "libemail-engine/mail-folder-cache.h"
35 #include "libemail-engine/e-mail-folder-utils.h"
36 #include "libemail-engine/e-mail-session.h"
37 #include "libemail-engine/e-mail-utils.h"
38 #include "libemail-engine/mail-ops.h"
39 #include "libemail-engine/mail-tools.h"
40
41 #include <libemail-utils/em-vfolder-context.h>
42 #include <libemail-utils/em-vfolder-rule.h>
43 #include "mail-vfolder.h"
44
45 #define d(x) /* (printf("%s:%s: ", G_STRLOC, G_STRFUNC), (x))*/
46
47 /* Note: Once we completely move mail to EDS, this context wont be available for UI.
48 * and vfoldertypes.xml should be moved here really. */
49 EMVFolderContext *context; /* context remains open all time */
50
51 /* lock for accessing shared resources (below) */
52 G_LOCK_DEFINE_STATIC (vfolder);
53
54 static GHashTable *vfolder_hash;
55 /* This is a slightly hacky solution to shutting down, we poll this variable in various
56 * loops, and just quit processing if it is set. */
57 static volatile gint vfolder_shutdown; /* are we shutting down? */
58
59 static void rule_changed (EFilterRule *rule, CamelFolder *folder);
60
61 /* ********************************************************************** */
62
63 static GList *
64 vfolder_get_include_subfolders_uris (EMailSession *session,
65 const gchar *base_uri,
66 GCancellable *cancellable)
67 {
68 GList *uris = NULL;
69 CamelStore *store = NULL;
70 gchar *folder_name = NULL;
71 CamelFolderInfo *fi;
72 const CamelFolderInfo *cur;
73
74 g_return_val_if_fail (session != NULL, NULL);
75 g_return_val_if_fail (base_uri != NULL, NULL);
76 g_return_val_if_fail (*base_uri == '*', NULL);
77
78 if (!e_mail_folder_uri_parse (CAMEL_SESSION (session), base_uri + 1, &store, &folder_name, NULL))
79 return NULL;
80
81 fi = camel_store_get_folder_info_sync (
82 store, folder_name,
83 CAMEL_STORE_FOLDER_INFO_RECURSIVE, cancellable, NULL);
84 cur = fi;
85 while (cur) {
86 if ((cur->flags & CAMEL_FOLDER_NOSELECT) == 0) {
87 gchar *fi_uri = e_mail_folder_uri_build (store, cur->full_name);
88
89 if (fi_uri)
90 uris = g_list_prepend (uris, fi_uri);
91 }
92
93 /* move to the next fi */
94 if (cur->child) {
95 cur = cur->child;
96 } else if (cur->next) {
97 cur = cur->next;
98 } else {
99 while (cur && !cur->next) {
100 cur = cur->parent;
101 }
102
103 if (cur)
104 cur = cur->next;
105 }
106 }
107
108 if (fi)
109 camel_store_free_folder_info (store, fi);
110
111 g_object_unref (store);
112 g_free (folder_name);
113
114 return g_list_reverse (uris);
115 }
116
117 struct _setup_msg {
118 MailMsg base;
119
120 EMailSession *session;
121 CamelFolder *folder;
122 gchar *query;
123 GList *sources_uri;
124 };
125
126 static gchar *
127 vfolder_setup_desc (struct _setup_msg *m)
128 {
129 return g_strdup_printf (
130 _("Setting up Search Folder: %s"),
131 camel_folder_get_full_name (m->folder));
132 }
133
134 static void
135 vfolder_setup_exec (struct _setup_msg *m,
136 GCancellable *cancellable,
137 GError **error)
138 {
139 GList *l, *list = NULL;
140 CamelFolder *folder;
141
142 camel_vee_folder_set_expression ((CamelVeeFolder *) m->folder, m->query);
143
144 for (l = m->sources_uri;
145 l && !vfolder_shutdown && !g_cancellable_is_cancelled (cancellable);
146 l = l->next) {
147 const gchar *uri = l->data;
148
149 d (printf (" Adding uri: %s\n", uri));
150
151 if (!uri || !*uri || !uri[1])
152 continue;
153
154 if (*uri == '*') {
155 /* include folder and its subfolders */
156 GList *uris, *iter;
157
158 uris = vfolder_get_include_subfolders_uris (m->session, uri, cancellable);
159 for (iter = uris; iter; iter = iter->next) {
160 const gchar *fi_uri = iter->data;
161
162 folder = e_mail_session_uri_to_folder_sync (
163 m->session, fi_uri, 0, cancellable, NULL);
164 if (folder != NULL)
165 list = g_list_append (list, folder);
166 }
167
168 g_list_free_full (uris, g_free);
169 } else {
170 folder = e_mail_session_uri_to_folder_sync (m->session, l->data, 0, cancellable, NULL);
171 if (folder != NULL)
172 list = g_list_append (list, folder);
173 }
174 }
175
176 if (!vfolder_shutdown && !g_cancellable_is_cancelled (cancellable))
177 camel_vee_folder_set_folders ((CamelVeeFolder *) m->folder, list, cancellable);
178
179 g_list_free_full (list, g_object_unref);
180 }
181
182 static void
183 vfolder_setup_done (struct _setup_msg *m)
184 {
185 }
186
187 static void
188 vfolder_setup_free (struct _setup_msg *m)
189 {
190 camel_folder_thaw (m->folder);
191
192 g_object_unref (m->session);
193 g_object_unref (m->folder);
194 g_free (m->query);
195 g_list_free_full (m->sources_uri, g_free);
196 }
197
198 static MailMsgInfo vfolder_setup_info = {
199 sizeof (struct _setup_msg),
200 (MailMsgDescFunc) vfolder_setup_desc,
201 (MailMsgExecFunc) vfolder_setup_exec,
202 (MailMsgDoneFunc) vfolder_setup_done,
203 (MailMsgFreeFunc) vfolder_setup_free
204 };
205
206 /* sources_uri should be camel uri's */
207 static gint
208 vfolder_setup (EMailSession *session,
209 CamelFolder *folder,
210 const gchar *query,
211 GList *sources_uri)
212 {
213 struct _setup_msg *m;
214 gint id;
215
216 m = mail_msg_new (&vfolder_setup_info);
217 m->session = g_object_ref (session);
218 m->folder = g_object_ref (folder);
219 m->query = g_strdup (query);
220 m->sources_uri = sources_uri;
221
222 camel_folder_freeze (m->folder);
223
224 id = m->base.seq;
225 mail_msg_slow_ordered_push (m);
226
227 return id;
228 }
229
230 /* ********************************************************************** */
231
232 static void
233 vfolder_add_remove_one (GList *vfolders,
234 gboolean remove,
235 CamelFolder *folder,
236 GCancellable *cancellable)
237 {
238 GList *iter;
239
240 for (iter = vfolders; iter && !vfolder_shutdown; iter = iter->next) {
241 CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (iter->data);
242
243 if (!vfolder)
244 continue;
245
246 if (remove)
247 camel_vee_folder_remove_folder (vfolder, folder, cancellable);
248 else
249 camel_vee_folder_add_folder (vfolder, folder, cancellable);
250 }
251 }
252
253 struct _adduri_msg {
254 MailMsg base;
255
256 EMailSession *session;
257 gchar *uri;
258 GList *folders;
259 gint remove;
260 };
261
262 static gchar *
263 vfolder_adduri_desc (struct _adduri_msg *m)
264 {
265 CamelStore *store;
266 CamelService *service;
267 const gchar *display_name;
268 gchar *folder_name;
269 gchar *description;
270 gboolean success;
271
272 success = e_mail_folder_uri_parse (
273 CAMEL_SESSION (m->session), m->uri,
274 &store, &folder_name, NULL);
275
276 if (!success)
277 return NULL;
278
279 service = CAMEL_SERVICE (store);
280 display_name = camel_service_get_display_name (service);
281
282 description = g_strdup_printf (
283 _("Updating Search Folders for '%s' - %s"),
284 display_name, folder_name);
285
286 g_object_unref (store);
287 g_free (folder_name);
288
289 return description;
290 }
291
292 static void
293 vfolder_adduri_exec (struct _adduri_msg *m,
294 GCancellable *cancellable,
295 GError **error)
296 {
297 CamelFolder *folder = NULL;
298 MailFolderCache *folder_cache;
299
300 if (vfolder_shutdown)
301 return;
302
303 folder_cache = e_mail_session_get_folder_cache (m->session);
304
305 /* we dont try lookup the cache if we are removing it, its no longer there */
306
307 if (!m->remove &&
308 !mail_folder_cache_get_folder_from_uri (folder_cache, m->uri[0] == '*' ? m->uri + 1 : m->uri, NULL)) {
309 g_warning (
310 "Folder '%s' disappeared while I was "
311 "adding/removing it to/from my vfolder", m->uri);
312 return;
313 }
314
315 if (m->uri[0] == '*') {
316 GList *uris, *iter;
317
318 uris = vfolder_get_include_subfolders_uris (m->session, m->uri, cancellable);
319 for (iter = uris; iter; iter = iter->next) {
320 const gchar *fi_uri = iter->data;
321
322 folder = e_mail_session_uri_to_folder_sync (
323 m->session, fi_uri, 0, cancellable, NULL);
324 if (folder != NULL) {
325 vfolder_add_remove_one (m->folders, m->remove, folder, cancellable);
326 g_object_unref (folder);
327 }
328 }
329
330 g_list_free_full (uris, g_free);
331 } else {
332 /* always pick fresh folders - they are
333 * from CamelStore's folders bag anyway */
334 folder = e_mail_session_uri_to_folder_sync (
335 m->session, m->uri, 0, cancellable, error);
336
337 if (folder != NULL) {
338 vfolder_add_remove_one (m->folders, m->remove, folder, cancellable);
339 g_object_unref (folder);
340 }
341 }
342 }
343
344 static void
345 vfolder_adduri_done (struct _adduri_msg *m)
346 {
347 }
348
349 static void
350 vfolder_adduri_free (struct _adduri_msg *m)
351 {
352 g_object_unref (m->session);
353 g_list_foreach (m->folders, (GFunc) camel_folder_thaw, NULL);
354 g_list_free_full (m->folders, g_object_unref);
355 g_free (m->uri);
356 }
357
358 static MailMsgInfo vfolder_adduri_info = {
359 sizeof (struct _adduri_msg),
360 (MailMsgDescFunc) vfolder_adduri_desc,
361 (MailMsgExecFunc) vfolder_adduri_exec,
362 (MailMsgDoneFunc) vfolder_adduri_done,
363 (MailMsgFreeFunc) vfolder_adduri_free
364 };
365
366 /* uri should be a camel uri */
367 static gint
368 vfolder_adduri (EMailSession *session,
369 const gchar *uri,
370 GList *folders,
371 gint remove)
372 {
373 struct _adduri_msg *m;
374 gint id;
375
376 m = mail_msg_new (&vfolder_adduri_info);
377 m->session = g_object_ref (session);
378 m->folders = folders;
379 m->uri = g_strdup (uri);
380 m->remove = remove;
381
382 g_list_foreach (m->folders, (GFunc) camel_folder_freeze, NULL);
383
384 id = m->base.seq;
385 mail_msg_slow_ordered_push (m);
386
387 return id;
388 }
389
390 /* ********************************************************************** */
391
392 /* so special we never use it */
393 static gint
394 folder_is_spethal (CamelStore *store,
395 const gchar *folder_name)
396 {
397 /* This is a bit of a hack, but really the only way it can be done
398 * at the moment. */
399
400 if (store->flags & CAMEL_STORE_VTRASH)
401 if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0)
402 return TRUE;
403
404 if (store->flags & CAMEL_STORE_VJUNK)
405 if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0)
406 return TRUE;
407
408 return FALSE;
409 }
410
411 /**
412 * mail_vfolder_add_folder:
413 * @store: a #CamelStore
414 * @folder: a folder name
415 * @remove: whether the folder should be removed or added
416 *
417 * Called when a new folder becomes (un)available. If @store is not a
418 * CamelVeeStore, the folder is added/removed from the list of cached source
419 * folders. Then each vfolder rule is checked to see if the specified folder
420 * matches a source of the rule. It builds a list of vfolders that use (or
421 * would use) the specified folder as a source. It then adds (or removes)
422 * this folder to (from) those vfolders via camel_vee_folder_add/
423 * remove_folder() but does not modify the actual filters or write changes
424 * to disk.
425 *
426 * NOTE: This function must be called from the main thread.
427 */
428 static void
429 mail_vfolder_add_folder (CamelStore *store,
430 const gchar *folder_name,
431 gint remove)
432 {
433 CamelService *service;
434 CamelSession *session;
435 EFilterRule *rule;
436 EMVFolderRule *vrule;
437 const gchar *source;
438 CamelVeeFolder *vf;
439 CamelProvider *provider;
440 GList *folders = NULL, *folders_include_subfolders = NULL;
441 gint remote;
442 gchar *uri;
443
444 g_return_if_fail (CAMEL_IS_STORE (store));
445 g_return_if_fail (folder_name != NULL);
446
447 service = CAMEL_SERVICE (store);
448 session = camel_service_get_session (service);
449 provider = camel_service_get_provider (service);
450
451 remote = (provider->flags & CAMEL_PROVIDER_IS_REMOTE) != 0;
452
453 if (folder_is_spethal (store, folder_name))
454 return;
455
456 g_return_if_fail (mail_in_main_thread ());
457
458 uri = e_mail_folder_uri_build (store, folder_name);
459
460 G_LOCK (vfolder);
461
462 if (context == NULL)
463 goto done;
464
465 rule = NULL;
466 while ((rule = e_rule_context_next_rule ((ERuleContext *) context, rule, NULL))) {
467 gint found = FALSE;
468
469 if (!rule->name) {
470 d (printf ("invalid rule (%p): rule->name is set to NULL\n", rule));
471 continue;
472 }
473
474 vrule = (EMVFolderRule *) rule;
475
476 /* Don't auto-add any sent/drafts folders etc,
477 * they must be explictly listed as a source. */
478 if (rule->source
479 && !CAMEL_IS_VEE_STORE (store)
480 && ((em_vfolder_rule_get_with (vrule) == EM_VFOLDER_RULE_WITH_LOCAL && !remote)
481 || (em_vfolder_rule_get_with (vrule) == EM_VFOLDER_RULE_WITH_REMOTE_ACTIVE && remote)
482 || (em_vfolder_rule_get_with (vrule) == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE)))
483 found = TRUE;
484
485 source = NULL;
486 while (!found && (source = em_vfolder_rule_next_source (vrule, source))) {
487 found = e_mail_folder_uri_equal (session, uri, source);
488 }
489
490 if (found) {
491 vf = g_hash_table_lookup (vfolder_hash, rule->name);
492 if (!vf) {
493 g_warning ("vf is NULL for %s\n", rule->name);
494 continue;
495 }
496 g_object_ref (vf);
497
498 if (em_vfolder_rule_source_get_include_subfolders (vrule, uri))
499 folders_include_subfolders = g_list_prepend (folders_include_subfolders, vf);
500 else
501 folders = g_list_prepend (folders, vf);
502 }
503 }
504
505 done:
506 G_UNLOCK (vfolder);
507
508 if (folders != NULL)
509 vfolder_adduri (
510 E_MAIL_SESSION (session),
511 uri, folders, remove);
512
513 if (folders_include_subfolders) {
514 gchar *exuri = g_strconcat ("*", uri, NULL);
515
516 vfolder_adduri (
517 E_MAIL_SESSION (session),
518 exuri, folders_include_subfolders, remove);
519
520 g_free (exuri);
521 }
522
523 g_free (uri);
524 }
525
526 /**
527 * mail_vfolder_delete_folder:
528 * @store: a #CamelStore
529 * @folder_name: a folder name
530 *
531 * Looks through all vfolder rules to see if @folder_name is listed as a
532 * source for any vfolder rules. If the folder is found in the source for
533 * any rule, it is removed and the user is alerted to the fact that the
534 * vfolder rules have been updated. The new vfolder rules are written
535 * to disk.
536 *
537 * XXX: It doesn't appear that the changes to the vfolder rules are sent
538 * down to the camel level, however. So the actual vfolders will not change
539 * behavior until evolution is restarted (?)
540 *
541 * NOTE: This function must be called from the main thread.
542 */
543 static void
544 mail_vfolder_delete_folder (CamelStore *store,
545 const gchar *folder_name)
546 {
547 ERuleContext *rule_context;
548 EFilterRule *rule;
549 CamelService *service;
550 CamelSession *session;
551 const gchar *source;
552 CamelVeeFolder *vf;
553 GString *changed;
554 guint changed_count;
555 gchar *uri;
556
557 g_return_if_fail (CAMEL_IS_STORE (store));
558 g_return_if_fail (folder_name != NULL);
559
560 if (folder_is_spethal (store, folder_name))
561 return;
562
563 d (printf ("Deleting uri to check: %s\n", uri));
564
565 g_return_if_fail (mail_in_main_thread ());
566
567 service = CAMEL_SERVICE (store);
568 session = camel_service_get_session (service);
569
570 uri = e_mail_folder_uri_build (store, folder_name);
571
572 changed_count = 0;
573 changed = g_string_new ("");
574
575 G_LOCK (vfolder);
576
577 if (context == NULL)
578 goto done;
579
580 rule_context = E_RULE_CONTEXT (context);
581
582 /* see if any rules directly reference this removed uri */
583 rule = NULL;
584 while ((rule = e_rule_context_next_rule (rule_context, rule, NULL))) {
585 EMVFolderRule *vf_rule = EM_VFOLDER_RULE (rule);
586
587 if (!rule->name)
588 continue;
589
590 source = NULL;
591 while ((source = em_vfolder_rule_next_source (vf_rule, source))) {
592 /* Remove all sources that match, ignore changed events though
593 * because the adduri call above does the work async */
594 if (e_mail_folder_uri_equal (session, uri, source)) {
595 vf = g_hash_table_lookup (
596 vfolder_hash, rule->name);
597
598 if (!vf) {
599 g_warning ("vf is NULL for %s\n", rule->name);
600 continue;
601 }
602
603 g_signal_handlers_disconnect_matched (
604 rule, G_SIGNAL_MATCH_FUNC |
605 G_SIGNAL_MATCH_DATA, 0, 0, NULL,
606 rule_changed, vf);
607
608 em_vfolder_rule_remove_source (vf_rule, source);
609
610 g_signal_connect (
611 rule, "changed",
612 G_CALLBACK (rule_changed), vf);
613
614 if (changed_count == 0) {
615 g_string_append (changed, rule->name);
616 } else {
617 if (changed_count == 1) {
618 g_string_prepend (changed, " ");
619 g_string_append (changed, "\n");
620 }
621 g_string_append_printf (
622 changed, " %s\n",
623 rule->name);
624 }
625
626 changed_count++;
627 source = NULL;
628 }
629 }
630 }
631
632 done:
633 G_UNLOCK (vfolder);
634
635 if (changed_count > 0) {
636 EAlertSink *alert_sink;
637 const gchar *config_dir;
638 gchar *user, *info;
639
640 alert_sink = mail_msg_get_alert_sink ();
641
642 info = g_strdup_printf (ngettext (
643 /* Translators: The first %s is name of the affected
644 * search folder(s), the second %s is the URI of the
645 * removed folder. For more than one search folder is
646 * each of them on a separate line, with four spaces
647 * in front of its name, without quotes. */
648 "The Search Folder \"%s\" has been modified to "
649 "account for the deleted folder\n\"%s\".",
650 "The following Search Folders\n%s have been modified "
651 "to account for the deleted folder\n\"%s\".",
652 changed_count), changed->str, uri);
653 e_alert_submit (
654 alert_sink, "mail:vfolder-updated", info, NULL);
655 g_free (info);
656
657 config_dir = mail_session_get_config_dir ();
658 user = g_build_filename (config_dir, "vfolders.xml", NULL);
659 e_rule_context_save ((ERuleContext *) context, user);
660 g_free (user);
661 }
662
663 g_string_free (changed, TRUE);
664
665 g_free (uri);
666 }
667
668 /* called when a uri is renamed in a store */
669 static void
670 mail_vfolder_rename_folder (CamelStore *store,
671 const gchar *old_folder_name,
672 const gchar *new_folder_name)
673 {
674 ERuleContext *rule_context;
675 EFilterRule *rule;
676 const gchar *source;
677 CamelVeeFolder *vf;
678 CamelService *service;
679 CamelSession *session;
680 gint changed = 0;
681 gchar *old_uri;
682 gchar *new_uri;
683
684 d (printf ("vfolder rename uri: %s to %s\n", cfrom, cto));
685
686 if (context == NULL)
687 return;
688
689 if (folder_is_spethal (store, old_folder_name))
690 return;
691
692 if (folder_is_spethal (store, new_folder_name))
693 return;
694
695 g_return_if_fail (mail_in_main_thread ());
696
697 service = CAMEL_SERVICE (store);
698 session = camel_service_get_session (service);
699
700 old_uri = e_mail_folder_uri_build (store, old_folder_name);
701 new_uri = e_mail_folder_uri_build (store, new_folder_name);
702
703 G_LOCK (vfolder);
704
705 rule_context = E_RULE_CONTEXT (context);
706
707 /* see if any rules directly reference this removed uri */
708 rule = NULL;
709 while ((rule = e_rule_context_next_rule (rule_context, rule, NULL))) {
710 EMVFolderRule *vf_rule = EM_VFOLDER_RULE (rule);
711
712 source = NULL;
713 while ((source = em_vfolder_rule_next_source (vf_rule, source))) {
714 /* Remove all sources that match, ignore changed events though
715 * because the adduri call above does the work async */
716 if (e_mail_folder_uri_equal (session, old_uri, source)) {
717 vf = g_hash_table_lookup (vfolder_hash, rule->name);
718 if (!vf) {
719 g_warning ("vf is NULL for %s\n", rule->name);
720 continue;
721 }
722
723 g_signal_handlers_disconnect_matched (
724 rule, G_SIGNAL_MATCH_FUNC |
725 G_SIGNAL_MATCH_DATA, 0, 0, NULL,
726 rule_changed, vf);
727
728 em_vfolder_rule_remove_source (vf_rule, source);
729 em_vfolder_rule_add_source (vf_rule, new_uri);
730
731 g_signal_connect (
732 vf_rule, "changed",
733 G_CALLBACK (rule_changed), vf);
734
735 changed++;
736 source = NULL;
737 }
738 }
739 }
740
741 G_UNLOCK (vfolder);
742
743 if (changed) {
744 const gchar *config_dir;
745 gchar *user;
746
747 d (printf ("Vfolders updated from renamed folder\n"));
748 config_dir = mail_session_get_config_dir ();
749 user = g_build_filename (config_dir, "vfolders.xml", NULL);
750 e_rule_context_save ((ERuleContext *) context, user);
751 g_free (user);
752 }
753
754 g_free (old_uri);
755 g_free (new_uri);
756 }
757
758 /* ********************************************************************** */
759
760 static void context_rule_added (ERuleContext *ctx, EFilterRule *rule, EMailSession *session);
761
762 static void
763 rule_add_sources (EMailSession *session,
764 GQueue *queue,
765 GList **sources_urip,
766 EMVFolderRule *rule)
767 {
768 GList *sources_uri = *sources_urip;
769 MailFolderCache *folder_cache;
770 GList *head, *link;
771
772 folder_cache = e_mail_session_get_folder_cache (session);
773
774 head = g_queue_peek_head_link (queue);
775 for (link = head; link != NULL; link = g_list_next (link)) {
776 const gchar *uri = link->data;
777
778 /* always pick fresh folders - they are
779 * from CamelStore's folders bag anyway */
780 if (mail_folder_cache_get_folder_from_uri (folder_cache, uri, NULL)) {
781 /* "tag" uris with subfolders with a star prefix */
782 if (!rule || !em_vfolder_rule_source_get_include_subfolders (rule, uri))
783 sources_uri = g_list_prepend (sources_uri, g_strdup (uri));
784 else
785 sources_uri = g_list_prepend (sources_uri, g_strconcat ("*", uri, NULL));
786 }
787 }
788
789 *sources_urip = sources_uri;
790 }
791
792 static EMailSession *
793 get_session (CamelFolder *folder)
794 {
795 CamelStore *store;
796
797 store = camel_folder_get_parent_store (folder);
798
799 return (EMailSession *) camel_service_get_session (CAMEL_SERVICE (store));
800 }
801
802 static void
803 rule_changed (EFilterRule *rule,
804 CamelFolder *folder)
805 {
806 EMailSession *session;
807 CamelService *service;
808 GList *sources_uri = NULL;
809 GString *query;
810 const gchar *full_name;
811
812 full_name = camel_folder_get_full_name (folder);
813 session = get_session (folder);
814
815 service = camel_session_ref_service (
816 CAMEL_SESSION (session), E_MAIL_SESSION_VFOLDER_UID);
817 g_return_if_fail (service != NULL);
818
819 /* If the folder has changed name, then
820 * add it, then remove the old manually. */
821 if (strcmp (full_name, rule->name) != 0) {
822 gchar *oldname;
823
824 gpointer key;
825 gpointer oldfolder;
826
827 G_LOCK (vfolder);
828 if (g_hash_table_lookup_extended (
829 vfolder_hash, full_name, &key, &oldfolder)) {
830 g_hash_table_remove (vfolder_hash, key);
831 g_free (key);
832 g_hash_table_insert (
833 vfolder_hash, g_strdup (rule->name), folder);
834 G_UNLOCK (vfolder);
835 } else {
836 G_UNLOCK (vfolder);
837 g_warning (
838 "couldn't find a vfolder rule "
839 "in our table? %s", full_name);
840 }
841
842 oldname = g_strdup (full_name);
843 /* FIXME Not passing a GCancellable or GError. */
844 camel_store_rename_folder_sync (
845 CAMEL_STORE (service),
846 oldname, rule->name, NULL, NULL);
847 g_free (oldname);
848 }
849
850 g_object_unref (service);
851 service = NULL;
852
853 d (printf ("Filter rule changed? for folder '%s'!!\n", folder->name));
854
855 camel_vee_folder_set_auto_update (
856 CAMEL_VEE_FOLDER (folder),
857 em_vfolder_rule_get_autoupdate ((EMVFolderRule *) rule));
858
859 if (em_vfolder_rule_get_with ((EMVFolderRule *) rule) == EM_VFOLDER_RULE_WITH_SPECIFIC) {
860 /* find any (currently available) folders, and add them to the ones to open */
861 rule_add_sources (
862 session, em_vfolder_rule_get_sources ((EMVFolderRule *) rule),
863 &sources_uri, (EMVFolderRule *) rule);
864 }
865
866 G_LOCK (vfolder);
867
868 if (em_vfolder_rule_get_with ((EMVFolderRule *) rule) == EM_VFOLDER_RULE_WITH_LOCAL ||
869 em_vfolder_rule_get_with ((EMVFolderRule *) rule) == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE) {
870
871 MailFolderCache *cache;
872 GQueue queue = G_QUEUE_INIT;
873
874 cache = e_mail_session_get_folder_cache (session);
875 mail_folder_cache_get_local_folder_uris (cache, &queue);
876
877 rule_add_sources (session, &queue, &sources_uri, NULL);
878
879 while (!g_queue_is_empty (&queue))
880 g_free (g_queue_pop_head (&queue));
881 }
882
883 if (em_vfolder_rule_get_with ((EMVFolderRule *) rule) == EM_VFOLDER_RULE_WITH_REMOTE_ACTIVE ||
884 em_vfolder_rule_get_with ((EMVFolderRule *) rule) == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE) {
885
886 MailFolderCache *cache;
887 GQueue queue = G_QUEUE_INIT;
888
889 cache = e_mail_session_get_folder_cache (session);
890 mail_folder_cache_get_remote_folder_uris (cache, &queue);
891
892 rule_add_sources (session, &queue, &sources_uri, NULL);
893
894 while (!g_queue_is_empty (&queue))
895 g_free (g_queue_pop_head (&queue));
896 }
897
898 G_UNLOCK (vfolder);
899
900 query = g_string_new ("");
901 e_filter_rule_build_code (rule, query);
902
903 vfolder_setup (session, folder, query->str, sources_uri);
904
905 g_string_free (query, TRUE);
906 }
907
908 static void
909 context_rule_added (ERuleContext *ctx,
910 EFilterRule *rule,
911 EMailSession *session)
912 {
913 CamelFolder *folder;
914 CamelService *service;
915
916 d (printf ("rule added: %s\n", rule->name));
917
918 service = camel_session_ref_service (
919 CAMEL_SESSION (session), E_MAIL_SESSION_VFOLDER_UID);
920 g_return_if_fail (service != NULL);
921
922 /* this always runs quickly */
923 /* FIXME Not passing a GCancellable or GError. */
924 folder = camel_store_get_folder_sync (
925 CAMEL_STORE (service), rule->name, 0, NULL, NULL);
926 if (folder) {
927 g_signal_connect (
928 rule, "changed",
929 G_CALLBACK (rule_changed), folder);
930
931 G_LOCK (vfolder);
932 g_hash_table_insert (vfolder_hash, g_strdup (rule->name), folder);
933 G_UNLOCK (vfolder);
934
935 rule_changed (rule, folder);
936 }
937
938 g_object_unref (service);
939 }
940
941 static void
942 context_rule_removed (ERuleContext *ctx,
943 EFilterRule *rule,
944 EMailSession *session)
945 {
946 CamelService *service;
947 gpointer key, folder = NULL;
948
949 d (printf ("rule removed; %s\n", rule->name));
950
951 service = camel_session_ref_service (
952 CAMEL_SESSION (session), E_MAIL_SESSION_VFOLDER_UID);
953 g_return_if_fail (service != NULL);
954
955 /* TODO: remove from folder info cache? */
956
957 G_LOCK (vfolder);
958 if (g_hash_table_lookup_extended (vfolder_hash, rule->name, &key, &folder)) {
959 g_hash_table_remove (vfolder_hash, key);
960 g_free (key);
961 }
962 G_UNLOCK (vfolder);
963
964 /* FIXME Not passing a GCancellable or GError. */
965 camel_store_delete_folder_sync (
966 CAMEL_STORE (service), rule->name, NULL, NULL);
967 /* this must be unref'd after its deleted */
968 if (folder)
969 g_object_unref ((CamelFolder *) folder);
970
971 g_object_unref (service);
972 }
973
974 static void
975 store_folder_deleted_cb (CamelStore *store,
976 CamelFolderInfo *info)
977 {
978 EFilterRule *rule;
979 gchar *user;
980
981 d (printf ("Folder deleted: %s\n", info->name));
982
983 /* Unmatched folder doesn't have any rule */
984 if (g_strcmp0 (CAMEL_UNMATCHED_NAME, info->full_name) == 0)
985 return;
986
987 /* Warning not thread safe, but might be enough */
988 G_LOCK (vfolder);
989
990 /* delete it from our list */
991 rule = e_rule_context_find_rule ((ERuleContext *) context, info->full_name, NULL);
992 if (rule) {
993 const gchar *config_dir;
994 EMailSession *session = E_MAIL_SESSION (camel_service_get_session (CAMEL_SERVICE (store)));
995
996 /* We need to stop listening to removed events,
997 * otherwise we'll try and remove it again. */
998 g_signal_handlers_disconnect_matched (
999 context, G_SIGNAL_MATCH_FUNC,
1000 0, 0, NULL, context_rule_removed, NULL);
1001 e_rule_context_remove_rule ((ERuleContext *) context, rule);
1002 g_object_unref (rule);
1003 g_signal_connect (
1004 context, "rule_removed",
1005 G_CALLBACK (context_rule_removed), session);
1006
1007 config_dir = mail_session_get_config_dir ();
1008 user = g_build_filename (config_dir, "vfolders.xml", NULL);
1009 e_rule_context_save ((ERuleContext *) context, user);
1010 g_free (user);
1011 } else {
1012 g_warning (
1013 "Cannot find rule for deleted vfolder '%s'",
1014 info->display_name);
1015 }
1016
1017 G_UNLOCK (vfolder);
1018 }
1019
1020 static void
1021 store_folder_renamed_cb (CamelStore *store,
1022 const gchar *old_name,
1023 CamelFolderInfo *info)
1024 {
1025 EFilterRule *rule;
1026 gchar *user;
1027
1028 gpointer key, folder;
1029
1030 /* This should be more-or-less thread-safe */
1031
1032 d (printf ("Folder renamed to '%s' from '%s'\n", info->full_name, old_name));
1033
1034 /* Folder is already renamed? */
1035 G_LOCK (vfolder);
1036 d (printf ("Changing folder name in hash table to '%s'\n", info->full_name));
1037 if (g_hash_table_lookup_extended (vfolder_hash, old_name, &key, &folder)) {
1038 const gchar *config_dir;
1039
1040 g_hash_table_remove (vfolder_hash, key);
1041 g_free (key);
1042 g_hash_table_insert (vfolder_hash, g_strdup (info->full_name), folder);
1043
1044 rule = e_rule_context_find_rule ((ERuleContext *) context, old_name, NULL);
1045 if (!rule) {
1046 G_UNLOCK (vfolder);
1047 g_warning ("Rule shouldn't be NULL\n");
1048 return;
1049 }
1050
1051 g_signal_handlers_disconnect_matched (
1052 rule, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
1053 0, 0, NULL, rule_changed, folder);
1054 e_filter_rule_set_name (rule, info->full_name);
1055 g_signal_connect (
1056 rule, "changed",
1057 G_CALLBACK (rule_changed), folder);
1058
1059 config_dir = mail_session_get_config_dir ();
1060 user = g_build_filename (config_dir, "vfolders.xml", NULL);
1061 e_rule_context_save ((ERuleContext *) context, user);
1062 g_free (user);
1063
1064 G_UNLOCK (vfolder);
1065 } else {
1066 G_UNLOCK (vfolder);
1067 g_warning ("couldn't find a vfolder rule in our table? %s", info->full_name);
1068 }
1069 }
1070
1071 static void
1072 folder_available_cb (MailFolderCache *cache,
1073 CamelStore *store,
1074 const gchar *folder_name)
1075 {
1076 mail_vfolder_add_folder (store, folder_name, FALSE);
1077 }
1078
1079 static void
1080 folder_unavailable_cb (MailFolderCache *cache,
1081 CamelStore *store,
1082 const gchar *folder_name)
1083 {
1084 mail_vfolder_add_folder (store, folder_name, TRUE);
1085 }
1086
1087 static void
1088 folder_deleted_cb (MailFolderCache *cache,
1089 CamelStore *store,
1090 const gchar *folder_name)
1091 {
1092 mail_vfolder_delete_folder (store, folder_name);
1093 }
1094
1095 static void
1096 folder_renamed_cb (MailFolderCache *cache,
1097 CamelStore *store,
1098 const gchar *old_folder_name,
1099 const gchar *new_folder_name,
1100 gpointer user_data)
1101 {
1102 mail_vfolder_rename_folder (store, old_folder_name, new_folder_name);
1103 }
1104
1105 void
1106 vfolder_load_storage (EMailSession *session)
1107 {
1108 /* lock for loading storage, it is safe to call it more than once */
1109 G_LOCK_DEFINE_STATIC (vfolder_hash);
1110
1111 CamelStore *vfolder_store;
1112 const gchar *config_dir;
1113 gchar *user;
1114 EFilterRule *rule;
1115 MailFolderCache *folder_cache;
1116 gchar *xmlfile;
1117
1118 G_LOCK (vfolder_hash);
1119
1120 if (vfolder_hash) {
1121 /* we have already initialized */
1122 G_UNLOCK (vfolder_hash);
1123 return;
1124 }
1125
1126 vfolder_hash = g_hash_table_new (g_str_hash, g_str_equal);
1127
1128 G_UNLOCK (vfolder_hash);
1129
1130 config_dir = mail_session_get_config_dir ();
1131 vfolder_store = e_mail_session_get_vfolder_store (session);
1132
1133 g_signal_connect (
1134 vfolder_store, "folder-deleted",
1135 G_CALLBACK (store_folder_deleted_cb), NULL);
1136
1137 g_signal_connect (
1138 vfolder_store, "folder-renamed",
1139 G_CALLBACK (store_folder_renamed_cb), NULL);
1140
1141 /* load our rules */
1142 user = g_build_filename (config_dir, "vfolders.xml", NULL);
1143 /* This needs editor context which is only in the mail/. But really to run here we dont need editor context.
1144 * So till we split this to EDS, we would let mail/ create this and later one it is any ways two separate
1145 * contexts. */
1146 context = e_mail_session_create_vfolder_context (session);
1147
1148 xmlfile = g_build_filename (EVOLUTION_PRIVDATADIR, "vfoldertypes.xml", NULL);
1149 if (e_rule_context_load ((ERuleContext *) context,
1150 xmlfile, user) != 0) {
1151 g_warning ("cannot load vfolders: %s\n", ((ERuleContext *) context)->error);
1152 }
1153 g_free (xmlfile);
1154 g_free (user);
1155
1156 g_signal_connect (
1157 context, "rule_added",
1158 G_CALLBACK (context_rule_added), session);
1159 g_signal_connect (
1160 context, "rule_removed",
1161 G_CALLBACK (context_rule_removed), session);
1162
1163 /* and setup the rules we have */
1164 rule = NULL;
1165 while ((rule = e_rule_context_next_rule ((ERuleContext *) context, rule, NULL))) {
1166 if (rule->name) {
1167 d (printf ("rule added: %s\n", rule->name));
1168 context_rule_added ((ERuleContext *) context, rule, session);
1169 } else {
1170 d (printf ("invalid rule (%p) encountered: rule->name is NULL\n", rule));
1171 }
1172 }
1173
1174 folder_cache = e_mail_session_get_folder_cache (session);
1175
1176 g_signal_connect (
1177 folder_cache, "folder-available",
1178 G_CALLBACK (folder_available_cb), NULL);
1179 g_signal_connect (
1180 folder_cache, "folder-unavailable",
1181 G_CALLBACK (folder_unavailable_cb), NULL);
1182 g_signal_connect (
1183 folder_cache, "folder-deleted",
1184 G_CALLBACK (folder_deleted_cb), NULL);
1185 g_signal_connect (
1186 folder_cache, "folder-renamed",
1187 G_CALLBACK (folder_renamed_cb), NULL);
1188 }
1189
1190 static void
1191 vfolder_foreach_cb (gpointer key,
1192 gpointer data,
1193 gpointer user_data)
1194 {
1195 CamelFolder *folder = CAMEL_FOLDER (data);
1196
1197 if (folder)
1198 g_object_unref (folder);
1199
1200 g_free (key);
1201 }
1202
1203 void
1204 mail_vfolder_shutdown (void)
1205 {
1206 vfolder_shutdown = 1;
1207
1208 if (vfolder_hash) {
1209 g_hash_table_foreach (vfolder_hash, vfolder_foreach_cb, NULL);
1210 g_hash_table_destroy (vfolder_hash);
1211 vfolder_hash = NULL;
1212 }
1213
1214 if (context) {
1215 g_object_unref (context);
1216 context = NULL;
1217 }
1218 }