use only libgnome stuff (no ui bits)
[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 };
45
46
47 /* Signals.  */
48
49 enum {
50         CHANGED,
51         SOURCE_REMOVED,
52         SOURCE_ADDED,
53         LAST_SIGNAL
54 };
55 static unsigned int signals[LAST_SIGNAL] = { 0 };
56
57
58 /* Callbacks.  */
59
60 static void
61 source_changed_callback (ESource *source,
62                          ESourceGroup *group)
63 {
64         if (! group->priv->ignore_source_changed)
65                 g_signal_emit (group, signals[CHANGED], 0);
66 }
67
68
69 /* GObject methods.  */
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 (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 (parent_class)->finalize) (object);
106 }
107
108
109 /* Initialization.  */
110
111 static void
112 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         parent_class = g_type_class_peek_parent (class);
120
121         signals[CHANGED] = 
122                 g_signal_new ("changed",
123                               G_OBJECT_CLASS_TYPE (object_class),
124                               G_SIGNAL_RUN_LAST,
125                               G_STRUCT_OFFSET (ESourceGroupClass, changed),
126                               NULL, NULL,
127                               e_data_server_marshal_VOID__VOID,
128                               G_TYPE_NONE, 0);
129
130         signals[SOURCE_ADDED] = 
131                 g_signal_new ("source_added",
132                               G_OBJECT_CLASS_TYPE (object_class),
133                               G_SIGNAL_RUN_LAST,
134                               G_STRUCT_OFFSET (ESourceGroupClass, source_added),
135                               NULL, NULL,
136                               e_data_server_marshal_VOID__OBJECT,
137                               G_TYPE_NONE, 1,
138                               G_TYPE_OBJECT);
139         signals[SOURCE_REMOVED] = 
140                 g_signal_new ("source_removed",
141                               G_OBJECT_CLASS_TYPE (object_class),
142                               G_SIGNAL_RUN_LAST,
143                               G_STRUCT_OFFSET (ESourceGroupClass, source_removed),
144                               NULL, NULL,
145                               e_data_server_marshal_VOID__OBJECT,
146                               G_TYPE_NONE, 1,
147                               G_TYPE_OBJECT);
148 }
149
150 static void
151 init (ESourceGroup *source_group)
152 {
153         ESourceGroupPrivate *priv;
154
155         priv = g_new0 (ESourceGroupPrivate, 1);
156         source_group->priv = priv;
157 }
158
159 GType
160 e_source_group_get_type (void)
161 {
162         static GType e_source_group_type = 0;
163
164         if (!e_source_group_type) {
165                 static GTypeInfo info = {
166                         sizeof (ESourceGroupClass),
167                         (GBaseInitFunc) NULL,
168                         (GBaseFinalizeFunc) NULL,
169                         (GClassInitFunc) class_init,
170                         NULL, NULL,
171                         sizeof (ESourceGroup),
172                         0,
173                         (GInstanceInitFunc) init
174                 };
175                 e_source_group_type = g_type_register_static (G_TYPE_OBJECT, "ESourceGroup", &info, 0);
176         }
177
178         return e_source_group_type;
179 }
180
181 /* Public methods.  */
182
183 ESourceGroup *
184 e_source_group_new (const char *name,
185                     const char *base_uri)
186 {
187         ESourceGroup *new;
188
189         g_return_val_if_fail (name != NULL, NULL);
190         g_return_val_if_fail (base_uri != NULL, NULL);
191
192         new = g_object_new (e_source_group_get_type (), NULL);
193         new->priv->uid = e_uid_new ();
194
195         e_source_group_set_name (new, name);
196         e_source_group_set_base_uri (new, base_uri);
197
198         return new;
199 }
200
201 ESourceGroup *
202 e_source_group_new_from_xml (const char *xml)
203 {
204         xmlDocPtr doc;
205         ESourceGroup *group;
206
207         doc = xmlParseDoc ((char *) xml);
208         if (doc == NULL)
209                 return NULL;
210
211         group = e_source_group_new_from_xmldoc (doc);
212         xmlFreeDoc (doc);
213
214         return group;
215 }
216
217 ESourceGroup *
218 e_source_group_new_from_xmldoc (xmlDocPtr doc)
219 {
220         xmlNodePtr root, p;
221         xmlChar *uid;
222         xmlChar *name;
223         xmlChar *base_uri;
224         ESourceGroup *new = NULL;
225
226         g_return_val_if_fail (doc != NULL, NULL);
227
228         root = doc->children;
229         if (strcmp (root->name, "group") != 0)
230                 return NULL;
231
232         uid = xmlGetProp (root, "uid");
233         name = xmlGetProp (root, "name");
234         base_uri = xmlGetProp (root, "base_uri");
235
236         if (uid == NULL || name == NULL || base_uri == NULL)
237                 goto done;
238
239         new = g_object_new (e_source_group_get_type (), NULL);
240         new->priv->uid = g_strdup (uid);
241
242         e_source_group_set_name (new, name);
243         e_source_group_set_base_uri (new, base_uri);
244
245         for (p = root->children; p != NULL; p = p->next) {
246                 ESource *new_source = e_source_new_from_xml_node (p);
247                 e_source_group_add_source (new, new_source, -1);
248         }
249
250  done:
251         if (name != NULL)
252                 xmlFree (name);
253         if (base_uri != NULL)
254                 xmlFree (base_uri);
255         return new;
256 }
257
258 gboolean
259 e_source_group_update_from_xml (ESourceGroup *group,
260                                 const char *xml,
261                                 gboolean *changed_return)
262 {
263         xmlDocPtr xmldoc;
264         gboolean success;
265
266         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
267         g_return_val_if_fail (xml != NULL, FALSE);
268
269         xmldoc = xmlParseDoc ((char *) xml);
270
271         success = e_source_group_update_from_xmldoc (group, xmldoc, changed_return);
272
273         xmlFreeDoc (xmldoc);
274
275         return success;
276 }
277
278 gboolean
279 e_source_group_update_from_xmldoc (ESourceGroup *group,
280                                    xmlDocPtr doc,
281                                    gboolean *changed_return)
282 {
283         GHashTable *new_sources_hash;
284         GSList *new_sources_list = NULL;
285         xmlNodePtr root, nodep;
286         xmlChar *name, *base_uri;
287         gboolean changed = FALSE;
288         GSList *p, *q;
289
290         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
291         g_return_val_if_fail (doc != NULL, FALSE);
292
293         *changed_return = FALSE;
294
295         root = doc->children;
296         if (strcmp (root->name, "group") != 0)
297                 return FALSE;
298
299         name = xmlGetProp (root, "name");
300         if (name == NULL)
301                 return FALSE;
302
303         base_uri = xmlGetProp (root, "base_uri");
304         if (base_uri == NULL) {
305                 xmlFree (name);
306                 return FALSE;
307         }
308
309         if (strcmp (group->priv->name, name) != 0) {
310                 g_free (group->priv->name);
311                 group->priv->name = g_strdup (name);
312                 changed = TRUE;
313         }
314         xmlFree (name);
315
316         if (strcmp (group->priv->base_uri, base_uri) != 0) {
317                 g_free (group->priv->base_uri);
318                 group->priv->base_uri = g_strdup (base_uri);
319                 changed = TRUE;
320         }
321         xmlFree (base_uri);
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->name == name)
436                 return;
437
438         g_free (group->priv->name);
439         group->priv->name = g_strdup (name);
440
441         g_signal_emit (group, signals[CHANGED], 0);
442 }
443
444 void e_source_group_set_base_uri (ESourceGroup *group,
445                                   const char *base_uri)
446 {
447         g_return_if_fail (E_IS_SOURCE_GROUP (group));
448         g_return_if_fail (base_uri != NULL);
449         
450         if (group->priv->base_uri == base_uri)
451                 return;
452
453         g_free (group->priv->base_uri);
454         group->priv->base_uri = g_strdup (base_uri);
455
456         g_signal_emit (group, signals[CHANGED], 0);
457 }
458
459
460 const char *
461 e_source_group_peek_uid (ESourceGroup *group)
462 {
463         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
464
465         return group->priv->uid;
466 }
467
468 const char *
469 e_source_group_peek_name (ESourceGroup *group)
470 {
471         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
472
473         return group->priv->name;
474 }
475
476 const char *
477 e_source_group_peek_base_uri (ESourceGroup *group)
478 {
479         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
480
481         return group->priv->base_uri;
482 }
483
484
485 GSList *
486 e_source_group_peek_sources (ESourceGroup *group)
487 {
488         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), NULL);
489
490         return group->priv->sources;
491 }
492
493 ESource *
494 e_source_group_peek_source_by_uid (ESourceGroup *group,
495                                    const char *uid)
496 {
497         GSList *p;
498
499         for (p = group->priv->sources; p != NULL; p = p->next) {
500                 if (strcmp (e_source_peek_uid (E_SOURCE (p->data)), uid) == 0)
501                         return E_SOURCE (p->data);
502         }
503
504         return NULL;
505 }
506
507 ESource *
508 e_source_group_peek_source_by_name (ESourceGroup *group,
509                                     const char *name)
510 {
511         GSList *p;
512
513         for (p = group->priv->sources; p != NULL; p = p->next) {
514                 if (strcmp (e_source_peek_name (E_SOURCE (p->data)), name) == 0)
515                         return E_SOURCE (p->data);
516         }
517
518         return NULL;
519 }
520
521 gboolean
522 e_source_group_add_source (ESourceGroup *group,
523                            ESource *source,
524                            int position)
525 {
526         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
527
528         if (e_source_group_peek_source_by_uid (group, e_source_peek_uid (source)) != NULL)
529                 return FALSE;
530
531         e_source_set_group (source, group);
532         g_object_ref (source);
533
534         g_signal_connect (source, "changed", G_CALLBACK (source_changed_callback), group);
535
536         group->priv->sources = g_slist_insert (group->priv->sources, source, position);
537         g_signal_emit (group, signals[SOURCE_ADDED], 0, source);
538         g_signal_emit (group, signals[CHANGED], 0);
539
540         return TRUE;
541 }
542
543 gboolean
544 e_source_group_remove_source (ESourceGroup *group,
545                               ESource *source)
546 {
547         GSList *p;
548
549         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
550         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
551
552         for (p = group->priv->sources; p != NULL; p = p->next) {
553                 if (E_SOURCE (p->data) == source) {
554                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
555                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
556                         g_signal_emit (group, signals[CHANGED], 0);
557                         return TRUE;
558                 }
559         }
560
561         return FALSE;
562 }
563
564 gboolean
565 e_source_group_remove_source_by_uid (ESourceGroup *group,
566                                      const char *uid)
567 {
568         GSList *p;
569
570         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
571         g_return_val_if_fail (uid != NULL, FALSE);
572
573         for (p = group->priv->sources; p != NULL; p = p->next) {
574                 ESource *source = E_SOURCE (p->data);
575
576                 if (strcmp (e_source_peek_uid (source), uid) == 0) {
577                         group->priv->sources = g_slist_remove_link (group->priv->sources, p);
578                         g_signal_emit (group, signals[SOURCE_REMOVED], 0, source);
579                         g_signal_emit (group, signals[CHANGED], 0);
580                         return TRUE;
581                 }
582         }
583
584         return FALSE;
585 }
586
587
588 char *
589 e_source_group_to_xml (ESourceGroup *group)
590 {
591         xmlDocPtr doc;
592         xmlNodePtr root;
593         xmlChar *xml_buffer;
594         char *returned_buffer;
595         int xml_buffer_size;
596         GSList *p;
597
598         doc = xmlNewDoc ("1.0");
599
600         root = xmlNewDocNode (doc, NULL, "group", NULL);
601         xmlSetProp (root, "uid", e_source_group_peek_uid (group));
602         xmlSetProp (root, "name", e_source_group_peek_name (group));
603         xmlSetProp (root, "base_uri", e_source_group_peek_base_uri (group));
604
605         xmlDocSetRootElement (doc, root);
606
607         for (p = group->priv->sources; p != NULL; p = p->next)
608                 e_source_dump_to_xml_node (E_SOURCE (p->data), root);
609
610         xmlDocDumpMemory (doc, &xml_buffer, &xml_buffer_size);
611         xmlFreeDoc (doc);
612
613         returned_buffer = g_malloc (xml_buffer_size + 1);
614         memcpy (returned_buffer, xml_buffer, xml_buffer_size);
615         returned_buffer [xml_buffer_size] = '\0';
616         xmlFree (xml_buffer);
617
618         return returned_buffer;
619 }