Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-source-list.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-source-list.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., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, 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-source-list.h"
29
30 struct _ESourceListPrivate {
31         GConfClient *gconf_client;
32         char *gconf_path;
33
34         int gconf_notify_id;
35
36         GSList *groups;
37
38         gboolean ignore_group_changed;
39         int sync_idle_id;
40 };
41
42
43 /* Signals.  */
44
45 enum {
46         CHANGED,
47         GROUP_REMOVED,
48         GROUP_ADDED,
49         LAST_SIGNAL
50 };
51 static unsigned int signals[LAST_SIGNAL] = { 0 };
52
53
54 /* Forward declarations.  */
55
56 static gboolean  sync_idle_callback      (ESourceList  *list);
57 static void      group_changed_callback  (ESourceGroup *group,
58                                           ESourceList  *list);
59 static void      conf_changed_callback   (GConfClient  *client,
60                                           unsigned int  connection_id,
61                                           GConfEntry   *entry,
62                                           ESourceList  *list);
63
64
65 /* Utility functions.  */
66
67 static void
68 load_from_gconf (ESourceList *list)
69 {
70         GSList *conf_list, *p, *q;
71         GSList *new_groups_list;
72         GHashTable *new_groups_hash;
73         gboolean changed = FALSE;
74         int pos;
75
76         conf_list = gconf_client_get_list (list->priv->gconf_client,
77                                            list->priv->gconf_path,
78                                            GCONF_VALUE_STRING, NULL);
79
80         new_groups_list = NULL;
81         new_groups_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
82
83         for (p = conf_list, pos = 0; p != NULL; p = p->next, pos++) {
84                 const xmlChar *xml = p->data;
85                 xmlDocPtr xmldoc;
86                 char *group_uid;
87                 ESourceGroup *existing_group;
88
89                 xmldoc = xmlParseDoc (xml);
90                 if (xmldoc == NULL)
91                         continue;
92
93                 group_uid = e_source_group_uid_from_xmldoc (xmldoc);
94                 if (group_uid == NULL) {
95                         xmlFreeDoc (xmldoc);
96                         continue;
97                 }
98
99                 existing_group = e_source_list_peek_group_by_uid (list, group_uid);
100                 if (g_hash_table_lookup (new_groups_hash, existing_group) != NULL) {
101                         xmlFreeDoc (xmldoc);
102                         g_free (group_uid);
103                         continue;
104                 }
105
106                 if (existing_group == NULL) {
107                         ESourceGroup *new_group = e_source_group_new_from_xmldoc (xmldoc);
108
109                         if (new_group != NULL) {
110                                 g_signal_connect (new_group, "changed", G_CALLBACK (group_changed_callback), list);
111                                 new_groups_list = g_slist_prepend (new_groups_list, new_group);
112
113                                 g_hash_table_insert (new_groups_hash, new_group, new_group);
114                                 g_signal_emit (list, signals[GROUP_ADDED], 0, new_group);
115                                 changed = TRUE;
116                         }
117                 } else {
118                         gboolean group_changed;
119
120                         list->priv->ignore_group_changed ++;
121
122                         if (e_source_group_update_from_xmldoc (existing_group, xmldoc, &group_changed)) {
123                                 new_groups_list = g_slist_prepend (new_groups_list, existing_group);
124                                 g_object_ref (existing_group);
125                                 g_hash_table_insert (new_groups_hash, existing_group, existing_group);
126
127                                 if (group_changed)
128                                         changed = TRUE;
129                         }
130
131                         list->priv->ignore_group_changed --;
132                 }
133
134                 xmlFreeDoc (xmldoc);
135                 g_free (group_uid);
136         }
137
138         new_groups_list = g_slist_reverse (new_groups_list);
139
140         g_slist_foreach (conf_list, (GFunc) g_free, NULL);
141         g_slist_free (conf_list);
142
143         /* Emit "group_removed" and disconnect the "changed" signal for all the
144            groups that we haven't found in the new list.  Also, check if the
145            order has changed.  */
146         q = new_groups_list;
147         for (p = list->priv->groups; p != NULL; p = p->next) {
148                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
149
150                 if (g_hash_table_lookup (new_groups_hash, group) == NULL) {
151                         changed = TRUE;
152                         g_signal_emit (list, signals[GROUP_REMOVED], 0, group);
153                         g_signal_handlers_disconnect_by_func (group, group_changed_callback, list);
154                 }
155
156                 if (! changed && q != NULL) {
157                         if (q->data != p->data)
158                                 changed = TRUE;
159                         q = q->next;
160                 }
161         }
162
163         g_hash_table_destroy (new_groups_hash);
164
165         /* Replace the original group list with the new one.  */
166
167         g_slist_foreach (list->priv->groups, (GFunc) g_object_unref, NULL);
168         g_slist_free (list->priv->groups);
169
170         list->priv->groups = new_groups_list;
171
172         /* FIXME if the order changes, the function doesn't notice.  */
173
174         if (changed) 
175                 g_signal_emit (list, signals[CHANGED], 0);
176 }
177
178 static void
179 remove_group (ESourceList *list,
180               ESourceGroup *group)
181 {
182         list->priv->groups = g_slist_remove (list->priv->groups, group);
183
184         g_signal_emit (list, signals[GROUP_REMOVED], 0, group);
185         g_object_unref (group);
186
187         g_signal_emit (list, signals[CHANGED], 0);
188 }
189
190
191 /* Callbacks.  */
192
193 static gboolean
194 sync_idle_callback (ESourceList *list)
195 {
196         GError *error = NULL;
197
198         if (! e_source_list_sync (list, &error)) {
199                 g_warning ("Cannot update \"%s\": %s", list->priv->gconf_path, error->message);
200                 g_error_free (error);
201         }
202
203         list->priv->sync_idle_id= 0;
204
205         return FALSE;
206 }
207
208 static void
209 group_changed_callback (ESourceGroup *group,
210                         ESourceList *list)
211 {
212         if (! list->priv->ignore_group_changed)
213                 g_signal_emit (list, signals[CHANGED], 0);
214
215         if (list->priv->sync_idle_id == 0)
216                 list->priv->sync_idle_id = g_idle_add ((GSourceFunc) sync_idle_callback, list);
217 }
218
219 static void
220 conf_changed_callback (GConfClient *client,
221                        unsigned int connection_id,
222                        GConfEntry *entry,
223                        ESourceList *list)
224 {
225         load_from_gconf (list);
226 }
227
228
229 /* GObject methods.  */
230
231 G_DEFINE_TYPE (ESourceList, e_source_list, G_TYPE_OBJECT);
232
233 static void
234 impl_dispose (GObject *object)
235 {
236         ESourceListPrivate *priv = E_SOURCE_LIST (object)->priv;
237
238         if (priv->sync_idle_id != 0) {
239                 GError *error = NULL;
240
241                 g_source_remove (priv->sync_idle_id);
242                 priv->sync_idle_id = 0;
243                 
244                 if (! e_source_list_sync (E_SOURCE_LIST (object), &error))
245                         g_warning ("Could not update \"%s\": %s",
246                                    priv->gconf_path, error->message);
247         }
248
249         if (priv->groups != NULL) {
250                 GSList *p;
251
252                 for (p = priv->groups; p != NULL; p = p->next)
253                         g_object_unref (p->data);
254
255                 g_slist_free (priv->groups);
256                 priv->groups = NULL;
257         }
258
259         if (priv->gconf_client != NULL) {
260                 if (priv->gconf_notify_id != 0) {
261                         gconf_client_notify_remove (priv->gconf_client,
262                                                     priv->gconf_notify_id);
263                         priv->gconf_notify_id = 0;
264                 }
265
266                 g_object_unref (priv->gconf_client);
267                 priv->gconf_client = NULL;
268         } else {
269                 g_assert_not_reached ();
270         }
271
272         (* G_OBJECT_CLASS (e_source_list_parent_class)->dispose) (object);
273 }
274
275 static void
276 impl_finalize (GObject *object)
277 {
278         ESourceListPrivate *priv = E_SOURCE_LIST (object)->priv;
279
280         if (priv->gconf_notify_id != 0) {
281                 gconf_client_notify_remove (priv->gconf_client,
282                                             priv->gconf_notify_id);
283                 priv->gconf_notify_id = 0;
284         }
285
286         g_free (priv->gconf_path);
287         g_free (priv);
288
289         (* G_OBJECT_CLASS (e_source_list_parent_class)->finalize) (object);
290 }
291
292
293 /* Initialization.  */
294
295 static void
296 e_source_list_class_init (ESourceListClass *class)
297 {
298         GObjectClass *object_class = G_OBJECT_CLASS (class);
299
300         object_class->dispose  = impl_dispose;
301         object_class->finalize = impl_finalize;
302
303         signals[CHANGED] = 
304                 g_signal_new ("changed",
305                               G_OBJECT_CLASS_TYPE (object_class),
306                               G_SIGNAL_RUN_LAST,
307                               G_STRUCT_OFFSET (ESourceListClass, changed),
308                               NULL, NULL,
309                               g_cclosure_marshal_VOID__VOID,
310                               G_TYPE_NONE, 0);
311
312         signals[GROUP_REMOVED] = 
313                 g_signal_new ("group_removed",
314                               G_OBJECT_CLASS_TYPE (object_class),
315                               G_SIGNAL_RUN_LAST,
316                               G_STRUCT_OFFSET (ESourceListClass, group_removed),
317                               NULL, NULL,
318                               g_cclosure_marshal_VOID__OBJECT,
319                               G_TYPE_NONE, 1,
320                               G_TYPE_POINTER);
321
322         signals[GROUP_ADDED] = 
323                 g_signal_new ("group_added",
324                               G_OBJECT_CLASS_TYPE (object_class),
325                               G_SIGNAL_RUN_LAST,
326                               G_STRUCT_OFFSET (ESourceListClass, group_added),
327                               NULL, NULL,
328                               g_cclosure_marshal_VOID__OBJECT,
329                               G_TYPE_NONE, 1,
330                               G_TYPE_POINTER);
331 }
332
333 static void
334 e_source_list_init (ESourceList *source_list)
335 {
336         ESourceListPrivate *priv;
337
338         priv = g_new0 (ESourceListPrivate, 1);
339
340         source_list->priv = priv;
341 }
342
343 /* returns the type */
344 static GType
345 get_source_list_type (void) 
346 {
347         static GType type = 0;
348         
349         if (!type) {
350                 if (!(type = g_type_from_name ("ESourceList")))
351                         type = e_source_list_get_type ();
352         }
353
354         return type;
355 }
356
357 /* Public methods.  */
358
359 ESourceList *
360 e_source_list_new (void)
361 {
362         ESourceList *list = g_object_new (get_source_list_type (), NULL);
363
364         return list;
365 }
366
367 ESourceList *
368 e_source_list_new_for_gconf (GConfClient *client,
369                              const char *path)
370 {
371         ESourceList *list;
372
373         g_return_val_if_fail (GCONF_IS_CLIENT (client), NULL);
374         g_return_val_if_fail (path != NULL, NULL);
375
376         list = g_object_new (get_source_list_type (), NULL);
377
378         list->priv->gconf_path = g_strdup (path);
379         list->priv->gconf_client = client;
380         g_object_ref (client);
381
382         gconf_client_add_dir (client, path, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
383
384         list->priv->gconf_notify_id
385                 = gconf_client_notify_add (client, path,
386                                            (GConfClientNotifyFunc) conf_changed_callback, list,
387                                            NULL, NULL);
388         load_from_gconf (list);
389
390         return list;
391 }
392
393 ESourceList *
394 e_source_list_new_for_gconf_default (const char  *path)
395 {       
396         ESourceList *list;
397
398         g_return_val_if_fail (path != NULL, NULL);
399
400         list = g_object_new (get_source_list_type (), NULL);
401
402         list->priv->gconf_path = g_strdup (path);
403         list->priv->gconf_client = gconf_client_get_default ();
404
405         gconf_client_add_dir (list->priv->gconf_client, path, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
406
407         list->priv->gconf_notify_id
408                 = gconf_client_notify_add (list->priv->gconf_client, path,
409                                            (GConfClientNotifyFunc) conf_changed_callback, list,
410                                            NULL, NULL);
411         load_from_gconf (list);
412
413         return list;
414 }
415
416 GSList *
417 e_source_list_peek_groups (ESourceList *list)
418 {
419         g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
420
421         return list->priv->groups;
422 }
423
424 ESourceGroup *
425 e_source_list_peek_group_by_uid (ESourceList *list,
426                                  const char *uid)
427 {
428         GSList *p;
429
430         g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
431         g_return_val_if_fail (uid != NULL, NULL);
432
433         for (p = list->priv->groups; p != NULL; p = p->next) {
434                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
435
436                 if (strcmp (e_source_group_peek_uid (group), uid) == 0)
437                         return group;
438         }
439
440         return NULL;
441 }
442
443 ESourceGroup *
444 e_source_list_peek_group_by_name (ESourceList *list,
445                                   const char *name)
446 {
447         GSList *p;
448
449         g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
450         g_return_val_if_fail (name != NULL, NULL);
451
452         for (p = list->priv->groups; p != NULL; p = p->next) {
453                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
454
455                 if (strcmp (e_source_group_peek_name (group), name) == 0)
456                         return group;
457         }
458
459         return NULL;
460 }
461
462 ESource *
463 e_source_list_peek_source_by_uid (ESourceList *list,
464                                   const char *uid)
465 {
466         GSList *p;
467
468         g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
469         g_return_val_if_fail (uid != NULL, NULL);
470
471         for (p = list->priv->groups; p != NULL; p = p->next) {
472                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
473                 ESource *source;
474                 
475                 source = e_source_group_peek_source_by_uid (group, uid);
476                 if (source)
477                         return source;
478         }
479
480         return NULL;
481 }
482
483 ESource *
484 e_source_list_peek_source_any (ESourceList *list)
485 {
486         GSList *p;
487
488         g_return_val_if_fail (E_IS_SOURCE_LIST (list), NULL);
489
490         for (p = list->priv->groups; p != NULL; p = p->next) {
491                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
492                 GSList *sources;
493                 
494                 sources = e_source_group_peek_sources (group);
495                 if (sources && sources->data)
496                         return E_SOURCE (sources->data);
497         }
498
499         return NULL;
500 }
501
502 gboolean
503 e_source_list_add_group (ESourceList *list,
504                          ESourceGroup *group,
505                          int position)
506 {
507         g_return_val_if_fail (E_IS_SOURCE_LIST (list), FALSE);
508         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
509
510         if (e_source_list_peek_group_by_uid (list, e_source_group_peek_uid (group)) != NULL)
511                 return FALSE;
512
513         list->priv->groups = g_slist_insert (list->priv->groups, group, position);
514         g_object_ref (group);
515
516         g_signal_connect (group, "changed", G_CALLBACK (group_changed_callback), list);
517
518         g_signal_emit (list, signals[GROUP_ADDED], 0, group);
519         g_signal_emit (list, signals[CHANGED], 0);
520
521         return TRUE;
522 }
523
524 gboolean
525 e_source_list_remove_group (ESourceList *list,
526                             ESourceGroup *group)
527 {
528         g_return_val_if_fail (E_IS_SOURCE_LIST (list), FALSE);
529         g_return_val_if_fail (E_IS_SOURCE_GROUP (group), FALSE);
530
531         if (e_source_list_peek_group_by_uid (list, e_source_group_peek_uid (group)) == NULL)
532                 return FALSE;
533
534         remove_group (list, group);
535         return TRUE;
536 }
537
538 gboolean
539 e_source_list_remove_group_by_uid (ESourceList *list,
540                                     const char *uid)
541 {
542         ESourceGroup *group;
543         
544         g_return_val_if_fail (E_IS_SOURCE_LIST (list), FALSE);
545         g_return_val_if_fail (uid != NULL, FALSE);
546
547         group = e_source_list_peek_group_by_uid (list, uid);
548         if (group== NULL)
549                 return FALSE;
550
551         remove_group (list, group);
552         return TRUE;
553 }
554
555 gboolean
556 e_source_list_remove_source_by_uid (ESourceList *list,
557                                      const char *uid)
558 {
559         GSList *p;
560         
561         g_return_val_if_fail (E_IS_SOURCE_LIST (list), FALSE);
562         g_return_val_if_fail (uid != NULL, FALSE);
563
564         for (p = list->priv->groups; p != NULL; p = p->next) {
565                 ESourceGroup *group = E_SOURCE_GROUP (p->data);
566                 ESource *source;
567                 
568                 source = e_source_group_peek_source_by_uid (group, uid);
569                 if (source)
570                         return e_source_group_remove_source_by_uid (group, uid);
571         }
572
573         return FALSE;
574 }
575
576
577 gboolean
578 e_source_list_sync (ESourceList *list,
579                     GError **error)
580 {
581         GSList *conf_list;
582         GSList *p;
583         gboolean retval;
584
585         g_return_val_if_fail (E_IS_SOURCE_LIST (list), FALSE);
586
587         conf_list = NULL;
588         for (p = list->priv->groups; p != NULL; p = p->next)
589                 conf_list = g_slist_prepend (conf_list, e_source_group_to_xml (E_SOURCE_GROUP (p->data)));
590         conf_list = g_slist_reverse (conf_list);
591
592         if (!e_source_list_is_gconf_updated (list))
593                 retval = gconf_client_set_list (list->priv->gconf_client,
594                                                 list->priv->gconf_path,
595                                                 GCONF_VALUE_STRING,
596                                                 conf_list,
597                                                 error);
598         else
599                 retval = TRUE;
600
601         g_slist_foreach (conf_list, (GFunc) g_free, NULL);
602         g_slist_free (conf_list);
603
604         return retval;
605 }
606
607 gboolean
608 e_source_list_is_gconf_updated (ESourceList *list)
609 {
610         char *source_group_xml = NULL;
611         char *gconf_xml = NULL;
612         char *group_uid = NULL;
613         GSList *conf_list = NULL, *temp = NULL, *p = NULL;
614         xmlDocPtr xmldoc;
615         ESourceGroup *group = NULL;
616         GSList *groups = NULL;  
617         gboolean conf_to_list = TRUE, list_to_conf = TRUE;
618         
619         g_return_val_if_fail (list != NULL, FALSE);
620
621         conf_list = gconf_client_get_list (list->priv->gconf_client,
622                                            list->priv->gconf_path,
623                                            GCONF_VALUE_STRING, NULL);
624
625         /* From conf to list */
626
627         for (temp = conf_list; temp != NULL; temp = temp->next) {
628                 gconf_xml = (char *)temp->data;
629                 xmldoc = xmlParseDoc ((const xmlChar *)gconf_xml);
630
631                 group_uid = e_source_group_uid_from_xmldoc (xmldoc);
632                 group = e_source_list_peek_group_by_uid (list, group_uid);
633                 g_free (group_uid);
634                 xmlFreeDoc (xmldoc);
635
636                 if (group) {
637                         source_group_xml = e_source_group_to_xml (group);
638                         if (!strcmp (gconf_xml, source_group_xml)) {
639                                 g_free (source_group_xml);
640                                 continue;
641                         }
642                         else {
643                                 conf_to_list  = FALSE;
644                                 g_free (source_group_xml);
645                                 break;
646                         }
647                 } else {
648                         conf_to_list = FALSE;   
649                         break;
650                 }
651         }
652
653
654         /* If there is mismatch, free the conf_list and return FALSE */
655         if (!conf_to_list) {
656                 for (p = conf_list; p != NULL ; p = p->next) {
657                         gconf_xml = (char *) p->data;
658                         g_free (gconf_xml);
659                 }
660                 g_slist_free (conf_list);
661                 return FALSE;
662         }
663         
664         groups = e_source_list_peek_groups (list);
665         
666         /* From list to conf */ 
667
668         for (p = groups ; p != NULL; p = p->next) {
669                 group = E_SOURCE_GROUP (p->data);
670                 source_group_xml = e_source_group_to_xml (group);
671
672                 for (temp = conf_list; temp != NULL; temp = temp->next) {
673                         gconf_xml = (char *)temp->data;
674                         if (strcmp (gconf_xml, source_group_xml))
675                                 continue;
676                         else
677                                 break; 
678                 }
679                 g_free (source_group_xml);
680
681                 if (!temp) {
682                         list_to_conf = FALSE;
683                         break;
684                 }
685
686         }
687
688         for (p = conf_list; p != NULL ; p = p->next) {
689                 gconf_xml = (char *) p->data;
690                 g_free (gconf_xml);
691         }
692         g_slist_free (conf_list);
693
694         /* If there is mismatch return FALSE */
695         if (!list_to_conf)
696                 return FALSE;
697         
698         return TRUE;
699 }