updated changelog
[platform/upstream/evolution-data-server.git] / calendar / backends / contacts / e-cal-backend-contacts.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar - iCalendar file backend
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  * Copyright (C) 2003 Gergõ Érdi
6  *
7  * Authors: Federico Mena-Quintero <federico@ximian.com>
8  *          Rodrigo Moya <rodrigo@ximian.com>
9  *          Gergõ Érdi <cactus@cactus.rulez.org>
10  *
11  * This library is free software you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation.
14  *
15  * This library is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18  *for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29
30 #include "e-cal-backend-contacts.h"
31
32 #include <glib/gi18n-lib.h>
33
34 #include <libebook/libebook.h>
35
36 #include "e-source-contacts.h"
37
38 #define E_CAL_BACKEND_CONTACTS_GET_PRIVATE(obj) \
39         (G_TYPE_INSTANCE_GET_PRIVATE \
40         ((obj), E_TYPE_CAL_BACKEND_CONTACTS, ECalBackendContactsPrivate))
41
42 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
43
44 G_DEFINE_TYPE (
45         ECalBackendContacts,
46         e_cal_backend_contacts,
47         E_TYPE_CAL_BACKEND_SYNC)
48
49 typedef enum
50 {
51         CAL_DAYS,
52         CAL_HOURS,
53         CAL_MINUTES
54 } CalUnits;
55
56 /* Private part of the ECalBackendContacts structure */
57 struct _ECalBackendContactsPrivate {
58
59         GRecMutex rec_mutex;            /* guards 'addressbooks' */
60         GHashTable   *addressbooks;     /* UID -> BookRecord */
61         gboolean      addressbook_loaded;
62
63         EBookClientView *book_view;
64         GHashTable *tracked_contacts;   /* UID -> ContactRecord */
65         GRecMutex tracked_contacts_lock;
66
67         /* properties related to track alarm settings for this backend */
68         GSettings *settings;
69         guint notifyid;
70         guint update_alarms_id;
71         gboolean alarm_enabled;
72         gint alarm_interval;
73         CalUnits alarm_units;
74 };
75
76 typedef struct _BookRecord {
77         volatile gint ref_count;
78
79         GMutex lock;
80         ECalBackendContacts *cbc;
81         EBookClient *book_client;
82         EBookClientView *book_view;
83 } BookRecord;
84
85 typedef struct _ContactRecord {
86         ECalBackendContacts *cbc;
87         EBookClient *book_client; /* where it comes from */
88         EContact            *contact;
89         ECalComponent       *comp_birthday, *comp_anniversary;
90 } ContactRecord;
91
92 #define d(x)
93
94 #define ANNIVERSARY_UID_EXT "-anniversary"
95 #define BIRTHDAY_UID_EXT   "-birthday"
96
97 static ECalComponent *
98                 create_birthday                 (ECalBackendContacts *cbc,
99                                                  EContact *contact);
100 static ECalComponent *
101                 create_anniversary              (ECalBackendContacts *cbc,
102                                                  EContact *contact);
103 static void     contacts_modified_cb            (EBookClientView *book_view,
104                                                  const GSList *contacts,
105                                                  gpointer user_data);
106 static void     contacts_added_cb               (EBookClientView *book_view,
107                                                  const GSList *contacts,
108                                                  gpointer user_data);
109 static void     contacts_removed_cb             (EBookClientView *book_view,
110                                                  const GSList *contact_ids,
111                                                  gpointer user_data);
112 static void     e_cal_backend_contacts_add_timezone
113                                                 (ECalBackendSync *backend,
114                                                  EDataCal *cal,
115                                                  GCancellable *cancellable,
116                                                  const gchar *tzobj,
117                                                  GError **perror);
118 static void     setup_alarm                     (ECalBackendContacts *cbc,
119                                                  ECalComponent *comp);
120 static void     book_client_connected_cb        (GObject *source_object,
121                                                  GAsyncResult *result,
122                                                  gpointer user_data);
123
124 static gboolean
125 remove_by_book (gpointer key,
126                 gpointer value,
127                 gpointer user_data)
128 {
129         ContactRecord *cr = value;
130         EBookClient *book_client = user_data;
131
132         return (cr && cr->book_client == book_client);
133 }
134
135 static void
136 create_book_record (ECalBackendContacts *cbc,
137                     ESource *source)
138 {
139         BookRecord *br;
140
141         br = g_slice_new0 (BookRecord);
142         br->ref_count = 1;
143         g_mutex_init (&br->lock);
144         br->cbc = g_object_ref (cbc);
145
146         e_book_client_connect (
147                 source, NULL, book_client_connected_cb, br);
148 }
149
150 static BookRecord *
151 book_record_ref (BookRecord *br)
152 {
153         g_return_val_if_fail (br != NULL, NULL);
154         g_return_val_if_fail (br->ref_count > 0, NULL);
155
156         g_atomic_int_inc (&br->ref_count);
157
158         return br;
159 }
160
161 static void
162 book_record_unref (BookRecord *br)
163 {
164         g_return_if_fail (br != NULL);
165         g_return_if_fail (br->ref_count > 0);
166
167         if (g_atomic_int_dec_and_test (&br->ref_count)) {
168                 g_rec_mutex_lock (&br->cbc->priv->tracked_contacts_lock);
169                 g_hash_table_foreach_remove (
170                         br->cbc->priv->tracked_contacts,
171                         remove_by_book, br->book_client);
172                 g_rec_mutex_unlock (&br->cbc->priv->tracked_contacts_lock);
173
174                 g_mutex_clear (&br->lock);
175                 g_object_unref (br->cbc);
176                 g_object_unref (br->book_client);
177
178                 if (br->book_view != NULL)
179                         g_object_unref (br->book_view);
180
181                 g_slice_free (BookRecord, br);
182         }
183 }
184
185 static void
186 book_record_set_book_view (BookRecord *br,
187                            EBookClientView *book_view)
188 {
189         g_return_if_fail (br != NULL);
190
191         g_mutex_lock (&br->lock);
192
193         if (book_view != NULL)
194                 g_object_ref (book_view);
195
196         if (br->book_view != NULL)
197                 g_object_unref (br->book_view);
198
199         br->book_view = book_view;
200
201         g_mutex_unlock (&br->lock);
202 }
203
204 static void
205 cal_backend_contacts_insert_book_record (ECalBackendContacts *cbc,
206                                          ESource *source,
207                                          BookRecord *br)
208 {
209         g_rec_mutex_lock (&cbc->priv->rec_mutex);
210
211         g_hash_table_insert (
212                 cbc->priv->addressbooks,
213                 g_object_ref (source),
214                 book_record_ref (br));
215
216         g_rec_mutex_unlock (&cbc->priv->rec_mutex);
217 }
218
219 static gboolean
220 cal_backend_contacts_remove_book_record (ECalBackendContacts *cbc,
221                                          ESource *source)
222 {
223         gboolean removed;
224
225         g_rec_mutex_lock (&cbc->priv->rec_mutex);
226
227         removed = g_hash_table_remove (cbc->priv->addressbooks, source);
228
229         g_rec_mutex_unlock (&cbc->priv->rec_mutex);
230
231         return removed;
232 }
233
234 static gpointer
235 book_record_get_view_thread (gpointer user_data)
236 {
237         BookRecord *br;
238         EBookQuery *query;
239         EBookClientView *book_view = NULL;
240         gchar *query_sexp;
241         GError *error = NULL;
242
243         br = user_data;
244         g_return_val_if_fail (br != NULL, NULL);
245
246         book_record_set_book_view (br, NULL);
247
248         query = e_book_query_andv (
249                 e_book_query_orv (
250                         e_book_query_field_exists (E_CONTACT_FILE_AS),
251                         e_book_query_field_exists (E_CONTACT_FULL_NAME),
252                         e_book_query_field_exists (E_CONTACT_GIVEN_NAME),
253                         e_book_query_field_exists (E_CONTACT_NICKNAME),
254                         NULL),
255                 e_book_query_orv (
256                         e_book_query_field_exists (E_CONTACT_BIRTH_DATE),
257                         e_book_query_field_exists (E_CONTACT_ANNIVERSARY),
258                         NULL),
259                 NULL);
260         query_sexp = e_book_query_to_string (query);
261         e_book_query_unref (query);
262
263         if (!e_book_client_get_view_sync (
264                 br->book_client, query_sexp, &book_view, NULL, &error)) {
265
266                 if (!error)
267                         error = g_error_new_literal (
268                                 E_CLIENT_ERROR,
269                                 E_CLIENT_ERROR_OTHER_ERROR,
270                                 _("Unknown error"));
271         }
272
273         /* Sanity check. */
274         g_return_val_if_fail (
275                 ((book_view != NULL) && (error == NULL)) ||
276                 ((book_view == NULL) && (error != NULL)), NULL);
277
278         if (error != NULL) {
279                 ESource *source;
280
281                 source = e_client_get_source (E_CLIENT (br->book_client));
282
283                 g_warning (
284                         "%s: Failed to get book view on '%s': %s",
285                         G_STRFUNC, e_source_get_display_name (source),
286                         error->message);
287
288                 g_clear_error (&error);
289
290                 goto exit;
291         }
292
293         g_signal_connect (
294                 book_view, "objects-added",
295                 G_CALLBACK (contacts_added_cb), br->cbc);
296         g_signal_connect (
297                 book_view, "objects-removed",
298                 G_CALLBACK (contacts_removed_cb), br->cbc);
299         g_signal_connect (
300                 book_view, "objects-modified",
301                 G_CALLBACK (contacts_modified_cb), br->cbc);
302
303         e_book_client_view_start (book_view, NULL);
304
305         book_record_set_book_view (br, book_view);
306
307         g_object_unref (book_view);
308
309 exit:
310         g_free (query_sexp);
311
312         book_record_unref (br);
313
314         return NULL;
315 }
316
317 static void
318 book_client_connected_cb (GObject *source_object,
319                           GAsyncResult *result,
320                           gpointer user_data)
321 {
322         EClient *client;
323         ESource *source;
324         GThread *thread;
325         BookRecord *br = user_data;
326         GError *error = NULL;
327
328         g_return_if_fail (br != NULL);
329
330         client = e_book_client_connect_finish (result, &error);
331
332         /* Sanity check. */
333         g_return_if_fail (
334                 ((client != NULL) && (error == NULL)) ||
335                 ((client == NULL) && (error != NULL)));
336
337         if (error != NULL) {
338                 g_warning ("%s: %s", G_STRFUNC, error->message);
339                 g_error_free (error);
340                 g_slice_free (BookRecord, br);
341                 return;
342         }
343
344         source = e_client_get_source (client);
345         br->book_client = g_object_ref (client);
346         cal_backend_contacts_insert_book_record (br->cbc, source, br);
347
348         thread = g_thread_new (
349                 NULL, book_record_get_view_thread, book_record_ref (br));
350         g_thread_unref (thread);
351
352         g_object_unref (client);
353 }
354
355 /* ContactRecord methods */
356 static ContactRecord *
357 contact_record_new (ECalBackendContacts *cbc,
358                     EBookClient *book_client,
359                     EContact *contact)
360 {
361         ContactRecord *cr = g_new0 (ContactRecord, 1);
362
363         cr->cbc = cbc;
364         cr->book_client = book_client;
365         cr->contact = contact;
366         cr->comp_birthday = create_birthday (cbc, contact);
367         cr->comp_anniversary = create_anniversary (cbc, contact);
368
369         if (cr->comp_birthday)
370                 e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_birthday);
371
372         if (cr->comp_anniversary)
373                 e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_anniversary);
374
375         g_object_ref (G_OBJECT (contact));
376
377         return cr;
378 }
379
380 static void
381 contact_record_free (ContactRecord *cr)
382 {
383         ECalComponentId *id;
384
385         g_object_unref (G_OBJECT (cr->contact));
386
387         /* Remove the birthday event */
388         if (cr->comp_birthday) {
389                 id = e_cal_component_get_id (cr->comp_birthday);
390                 e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_birthday, NULL);
391
392                 e_cal_component_free_id (id);
393                 g_object_unref (G_OBJECT (cr->comp_birthday));
394         }
395
396         /* Remove the anniversary event */
397         if (cr->comp_anniversary) {
398                 id = e_cal_component_get_id (cr->comp_anniversary);
399
400                 e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_anniversary, NULL);
401
402                 e_cal_component_free_id (id);
403                 g_object_unref (G_OBJECT (cr->comp_anniversary));
404         }
405
406         g_free (cr);
407 }
408
409 /* ContactRecordCB methods */
410 typedef struct _ContactRecordCB {
411         ECalBackendContacts *cbc;
412         ECalBackendSExp     *sexp;
413         gboolean             as_string;
414         GSList              *result;
415 } ContactRecordCB;
416
417 static ContactRecordCB *
418 contact_record_cb_new (ECalBackendContacts *cbc,
419                        ECalBackendSExp *sexp,
420                        gboolean as_string)
421 {
422         ContactRecordCB *cb_data = g_new (ContactRecordCB, 1);
423
424         cb_data->cbc = cbc;
425         cb_data->sexp = sexp;
426         cb_data->as_string = as_string;
427         cb_data->result = NULL;
428
429         return cb_data;
430 }
431
432 static void
433 contact_record_cb_free (ContactRecordCB *cb_data,
434                         gboolean can_free_result)
435 {
436         if (can_free_result) {
437                 if (cb_data->as_string)
438                         g_slist_foreach (cb_data->result, (GFunc) g_free, NULL);
439                 g_slist_free (cb_data->result);
440         }
441
442         g_free (cb_data);
443 }
444
445 static void
446 contact_record_cb (gpointer key,
447                    gpointer value,
448                    gpointer user_data)
449 {
450         ETimezoneCache *timezone_cache;
451         ContactRecordCB *cb_data = user_data;
452         ContactRecord   *record = value;
453         gpointer data;
454
455         timezone_cache = E_TIMEZONE_CACHE (cb_data->cbc);
456
457         if (record->comp_birthday && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_birthday, timezone_cache)) {
458                 if (cb_data->as_string)
459                         data = e_cal_component_get_as_string (record->comp_birthday);
460                 else
461                         data = record->comp_birthday;
462
463                 cb_data->result = g_slist_prepend (cb_data->result, data);
464         }
465
466         if (record->comp_anniversary && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_anniversary, timezone_cache)) {
467                 if (cb_data->as_string)
468                         data = e_cal_component_get_as_string (record->comp_anniversary);
469                 else
470                         data = record->comp_anniversary;
471
472                 cb_data->result = g_slist_prepend (cb_data->result, data);
473         }
474 }
475
476 static void
477 source_added_cb (ESourceRegistry *registry,
478                  ESource *source,
479                  ECalBackendContacts *cbc)
480 {
481         ESourceContacts *extension;
482         const gchar *extension_name;
483
484         /* We're only interested in address books. */
485         extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
486         if (!e_source_has_extension (source, extension_name))
487                 return;
488
489         extension_name = E_SOURCE_EXTENSION_CONTACTS_BACKEND;
490         extension = e_source_get_extension (source, extension_name);
491
492         if (extension == NULL)
493                 return;
494
495         if (e_source_contacts_get_include_me (extension))
496                 create_book_record (cbc, source);
497 }
498
499 static void
500 source_removed_cb (ESourceRegistry *registry,
501                    ESource *source,
502                    ECalBackendContacts *cbc)
503 {
504         cal_backend_contacts_remove_book_record (cbc, source);
505 }
506
507 static gboolean
508 cal_backend_contacts_load_sources (gpointer user_data)
509 {
510         ESourceRegistry *registry;
511         ECalBackend *backend;
512         GList *list, *link;
513         const gchar *extension_name;
514
515         backend = E_CAL_BACKEND (user_data);
516         registry = e_cal_backend_get_registry (backend);
517
518         /* Query all address book sources from the registry. */
519
520         extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
521         list = e_source_registry_list_sources (registry, extension_name);
522         for (link = list; link != NULL; link = g_list_next (link))
523                 source_added_cb (
524                         registry, E_SOURCE (link->data),
525                         E_CAL_BACKEND_CONTACTS (backend));
526         g_list_free_full (list, (GDestroyNotify) g_object_unref);
527
528         g_signal_connect (
529                 registry, "source-added",
530                 G_CALLBACK (source_added_cb), backend);
531
532         g_signal_connect (
533                 registry, "source-removed",
534                 G_CALLBACK (source_removed_cb), backend);
535
536         return FALSE;
537 }
538
539 /************************************************************************************/
540
541 static void
542 contacts_modified_cb (EBookClientView *book_view,
543                       const GSList *contacts,
544                       gpointer user_data)
545 {
546         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
547         EBookClient *book_client;
548         const GSList *ii;
549
550         book_client = e_book_client_view_ref_client (book_view);
551         if (book_client == NULL)
552                 return;
553
554         g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
555
556         for (ii = contacts; ii; ii = ii->next) {
557                 EContact *contact = E_CONTACT (ii->data);
558                 const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
559                 EContactDate *birthday, *anniversary;
560
561                 /* Because this is a change of contact, then always remove old tracked data
562                  * and if possible, add with (possibly) new values.
563                 */
564                 g_hash_table_remove (cbc->priv->tracked_contacts, (gchar *) uid);
565
566                 birthday = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
567                 anniversary = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
568
569                 if (birthday || anniversary) {
570                         ContactRecord *cr = contact_record_new (cbc, book_client, contact);
571                         g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
572                 }
573
574                 e_contact_date_free (birthday);
575                 e_contact_date_free (anniversary);
576         }
577
578         g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
579
580         g_object_unref (book_client);
581 }
582
583 static void
584 contacts_added_cb (EBookClientView *book_view,
585                    const GSList *contacts,
586                    gpointer user_data)
587 {
588         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
589         EBookClient *book_client;
590         const GSList *ii;
591
592         book_client = e_book_client_view_ref_client (book_view);
593         if (book_client == NULL)
594                 return;
595
596         g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
597
598         /* See if any new contacts have BIRTHDAY or ANNIVERSARY fields */
599         for (ii = contacts; ii; ii = ii->next) {
600                 EContact *contact = E_CONTACT (ii->data);
601                 EContactDate *birthday, *anniversary;
602
603                 birthday = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
604                 anniversary = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
605
606                 if (birthday || anniversary) {
607                         ContactRecord *cr = contact_record_new (cbc, book_client, contact);
608                         const gchar    *uid = e_contact_get_const (contact, E_CONTACT_UID);
609
610                         g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
611                 }
612
613                 e_contact_date_free (birthday);
614                 e_contact_date_free (anniversary);
615         }
616
617         g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
618
619         g_object_unref (book_client);
620 }
621
622 static void
623 contacts_removed_cb (EBookClientView *book_view,
624                      const GSList *contact_ids,
625                      gpointer user_data)
626 {
627         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
628         const GSList *ii;
629
630         g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
631
632         /* Stop tracking these */
633         for (ii = contact_ids; ii; ii = ii->next)
634                 g_hash_table_remove (cbc->priv->tracked_contacts, ii->data);
635
636         g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
637 }
638
639 /************************************************************************************/
640 static struct icaltimetype
641 cdate_to_icaltime (EContactDate *cdate)
642 {
643         struct icaltimetype ret = icaltime_null_time ();
644
645         ret.year = cdate->year;
646         ret.month = cdate->month;
647         ret.day = cdate->day;
648         ret.is_date = TRUE;
649         ret.is_utc = FALSE;
650         ret.zone = NULL;
651         ret.is_daylight = FALSE;
652
653         ret.hour = ret.minute = ret.second = 0;
654
655         return ret;
656 }
657
658 static void
659 manage_comp_alarm_update (ECalBackendContacts *cbc,
660                           ECalComponent *comp)
661 {
662         gchar *old_comp_str, *new_comp_str;
663         ECalComponent *old_comp;
664
665         g_return_if_fail (cbc != NULL);
666         g_return_if_fail (comp != NULL);
667
668         old_comp = e_cal_component_clone (comp);
669         setup_alarm (cbc, comp);
670
671         old_comp_str = e_cal_component_get_as_string (old_comp);
672         new_comp_str = e_cal_component_get_as_string (comp);
673
674         /* check if component changed and notify if so */
675         if (old_comp_str && new_comp_str && !g_str_equal (old_comp_str, new_comp_str))
676                 e_cal_backend_notify_component_modified (E_CAL_BACKEND (cbc), old_comp, comp);
677
678         g_free (old_comp_str);
679         g_free (new_comp_str);
680         g_object_unref (old_comp);
681 }
682
683 static void
684 update_alarm_cb (gpointer key,
685                  gpointer value,
686                  gpointer user_data)
687 {
688         ECalBackendContacts *cbc = user_data;
689         ContactRecord   *record = value;
690
691         g_return_if_fail (cbc != NULL);
692         g_return_if_fail (record != NULL);
693
694         if (record->comp_birthday)
695                 manage_comp_alarm_update (cbc, record->comp_birthday);
696
697         if (record->comp_anniversary)
698                 manage_comp_alarm_update (cbc, record->comp_anniversary);
699 }
700
701 static gboolean
702 update_tracked_alarms_cb (gpointer user_data)
703 {
704         ECalBackendContacts *cbc = user_data;
705
706         g_return_val_if_fail (cbc != NULL, FALSE);
707
708         g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
709         g_hash_table_foreach (cbc->priv->tracked_contacts, update_alarm_cb, cbc);
710         g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
711
712         cbc->priv->update_alarms_id = 0;
713
714         return FALSE;
715 }
716
717 #define BA_CONF_ENABLED         "contacts-reminder-enabled"
718 #define BA_CONF_INTERVAL        "contacts-reminder-interval"
719 #define BA_CONF_UNITS           "contacts-reminder-units"
720
721 static void
722 alarm_config_changed_cb (GSettings *settings,
723                          const gchar *key,
724                          gpointer user_data)
725 {
726         ECalBackendContacts *cbc = user_data;
727
728         g_return_if_fail (cbc != NULL);
729
730         if (g_strcmp0 (key, BA_CONF_ENABLED) != 0 &&
731             g_strcmp0 (key, BA_CONF_INTERVAL) != 0 &&
732             g_strcmp0 (key, BA_CONF_UNITS) != 0)
733                 return;
734
735         setup_alarm (cbc, NULL);
736
737         if (!cbc->priv->update_alarms_id)
738                 cbc->priv->update_alarms_id = g_idle_add (update_tracked_alarms_cb, cbc);
739 }
740
741 /* When called with NULL, then just refresh local static variables on setup change from the user. */
742 static void
743 setup_alarm (ECalBackendContacts *cbc,
744              ECalComponent *comp)
745 {
746         ECalComponentAlarm *alarm;
747         ECalComponentAlarmTrigger trigger;
748         ECalComponentText summary;
749
750         g_return_if_fail (cbc != NULL);
751
752         if (!comp || cbc->priv->alarm_interval == -1) {
753                 gchar *str;
754
755                 if (cbc->priv->alarm_interval == -1) {
756                         /* initial setup, hook callback for changes too */
757                         cbc->priv->notifyid = g_signal_connect (cbc->priv->settings,
758                                 "changed", G_CALLBACK (alarm_config_changed_cb), cbc);
759                 }
760
761                 cbc->priv->alarm_enabled = g_settings_get_boolean (cbc->priv->settings, BA_CONF_ENABLED);
762                 cbc->priv->alarm_interval = g_settings_get_int (cbc->priv->settings, BA_CONF_INTERVAL);
763
764                 str = g_settings_get_string (cbc->priv->settings, BA_CONF_UNITS);
765                 if (str && !strcmp (str, "days"))
766                         cbc->priv->alarm_units = CAL_DAYS;
767                 else if (str && !strcmp (str, "hours"))
768                         cbc->priv->alarm_units = CAL_HOURS;
769                 else
770                         cbc->priv->alarm_units = CAL_MINUTES;
771
772                 g_free (str);
773
774                 if (cbc->priv->alarm_interval <= 0)
775                         cbc->priv->alarm_interval = 1;
776
777                 if (!comp)
778                         return;
779         }
780
781         /* ensure no alarms left */
782         e_cal_component_remove_all_alarms (comp);
783
784         /* do not want alarms, return */
785         if (!cbc->priv->alarm_enabled)
786                 return;
787
788         alarm = e_cal_component_alarm_new ();
789         e_cal_component_get_summary (comp, &summary);
790         e_cal_component_alarm_set_description (alarm, &summary);
791         e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
792
793         trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
794
795         memset (&trigger.u.rel_duration, 0, sizeof (trigger.u.rel_duration));
796
797         trigger.u.rel_duration.is_neg = TRUE;
798
799         switch (cbc->priv->alarm_units) {
800         case CAL_MINUTES:
801                 trigger.u.rel_duration.minutes = cbc->priv->alarm_interval;
802                 break;
803
804         case CAL_HOURS:
805                 trigger.u.rel_duration.hours = cbc->priv->alarm_interval;
806                 break;
807
808         case CAL_DAYS:
809                 trigger.u.rel_duration.days = cbc->priv->alarm_interval;
810                 break;
811
812         default:
813                 g_warning ("%s: wrong units %d\n", G_STRFUNC, cbc->priv->alarm_units);
814                 e_cal_component_alarm_free (alarm);
815                 return;
816         }
817
818         e_cal_component_alarm_set_trigger (alarm, trigger);
819         e_cal_component_add_alarm (comp, alarm);
820         e_cal_component_alarm_free (alarm);
821 }
822
823 #undef BA_CONF_ENABLED
824 #undef BA_CONF_INTERVAL
825 #undef BA_CONF_UNITS
826
827 /* Contact -> Event creator */
828 static ECalComponent *
829 create_component (ECalBackendContacts *cbc,
830                   const gchar *uid,
831                   EContactDate *cdate,
832                   const gchar *summary)
833 {
834         ECalComponent             *cal_comp;
835         ECalComponentText          comp_summary;
836         icalcomponent             *ical_comp;
837         icalproperty              *prop;
838         struct icaltimetype        itt;
839         ECalComponentDateTime      dt;
840         struct icalrecurrencetype  r;
841         gchar                     *since_year;
842         GSList recur_list;
843
844         g_return_val_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbc), NULL);
845
846         if (!cdate)
847                 return NULL;
848
849         ical_comp = icalcomponent_new (ICAL_VEVENT_COMPONENT);
850
851         since_year = g_strdup_printf ("%04d", cdate->year);
852         prop = icalproperty_new_x (since_year);
853         icalproperty_set_x_name (prop, "X-EVOLUTION-SINCE-YEAR");
854         icalcomponent_add_property (ical_comp, prop);
855         g_free (since_year);
856
857         /* Create the event object */
858         cal_comp = e_cal_component_new ();
859         e_cal_component_set_icalcomponent (cal_comp, ical_comp);
860
861         /* Set uid */
862         d (g_message ("Creating UID: %s", uid));
863         e_cal_component_set_uid (cal_comp, uid);
864
865         /* Set all-day event's date from contact data */
866         itt = cdate_to_icaltime (cdate);
867         dt.value = &itt;
868         dt.tzid = NULL;
869         e_cal_component_set_dtstart (cal_comp, &dt);
870
871         itt = cdate_to_icaltime (cdate);
872         icaltime_adjust (&itt, 1, 0, 0, 0);
873         dt.value = &itt;
874         dt.tzid = NULL;
875         /* We have to add 1 day to DTEND, as it is not inclusive. */
876         e_cal_component_set_dtend (cal_comp, &dt);
877
878         /* Create yearly recurrence */
879         icalrecurrencetype_clear (&r);
880         r.freq = ICAL_YEARLY_RECURRENCE;
881         r.interval = 1;
882         recur_list.data = &r;
883         recur_list.next = NULL;
884         e_cal_component_set_rrule_list (cal_comp, &recur_list);
885
886         /* Create summary */
887         comp_summary.value = summary;
888         comp_summary.altrep = NULL;
889         e_cal_component_set_summary (cal_comp, &comp_summary);
890
891         /* Set category and visibility */
892         if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
893                 e_cal_component_set_categories (cal_comp, _("Anniversary"));
894         else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
895                 e_cal_component_set_categories (cal_comp, _("Birthday"));
896
897         e_cal_component_set_classification (cal_comp, E_CAL_COMPONENT_CLASS_PRIVATE);
898
899         /* Birthdays/anniversaries are shown as free time */
900         e_cal_component_set_transparency (cal_comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT);
901
902         /* setup alarms if required */
903         setup_alarm (cbc, cal_comp);
904
905         /* Don't forget to call commit()! */
906         e_cal_component_commit_sequence (cal_comp);
907
908         return cal_comp;
909 }
910
911 static ECalComponent *
912 create_birthday (ECalBackendContacts *cbc,
913                  EContact *contact)
914 {
915         EContactDate  *cdate;
916         ECalComponent *cal_comp;
917         gchar          *summary;
918         const gchar    *name;
919         gchar *uid;
920
921         cdate = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
922         name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
923         if (!name || !*name)
924                 name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
925         if (!name || !*name)
926                 name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
927         if (!name)
928                 name = "";
929
930         uid = g_strdup_printf ("%s%s", (gchar *) e_contact_get_const (contact, E_CONTACT_UID), BIRTHDAY_UID_EXT);
931         summary = g_strdup_printf (_("Birthday: %s"), name);
932
933         cal_comp = create_component (cbc, uid, cdate, summary);
934
935         e_contact_date_free (cdate);
936         g_free (uid);
937         g_free (summary);
938
939         return cal_comp;
940 }
941
942 static ECalComponent *
943 create_anniversary (ECalBackendContacts *cbc,
944                     EContact *contact)
945 {
946         EContactDate  *cdate;
947         ECalComponent *cal_comp;
948         gchar          *summary;
949         const gchar    *name;
950         gchar *uid;
951
952         cdate = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
953         name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
954         if (!name || !*name)
955                 name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
956         if (!name || !*name)
957                 name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
958         if (!name)
959                 name = "";
960
961         uid = g_strdup_printf ("%s%s", (gchar *) e_contact_get_const (contact, E_CONTACT_UID), ANNIVERSARY_UID_EXT);
962         summary = g_strdup_printf (_("Anniversary: %s"), name);
963
964         cal_comp = create_component (cbc, uid, cdate, summary);
965
966         e_contact_date_free (cdate);
967         g_free (uid);
968         g_free (summary);
969
970         return cal_comp;
971 }
972
973 /************************************************************************************/
974 /* Calendar backend method implementations */
975
976 /* First the empty stubs */
977
978 static gchar *
979 e_cal_backend_contacts_get_backend_property (ECalBackend *backend,
980                                              const gchar *prop_name)
981 {
982         g_return_val_if_fail (prop_name != NULL, FALSE);
983
984         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
985                 return NULL;
986
987         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
988                    g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
989                 /* A contact backend has no particular email address associated
990                  * with it (although that would be a useful feature some day).
991                  */
992                 return NULL;
993
994         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
995                 return NULL;
996         }
997
998         /* Chain up to parent's get_backend_property() method. */
999         return E_CAL_BACKEND_CLASS (e_cal_backend_contacts_parent_class)->
1000                 get_backend_property (backend, prop_name);
1001 }
1002
1003 static void
1004 e_cal_backend_contacts_get_object (ECalBackendSync *backend,
1005                                    EDataCal *cal,
1006                                    GCancellable *cancellable,
1007                                    const gchar *uid,
1008                                    const gchar *rid,
1009                                    gchar **object,
1010                                    GError **perror)
1011 {
1012         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
1013         ECalBackendContactsPrivate *priv = cbc->priv;
1014         ContactRecord *record;
1015         gchar *real_uid;
1016
1017         if (!uid) {
1018                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
1019                 return;
1020         } else if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
1021                 real_uid = g_strndup (uid, strlen (uid) - strlen (ANNIVERSARY_UID_EXT));
1022         else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
1023                 real_uid = g_strndup (uid, strlen (uid) - strlen (BIRTHDAY_UID_EXT));
1024         else {
1025                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
1026                 return;
1027         }
1028
1029         g_rec_mutex_lock (&priv->tracked_contacts_lock);
1030         record = g_hash_table_lookup (priv->tracked_contacts, real_uid);
1031         g_free (real_uid);
1032
1033         if (!record) {
1034                 g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1035                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
1036                 return;
1037         }
1038
1039         if (record->comp_birthday && g_str_has_suffix (uid, BIRTHDAY_UID_EXT)) {
1040                 *object = e_cal_component_get_as_string (record->comp_birthday);
1041                 g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1042
1043                 d (g_message ("Return birthday: %s", *object));
1044                 return;
1045         }
1046
1047         if (record->comp_anniversary && g_str_has_suffix (uid, ANNIVERSARY_UID_EXT)) {
1048                 *object = e_cal_component_get_as_string (record->comp_anniversary);
1049                 g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1050
1051                 d (g_message ("Return anniversary: %s", *object));
1052                 return;
1053         }
1054
1055         g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1056
1057         d (g_message ("Returning nothing for uid: %s", uid));
1058
1059         g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
1060 }
1061
1062 static void
1063 e_cal_backend_contacts_get_free_busy (ECalBackendSync *backend,
1064                                       EDataCal *cal,
1065                                       GCancellable *cancellable,
1066                                       const GSList *users,
1067                                       time_t start,
1068                                       time_t end,
1069                                       GSList **freebusy,
1070                                       GError **perror)
1071 {
1072         /* Birthdays/anniversaries don't count as busy time */
1073
1074         icalcomponent *vfb = icalcomponent_new_vfreebusy ();
1075         icaltimezone *utc_zone = icaltimezone_get_utc_timezone ();
1076         gchar *calobj;
1077
1078 #if 0
1079         icalproperty *prop;
1080         icalparameter *param;
1081
1082         prop = icalproperty_new_organizer (address);
1083         if (prop != NULL && cn != NULL) {
1084                 param = icalparameter_new_cn (cn);
1085                 icalproperty_add_parameter (prop, param);
1086         }
1087         if (prop != NULL)
1088                 icalcomponent_add_property (vfb, prop);
1089 #endif
1090
1091         icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1092         icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1093
1094         calobj = icalcomponent_as_ical_string_r (vfb);
1095         *freebusy = g_slist_append (NULL, calobj);
1096         icalcomponent_free (vfb);
1097
1098         /* WRITE ME */
1099         /* Success */
1100 }
1101
1102 static void
1103 e_cal_backend_contacts_receive_objects (ECalBackendSync *backend,
1104                                         EDataCal *cal,
1105                                         GCancellable *cancellable,
1106                                         const gchar *calobj,
1107                                         GError **perror)
1108 {
1109         g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1110 }
1111
1112 static void
1113 e_cal_backend_contacts_send_objects (ECalBackendSync *backend,
1114                                      EDataCal *cal,
1115                                      GCancellable *cancellable,
1116                                      const gchar *calobj,
1117                                      GSList **users,
1118                                      gchar **modified_calobj,
1119                                      GError **perror)
1120 {
1121         *users = NULL;
1122         *modified_calobj = NULL;
1123         /* TODO: Investigate this */
1124         g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1125 }
1126
1127 /* Then the real implementations */
1128
1129 static void
1130 e_cal_backend_contacts_notify_online_cb (ECalBackend *backend,
1131                                          GParamSpec *pspec)
1132 {
1133         e_cal_backend_set_writable (backend, FALSE);
1134 }
1135
1136 static void
1137 e_cal_backend_contacts_open (ECalBackendSync *backend,
1138                              EDataCal *cal,
1139                              GCancellable *cancellable,
1140                              gboolean only_if_exists,
1141                              GError **perror)
1142 {
1143         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
1144         ECalBackendContactsPrivate *priv = cbc->priv;
1145
1146         if (priv->addressbook_loaded)
1147                 return;
1148
1149         priv->addressbook_loaded = TRUE;
1150         e_cal_backend_set_writable (E_CAL_BACKEND (backend), FALSE);
1151         e_backend_set_online (E_BACKEND (backend), TRUE);
1152 }
1153
1154 /* Add_timezone handler for the file backend */
1155 static void
1156 e_cal_backend_contacts_add_timezone (ECalBackendSync *backend,
1157                                      EDataCal *cal,
1158                                      GCancellable *cancellable,
1159                                      const gchar *tzobj,
1160                                      GError **error)
1161 {
1162         icalcomponent *tz_comp;
1163         icaltimezone *zone;
1164
1165         tz_comp = icalparser_parse_string (tzobj);
1166         if (!tz_comp) {
1167                 g_propagate_error (error, EDC_ERROR (InvalidObject));
1168                 return;
1169         }
1170
1171         if (icalcomponent_isa (tz_comp) != ICAL_VTIMEZONE_COMPONENT) {
1172                 g_propagate_error (error, EDC_ERROR (InvalidObject));
1173                 return;
1174         }
1175
1176         zone = icaltimezone_new ();
1177         icaltimezone_set_component (zone, tz_comp);
1178         e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (backend), zone);
1179         icaltimezone_free (zone, TRUE);
1180 }
1181
1182 static void
1183 e_cal_backend_contacts_get_object_list (ECalBackendSync *backend,
1184                                         EDataCal *cal,
1185                                         GCancellable *cancellable,
1186                                         const gchar *sexp_string,
1187                                         GSList **objects,
1188                                         GError **perror)
1189 {
1190         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
1191         ECalBackendContactsPrivate *priv = cbc->priv;
1192         ECalBackendSExp *sexp = e_cal_backend_sexp_new (sexp_string);
1193         ContactRecordCB *cb_data;
1194
1195         if (!sexp) {
1196                 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
1197                 return;
1198         }
1199
1200         cb_data = contact_record_cb_new (cbc, sexp, TRUE);
1201
1202         g_rec_mutex_lock (&priv->tracked_contacts_lock);
1203         g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
1204         g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1205
1206         *objects = cb_data->result;
1207
1208         contact_record_cb_free (cb_data, FALSE);
1209 }
1210
1211 static void
1212 e_cal_backend_contacts_start_view (ECalBackend *backend,
1213                                    EDataCalView *query)
1214 {
1215         ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
1216         ECalBackendContactsPrivate *priv = cbc->priv;
1217         ECalBackendSExp *sexp;
1218         ContactRecordCB *cb_data;
1219
1220         sexp = e_data_cal_view_get_sexp (query);
1221         if (!sexp) {
1222                 GError *error = EDC_ERROR (InvalidQuery);
1223                 e_data_cal_view_notify_complete (query, error);
1224                 g_error_free (error);
1225                 return;
1226         }
1227
1228         cb_data = contact_record_cb_new (cbc, sexp, FALSE);
1229
1230         g_rec_mutex_lock (&priv->tracked_contacts_lock);
1231         g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
1232         e_data_cal_view_notify_components_added (query, cb_data->result);
1233         g_rec_mutex_unlock (&priv->tracked_contacts_lock);
1234
1235         contact_record_cb_free (cb_data, TRUE);
1236
1237         e_data_cal_view_notify_complete (query, NULL /* Success */);
1238 }
1239
1240 /***********************************************************************************
1241  */
1242
1243 /* Finalize handler for the contacts backend */
1244 static void
1245 e_cal_backend_contacts_finalize (GObject *object)
1246 {
1247         ECalBackendContactsPrivate *priv;
1248
1249         priv = E_CAL_BACKEND_CONTACTS_GET_PRIVATE (object);
1250
1251         if (priv->update_alarms_id) {
1252                 g_source_remove (priv->update_alarms_id);
1253                 priv->update_alarms_id = 0;
1254         }
1255
1256         g_hash_table_destroy (priv->addressbooks);
1257         g_hash_table_destroy (priv->tracked_contacts);
1258         if (priv->notifyid)
1259                 g_signal_handler_disconnect (priv->settings, priv->notifyid);
1260
1261         g_object_unref (priv->settings);
1262         g_rec_mutex_clear (&priv->rec_mutex);
1263         g_rec_mutex_clear (&priv->tracked_contacts_lock);
1264
1265         /* Chain up to parent's finalize() method. */
1266         G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->finalize (object);
1267 }
1268
1269 static void
1270 e_cal_backend_contacts_dispose (GObject *object)
1271 {
1272         ESourceRegistry *registry;
1273
1274         registry = e_cal_backend_get_registry (E_CAL_BACKEND (object));
1275         g_signal_handlers_disconnect_by_data (registry, object);
1276
1277         /* Chain up to parent's dispose() method. */
1278         G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->dispose (object);
1279 }
1280
1281 static void
1282 e_cal_backend_contacts_constructed (GObject *object)
1283 {
1284         /* Load address book sources from an idle callback
1285          * to avoid deadlocking e_data_factory_ref_backend(). */
1286         g_idle_add_full (
1287                 G_PRIORITY_DEFAULT_IDLE,
1288                 cal_backend_contacts_load_sources,
1289                 g_object_ref (object),
1290                 (GDestroyNotify) g_object_unref);
1291
1292         /* Chain up to parent's constructed() method. */
1293         G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->
1294                 constructed (object);
1295 }
1296
1297 /* Object initialization function for the contacts backend */
1298 static void
1299 e_cal_backend_contacts_init (ECalBackendContacts *cbc)
1300 {
1301         cbc->priv = E_CAL_BACKEND_CONTACTS_GET_PRIVATE (cbc);
1302
1303         g_rec_mutex_init (&cbc->priv->rec_mutex);
1304         g_rec_mutex_init (&cbc->priv->tracked_contacts_lock);
1305
1306         cbc->priv->addressbooks = g_hash_table_new_full (
1307                 (GHashFunc) e_source_hash,
1308                 (GEqualFunc) e_source_equal,
1309                 (GDestroyNotify) g_object_unref,
1310                 (GDestroyNotify) book_record_unref);
1311
1312         cbc->priv->tracked_contacts = g_hash_table_new_full (
1313                 (GHashFunc) g_str_hash,
1314                 (GEqualFunc) g_str_equal,
1315                 (GDestroyNotify) g_free,
1316                 (GDestroyNotify) contact_record_free);
1317
1318         cbc->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
1319         cbc->priv->notifyid = 0;
1320         cbc->priv->update_alarms_id = 0;
1321         cbc->priv->alarm_enabled = FALSE;
1322         cbc->priv->alarm_interval = -1;
1323         cbc->priv->alarm_units = CAL_MINUTES;
1324
1325         g_signal_connect (
1326                 cbc, "notify::online",
1327                 G_CALLBACK (e_cal_backend_contacts_notify_online_cb), NULL);
1328 }
1329
1330 static void
1331 e_cal_backend_contacts_create_objects (ECalBackendSync *backend,
1332                                        EDataCal *cal,
1333                                        GCancellable *cancellable,
1334                                        const GSList *calobjs,
1335                                        GSList **uids,
1336                                        GSList **new_components,
1337                                        GError **perror)
1338 {
1339         g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1340 }
1341
1342 /* Class initialization function for the contacts backend */
1343 static void
1344 e_cal_backend_contacts_class_init (ECalBackendContactsClass *class)
1345 {
1346         GObjectClass *object_class;
1347         ECalBackendClass *backend_class;
1348         ECalBackendSyncClass *sync_class;
1349
1350         g_type_class_add_private (class, sizeof (ECalBackendContactsPrivate));
1351
1352         object_class = (GObjectClass *) class;
1353         backend_class = (ECalBackendClass *) class;
1354         sync_class = (ECalBackendSyncClass *) class;
1355
1356         object_class->finalize = e_cal_backend_contacts_finalize;
1357         object_class->dispose = e_cal_backend_contacts_dispose;
1358         object_class->constructed = e_cal_backend_contacts_constructed;
1359
1360         /* Execute one method at a time. */
1361         backend_class->use_serial_dispatch_queue = TRUE;
1362
1363         backend_class->get_backend_property = e_cal_backend_contacts_get_backend_property;
1364
1365         sync_class->open_sync = e_cal_backend_contacts_open;
1366         sync_class->create_objects_sync = e_cal_backend_contacts_create_objects;
1367         sync_class->receive_objects_sync = e_cal_backend_contacts_receive_objects;
1368         sync_class->send_objects_sync = e_cal_backend_contacts_send_objects;
1369         sync_class->get_object_sync = e_cal_backend_contacts_get_object;
1370         sync_class->get_object_list_sync = e_cal_backend_contacts_get_object_list;
1371         sync_class->add_timezone_sync = e_cal_backend_contacts_add_timezone;
1372         sync_class->get_free_busy_sync = e_cal_backend_contacts_get_free_busy;
1373
1374         backend_class->start_view = e_cal_backend_contacts_start_view;
1375
1376         /* Register our ESource extension. */
1377         E_TYPE_SOURCE_CONTACTS;
1378 }