abort and fail if we get an xml error setting up the new_source entries.
[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 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 General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, 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-data-server-marshal.h"
29 #include "e-uid.h"
30 #include "e-source-group.h"
31
32 static GObjectClass *parent_class = NULL;
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
47
48 /* Signals.  */
49
50 enum {
51         CHANGED,
52         SOURCE_REMOVED,
53         SOURCE_ADDED,
54         LAST_SIGNAL
55 };
56 static unsigned int signals[LAST_SIGNAL] = { 0 };
57
58
59 /* Callbacks.  */
60
61 static void
62 source_changed_callback (ESource *source,
63                          ESourceGroup *group)
64 {
65         if (! group->priv->ignore_source_changed)
66                 g_signal_emit (group, signals[CHANGED], 0);
67 }
68
69
70 /* GObject methods.  */
71
72 static void
73 impl_dispose (GObject *object)
74 {
75         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
76
77         if (priv->sources != NULL) {
78                 GSList *p;
79
80                 for (p = priv->sources; p != NULL; p = p->next) {
81                         ESource *source = E_SOURCE (p->data);
82
83                         g_signal_handlers_disconnect_by_func (source,
84                                                               G_CALLBACK (source_changed_callback),
85                                                               object);
86                         g_object_unref (source);
87                 }
88
89                 g_slist_free (priv->sources);
90                 priv->sources = NULL;
91         }
92
93         (* G_OBJECT_CLASS (parent_class)->dispose) (object);
94 }
95
96 static void
97 impl_finalize (GObject *object)
98 {
99         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
100
101         g_free (priv->uid);
102         g_free (priv->name);
103         g_free (priv->base_uri);
104         g_free (priv);
105
106         (* G_OBJECT_CLASS (parent_class)->finalize) (object);
107 }
108
109
110 /* Initialization.  */
111
112 static void
113 class_init (ESourceGroupClass *class)
114 {
115         GObjectClass *object_class = G_OBJECT_CLASS (class);
116
117         object_class->dispose  = impl_dispose;
118         object_class->finalize = impl_finalize;
119
120         parent_class = g_type_class_peek_parent (class);
121
122         signals[CHANGED] = 
123                 g_signal_new ("changed",
124                               G_OBJECT_CLASS_TYPE (object_class),
125                               G_SIGNAL_RUN_LAST,
126                               G_STRUCT_OFFSET (ESourceGroupClass, changed),
127                               NULL, NULL,
128                               e_data_server_marshal_VOID__VOID,
129                               G_TYPE_NONE, 0);
130
131         signals[SOURCE_ADDED] = 
132                 g_signal_new ("source_added",
133                               G_OBJECT_CLASS_TYPE (object_class),
134                               G_SIGNAL_RUN_LAST,
135                               G_STRUCT_OFFSET (ESourceGroupClass, source_added),
136                               NULL, NULL,
137                               e_data_server_marshal_VOID__OBJECT,
138                               G_TYPE_NONE, 1,
139                               G_TYPE_OBJECT);
140         signals[SOURCE_REMOVED] = 
141                 g_signal_new ("source_removed",
142                               G_OBJECT_CLASS_TYPE (object_class),
143                               G_SIGNAL_RUN_LAST,
144                               G_STRUCT_OFFSET (ESourceGroupClass, source_removed),
145                               NULL, NULL,
146                               e_data_server_marshal_VOID__OBJECT,
147                               G_TYPE_NONE, 1,
148                               G_TYPE_OBJECT);
149 }
150
151 static void
152 init (ESourceGroup *source_group)
153 {
154         ESourceGroupPrivate *priv;
155
156         priv = g_new0 (ESourceGroupPrivate, 1);
157         source_group->priv = priv;
158 }
159
160 GType
161 e_source_group_get_type (void)
162 {
163         static GType e_source_group_type = 0;
164
165         if (!e_source_group_type) {
166                 static GTypeInfo info = {
167                         sizeof (ESourceGroupClass),
168                         (GBaseInitFunc) NULL,
169                         (GBaseFinalizeFunc) NULL,
170                         (GClassInitFunc) class_init,
171                         NULL, NULL,
172                         sizeof (ESourceGroup),
173                         0,
174                         (GInstanceInitFunc) init
175                 };
176                 e_source_group_type = g_type_register_static (G_TYPE_OBJECT, "ESourceGroup", &info, 0);
177         }
178
179         return e_source_group_type;
180 }
181
182 /* Public methods.  */
183
184 ESourceGroup *
185 e_source_group_new (const char *name,
186                     const char *base_uri)
187 {
188         ESourceGroup *new;
189
190         g_return_val_if_fail (name != NULL, NULL);
191         g_return_val_if_fail (base_uri != NULL, NULL);
192
193         new = g_object_new (e_source_group_get_type (), NULL);
194         new->priv->uid = e_uid_new ();
195
196         e_source_group_set_name (new, name);
197         e_source_group_set_base_uri (new, base_uri);
198
199         return new;
200 }
201
202 ESourceGroup *
203 e_source_group_new_from_xml (const char *xml)
204 {
205         xmlDocPtr doc;
206         ESourceGroup *group;
207
208         doc = xmlParseDoc ((char *) xml);
209         if (doc == NULL)
210                 return NULL;
211
212         group = e_source_group_new_from_xmldoc (doc);
213         xmlFreeDoc (doc);
214
215         return group;
216 }
217
218 ESourceGroup *
219 e_source_group_new_from_xmldoc (xmlDocPtr doc)
220 {
221         xmlNodePtr root, p;
222         xmlChar *uid;
223         xmlChar *name;
224         xmlChar *base_uri;
225         xmlChar *readonly_str;
226         ESourceGroup *new = NULL;
227
228         g_return_val_if_fail (doc != NULL, NULL);
229
230         root = doc->children;
231         if (strcmp (root->name, "group") != 0)
232                 return NULL;
233
234         uid = xmlGetProp (root, "uid");
235         name = xmlGetProp (root, "name");
236         base_uri = xmlGetProp (root, "base_uri");
237         readonly_str = xmlGetProp (root, "readonly");
238
239         if (uid == NULL || name == NULL || base_uri == NULL)
240                 goto done;
241
242         new = g_object_new (e_source_group_get_type (), NULL);
243         new->priv->uid = g_strdup (uid);
244
245         e_source_group_set_name (new, name);
246         e_source_group_set_base_uri (new, base_uri);
247         
248         for (p = root->children; p != NULL; p = p->next) {
249                 ESource *new_source = e_source_new_from_xml_node (p);
250
251                 if (new_source == NULL) {
252                         g_object_unref (new);
253                         goto done;
254                 }
255                 e_source_group_add_source (new, new_source, -1);
256         }
257
258         e_source_group_set_readonly (new, readonly_str && !strcmp (readonly_str, "yes"));
259
260  done:
261         if (uid != NULL)
262                 xmlFree (uid);
263
264         if (name != NULL)
265                 xmlFree (name);
266         if (base_uri != NULL)
267                 xmlFree (base_uri);
268         if (readonly_str != NULL)
269                 xmlFree (readonly_str);
270         return new;
271 }
272
273 gboolean
274 e_source_group_update_from_xml (ESourceGroup *group,
275                                 const char *xml,
276                                 gboolean *changed_return)
277 {
278         xmlDocPtr xmldoc;
279         gboolean success;
280
281         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
282         g_return_val_if_fail (xml != NULL, FALSE);
283
284         xmldoc = xmlParseDoc ((char *) xml);
285
286         success = e_source_group_update_from_xmldoc (group, xmldoc, changed_return);
287
288         xmlFreeDoc (xmldoc);
289
290         return success;
291 }
292
293 gboolean
294 e_source_group_update_from_xmldoc (ESourceGroup *group,
295                                    xmlDocPtr doc,
296                                    gboolean *changed_return)
297 {
298         GHashTable *new_sources_hash;
299         GSList *new_sources_list = NULL;
300         xmlNodePtr root, nodep;
301         xmlChar *name, *base_uri, *readonly_str;
302         gboolean readonly;
303         gboolean changed = FALSE;
304         GSList *p, *q;
305
306         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
307         g_return_val_if_fail (doc != NULL, FALSE);
308
309         *changed_return = FALSE;
310
311         root = doc->children;
312         if (strcmp (root->name, "group") != 0)
313                 return FALSE;
314
315         name = xmlGetProp (root, "name");
316         if (name == NULL)
317                 return FALSE;
318
319         base_uri = xmlGetProp (root, "base_uri");
320         if (base_uri == NULL) {
321                 xmlFree (name);
322                 return FALSE;
323         }
324
325         if (strcmp (group->priv->name, name) != 0) {
326                 g_free (group->priv->name);
327                 group->priv->name = g_strdup (name);
328                 changed = TRUE;
329         }
330         xmlFree (name);
331
332         if (strcmp (group->priv->base_uri, base_uri) != 0) {
333                 g_free (group->priv->base_uri);
334                 group->priv->base_uri = g_strdup (base_uri);
335                 changed = TRUE;
336         }
337         xmlFree (base_uri);
338
339         readonly_str = xmlGetProp (root, "readonly");
340         readonly = readonly_str && !strcmp (readonly_str, "yes");
341         if (readonly != group->priv->readonly) {
342                 group->priv->readonly = readonly;
343                 changed = TRUE;
344         }
345         xmlFree (readonly_str);
346         
347         new_sources_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
348
349         for (nodep = root->children; nodep != NULL; nodep = nodep->next) {
350                 ESource *existing_source;
351                 char *uid = e_source_uid_from_xml_node (nodep);
352
353                 if (uid == NULL)
354                         continue;
355
356                 existing_source = e_source_group_peek_source_by_uid (group, uid);
357                 if (g_hash_table_lookup (new_sources_hash, existing_source) != NULL)
358                         continue;
359
360                 if (existing_source == NULL) {
361                         ESource *new_source = e_source_new_from_xml_node (nodep);
362
363                         if (new_source != NULL) {
364                                 e_source_set_group (new_source, group);
365                                 g_signal_connect (new_source, "changed", G_CALLBACK (source_changed_callback), group);
366                                 new_sources_list = g_slist_prepend (new_sources_list, new_source);
367
368                                 g_hash_table_insert (new_sources_hash, new_source, new_source);
369
370                                 g_signal_emit (group, signals[SOURCE_ADDED], 0, new_source);
371                                 changed = TRUE;
372                         }
373                 } else {
374                         gboolean source_changed;
375
376                         group->priv->ignore_source_changed ++;
377
378                         if (e_source_update_from_xml_node (existing_source, nodep, &source_changed)) {
379                                 new_sources_list = g_slist_prepend (new_sources_list, existing_source);
380                                 g_object_ref (existing_source);
381                                 g_hash_table_insert (new_sources_hash, existing_source, existing_source);
382
383                                 if (source_changed)
384                                         changed = TRUE;
385                         }
386
387                         group->priv->ignore_source_changed --;
388                 }
389
390                 g_free (uid);
391         }
392
393         new_sources_list = g_slist_reverse (new_sources_list);
394
395         /* Emit "group_removed" and disconnect the "changed" signal for all the
396            groups that we haven't found in the new list.  */
397         q = new_sources_list;
398         for (p = group->priv->sources; p != NULL; p = p->next) {
399                 ESource *source = E_SOURCE (p->data);
400
401                 if (g_hash_table_lookup (new_sources_hash, source) == NULL) {
402                         changed = TRUE;
403
404                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
405                         g_signal_handlers_disconnect_by_func (source, source_changed_callback, group);
406                 }
407
408                 if (! changed && q != NULL) {
409                         if (q->data != p->data)
410                                 changed = TRUE;
411                         q = q->next;
412                 }
413         }
414
415         g_hash_table_destroy (new_sources_hash);
416
417         /* Replace the original group list with the new one.  */
418         g_slist_foreach (group->priv->sources, (GFunc) g_object_unref, NULL);
419         g_slist_free (group->priv->sources);
420
421         group->priv->sources = new_sources_list;
422
423         /* FIXME if the order changes, the function doesn't notice.  */
424
425         if (changed) {
426                 g_signal_emit (group, signals[CHANGED], 0);
427                 *changed_return = TRUE;
428         }
429
430         return TRUE;            /* Success. */
431 }
432
433 char *
434 e_source_group_uid_from_xmldoc (xmlDocPtr doc)
435 {
436         xmlNodePtr root = doc->children;
437         xmlChar *name;
438         char *retval;
439
440         if (strcmp (root->name, "group") != 0)
441                 return NULL;
442
443         name = xmlGetProp (root, "uid");
444         if (name == NULL)
445                 return NULL;
446
447         retval = g_strdup (name);
448         xmlFree (name);
449         return retval;
450 }
451
452 void
453 e_source_group_set_name (ESourceGroup *group,
454                          const char *name)
455 {
456         g_return_if_fail (E_IS_SOURCE_GROUP (group));
457         g_return_if_fail (name != NULL);
458
459         if (group->priv->readonly)
460                 return;
461         
462         if (group->priv->name == name)
463                 return;
464
465         g_free (group->priv->name);
466         group->priv->name = g_strdup (name);
467
468         g_signal_emit (group, signals[CHANGED], 0);
469 }
470
471 void e_source_group_set_base_uri (ESourceGroup *group,
472                                   const char *base_uri)
473 {
474         g_return_if_fail (E_IS_SOURCE_GROUP (group));
475         g_return_if_fail (base_uri != NULL);
476         
477         if (group->priv->readonly)
478                 return;
479         
480         if (group->priv->base_uri == base_uri)
481                 return;
482
483         g_free (group->priv->base_uri);
484         group->priv->base_uri = g_strdup (base_uri);
485
486         g_signal_emit (group, signals[CHANGED], 0);
487 }
488
489 void e_source_group_set_readonly (ESourceGroup *group,
490                                   gboolean      readonly)
491 {
492         GSList *i;
493         
494         g_return_if_fail (E_IS_SOURCE_GROUP (group));
495         
496         if (group->priv->readonly)
497                 return;
498         
499         if (group->priv->readonly == readonly)
500                 return;
501
502         group->priv->readonly = readonly;
503         for (i = group->priv->sources; i != NULL; i = i->next)
504                 e_source_set_readonly (E_SOURCE (i->data), readonly);   
505
506         g_signal_emit (group, signals[CHANGED], 0);
507 }
508
509 const char *
510 e_source_group_peek_uid (ESourceGroup *group)
511 {
512         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
513
514         return group->priv->uid;
515 }
516
517 const char *
518 e_source_group_peek_name (ESourceGroup *group)
519 {
520         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
521
522         return group->priv->name;
523 }
524
525 const char *
526 e_source_group_peek_base_uri (ESourceGroup *group)
527 {
528         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
529
530         return group->priv->base_uri;
531 }
532
533 gboolean
534 e_source_group_get_readonly (ESourceGroup *group)
535 {
536         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
537
538         return group->priv->readonly;
539 }
540
541 GSList *
542 e_source_group_peek_sources (ESourceGroup *group)
543 {
544         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
545
546         return group->priv->sources;
547 }
548
549 ESource *
550 e_source_group_peek_source_by_uid (ESourceGroup *group,
551                                    const char *uid)
552 {
553         GSList *p;
554
555         for (p = group->priv->sources; p != NULL; p = p->next) {
556                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
557                         return E_SOURCE (p->data);
558         }
559
560         return NULL;
561 }
562
563 ESource *
564 e_source_group_peek_source_by_name (ESourceGroup *group,
565                                     const char *name)
566 {
567         GSList *p;
568
569         for (p = group->priv->sources; p != NULL; p = p->next) {
570                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
571                         return E_SOURCE (p->data);
572         }
573
574         return NULL;
575 }
576
577 gboolean
578 e_source_group_add_source (ESourceGroup *group,
579                            ESource *source,
580                            int position)
581 {
582         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
583
584         if (group->priv->readonly)
585                 return FALSE;
586         
587         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
588                 return FALSE;
589
590         e_source_set_group (source, group);
591         e_source_set_readonly (source, group->priv->readonly);
592         g_object_ref (source);
593
594         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
595
596         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
597         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
598         g_signal_emit (group, signals[CHANGED], 0);
599
600         return TRUE;
601 }
602
603 gboolean
604 e_source_group_remove_source (ESourceGroup *group,
605                               ESource *source)
606 {
607         GSList *p;
608
609         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
610         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
611
612         if (group->priv->readonly)
613                 return FALSE;
614
615         for (p = group->priv->sources; p != NULL; p = p->next) {
616                 if (E_SOURCE (p->data) == source) {
617                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
618                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
619                         g_signal_emit (group, signals[CHANGED], 0);
620                         return TRUE;
621                 }
622         }
623
624         return FALSE;
625 }
626
627 gboolean
628 e_source_group_remove_source_by_uid (ESourceGroup *group,
629                                      const char *uid)
630 {
631         GSList *p;
632
633         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
634         g_return_val_if_fail (uid != NULL, FALSE);
635
636         if (group->priv->readonly)
637                 return FALSE;
638         
639         for (p = group->priv->sources; p != NULL; p = p->next) {
640                 ESource *source = E_SOURCE (p->data);
641
642                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
643                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
644                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
645                         g_signal_emit (group, signals[CHANGED], 0);
646                         return TRUE;
647                 }
648         }
649
650         return FALSE;
651 }
652
653
654 char *
655 e_source_group_to_xml (ESourceGroup *group)
656 {
657         xmlDocPtr doc;
658         xmlNodePtr root;
659         xmlChar *xml_buffer;
660         char *returned_buffer;
661         int xml_buffer_size;
662         GSList *p;
663
664         doc = xmlNewDoc ("1.0");
665
666         root = xmlNewDocNode (doc, NULL, "group", NULL);
667         xmlSetProp (root, "uid", e_source_group_peek_uid (group));
668         xmlSetProp (root, "name", e_source_group_peek_name (group));
669         xmlSetProp (root, "base_uri", e_source_group_peek_base_uri (group));
670         xmlSetProp (root, "readonly", group->priv->readonly ? "yes" : "no");
671         
672         xmlDocSetRootElement (doc, root);
673
674         for (p = group->priv->sources; p != NULL; p = p->next)
675                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
676
677         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
678         xmlFreeDoc (doc);
679
680         returned_buffer = g_malloc (xml_buffer_size + 1);
681         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
682         returned_buffer [xml_buffer_size] = '\0';
683         xmlFree (xml_buffer);
684
685         return returned_buffer;
686 }