Fixes #334807
[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
220         if (!new)
221                 goto done;
222
223         new->priv->uid = g_strdup (uid);
224
225         e_source_group_set_name (new, name);
226         e_source_group_set_base_uri (new, base_uri);
227         
228         for (p = root->children; p != NULL; p = p->next) {
229                 ESource *new_source = e_source_new_from_xml_node (p);
230
231                 if (new_source == NULL) {
232                         g_object_unref (new);
233                         goto done;
234                 }
235                 e_source_group_add_source (new, new_source, -1);
236         }
237
238         e_source_group_set_readonly (new, readonly_str && !strcmp (readonly_str, "yes"));
239
240  done:
241         if (uid != NULL)
242                 xmlFree (uid);
243
244         if (name != NULL)
245                 xmlFree (name);
246         if (base_uri != NULL)
247                 xmlFree (base_uri);
248         if (readonly_str != NULL)
249                 xmlFree (readonly_str);
250         return new;
251 }
252
253 gboolean
254 e_source_group_update_from_xml (ESourceGroup *group,
255                                 const char *xml,
256                                 gboolean *changed_return)
257 {
258         xmlDocPtr xmldoc;
259         gboolean success;
260
261         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
262         g_return_val_if_fail (xml != NULL, FALSE);
263
264         xmldoc = xmlParseDoc ((char *) xml);
265
266         success = e_source_group_update_from_xmldoc (group, xmldoc, changed_return);
267
268         xmlFreeDoc (xmldoc);
269
270         return success;
271 }
272
273 gboolean
274 e_source_group_update_from_xmldoc (ESourceGroup *group,
275                                    xmlDocPtr doc,
276                                    gboolean *changed_return)
277 {
278         GHashTable *new_sources_hash;
279         GSList *new_sources_list = NULL;
280         xmlNodePtr root, nodep;
281         xmlChar *name, *base_uri, *readonly_str;
282         gboolean readonly;
283         gboolean changed = FALSE;
284         GSList *p, *q;
285
286         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
287         g_return_val_if_fail (doc != NULL, FALSE);
288
289         *changed_return = FALSE;
290
291         root = doc->children;
292         if (strcmp (root->name, "group") != 0)
293                 return FALSE;
294
295         name = xmlGetProp (root, "name");
296         if (name == NULL)
297                 return FALSE;
298
299         base_uri = xmlGetProp (root, "base_uri");
300         if (base_uri == NULL) {
301                 xmlFree (name);
302                 return FALSE;
303         }
304
305         if (strcmp (group->priv->name, name) != 0) {
306                 g_free (group->priv->name);
307                 group->priv->name = g_strdup (name);
308                 changed = TRUE;
309         }
310         xmlFree (name);
311
312         if (strcmp (group->priv->base_uri, base_uri) != 0) {
313                 g_free (group->priv->base_uri);
314                 group->priv->base_uri = g_strdup (base_uri);
315                 changed = TRUE;
316         }
317         xmlFree (base_uri);
318
319         readonly_str = xmlGetProp (root, "readonly");
320         readonly = readonly_str && !strcmp (readonly_str, "yes");
321         if (readonly != group->priv->readonly) {
322                 group->priv->readonly = readonly;
323                 changed = TRUE;
324         }
325         xmlFree (readonly_str);
326         
327         new_sources_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
328
329         for (nodep = root->children; nodep != NULL; nodep = nodep->next) {
330                 ESource *existing_source;
331                 char *uid = e_source_uid_from_xml_node (nodep);
332
333                 if (uid == NULL)
334                         continue;
335
336                 existing_source = e_source_group_peek_source_by_uid (group, uid);
337                 if (g_hash_table_lookup (new_sources_hash, existing_source) != NULL)
338                         continue;
339
340                 if (existing_source == NULL) {
341                         ESource *new_source = e_source_new_from_xml_node (nodep);
342
343                         if (new_source != NULL) {
344                                 e_source_set_group (new_source, group);
345                                 g_signal_connect (new_source, "changed", G_CALLBACK (source_changed_callback), group);
346                                 new_sources_list = g_slist_prepend (new_sources_list, new_source);
347
348                                 g_hash_table_insert (new_sources_hash, new_source, new_source);
349
350                                 g_signal_emit (group, signals[SOURCE_ADDED], 0, new_source);
351                                 changed = TRUE;
352                         }
353                 } else {
354                         gboolean source_changed;
355
356                         group->priv->ignore_source_changed ++;
357
358                         if (e_source_update_from_xml_node (existing_source, nodep, &source_changed)) {
359                                 new_sources_list = g_slist_prepend (new_sources_list, existing_source);
360                                 g_object_ref (existing_source);
361                                 g_hash_table_insert (new_sources_hash, existing_source, existing_source);
362
363                                 if (source_changed)
364                                         changed = TRUE;
365                         }
366
367                         group->priv->ignore_source_changed --;
368                 }
369
370                 g_free (uid);
371         }
372
373         new_sources_list = g_slist_reverse (new_sources_list);
374
375         /* Emit "group_removed" and disconnect the "changed" signal for all the
376            groups that we haven't found in the new list.  */
377         q = new_sources_list;
378         for (p = group->priv->sources; p != NULL; p = p->next) {
379                 ESource *source = E_SOURCE (p->data);
380
381                 if (g_hash_table_lookup (new_sources_hash, source) == NULL) {
382                         changed = TRUE;
383
384                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
385                         g_signal_handlers_disconnect_by_func (source, source_changed_callback, group);
386                 }
387
388                 if (! changed && q != NULL) {
389                         if (q->data != p->data)
390                                 changed = TRUE;
391                         q = q->next;
392                 }
393         }
394
395         g_hash_table_destroy (new_sources_hash);
396
397         /* Replace the original group list with the new one.  */
398         g_slist_foreach (group->priv->sources, (GFunc) g_object_unref, NULL);
399         g_slist_free (group->priv->sources);
400
401         group->priv->sources = new_sources_list;
402
403         /* FIXME if the order changes, the function doesn't notice.  */
404
405         if (changed) {
406                 g_signal_emit (group, signals[CHANGED], 0);
407                 *changed_return = TRUE;
408         }
409
410         return TRUE;            /* Success. */
411 }
412
413 char *
414 e_source_group_uid_from_xmldoc (xmlDocPtr doc)
415 {
416         xmlNodePtr root = doc->children;
417         xmlChar *name;
418         char *retval;
419
420         if (strcmp (root->name, "group") != 0)
421                 return NULL;
422
423         name = xmlGetProp (root, "uid");
424         if (name == NULL)
425                 return NULL;
426
427         retval = g_strdup (name);
428         xmlFree (name);
429         return retval;
430 }
431
432 void
433 e_source_group_set_name (ESourceGroup *group,
434                          const char *name)
435 {
436         g_return_if_fail (E_IS_SOURCE_GROUP (group));
437         g_return_if_fail (name != NULL);
438
439         if (group->priv->readonly)
440                 return;
441         
442         if (group->priv->name != NULL &&
443             strcmp (group->priv->name, name) == 0)
444                 return;
445
446         g_free (group->priv->name);
447         group->priv->name = g_strdup (name);
448
449         g_signal_emit (group, signals[CHANGED], 0);
450 }
451
452 void e_source_group_set_base_uri (ESourceGroup *group,
453                                   const char *base_uri)
454 {
455         g_return_if_fail (E_IS_SOURCE_GROUP (group));
456         g_return_if_fail (base_uri != NULL);
457         
458         if (group->priv->readonly)
459                 return;
460         
461         if (group->priv->base_uri == base_uri)
462                 return;
463
464         g_free (group->priv->base_uri);
465         group->priv->base_uri = g_strdup (base_uri);
466
467         g_signal_emit (group, signals[CHANGED], 0);
468 }
469
470 void e_source_group_set_readonly (ESourceGroup *group,
471                                   gboolean      readonly)
472 {
473         GSList *i;
474         
475         g_return_if_fail (E_IS_SOURCE_GROUP (group));
476         
477         if (group->priv->readonly)
478                 return;
479         
480         if (group->priv->readonly == readonly)
481                 return;
482
483         group->priv->readonly = readonly;
484         for (i = group->priv->sources; i != NULL; i = i->next)
485                 e_source_set_readonly (E_SOURCE (i->data), readonly);   
486
487         g_signal_emit (group, signals[CHANGED], 0);
488 }
489
490 const char *
491 e_source_group_peek_uid (ESourceGroup *group)
492 {
493         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
494
495         return group->priv->uid;
496 }
497
498 const char *
499 e_source_group_peek_name (ESourceGroup *group)
500 {
501         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
502
503         return group->priv->name;
504 }
505
506 const char *
507 e_source_group_peek_base_uri (ESourceGroup *group)
508 {
509         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
510
511         return group->priv->base_uri;
512 }
513
514 gboolean
515 e_source_group_get_readonly (ESourceGroup *group)
516 {
517         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
518
519         return group->priv->readonly;
520 }
521
522 GSList *
523 e_source_group_peek_sources (ESourceGroup *group)
524 {
525         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
526
527         return group->priv->sources;
528 }
529
530 ESource *
531 e_source_group_peek_source_by_uid (ESourceGroup *group,
532                                    const char *uid)
533 {
534         GSList *p;
535
536         for (p = group->priv->sources; p != NULL; p = p->next) {
537                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
538                         return E_SOURCE (p->data);
539         }
540
541         return NULL;
542 }
543
544 ESource *
545 e_source_group_peek_source_by_name (ESourceGroup *group,
546                                     const char *name)
547 {
548         GSList *p;
549
550         for (p = group->priv->sources; p != NULL; p = p->next) {
551                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
552                         return E_SOURCE (p->data);
553         }
554
555         return NULL;
556 }
557
558 gboolean
559 e_source_group_add_source (ESourceGroup *group,
560                            ESource *source,
561                            int position)
562 {
563         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
564
565         if (group->priv->readonly)
566                 return FALSE;
567         
568         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
569                 return FALSE;
570
571         e_source_set_group (source, group);
572         e_source_set_readonly (source, group->priv->readonly);
573         g_object_ref (source);
574
575         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
576
577         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
578         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
579         g_signal_emit (group, signals[CHANGED], 0);
580
581         return TRUE;
582 }
583
584 gboolean
585 e_source_group_remove_source (ESourceGroup *group,
586                               ESource *source)
587 {
588         GSList *p;
589
590         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
591         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
592
593         if (group->priv->readonly)
594                 return FALSE;
595
596         for (p = group->priv->sources; p != NULL; p = p->next) {
597                 if (E_SOURCE (p->data) == source) {
598                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
599                         g_signal_handlers_disconnect_by_func (source,
600                                                               G_CALLBACK (source_changed_callback),
601                                                               group);
602                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
603                         g_signal_emit (group, signals[CHANGED], 0);
604                         return TRUE;
605                 }
606         }
607
608         return FALSE;
609 }
610
611 gboolean
612 e_source_group_remove_source_by_uid (ESourceGroup *group,
613                                      const char *uid)
614 {
615         GSList *p;
616
617         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
618         g_return_val_if_fail (uid != NULL, FALSE);
619
620         if (group->priv->readonly)
621                 return FALSE;
622         
623         for (p = group->priv->sources; p != NULL; p = p->next) {
624                 ESource *source = E_SOURCE (p->data);
625
626                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
627                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
628                         g_signal_handlers_disconnect_by_func (source,
629                                                               G_CALLBACK (source_changed_callback),
630                                                               group);
631                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
632                         g_signal_emit (group, signals[CHANGED], 0);
633                         return TRUE;
634                 }
635         }
636
637         return FALSE;
638 }
639
640
641 char *
642 e_source_group_to_xml (ESourceGroup *group)
643 {
644         xmlDocPtr doc;
645         xmlNodePtr root;
646         xmlChar *xml_buffer;
647         char *returned_buffer;
648         int xml_buffer_size;
649         GSList *p;
650
651         doc = xmlNewDoc ("1.0");
652
653         root = xmlNewDocNode (doc, NULL, "group", NULL);
654         xmlSetProp (root, "uid", e_source_group_peek_uid (group));
655         xmlSetProp (root, "name", e_source_group_peek_name (group));
656         xmlSetProp (root, "base_uri", e_source_group_peek_base_uri (group));
657         xmlSetProp (root, "readonly", group->priv->readonly ? "yes" : "no");
658         
659         xmlDocSetRootElement (doc, root);
660
661         for (p = group->priv->sources; p != NULL; p = p->next)
662                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
663
664         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
665         xmlFreeDoc (doc);
666
667         returned_buffer = g_malloc (xml_buffer_size + 1);
668         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
669         returned_buffer [xml_buffer_size] = '\0';
670         xmlFree (xml_buffer);
671
672         return returned_buffer;
673 }