Nullify 'new' after dropping its reference, so that we return NULL.
[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-uid.h"
29 #include "e-source-group.h"
30
31 /* Private members.  */
32
33 struct _ESourceGroupPrivate {
34         char *uid;
35         char *name;
36         char *base_uri;
37
38         GSList *sources;
39
40         gboolean ignore_source_changed;
41         gboolean readonly;
42 };
43
44
45 /* Signals.  */
46
47 enum {
48         CHANGED,
49         SOURCE_REMOVED,
50         SOURCE_ADDED,
51         LAST_SIGNAL
52 };
53 static unsigned int signals[LAST_SIGNAL] = { 0 };
54
55
56 /* Callbacks.  */
57
58 static void
59 source_changed_callback (ESource *source,
60                          ESourceGroup *group)
61 {
62         if (! group->priv->ignore_source_changed)
63                 g_signal_emit (group, signals[CHANGED], 0);
64 }
65
66
67 /* GObject methods.  */
68
69 G_DEFINE_TYPE (ESourceGroup, e_source_group, G_TYPE_OBJECT);
70
71 static void
72 impl_dispose (GObject *object)
73 {
74         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
75
76         if (priv->sources != NULL) {
77                 GSList *p;
78
79                 for (p = priv->sources; p != NULL; p = p->next) {
80                         ESource *source = E_SOURCE (p->data);
81
82                         g_signal_handlers_disconnect_by_func (source,
83                                                               G_CALLBACK (source_changed_callback),
84                                                               object);
85                         g_object_unref (source);
86                 }
87
88                 g_slist_free (priv->sources);
89                 priv->sources = NULL;
90         }
91
92         (* G_OBJECT_CLASS (e_source_group_parent_class)->dispose) (object);
93 }
94
95 static void
96 impl_finalize (GObject *object)
97 {
98         ESourceGroupPrivate *priv = E_SOURCE_GROUP (object)->priv;
99
100         g_free (priv->uid);
101         g_free (priv->name);
102         g_free (priv->base_uri);
103         g_free (priv);
104
105         (* G_OBJECT_CLASS (e_source_group_parent_class)->finalize) (object);
106 }
107
108
109 /* Initialization.  */
110
111 static void
112 e_source_group_class_init (ESourceGroupClass *class)
113 {
114         GObjectClass *object_class = G_OBJECT_CLASS (class);
115
116         object_class->dispose  = impl_dispose;
117         object_class->finalize = impl_finalize;
118
119         signals[CHANGED] = 
120                 g_signal_new ("changed",
121                               G_OBJECT_CLASS_TYPE (object_class),
122                               G_SIGNAL_RUN_LAST,
123                               G_STRUCT_OFFSET (ESourceGroupClass, changed),
124                               NULL, NULL,
125                               g_cclosure_marshal_VOID__VOID,
126                               G_TYPE_NONE, 0);
127
128         signals[SOURCE_ADDED] = 
129                 g_signal_new ("source_added",
130                               G_OBJECT_CLASS_TYPE (object_class),
131                               G_SIGNAL_RUN_LAST,
132                               G_STRUCT_OFFSET (ESourceGroupClass, source_added),
133                               NULL, NULL,
134                               g_cclosure_marshal_VOID__OBJECT,
135                               G_TYPE_NONE, 1,
136                               G_TYPE_OBJECT);
137         signals[SOURCE_REMOVED] = 
138                 g_signal_new ("source_removed",
139                               G_OBJECT_CLASS_TYPE (object_class),
140                               G_SIGNAL_RUN_LAST,
141                               G_STRUCT_OFFSET (ESourceGroupClass, source_removed),
142                               NULL, NULL,
143                               g_cclosure_marshal_VOID__OBJECT,
144                               G_TYPE_NONE, 1,
145                               G_TYPE_OBJECT);
146 }
147
148 static void
149 e_source_group_init (ESourceGroup *source_group)
150 {
151         ESourceGroupPrivate *priv;
152
153         priv = g_new0 (ESourceGroupPrivate, 1);
154         source_group->priv = priv;
155 }
156
157 /* Public methods.  */
158
159 ESourceGroup *
160 e_source_group_new (const char *name,
161                     const char *base_uri)
162 {
163         ESourceGroup *new;
164
165         g_return_val_if_fail (name != NULL, NULL);
166         g_return_val_if_fail (base_uri != NULL, NULL);
167
168         new = g_object_new (e_source_group_get_type (), NULL);
169         new->priv->uid = e_uid_new ();
170
171         e_source_group_set_name (new, name);
172         e_source_group_set_base_uri (new, base_uri);
173
174         return new;
175 }
176
177 ESourceGroup *
178 e_source_group_new_from_xml (const char *xml)
179 {
180         xmlDocPtr doc;
181         ESourceGroup *group;
182
183         doc = xmlParseDoc ((char *) xml);
184         if (doc == NULL)
185                 return NULL;
186
187         group = e_source_group_new_from_xmldoc (doc);
188         xmlFreeDoc (doc);
189
190         return group;
191 }
192
193 ESourceGroup *
194 e_source_group_new_from_xmldoc (xmlDocPtr doc)
195 {
196         xmlNodePtr root, p;
197         xmlChar *uid;
198         xmlChar *name;
199         xmlChar *base_uri;
200         xmlChar *readonly_str;
201         ESourceGroup *new = NULL;
202
203         g_return_val_if_fail (doc != NULL, NULL);
204
205         root = doc->children;
206         if (strcmp (root->name, "group") != 0)
207                 return NULL;
208
209         uid = xmlGetProp (root, "uid");
210         name = xmlGetProp (root, "name");
211         base_uri = xmlGetProp (root, "base_uri");
212         readonly_str = xmlGetProp (root, "readonly");
213
214         if (uid == NULL || name == NULL || base_uri == NULL)
215                 goto done;
216
217         new = g_object_new (e_source_group_get_type (), NULL);
218
219         if (!new)
220                 goto done;
221
222         new->priv->uid = g_strdup (uid);
223
224         e_source_group_set_name (new, name);
225         e_source_group_set_base_uri (new, base_uri);
226         
227         for (p = root->children; p != NULL; p = p->next) {
228                 ESource *new_source = e_source_new_from_xml_node (p);
229
230                 if (new_source == NULL) {
231                         g_object_unref (new);
232                         new = NULL;
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 (root && root->name) {
421                 if (strcmp (root->name, "group") != 0)
422                         return NULL;
423         }
424         else 
425                 return NULL;
426
427         name = xmlGetProp (root, "uid");
428         if (name == NULL)
429                 return NULL;
430
431         retval = g_strdup (name);
432         xmlFree (name);
433         return retval;
434 }
435
436 void
437 e_source_group_set_name (ESourceGroup *group,
438                          const char *name)
439 {
440         g_return_if_fail (E_IS_SOURCE_GROUP (group));
441         g_return_if_fail (name != NULL);
442
443         if (group->priv->readonly)
444                 return;
445         
446         if (group->priv->name != NULL &&
447             strcmp (group->priv->name, name) == 0)
448                 return;
449
450         g_free (group->priv->name);
451         group->priv->name = g_strdup (name);
452
453         g_signal_emit (group, signals[CHANGED], 0);
454 }
455
456 void e_source_group_set_base_uri (ESourceGroup *group,
457                                   const char *base_uri)
458 {
459         g_return_if_fail (E_IS_SOURCE_GROUP (group));
460         g_return_if_fail (base_uri != NULL);
461         
462         if (group->priv->readonly)
463                 return;
464         
465         if (group->priv->base_uri == base_uri)
466                 return;
467
468         g_free (group->priv->base_uri);
469         group->priv->base_uri = g_strdup (base_uri);
470
471         g_signal_emit (group, signals[CHANGED], 0);
472 }
473
474 void e_source_group_set_readonly (ESourceGroup *group,
475                                   gboolean      readonly)
476 {
477         GSList *i;
478         
479         g_return_if_fail (E_IS_SOURCE_GROUP (group));
480         
481         if (group->priv->readonly)
482                 return;
483         
484         if (group->priv->readonly == readonly)
485                 return;
486
487         group->priv->readonly = readonly;
488         for (i = group->priv->sources; i != NULL; i = i->next)
489                 e_source_set_readonly (E_SOURCE (i->data), readonly);   
490
491         g_signal_emit (group, signals[CHANGED], 0);
492 }
493
494 const char *
495 e_source_group_peek_uid (ESourceGroup *group)
496 {
497         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
498
499         return group->priv->uid;
500 }
501
502 const char *
503 e_source_group_peek_name (ESourceGroup *group)
504 {
505         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
506
507         return group->priv->name;
508 }
509
510 const char *
511 e_source_group_peek_base_uri (ESourceGroup *group)
512 {
513         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
514
515         return group->priv->base_uri;
516 }
517
518 gboolean
519 e_source_group_get_readonly (ESourceGroup *group)
520 {
521         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
522
523         return group->priv->readonly;
524 }
525
526 GSList *
527 e_source_group_peek_sources (ESourceGroup *group)
528 {
529         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
530
531         return group->priv->sources;
532 }
533
534 ESource *
535 e_source_group_peek_source_by_uid (ESourceGroup *group,
536                                    const char *uid)
537 {
538         GSList *p;
539
540         for (p = group->priv->sources; p != NULL; p = p->next) {
541                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
542                         return E_SOURCE (p->data);
543         }
544
545         return NULL;
546 }
547
548 ESource *
549 e_source_group_peek_source_by_name (ESourceGroup *group,
550                                     const char *name)
551 {
552         GSList *p;
553
554         for (p = group->priv->sources; p != NULL; p = p->next) {
555                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
556                         return E_SOURCE (p->data);
557         }
558
559         return NULL;
560 }
561
562 gboolean
563 e_source_group_add_source (ESourceGroup *group,
564                            ESource *source,
565                            int position)
566 {
567         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
568
569         if (group->priv->readonly)
570                 return FALSE;
571         
572         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
573                 return FALSE;
574
575         e_source_set_group (source, group);
576         e_source_set_readonly (source, group->priv->readonly);
577         g_object_ref (source);
578
579         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
580
581         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
582         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
583         g_signal_emit (group, signals[CHANGED], 0);
584
585         return TRUE;
586 }
587
588 gboolean
589 e_source_group_remove_source (ESourceGroup *group,
590                               ESource *source)
591 {
592         GSList *p;
593
594         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
595         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
596
597         if (group->priv->readonly)
598                 return FALSE;
599
600         for (p = group->priv->sources; p != NULL; p = p->next) {
601                 if (E_SOURCE (p->data) == source) {
602                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
603                         g_signal_handlers_disconnect_by_func (source,
604                                                               G_CALLBACK (source_changed_callback),
605                                                               group);
606                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
607                         g_signal_emit (group, signals[CHANGED], 0);
608                         return TRUE;
609                 }
610         }
611
612         return FALSE;
613 }
614
615 gboolean
616 e_source_group_remove_source_by_uid (ESourceGroup *group,
617                                      const char *uid)
618 {
619         GSList *p;
620
621         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
622         g_return_val_if_fail (uid != NULL, FALSE);
623
624         if (group->priv->readonly)
625                 return FALSE;
626         
627         for (p = group->priv->sources; p != NULL; p = p->next) {
628                 ESource *source = E_SOURCE (p->data);
629
630                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
631                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
632                         g_signal_handlers_disconnect_by_func (source,
633                                                               G_CALLBACK (source_changed_callback),
634                                                               group);
635                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
636                         g_signal_emit (group, signals[CHANGED], 0);
637                         return TRUE;
638                 }
639         }
640
641         return FALSE;
642 }
643
644
645 char *
646 e_source_group_to_xml (ESourceGroup *group)
647 {
648         xmlDocPtr doc;
649         xmlNodePtr root;
650         xmlChar *xml_buffer;
651         char *returned_buffer;
652         int xml_buffer_size;
653         GSList *p;
654
655         doc = xmlNewDoc ("1.0");
656
657         root = xmlNewDocNode (doc, NULL, "group", NULL);
658         xmlSetProp (root, "uid", e_source_group_peek_uid (group));
659         xmlSetProp (root, "name", e_source_group_peek_name (group));
660         xmlSetProp (root, "base_uri", e_source_group_peek_base_uri (group));
661         xmlSetProp (root, "readonly", group->priv->readonly ? "yes" : "no");
662         
663         xmlDocSetRootElement (doc, root);
664
665         for (p = group->priv->sources; p != NULL; p = p->next)
666                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
667
668         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
669         xmlFreeDoc (doc);
670
671         returned_buffer = g_malloc (xml_buffer_size + 1);
672         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
673         returned_buffer [xml_buffer_size] = '\0';
674         xmlFree (xml_buffer);
675
676         return returned_buffer;
677 }