Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-source-group.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-source-group.c
3  *
4  * Copyright (C) 2003  Ximian, Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU Lesser General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Ettore Perazzoli <ettore@ximian.com>
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <string.h>
28 #include "e-uid.h"
29 #include "e-source-group.h"
30
31 #define XC (const xmlChar *)
32 #define GC (const gchar *)
33
34 /* Private members.  */
35
36 struct _ESourceGroupPrivate {
37         char *uid;
38         char *name;
39         char *base_uri;
40
41         GSList *sources;
42
43         gboolean ignore_source_changed;
44         gboolean readonly;
45
46         GHashTable *properties;
47 };
48
49
50 /* Signals.  */
51
52 enum {
53         CHANGED,
54         SOURCE_REMOVED,
55         SOURCE_ADDED,
56         LAST_SIGNAL
57 };
58 static unsigned int signals[LAST_SIGNAL] = { 0 };
59
60
61 /* Callbacks.  */
62
63 static void
64 source_changed_callback (ESource *source,
65                          ESourceGroup *group)
66 {
67         if (! group->priv->ignore_source_changed)
68                 g_signal_emit (group, signals[CHANGED], 0);
69 }
70
71
72 /* GObject methods.  */
73
74 G_DEFINE_TYPE (ESourceGroup, e_source_group, G_TYPE_OBJECT);
75
76 static void
77 impl_dispose (GObject *object)
78 {
79         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
80
81         if (priv->sources != NULL) {
82                 GSList *p;
83
84                 for (p = priv->sources; p != NULL; p = p->next) {
85                         ESource *source = E_SOURCE (p->data);
86
87                         g_signal_handlers_disconnect_by_func (source,
88                                                               G_CALLBACK (source_changed_callback),
89                                                               object);
90                         g_object_unref (source);
91                 }
92
93                 g_slist_free (priv->sources);
94                 priv->sources = NULL;
95         }
96
97         (* G_OBJECT_CLASS (e_source_group_parent_class)->dispose) (object);
98 }
99
100 static void
101 impl_finalize (GObject *object)
102 {
103         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
104
105         g_free (priv->uid);
106         g_free (priv->name);
107         g_free (priv->base_uri);
108
109         g_hash_table_destroy (priv->properties);
110
111         g_free (priv);
112
113         (* G_OBJECT_CLASS (e_source_group_parent_class)->finalize) (object);
114 }
115
116
117 /* Initialization.  */
118
119 static void
120 e_source_group_class_init (ESourceGroupClass *class)
121 {
122         GObjectClass *object_class = G_OBJECT_CLASS (class);
123
124         object_class->dispose  = impl_dispose;
125         object_class->finalize = impl_finalize;
126
127         signals[CHANGED] = 
128                 g_signal_new ("changed",
129                               G_OBJECT_CLASS_TYPE (object_class),
130                               G_SIGNAL_RUN_LAST,
131                               G_STRUCT_OFFSET (ESourceGroupClass, changed),
132                               NULL, NULL,
133                               g_cclosure_marshal_VOID__VOID,
134                               G_TYPE_NONE, 0);
135
136         signals[SOURCE_ADDED] = 
137                 g_signal_new ("source_added",
138                               G_OBJECT_CLASS_TYPE (object_class),
139                               G_SIGNAL_RUN_LAST,
140                               G_STRUCT_OFFSET (ESourceGroupClass, source_added),
141                               NULL, NULL,
142                               g_cclosure_marshal_VOID__OBJECT,
143                               G_TYPE_NONE, 1,
144                               G_TYPE_OBJECT);
145         signals[SOURCE_REMOVED] = 
146                 g_signal_new ("source_removed",
147                               G_OBJECT_CLASS_TYPE (object_class),
148                               G_SIGNAL_RUN_LAST,
149                               G_STRUCT_OFFSET (ESourceGroupClass, source_removed),
150                               NULL, NULL,
151                               g_cclosure_marshal_VOID__OBJECT,
152                               G_TYPE_NONE, 1,
153                               G_TYPE_OBJECT);
154 }
155
156 static void
157 e_source_group_init (ESourceGroup *source_group)
158 {
159         ESourceGroupPrivate *priv;
160
161         priv = g_new0 (ESourceGroupPrivate, 1);
162         source_group->priv = priv;
163
164         priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
165                                                   g_free, g_free);
166 }
167
168 static void
169 import_properties (ESourceGroup *source_group,
170                    xmlNodePtr prop_root)
171 {
172         ESourceGroupPrivate *priv = source_group->priv;
173         xmlNodePtr prop_node;
174
175         for (prop_node = prop_root->children; prop_node; prop_node = prop_node->next) {
176                 xmlChar *name, *value;
177
178                 if (!prop_node->name || strcmp (GC prop_node->name, "property"))
179                         continue;
180
181                 name = xmlGetProp (prop_node, XC "name");
182                 value = xmlGetProp (prop_node, XC "value");
183
184                 if (name && value) {
185                         g_hash_table_insert (priv->properties, g_strdup (GC name), g_strdup (GC value));
186                 }
187
188                 if (name)
189                         xmlFree (name);
190                 if (value)
191                         xmlFree (value);
192         }
193 }
194
195 typedef struct
196 {
197         gboolean equal;
198         GHashTable *table2;
199 } hash_compare_data;
200
201 static void
202 compare_str_hash (gpointer key, gpointer value, hash_compare_data *cd)
203 {
204         gpointer value2 = g_hash_table_lookup (cd->table2, key);
205         if (value2 == NULL || g_str_equal (value, value2) == FALSE)
206                 cd->equal = FALSE;
207 }
208
209 static gboolean
210 compare_str_hashes (GHashTable *table1, GHashTable *table2)
211 {
212         hash_compare_data cd;
213
214         if (g_hash_table_size (table1) != g_hash_table_size (table2))
215                 return FALSE;
216
217         cd.equal = TRUE;
218         cd.table2 = table2;
219         g_hash_table_foreach (table1, (GHFunc) compare_str_hash, &cd);
220         return cd.equal;
221 }
222
223 static void
224 property_dump_cb (const gchar *key, const gchar *value, xmlNodePtr root)
225 {
226         xmlNodePtr node;
227
228         node = xmlNewChild (root, NULL, XC "property", NULL);
229         xmlSetProp (node, XC "name", XC key);
230         xmlSetProp (node, XC "value", XC value);
231 }
232
233 /* Public methods.  */
234
235 ESourceGroup *
236 e_source_group_new (const char *name,
237                     const char *base_uri)
238 {
239         ESourceGroup *new;
240
241         g_return_val_if_fail (name != NULL, NULL);
242         g_return_val_if_fail (base_uri != NULL, NULL);
243
244         new = g_object_new (e_source_group_get_type (), NULL);
245         new->priv->uid = e_uid_new ();
246
247         e_source_group_set_name (new, name);
248         e_source_group_set_base_uri (new, base_uri);
249
250         return new;
251 }
252
253 ESourceGroup *
254 e_source_group_new_from_xml (const char *xml)
255 {
256         xmlDocPtr doc;
257         ESourceGroup *group;
258
259         doc = xmlParseDoc (XC xml);
260         if (doc == NULL)
261                 return NULL;
262
263         group = e_source_group_new_from_xmldoc (doc);
264         xmlFreeDoc (doc);
265
266         return group;
267 }
268
269 ESourceGroup *
270 e_source_group_new_from_xmldoc (xmlDocPtr doc)
271 {
272         xmlNodePtr root, p;
273         xmlChar *uid;
274         xmlChar *name;
275         xmlChar *base_uri;
276         xmlChar *readonly_str;
277         ESourceGroup *new = NULL;
278
279         g_return_val_if_fail (doc != NULL, NULL);
280
281         root = doc->children;
282         if (strcmp (GC root->name, "group") != 0)
283                 return NULL;
284
285         uid = xmlGetProp (root, XC "uid");
286         name = xmlGetProp (root, XC "name");
287         base_uri = xmlGetProp (root, XC "base_uri");
288         readonly_str = xmlGetProp (root, XC "readonly");
289
290         if (uid == NULL || name == NULL || base_uri == NULL)
291                 goto done;
292
293         new = g_object_new (e_source_group_get_type (), NULL);
294
295         if (!new)
296                 goto done;
297
298         new->priv->uid = g_strdup (GC uid);
299
300         e_source_group_set_name (new, GC name);
301         e_source_group_set_base_uri (new, GC base_uri);
302         
303         for (p = root->children; p != NULL; p = p->next) {
304                 ESource *new_source;
305
306                 if (p->name && !strcmp (GC p->name, "properties")) {
307                         import_properties (new, p);
308                         continue;
309                 }
310
311                 new_source = e_source_new_from_xml_node (p);
312
313                 if (new_source == NULL) {
314                         g_object_unref (new);
315                         new = NULL;
316                         goto done;
317                 }
318                 e_source_group_add_source (new, new_source, -1);
319         }
320
321         e_source_group_set_readonly (new, readonly_str && !strcmp (GC readonly_str, "yes"));
322
323  done:
324         if (uid != NULL)
325                 xmlFree (uid);
326
327         if (name != NULL)
328                 xmlFree (name);
329         if (base_uri != NULL)
330                 xmlFree (base_uri);
331         if (readonly_str != NULL)
332                 xmlFree (readonly_str);
333         return new;
334 }
335
336 gboolean
337 e_source_group_update_from_xml (ESourceGroup *group,
338                                 const char *xml,
339                                 gboolean *changed_return)
340 {
341         xmlDocPtr xmldoc;
342         gboolean success;
343
344         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
345         g_return_val_if_fail (xml != NULL, FALSE);
346
347         
348         xmldoc = xmlParseDoc (XC xml);
349
350         success = e_source_group_update_from_xmldoc (group, xmldoc, changed_return);
351
352         xmlFreeDoc (xmldoc);
353
354         return success;
355 }
356
357 gboolean
358 e_source_group_update_from_xmldoc (ESourceGroup *group,
359                                    xmlDocPtr doc,
360                                    gboolean *changed_return)
361 {
362         GHashTable *new_sources_hash;
363         GSList *new_sources_list = NULL;
364         xmlNodePtr root, nodep;
365         xmlChar *name, *base_uri, *readonly_str;
366         gboolean readonly;
367         gboolean changed = FALSE;
368         GSList *p, *q;
369
370         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
371         g_return_val_if_fail (doc != NULL, FALSE);
372
373         *changed_return = FALSE;
374
375         root = doc->children;
376         if (strcmp (GC root->name, "group") != 0)
377                 return FALSE;
378
379         name = xmlGetProp (root, XC "name");
380         if (name == NULL)
381                 return FALSE;
382
383         base_uri = xmlGetProp (root, XC "base_uri");
384         if (base_uri == NULL) {
385                 xmlFree (name);
386                 return FALSE;
387         }
388
389         if (strcmp (group->priv->name, GC name) != 0) {
390                 g_free (group->priv->name);
391                 group->priv->name = g_strdup (GC name);
392                 changed = TRUE;
393         }
394         xmlFree (name);
395
396         if (strcmp (group->priv->base_uri, GC base_uri) != 0) {
397                 g_free (group->priv->base_uri);
398                 group->priv->base_uri = g_strdup (GC base_uri);
399                 changed = TRUE;
400         }
401         xmlFree (base_uri);
402
403         readonly_str = xmlGetProp (root, XC "readonly");
404         readonly = readonly_str && !strcmp (GC readonly_str, "yes");
405         if (readonly != group->priv->readonly) {
406                 group->priv->readonly = readonly;
407                 changed = TRUE;
408         }
409         xmlFree (readonly_str);
410
411         if (g_hash_table_size (group->priv->properties) && !root->children) {
412                 g_hash_table_destroy (group->priv->properties);
413                 group->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
414                                                                   g_free, g_free);
415                 changed = TRUE;
416         }
417         
418         new_sources_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
419
420         for (nodep = root->children; nodep != NULL; nodep = nodep->next) {
421                 ESource *existing_source;
422                 char *uid;
423
424                 if (!nodep->name)
425                         continue;
426
427                 if (!strcmp (GC nodep->name, "properties")) {
428                         GHashTable *temp = group->priv->properties;
429                         group->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
430                                                                           g_free, g_free);
431                         import_properties (group, nodep);
432                         if (!compare_str_hashes (temp, group->priv->properties))
433                                 changed = TRUE;
434                         g_hash_table_destroy (temp);
435                         continue;
436                 }
437
438                 uid = e_source_uid_from_xml_node (nodep);
439                 if (uid == NULL)
440                         continue;
441
442                 existing_source = e_source_group_peek_source_by_uid (group, uid);
443                 if (g_hash_table_lookup (new_sources_hash, existing_source) != NULL) {
444                         g_free (uid);
445                         continue;
446                 }
447
448                 if (existing_source == NULL) {
449                         ESource *new_source = e_source_new_from_xml_node (nodep);
450
451                         if (new_source != NULL) {
452                                 e_source_set_group (new_source, group);
453                                 g_signal_connect (new_source, "changed", G_CALLBACK (source_changed_callback), group);
454                                 new_sources_list = g_slist_prepend (new_sources_list, new_source);
455
456                                 g_hash_table_insert (new_sources_hash, new_source, new_source);
457
458                                 g_signal_emit (group, signals[SOURCE_ADDED], 0, new_source);
459                                 changed = TRUE;
460                         }
461                 } else {
462                         gboolean source_changed;
463
464                         group->priv->ignore_source_changed ++;
465
466                         if (e_source_update_from_xml_node (existing_source, nodep, &source_changed)) {
467                                 new_sources_list = g_slist_prepend (new_sources_list, existing_source);
468                                 g_object_ref (existing_source);
469                                 g_hash_table_insert (new_sources_hash, existing_source, existing_source);
470
471                                 if (source_changed)
472                                         changed = TRUE;
473                         }
474
475                         group->priv->ignore_source_changed --;
476                 }
477
478                 g_free (uid);
479         }
480
481         new_sources_list = g_slist_reverse (new_sources_list);
482
483         /* Emit "group_removed" and disconnect the "changed" signal for all the
484            groups that we haven't found in the new list.  */
485         q = new_sources_list;
486         for (p = group->priv->sources; p != NULL; p = p->next) {
487                 ESource *source = E_SOURCE (p->data);
488
489                 if (g_hash_table_lookup (new_sources_hash, source) == NULL) {
490                         changed = TRUE;
491
492                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
493                         g_signal_handlers_disconnect_by_func (source, source_changed_callback, group);
494                 }
495
496                 if (! changed && q != NULL) {
497                         if (q->data != p->data)
498                                 changed = TRUE;
499                         q = q->next;
500                 }
501         }
502
503         g_hash_table_destroy (new_sources_hash);
504
505         /* Replace the original group list with the new one.  */
506         g_slist_foreach (group->priv->sources, (GFunc) g_object_unref, NULL);
507         g_slist_free (group->priv->sources);
508
509         group->priv->sources = new_sources_list;
510
511         /* FIXME if the order changes, the function doesn't notice.  */
512
513         if (changed) {
514                 g_signal_emit (group, signals[CHANGED], 0);
515                 *changed_return = TRUE;
516         }
517
518         return TRUE;            /* Success. */
519 }
520
521 char *
522 e_source_group_uid_from_xmldoc (xmlDocPtr doc)
523 {
524         xmlNodePtr root = doc->children;
525         xmlChar *name;
526         char *retval;
527         
528         if (root && root->name) {
529                 if (strcmp (GC root->name, "group") != 0)
530                         return NULL;
531         }
532         else 
533                 return NULL;
534
535         name = xmlGetProp (root, XC "uid");
536         if (name == NULL)
537                 return NULL;
538
539         retval = g_strdup (GC name);
540         xmlFree (name);
541         return retval;
542 }
543
544 void
545 e_source_group_set_name (ESourceGroup *group,
546                          const char *name)
547 {
548         g_return_if_fail (E_IS_SOURCE_GROUP (group));
549         g_return_if_fail (name != NULL);
550
551         if (group->priv->readonly)
552                 return;
553         
554         if (group->priv->name != NULL &&
555             strcmp (group->priv->name, name) == 0)
556                 return;
557
558         g_free (group->priv->name);
559         group->priv->name = g_strdup (name);
560
561         g_signal_emit (group, signals[CHANGED], 0);
562 }
563
564 void e_source_group_set_base_uri (ESourceGroup *group,
565                                   const char *base_uri)
566 {
567         g_return_if_fail (E_IS_SOURCE_GROUP (group));
568         g_return_if_fail (base_uri != NULL);
569         
570         if (group->priv->readonly)
571                 return;
572         
573         if (group->priv->base_uri == base_uri)
574                 return;
575
576         g_free (group->priv->base_uri);
577         group->priv->base_uri = g_strdup (base_uri);
578
579         g_signal_emit (group, signals[CHANGED], 0);
580 }
581
582 void e_source_group_set_readonly (ESourceGroup *group,
583                                   gboolean      readonly)
584 {
585         GSList *i;
586         
587         g_return_if_fail (E_IS_SOURCE_GROUP (group));
588         
589         if (group->priv->readonly)
590                 return;
591         
592         if (group->priv->readonly == readonly)
593                 return;
594
595         group->priv->readonly = readonly;
596         for (i = group->priv->sources; i != NULL; i = i->next)
597                 e_source_set_readonly (E_SOURCE (i->data), readonly);   
598
599         g_signal_emit (group, signals[CHANGED], 0);
600 }
601
602 const char *
603 e_source_group_peek_uid (ESourceGroup *group)
604 {
605         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
606
607         return group->priv->uid;
608 }
609
610 const char *
611 e_source_group_peek_name (ESourceGroup *group)
612 {
613         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
614
615         return group->priv->name;
616 }
617
618 const char *
619 e_source_group_peek_base_uri (ESourceGroup *group)
620 {
621         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
622
623         return group->priv->base_uri;
624 }
625
626 gboolean
627 e_source_group_get_readonly (ESourceGroup *group)
628 {
629         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
630
631         return group->priv->readonly;
632 }
633
634 GSList *
635 e_source_group_peek_sources (ESourceGroup *group)
636 {
637         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
638
639         return group->priv->sources;
640 }
641
642 ESource *
643 e_source_group_peek_source_by_uid (ESourceGroup *group,
644                                    const char *uid)
645 {
646         GSList *p;
647
648         for (p = group->priv->sources; p != NULL; p = p->next) {
649                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
650                         return E_SOURCE (p->data);
651         }
652
653         return NULL;
654 }
655
656 ESource *
657 e_source_group_peek_source_by_name (ESourceGroup *group,
658                                     const char *name)
659 {
660         GSList *p;
661
662         for (p = group->priv->sources; p != NULL; p = p->next) {
663                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
664                         return E_SOURCE (p->data);
665         }
666
667         return NULL;
668 }
669
670 gboolean
671 e_source_group_add_source (ESourceGroup *group,
672                            ESource *source,
673                            int position)
674 {
675         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
676
677         if (group->priv->readonly)
678                 return FALSE;
679         
680         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
681                 return FALSE;
682
683         e_source_set_group (source, group);
684         e_source_set_readonly (source, group->priv->readonly);
685         g_object_ref (source);
686
687         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
688
689         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
690         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
691         g_signal_emit (group, signals[CHANGED], 0);
692
693         return TRUE;
694 }
695
696 gboolean
697 e_source_group_remove_source (ESourceGroup *group,
698                               ESource *source)
699 {
700         GSList *p;
701
702         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
703         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
704
705         if (group->priv->readonly)
706                 return FALSE;
707
708         for (p = group->priv->sources; p != NULL; p = p->next) {
709                 if (E_SOURCE (p->data) == source) {
710                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
711                         g_signal_handlers_disconnect_by_func (source,
712                                                               G_CALLBACK (source_changed_callback),
713                                                               group);
714                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
715                         g_signal_emit (group, signals[CHANGED], 0);
716                         return TRUE;
717                 }
718         }
719
720         return FALSE;
721 }
722
723 gboolean
724 e_source_group_remove_source_by_uid (ESourceGroup *group,
725                                      const char *uid)
726 {
727         GSList *p;
728
729         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
730         g_return_val_if_fail (uid != NULL, FALSE);
731
732         if (group->priv->readonly)
733                 return FALSE;
734         
735         for (p = group->priv->sources; p != NULL; p = p->next) {
736                 ESource *source = E_SOURCE (p->data);
737
738                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
739                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
740                         g_signal_handlers_disconnect_by_func (source,
741                                                               G_CALLBACK (source_changed_callback),
742                                                               group);
743                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
744                         g_signal_emit (group, signals[CHANGED], 0);
745                         return TRUE;
746                 }
747         }
748
749         return FALSE;
750 }
751
752
753 char *
754 e_source_group_to_xml (ESourceGroup *group)
755 {
756         xmlDocPtr doc;
757         xmlNodePtr root;
758         xmlChar *xml_buffer;
759         char *returned_buffer;
760         int xml_buffer_size;
761         GSList *p;
762
763         doc = xmlNewDoc (XC "1.0");
764
765         root = xmlNewDocNode (doc, NULL, XC "group", NULL);
766         xmlSetProp (root, XC "uid", XC e_source_group_peek_uid (group));
767         xmlSetProp (root, XC "name", XC e_source_group_peek_name (group));
768         xmlSetProp (root, XC "base_uri", XC e_source_group_peek_base_uri (group));
769         xmlSetProp (root, XC "readonly", XC (group->priv->readonly ? "yes" : "no"));
770
771         if (g_hash_table_size (group->priv->properties) != 0) {
772                 xmlNodePtr properties_node;
773
774                 properties_node = xmlNewChild (root, NULL, XC "properties", NULL);
775                 g_hash_table_foreach (group->priv->properties, (GHFunc) property_dump_cb, properties_node);
776         }
777         
778         xmlDocSetRootElement (doc, root);
779
780         for (p = group->priv->sources; p != NULL; p = p->next)
781                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
782
783         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
784         xmlFreeDoc (doc);
785
786         returned_buffer = g_malloc (xml_buffer_size + 1);
787         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
788         returned_buffer [xml_buffer_size] = '\0';
789         xmlFree (xml_buffer);
790
791         return returned_buffer;
792 }
793
794 gchar *
795 e_source_group_get_property (ESourceGroup *source_group,
796                              const gchar *property)
797 {
798         ESourceGroupPrivate *priv;
799
800         g_return_val_if_fail (E_IS_SOURCE_GROUP (source_group), NULL);
801         priv = source_group->priv;
802
803         return g_strdup (g_hash_table_lookup (priv->properties, property));
804 }
805
806 void
807 e_source_group_set_property (ESourceGroup *source_group,
808                              const gchar *property,
809                              const gchar *value)
810 {
811         ESourceGroupPrivate *priv;
812
813         g_return_if_fail (E_IS_SOURCE_GROUP (source_group));
814         priv = source_group->priv;
815
816         if (value)
817                 g_hash_table_replace (priv->properties, g_strdup (property), g_strdup (value));
818         else
819                 g_hash_table_remove (priv->properties, property);
820
821         g_signal_emit (source_group, signals[CHANGED], 0);
822 }
823
824 void
825 e_source_group_foreach_property (ESourceGroup *source_group, GHFunc func, gpointer data)
826 {
827         ESourceGroupPrivate *priv;
828
829         g_return_if_fail (E_IS_SOURCE_GROUP (source_group));
830         priv = source_group->priv;
831
832         g_hash_table_foreach (priv->properties, func, data);
833 }
834
835 #undef XC
836 #undef GC