Use G_DEFINE_TYPE and add some more const keywords.
[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 == name)
439                 return;
440
441         g_free (group->priv->name);
442         group->priv->name = g_strdup (name);
443
444         g_signal_emit (group, signals[CHANGED], 0);
445 }
446
447 void e_source_group_set_base_uri (ESourceGroup *group,
448                                   const char *base_uri)
449 {
450         g_return_if_fail (E_IS_SOURCE_GROUP (group));
451         g_return_if_fail (base_uri != NULL);
452         
453         if (group->priv->readonly)
454                 return;
455         
456         if (group->priv->base_uri == base_uri)
457                 return;
458
459         g_free (group->priv->base_uri);
460         group->priv->base_uri = g_strdup (base_uri);
461
462         g_signal_emit (group, signals[CHANGED], 0);
463 }
464
465 void e_source_group_set_readonly (ESourceGroup *group,
466                                   gboolean      readonly)
467 {
468         GSList *i;
469         
470         g_return_if_fail (E_IS_SOURCE_GROUP (group));
471         
472         if (group->priv->readonly)
473                 return;
474         
475         if (group->priv->readonly == readonly)
476                 return;
477
478         group->priv->readonly = readonly;
479         for (i = group->priv->sources; i != NULL; i = i->next)
480                 e_source_set_readonly (E_SOURCE (i->data), readonly);   
481
482         g_signal_emit (group, signals[CHANGED], 0);
483 }
484
485 const char *
486 e_source_group_peek_uid (ESourceGroup *group)
487 {
488         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
489
490         return group->priv->uid;
491 }
492
493 const char *
494 e_source_group_peek_name (ESourceGroup *group)
495 {
496         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
497
498         return group->priv->name;
499 }
500
501 const char *
502 e_source_group_peek_base_uri (ESourceGroup *group)
503 {
504         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
505
506         return group->priv->base_uri;
507 }
508
509 gboolean
510 e_source_group_get_readonly (ESourceGroup *group)
511 {
512         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
513
514         return group->priv->readonly;
515 }
516
517 GSList *
518 e_source_group_peek_sources (ESourceGroup *group)
519 {
520         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
521
522         return group->priv->sources;
523 }
524
525 ESource *
526 e_source_group_peek_source_by_uid (ESourceGroup *group,
527                                    const char *uid)
528 {
529         GSList *p;
530
531         for (p = group->priv->sources; p != NULL; p = p->next) {
532                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
533                         return E_SOURCE (p->data);
534         }
535
536         return NULL;
537 }
538
539 ESource *
540 e_source_group_peek_source_by_name (ESourceGroup *group,
541                                     const char *name)
542 {
543         GSList *p;
544
545         for (p = group->priv->sources; p != NULL; p = p->next) {
546                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
547                         return E_SOURCE (p->data);
548         }
549
550         return NULL;
551 }
552
553 gboolean
554 e_source_group_add_source (ESourceGroup *group,
555                            ESource *source,
556                            int position)
557 {
558         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
559
560         if (group->priv->readonly)
561                 return FALSE;
562         
563         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
564                 return FALSE;
565
566         e_source_set_group (source, group);
567         e_source_set_readonly (source, group->priv->readonly);
568         g_object_ref (source);
569
570         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
571
572         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
573         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
574         g_signal_emit (group, signals[CHANGED], 0);
575
576         return TRUE;
577 }
578
579 gboolean
580 e_source_group_remove_source (ESourceGroup *group,
581                               ESource *source)
582 {
583         GSList *p;
584
585         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
586         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
587
588         if (group->priv->readonly)
589                 return FALSE;
590
591         for (p = group->priv->sources; p != NULL; p = p->next) {
592                 if (E_SOURCE (p->data) == source) {
593                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
594                         g_signal_handlers_disconnect_by_func (source,
595                                                               G_CALLBACK (source_changed_callback),
596                                                               group);
597                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
598                         g_signal_emit (group, signals[CHANGED], 0);
599                         return TRUE;
600                 }
601         }
602
603         return FALSE;
604 }
605
606 gboolean
607 e_source_group_remove_source_by_uid (ESourceGroup *group,
608                                      const char *uid)
609 {
610         GSList *p;
611
612         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
613         g_return_val_if_fail (uid != NULL, FALSE);
614
615         if (group->priv->readonly)
616                 return FALSE;
617         
618         for (p = group->priv->sources; p != NULL; p = p->next) {
619                 ESource *source = E_SOURCE (p->data);
620
621                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
622                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
623                         g_signal_handlers_disconnect_by_func (source,
624                                                               G_CALLBACK (source_changed_callback),
625                                                               group);
626                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
627                         g_signal_emit (group, signals[CHANGED], 0);
628                         return TRUE;
629                 }
630         }
631
632         return FALSE;
633 }
634
635
636 char *
637 e_source_group_to_xml (ESourceGroup *group)
638 {
639         xmlDocPtr doc;
640         xmlNodePtr root;
641         xmlChar *xml_buffer;
642         char *returned_buffer;
643         int xml_buffer_size;
644         GSList *p;
645
646         doc = xmlNewDoc ("1.0");
647
648         root = xmlNewDocNode (doc, NULL, "group", NULL);
649         xmlSetProp (root, "uid", e_source_group_peek_uid (group));
650         xmlSetProp (root, "name", e_source_group_peek_name (group));
651         xmlSetProp (root, "base_uri", e_source_group_peek_base_uri (group));
652         xmlSetProp (root, "readonly", group->priv->readonly ? "yes" : "no");
653         
654         xmlDocSetRootElement (doc, root);
655
656         for (p = group->priv->sources; p != NULL; p = p->next)
657                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
658
659         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
660         xmlFreeDoc (doc);
661
662         returned_buffer = g_malloc (xml_buffer_size + 1);
663         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
664         returned_buffer [xml_buffer_size] = '\0';
665         xmlFree (xml_buffer);
666
667         return returned_buffer;
668 }