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