Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-source.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-source.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.h"
30
31 #define ES_CLASS(obj)  E_SOURCE_CLASS (G_OBJECT_GET_CLASS (obj))
32
33
34 /* Private members.  */
35
36 struct _ESourcePrivate {
37         ESourceGroup *group;
38
39         char *uid;
40         char *name;
41         char *relative_uri;
42         char *absolute_uri;
43
44         gboolean readonly;
45         
46         gchar *color_spec;
47
48         GHashTable *properties;
49 };
50
51
52 /* Signals.  */
53
54 enum {
55         CHANGED,
56         LAST_SIGNAL
57 };
58 static unsigned int signals[LAST_SIGNAL] = { 0 };
59
60
61 /* Callbacks.  */
62
63 static void
64 group_weak_notify (ESource *source,
65                    GObject **where_the_object_was)
66 {
67         source->priv->group = NULL;
68
69         g_signal_emit (source, signals[CHANGED], 0);
70 }
71
72
73 /* GObject methods.  */
74
75 G_DEFINE_TYPE (ESource, e_source, G_TYPE_OBJECT);
76
77 static void
78 impl_finalize (GObject *object)
79 {
80         ESourcePrivate *priv = E_SOURCE (object)->priv;
81
82         g_free (priv->uid);
83         g_free (priv->name);
84         g_free (priv->relative_uri);
85         g_free (priv->absolute_uri);
86         g_free (priv->color_spec);
87
88         g_hash_table_destroy (priv->properties);
89
90         g_free (priv);
91
92         (* G_OBJECT_CLASS (e_source_parent_class)->finalize) (object);
93 }
94
95 static void
96 impl_dispose (GObject *object)
97 {
98         ESourcePrivate *priv = E_SOURCE (object)->priv;
99
100         if (priv->group != NULL) {
101                 g_object_weak_unref (G_OBJECT (priv->group), (GWeakNotify) group_weak_notify, object);
102                 priv->group = NULL;
103         }
104
105         (* G_OBJECT_CLASS (e_source_parent_class)->dispose) (object);
106 }
107
108
109 /* Initialization.  */
110
111 static void
112 e_source_class_init (ESourceClass *class)
113 {
114         GObjectClass *object_class = G_OBJECT_CLASS (class);
115
116         object_class->dispose  = impl_dispose;
117         object_class->finalize = impl_finalize;
118
119         signals[CHANGED] = 
120                 g_signal_new ("changed",
121                               G_OBJECT_CLASS_TYPE (object_class),
122                               G_SIGNAL_RUN_LAST,
123                               G_STRUCT_OFFSET (ESourceClass, changed),
124                               NULL, NULL,
125                               g_cclosure_marshal_VOID__VOID,
126                               G_TYPE_NONE, 0);
127 }
128
129 static void
130 e_source_init (ESource *source)
131 {
132         ESourcePrivate *priv;
133
134         priv = g_new0 (ESourcePrivate, 1);
135         source->priv = priv;
136
137         priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
138                                                   g_free, g_free);
139 }
140
141 /* Private methods. */
142
143 static gboolean
144 set_color_spec (ESource *source,
145                 const gchar *color_spec)
146 {
147         ESourcePrivate *priv = source->priv;
148         gboolean do_cmp;
149
150         if (color_spec == priv->color_spec)
151                 return FALSE;
152
153         do_cmp = (color_spec != NULL && priv->color_spec != NULL);
154         if (do_cmp && g_ascii_strcasecmp (color_spec, priv->color_spec) == 0)
155                 return FALSE;
156
157         g_free (priv->color_spec);
158         priv->color_spec = g_strdup (color_spec);
159
160         return TRUE;
161 }
162
163 /* Public methods.  */
164
165 ESource *
166 e_source_new  (const char *name,
167                const char *relative_uri)
168 {
169         ESource *source;
170
171         g_return_val_if_fail (name != NULL, NULL);
172         g_return_val_if_fail (relative_uri != NULL, NULL);
173
174         source = g_object_new (e_source_get_type (), NULL);
175         source->priv->uid = e_uid_new ();
176
177         e_source_set_name (source, name);
178         e_source_set_relative_uri (source, relative_uri);
179         return source;
180 }
181
182 ESource *
183 e_source_new_with_absolute_uri (const char *name,
184                                 const char *absolute_uri)
185 {
186         ESource *source;
187
188         g_return_val_if_fail (name != NULL, NULL);
189         g_return_val_if_fail (absolute_uri != NULL, NULL);
190
191         source = g_object_new (e_source_get_type (), NULL);
192         source->priv->uid = e_uid_new ();
193
194         e_source_set_name (source, name);
195         e_source_set_absolute_uri (source, absolute_uri);
196         return source;
197 }
198
199 ESource *
200 e_source_new_from_xml_node (xmlNodePtr node)
201 {
202         ESource *source;
203         xmlChar *uid;
204
205         uid = xmlGetProp (node, (xmlChar*)"uid");
206         if (uid == NULL)
207                 return NULL;
208
209         source = g_object_new (e_source_get_type (), NULL);
210
211         source->priv->uid = g_strdup ((char*)uid);
212         xmlFree (uid);
213
214         if (e_source_update_from_xml_node (source, node, NULL))
215                 return source;
216
217         g_object_unref (source);
218         return NULL;
219 }
220
221 gboolean
222 e_source_equal (ESource *source_1, ESource *source_2)
223 {
224         gboolean equal = FALSE;
225
226         g_return_val_if_fail (E_IS_SOURCE (source_1), FALSE);
227         g_return_val_if_fail (E_IS_SOURCE (source_2), FALSE);
228
229         if (source_1->priv->uid && source_2->priv->uid &&
230             !strcmp (source_1->priv->uid, source_2->priv->uid)) {
231                 equal = TRUE;
232         } else {
233                 gchar *uri_1, *uri_2;
234
235                 uri_1 = e_source_get_uri (source_1);
236                 uri_2 = e_source_get_uri (source_2);
237
238                 if (uri_1 && uri_2 && !strcmp (uri_1, uri_2))
239                         equal = TRUE;
240
241                 g_free (uri_1);
242                 g_free (uri_2);
243         }
244
245         return equal;
246 }
247
248 static void
249 import_properties (ESource *source,
250                    xmlNodePtr prop_root)
251 {
252         ESourcePrivate *priv = source->priv;
253         xmlNodePtr prop_node;
254
255         for (prop_node = prop_root->children; prop_node; prop_node = prop_node->next) {
256                 xmlChar *name, *value;
257
258                 if (!prop_node->name || strcmp ((char*)prop_node->name, "property"))
259                         continue;
260
261                 name = xmlGetProp (prop_node, (xmlChar*)"name");
262                 value = xmlGetProp (prop_node, (xmlChar*)"value");
263
264                 if (name && value)
265                         g_hash_table_insert (priv->properties, g_strdup ((char*)name), g_strdup ((char*)value));
266
267                 if (name)
268                         xmlFree (name);
269                 if (value)
270                         xmlFree (value);
271         }
272 }
273
274 typedef struct
275 {
276         gboolean equal;
277         GHashTable *table2;
278 } hash_compare_data;
279
280 static void
281 compare_str_hash (gpointer key, gpointer value, hash_compare_data *cd)
282 {
283         gpointer value2 = g_hash_table_lookup (cd->table2, key);
284         if (value2 == NULL || g_str_equal (value, value2) == FALSE)
285                 cd->equal = FALSE;
286 }
287
288 static gboolean
289 compare_str_hashes (GHashTable *table1, GHashTable *table2)
290 {
291         hash_compare_data cd;
292
293         if (g_hash_table_size (table1) != g_hash_table_size (table2))
294                 return FALSE;
295
296         cd.equal = TRUE;
297         cd.table2 = table2;
298         g_hash_table_foreach (table1, (GHFunc) compare_str_hash, &cd);
299         return cd.equal;
300 }
301
302 /**
303  * e_source_update_from_xml_node:
304  * @source: An ESource.
305  * @node: A pointer to the node to parse.
306  * 
307  * Update the ESource properties from @node.
308  * 
309  * Return value: %TRUE if the data in @node was recognized and parsed into
310  * acceptable values for @source, %FALSE otherwise.
311  **/
312 gboolean
313 e_source_update_from_xml_node (ESource *source,
314                                xmlNodePtr node,
315                                gboolean *changed_return)
316 {
317         xmlChar *name;
318         xmlChar *relative_uri;
319         xmlChar *absolute_uri;
320         xmlChar *color_spec;
321         xmlChar *color;
322         gboolean retval = FALSE;
323         gboolean changed = FALSE;
324
325         name = xmlGetProp (node, (xmlChar*)"name");
326         relative_uri = xmlGetProp (node, (xmlChar*)"relative_uri");
327         absolute_uri = xmlGetProp (node, (xmlChar*)"uri");
328         color_spec = xmlGetProp (node, (xmlChar*)"color_spec");
329         color = xmlGetProp (node, (xmlChar*)"color");  /* obsolete */
330
331         if (name == NULL || (relative_uri == NULL && absolute_uri == NULL))
332                 goto done;
333
334         if (color_spec != NULL && color != NULL)
335                 goto done;
336
337         if (source->priv->name == NULL
338             || strcmp ((char*)name, source->priv->name) != 0
339             || source->priv->relative_uri == NULL
340             || relative_uri != NULL
341             || strcmp ((char*)relative_uri, source->priv->relative_uri) != 0) {
342                 g_free (source->priv->name);
343                 source->priv->name = g_strdup ((char*)name);
344
345                 g_free (source->priv->relative_uri);
346                 source->priv->relative_uri = g_strdup ((char*)relative_uri);
347
348                 changed = TRUE;
349         }
350
351         if (absolute_uri != NULL) {
352                 g_free (source->priv->absolute_uri);
353                 source->priv->absolute_uri = g_strdup ((char*)absolute_uri);
354                 changed = TRUE;
355         }
356
357         if (color == NULL) {
358                 /* It is okay for color_spec to be NULL. */
359                 changed |= set_color_spec (source, (char*)color_spec);
360         } else {
361                 gchar buffer[8];
362                 g_snprintf (buffer, sizeof (buffer), "#%s", color);
363                 changed |= set_color_spec (source, buffer);
364         }
365
366         if (g_hash_table_size (source->priv->properties) && !node->children) {
367                 g_hash_table_destroy (source->priv->properties);
368                 source->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
369                                                                   g_free, g_free);
370                 changed = TRUE;
371         }
372         
373         for (node = node->children; node; node = node->next) {
374                 if (!node->name)
375                         continue;
376
377                 if (!strcmp ((char*)node->name, "properties")) {
378                         GHashTable *temp = source->priv->properties;
379                         source->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
380                                                                           g_free, g_free);
381                         import_properties (source, node);
382                         if (!compare_str_hashes (temp, source->priv->properties))
383                                 changed = TRUE;
384                         g_hash_table_destroy (temp);
385                         break;
386                 }
387         }
388
389         retval = TRUE;
390         
391  done:
392         if (changed)
393                 g_signal_emit (source, signals[CHANGED], 0);
394
395         if (changed_return != NULL)
396                 *changed_return = changed;
397
398         if (name != NULL)
399                 xmlFree (name);
400         if (relative_uri != NULL)
401                 xmlFree (relative_uri);
402         if (absolute_uri != NULL)
403                 xmlFree (absolute_uri);
404         if (color_spec != NULL)
405                 xmlFree (color_spec);
406         if (color != NULL)
407                 xmlFree (color);
408
409         return retval;
410 }
411
412 /**
413  * e_source_name_from_xml_node:
414  * @node: A pointer to an XML node.
415  * 
416  * Assuming that @node is a valid ESource specification, retrieve the name of
417  * the source from it.
418  * 
419  * Return value: Name of the source in the specified @node.  The caller must
420  * free the string.
421  **/
422 char *
423 e_source_uid_from_xml_node (xmlNodePtr node)
424 {
425         xmlChar *uid = xmlGetProp (node, (xmlChar*)"uid");
426         char *retval;
427
428         if (uid == NULL)
429                 return NULL;
430
431         retval = g_strdup ((char*)uid);
432         xmlFree (uid);
433         return retval;
434 }
435
436 char *
437 e_source_build_absolute_uri (ESource *source)
438 {
439         const gchar *base_uri_str;
440         gchar *uri_str;
441
442         g_return_val_if_fail (source->priv->group != NULL, NULL);
443         
444         base_uri_str = e_source_group_peek_base_uri (source->priv->group);
445
446         /* If last character in base URI is a slash, just concat the
447          * strings.  We don't want to compress e.g. the trailing ://
448          * in a protocol specification Note: Do not use
449          * G_DIR_SEPARATOR or g_build_filename() when manipulating
450          * URIs. URIs use normal ("forward") slashes also on Windows.
451          */
452         if (*base_uri_str && *(base_uri_str + strlen (base_uri_str) - 1) == '/')
453                 uri_str = g_strconcat (base_uri_str, source->priv->relative_uri, NULL);
454         else {
455                 if (source->priv->relative_uri != NULL)
456                         uri_str = g_strconcat (base_uri_str, "/", source->priv->relative_uri,
457                                        NULL);
458                 else
459                         uri_str = g_strdup (base_uri_str);
460         }
461
462         return uri_str;
463 }
464
465 void
466 e_source_set_group (ESource *source,
467                     ESourceGroup *group)
468 {
469         g_return_if_fail (E_IS_SOURCE (source));
470         g_return_if_fail (group == NULL || E_IS_SOURCE_GROUP (group));
471
472         if (source->priv->readonly)
473                 return;
474         
475         if (source->priv->group == group)
476                 return;
477
478         if (source->priv->group != NULL)
479                 g_object_weak_unref (G_OBJECT (source->priv->group), (GWeakNotify) group_weak_notify, source);
480
481         source->priv->group = group;
482         if (group != NULL) {
483                 g_object_weak_ref (G_OBJECT (group), (GWeakNotify) group_weak_notify, source);
484         }
485
486         g_signal_emit (source, signals[CHANGED], 0);
487 }
488
489 void
490 e_source_set_name (ESource *source,
491                    const char *name)
492 {
493         g_return_if_fail (E_IS_SOURCE (source));
494         g_return_if_fail (name != NULL);
495
496         if (source->priv->readonly)
497                 return;
498         
499         if (source->priv->name != NULL &&
500             strcmp (source->priv->name, name) == 0)
501                 return;
502
503         g_free (source->priv->name);
504         source->priv->name = g_strdup (name);
505
506         g_signal_emit (source, signals[CHANGED], 0);
507 }
508
509 void
510 e_source_set_relative_uri (ESource *source,
511                            const char *relative_uri)
512 {
513         char *absolute_uri;
514
515         g_return_if_fail (E_IS_SOURCE (source));
516
517         if (source->priv->readonly)
518                 return;
519         
520         if (source->priv->relative_uri == relative_uri)
521                 return;
522
523         g_free (source->priv->relative_uri);
524         source->priv->relative_uri = g_strdup (relative_uri);
525
526         /* reset the absolute uri */
527         if (source->priv->absolute_uri && 
528             (absolute_uri = e_source_build_absolute_uri (source))) {
529                 g_free (source->priv->absolute_uri);
530                 source->priv->absolute_uri = absolute_uri;
531         }
532
533         g_signal_emit (source, signals[CHANGED], 0);
534 }
535
536 void
537 e_source_set_absolute_uri (ESource *source,
538                            const char *absolute_uri)
539 {
540         g_return_if_fail (E_IS_SOURCE (source));
541
542         if (!!absolute_uri == !!source->priv->absolute_uri
543             || (absolute_uri && source->priv->absolute_uri && !strcmp (source->priv->absolute_uri, absolute_uri)))
544                 return;
545
546         g_free (source->priv->absolute_uri);
547         source->priv->absolute_uri = g_strdup (absolute_uri);
548
549         g_signal_emit (source, signals[CHANGED], 0);
550 }
551
552 void
553 e_source_set_readonly (ESource  *source,
554                        gboolean  readonly)
555 {
556         g_return_if_fail (E_IS_SOURCE (source));
557
558         if (source->priv->readonly == readonly)
559                 return;
560
561         source->priv->readonly = readonly;
562
563         g_signal_emit (source, signals[CHANGED], 0);
564         
565 }
566
567 void
568 e_source_set_color (ESource *source,
569                     guint32 color)
570 {
571         gchar color_spec[8];
572
573         g_return_if_fail (E_IS_SOURCE (source));
574
575         g_snprintf (color_spec, sizeof (color_spec), "#%06x", color);
576         e_source_set_color_spec (source, color_spec);
577 }
578
579 void
580 e_source_unset_color (ESource *source)
581 {
582         g_return_if_fail (E_IS_SOURCE (source));
583
584         e_source_set_color_spec (source, NULL);
585 }
586
587 /**
588  * e_source_set_color_spec:
589  * @source: an ESource
590  * @color_spec: a string specifying the color
591  *
592  * Store a textual representation of a color in @source.  The @color_spec
593  * string should be parsable by #gdk_color_parse(), or %NULL to unset the
594  * color in @source.
595  *
596  * Since: 1.10
597  **/
598 void
599 e_source_set_color_spec (ESource *source,
600                          const gchar *color_spec)
601 {
602         g_return_if_fail (E_IS_SOURCE (source));
603
604         if (!source->priv->readonly && set_color_spec (source, color_spec))
605                 g_signal_emit (source, signals[CHANGED], 0);
606 }
607
608 ESourceGroup *
609 e_source_peek_group (ESource *source)
610 {
611         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
612
613         return source->priv->group;
614 }
615
616 const char *
617 e_source_peek_uid (ESource *source)
618 {
619         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
620
621         return source->priv->uid;
622 }
623
624 const char *
625 e_source_peek_name (ESource *source)
626 {
627         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
628
629         return source->priv->name;
630 }
631
632 const char *
633 e_source_peek_relative_uri (ESource *source)
634 {
635         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
636
637         return source->priv->relative_uri;
638 }
639
640 const char *
641 e_source_peek_absolute_uri (ESource *source)
642 {
643         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
644
645         return source->priv->absolute_uri;
646 }
647
648 /**
649  * e_source_peek_color_spec:
650  * @source: an ESource
651  * 
652  * Return the textual representation of the color for @source, or %NULL if it
653  * has none.  The returned string should be parsable by #gdk_color_parse().
654  * 
655  * Return value: a string specifying the color
656  *
657  * Since: 1.10
658  **/
659 const char *
660 e_source_peek_color_spec (ESource *source)
661 {
662         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
663
664         return source->priv->color_spec;
665 }
666
667 gboolean
668 e_source_get_readonly (ESource *source)
669 {
670         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
671
672         return source->priv->readonly;
673 }
674
675
676 /**
677  * e_source_get_color:
678  * @source: An ESource
679  * @color_return: Pointer to a variable where the returned color will be
680  * stored.
681  * 
682  * If @source has an associated color, return it in *@color_return.
683  * 
684  * Return value: %TRUE if the @source has a defined color (and hence
685  * *@color_return was set), %FALSE otherwise.
686  **/
687 gboolean
688 e_source_get_color (ESource *source,
689                     guint32 *color_return)
690 {
691         const gchar *color_spec;
692         guint32 color;
693
694         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
695
696         color_spec = e_source_peek_color_spec (source);
697
698         if (color_spec == NULL)
699                 return FALSE;
700
701         if (sscanf (color_spec, "#%06x", &color) != 1)
702                 return FALSE;
703
704         if (color_return != NULL)
705                 *color_return = color;
706
707         return TRUE;
708 }
709
710 char *
711 e_source_get_uri (ESource *source)
712 {
713         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
714
715         if (source->priv->group == NULL) {
716                 if (source->priv->absolute_uri != NULL)
717                         return g_strdup (source->priv->absolute_uri);
718
719                 g_warning ("e_source_get_uri () called on source with no absolute URI!");
720                 return NULL;
721         }
722         else if (source->priv->absolute_uri != NULL) /* source->priv->group != NULL */
723                 return g_strdup (source->priv->absolute_uri);
724         else
725                 return e_source_build_absolute_uri (source);
726 }
727
728
729 static void
730 property_dump_cb (const xmlChar *key, const xmlChar *value, xmlNodePtr root)
731 {
732         xmlNodePtr node;
733
734         node = xmlNewChild (root, NULL, (xmlChar*)"property", NULL);
735         xmlSetProp (node, (xmlChar*)"name", key);
736         xmlSetProp (node, (xmlChar*)"value", value);
737 }
738
739
740 static xmlNodePtr
741 dump_common_to_xml_node (ESource *source,
742                          xmlNodePtr parent_node)
743 {
744         ESourcePrivate *priv;
745         xmlNodePtr node;
746         const char *abs_uri = NULL, *relative_uri = NULL;
747
748         priv = source->priv;
749
750         if (parent_node)
751                 node = xmlNewChild (parent_node, NULL, (xmlChar*)"source", NULL);
752         else
753                 node = xmlNewNode (NULL, (xmlChar*)"source");
754
755         xmlSetProp (node, (xmlChar*)"uid", (xmlChar*)e_source_peek_uid (source));
756         xmlSetProp (node, (xmlChar*)"name", (xmlChar*)e_source_peek_name (source));
757         abs_uri = e_source_peek_absolute_uri (source);
758         relative_uri = e_source_peek_relative_uri (source);
759         if (abs_uri)
760                 xmlSetProp (node, (xmlChar*)"uri", (xmlChar*)abs_uri);
761         if (relative_uri)
762                 xmlSetProp (node, (xmlChar*)"relative_uri", (xmlChar*)relative_uri);
763
764         if (priv->color_spec != NULL)
765                 xmlSetProp (node, (xmlChar*)"color_spec", (xmlChar*)priv->color_spec);
766
767         if (g_hash_table_size (priv->properties) != 0) {
768                 xmlNodePtr properties_node;
769
770                 properties_node = xmlNewChild (node, NULL, (xmlChar*)"properties", NULL);
771                 g_hash_table_foreach (priv->properties, (GHFunc) property_dump_cb, properties_node);
772         }
773
774         return node;
775 }
776
777
778 void
779 e_source_dump_to_xml_node (ESource *source,
780                            xmlNodePtr parent_node)
781 {
782         g_return_if_fail (E_IS_SOURCE (source));
783
784         dump_common_to_xml_node (source, parent_node);
785 }
786
787
788 char *
789 e_source_to_standalone_xml (ESource *source)
790 {
791         xmlDocPtr doc;
792         xmlNodePtr node;
793         xmlChar *xml_buffer;
794         char *returned_buffer;
795         int xml_buffer_size;
796         gchar *uri;
797
798         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
799
800         doc = xmlNewDoc ((xmlChar*)"1.0");
801         node = dump_common_to_xml_node (source, NULL);
802
803         xmlDocSetRootElement (doc, node);
804
805         uri = e_source_get_uri (source);
806         xmlSetProp (node, (xmlChar*)"uri", (xmlChar*)uri);
807         g_free (uri);
808
809         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
810         xmlFreeDoc (doc);
811
812         returned_buffer = g_malloc (xml_buffer_size + 1);
813         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
814         returned_buffer [xml_buffer_size] = '\0';
815         xmlFree (xml_buffer);
816
817         return returned_buffer;
818 }
819
820
821 ESource *
822 e_source_new_from_standalone_xml (const char *xml)
823 {
824         xmlDocPtr doc;
825         xmlNodePtr root;
826         ESource *source;
827
828         doc = xmlParseDoc ((xmlChar*)xml);
829         if (doc == NULL)
830                 return NULL;
831
832         root = doc->children;
833         if (strcmp ((char*)root->name, "source") != 0)
834                 return NULL;
835
836         source = e_source_new_from_xml_node (root);
837         xmlFreeDoc (doc);
838
839         return source;
840 }
841
842
843 const gchar *
844 e_source_get_property (ESource *source,
845                        const gchar *property)
846 {
847         ESourcePrivate *priv;
848
849         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
850         priv = source->priv;
851
852         return g_hash_table_lookup (priv->properties, property);
853 }
854
855 char *
856 e_source_get_duped_property (ESource *source, const char *property)
857 {
858         ESourcePrivate *priv;
859
860         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
861         priv = source->priv;
862
863         return g_strdup (g_hash_table_lookup (priv->properties, property));
864 }
865
866 void
867 e_source_set_property (ESource *source,
868                        const gchar *property,
869                        const gchar *value)
870 {
871         ESourcePrivate *priv;
872
873         g_return_if_fail (E_IS_SOURCE (source));
874         priv = source->priv;
875
876         if (value)
877                 g_hash_table_replace (priv->properties, g_strdup (property), g_strdup (value));
878         else
879                 g_hash_table_remove (priv->properties, property);
880
881         g_signal_emit (source, signals[CHANGED], 0);
882 }
883
884
885 void
886 e_source_foreach_property (ESource *source, GHFunc func, gpointer data)
887 {
888         ESourcePrivate *priv;
889
890         g_return_if_fail (E_IS_SOURCE (source));
891         priv = source->priv;
892
893         g_hash_table_foreach (priv->properties, func, data);
894 }
895
896
897 static void
898 copy_property (const gchar *key, const gchar *value, ESource *new_source)
899 {
900         e_source_set_property (new_source, key, value);
901 }
902
903
904 ESource *
905 e_source_copy (ESource *source)
906 {
907         ESource *new_source;
908
909         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
910
911         new_source = g_object_new (e_source_get_type (), NULL);
912         new_source->priv->uid = g_strdup (e_source_peek_uid (source));
913
914         e_source_set_name (new_source, e_source_peek_name (source));
915
916         new_source->priv->color_spec = g_strdup (source->priv->color_spec);
917
918         new_source->priv->absolute_uri = g_strdup (e_source_peek_absolute_uri (source));
919
920         e_source_foreach_property (source, (GHFunc) copy_property, new_source);
921
922         return new_source;
923 }
924