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