/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Evolution calendar - iCalendar file backend
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
* Copyright (C) 2003 Gergő Érdi
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Federico Mena-Quintero
* Rodrigo Moya
* Gergő Érdi
*/
#include "evolution-data-server-config.h"
#include
#include "e-cal-backend-contacts.h"
#include
#include
#include
#define EC_ERROR(_code) e_client_error_create (_code, NULL)
#define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
typedef enum
{
CAL_DAYS,
CAL_HOURS,
CAL_MINUTES
} CalUnits;
/* Private part of the ECalBackendContacts structure */
struct _ECalBackendContactsPrivate {
GRecMutex rec_mutex; /* guards 'addressbooks' */
GHashTable *addressbooks; /* UID -> BookRecord */
gboolean addressbook_loaded;
EBookClientView *book_view;
GHashTable *tracked_contacts; /* UID -> ContactRecord */
GRecMutex tracked_contacts_lock;
/* properties related to track alarm settings for this backend */
GSettings *settings;
guint notifyid;
guint update_alarms_id;
gboolean alarm_enabled;
gint alarm_interval;
CalUnits alarm_units;
ESourceRegistryWatcher *registry_watcher;
};
typedef struct _BookRecord {
volatile gint ref_count;
GMutex lock;
ECalBackendContacts *cbc;
EBookClient *book_client;
EBookClientView *book_view;
GCancellable *cancellable;
gboolean online;
gulong notify_online_id;
} BookRecord;
typedef struct _ContactRecord {
ECalBackendContacts *cbc;
EBookClient *book_client; /* where it comes from */
EContact *contact;
ECalComponent *comp_birthday, *comp_anniversary;
} ContactRecord;
G_DEFINE_TYPE_WITH_PRIVATE (
ECalBackendContacts,
e_cal_backend_contacts,
E_TYPE_CAL_BACKEND_SYNC)
#define d(x)
#define ANNIVERSARY_UID_EXT "-anniversary"
#define BIRTHDAY_UID_EXT "-birthday"
static ECalComponent *
create_birthday (ECalBackendContacts *cbc,
EContact *contact);
static ECalComponent *
create_anniversary (ECalBackendContacts *cbc,
EContact *contact);
static void contacts_modified_cb (EBookClientView *book_view,
const GSList *contacts,
gpointer user_data);
static void contacts_added_cb (EBookClientView *book_view,
const GSList *contacts,
gpointer user_data);
static void contacts_removed_cb (EBookClientView *book_view,
const GSList *contact_ids,
gpointer user_data);
static void e_cal_backend_contacts_add_timezone
(ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *tzobj,
GError **perror);
static void setup_alarm (ECalBackendContacts *cbc,
ECalComponent *comp);
static void book_client_connected_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static gboolean
remove_by_book (gpointer key,
gpointer value,
gpointer user_data)
{
ContactRecord *cr = value;
EBookClient *book_client = user_data;
return (cr && cr->book_client == book_client);
}
static void
create_book_record (ECalBackendContacts *cbc,
ESource *source)
{
BookRecord *br;
br = g_slice_new0 (BookRecord);
br->ref_count = 1;
g_mutex_init (&br->lock);
br->cbc = g_object_ref (cbc);
br->cancellable = g_cancellable_new ();
e_book_client_connect (source, 30, br->cancellable, book_client_connected_cb, br);
}
static BookRecord *
book_record_ref (BookRecord *br)
{
g_return_val_if_fail (br != NULL, NULL);
g_return_val_if_fail (br->ref_count > 0, NULL);
g_atomic_int_inc (&br->ref_count);
return br;
}
static void
book_record_unref (BookRecord *br)
{
g_return_if_fail (br != NULL);
g_return_if_fail (br->ref_count > 0);
if (g_atomic_int_dec_and_test (&br->ref_count)) {
g_cancellable_cancel (br->cancellable);
if (br->book_client) {
g_rec_mutex_lock (&br->cbc->priv->tracked_contacts_lock);
g_hash_table_foreach_remove (
br->cbc->priv->tracked_contacts,
remove_by_book, br->book_client);
g_rec_mutex_unlock (&br->cbc->priv->tracked_contacts_lock);
}
g_mutex_lock (&br->lock);
if (br->notify_online_id)
g_signal_handler_disconnect (br->book_client, br->notify_online_id);
g_clear_object (&br->cbc);
g_clear_object (&br->cancellable);
g_clear_object (&br->book_client);
g_clear_object (&br->book_view);
g_mutex_unlock (&br->lock);
g_mutex_clear (&br->lock);
g_slice_free (BookRecord, br);
}
}
static void
cancel_and_unref_book_record (BookRecord *br)
{
g_return_if_fail (br != NULL);
if (br->cancellable)
g_cancellable_cancel (br->cancellable);
book_record_unref (br);
}
static void
book_record_set_book_view (BookRecord *br,
EBookClientView *book_view)
{
g_return_if_fail (br != NULL);
g_mutex_lock (&br->lock);
if (book_view != NULL)
g_object_ref (book_view);
if (br->book_view != NULL)
g_object_unref (br->book_view);
br->book_view = book_view;
g_mutex_unlock (&br->lock);
}
static void
cal_backend_contacts_insert_book_record (ECalBackendContacts *cbc,
ESource *source,
BookRecord *br)
{
g_rec_mutex_lock (&cbc->priv->rec_mutex);
g_hash_table_insert (
cbc->priv->addressbooks,
g_object_ref (source),
book_record_ref (br));
g_rec_mutex_unlock (&cbc->priv->rec_mutex);
}
static gboolean
cal_backend_contacts_remove_book_record (ECalBackendContacts *cbc,
ESource *source)
{
gboolean removed;
g_rec_mutex_lock (&cbc->priv->rec_mutex);
removed = g_hash_table_remove (cbc->priv->addressbooks, source);
g_rec_mutex_unlock (&cbc->priv->rec_mutex);
return removed;
}
static gpointer
book_record_get_view_thread (gpointer user_data)
{
BookRecord *br;
EBookQuery *query;
EBookClientView *book_view = NULL;
gchar *query_sexp;
GError *error = NULL;
br = user_data;
g_return_val_if_fail (br != NULL, NULL);
book_record_set_book_view (br, NULL);
query = e_book_query_andv (
e_book_query_orv (
e_book_query_field_exists (E_CONTACT_FILE_AS),
e_book_query_field_exists (E_CONTACT_FULL_NAME),
e_book_query_field_exists (E_CONTACT_GIVEN_NAME),
e_book_query_field_exists (E_CONTACT_NICKNAME),
NULL),
e_book_query_orv (
e_book_query_field_exists (E_CONTACT_BIRTH_DATE),
e_book_query_field_exists (E_CONTACT_ANNIVERSARY),
NULL),
NULL);
query_sexp = e_book_query_to_string (query);
e_book_query_unref (query);
if (!e_book_client_get_view_sync (br->book_client, query_sexp, &book_view, br->cancellable, &error)) {
if (!error)
error = g_error_new_literal (
E_CLIENT_ERROR,
E_CLIENT_ERROR_OTHER_ERROR,
_("Unknown error"));
}
/* Sanity check. */
g_return_val_if_fail (
((book_view != NULL) && (error == NULL)) ||
((book_view == NULL) && (error != NULL)), NULL);
if (error != NULL) {
ESource *source;
source = e_client_get_source (E_CLIENT (br->book_client));
g_warning (
"%s: Failed to get book view on '%s': %s",
G_STRFUNC, e_source_get_display_name (source),
error->message);
g_clear_error (&error);
goto exit;
}
g_signal_connect (
book_view, "objects-added",
G_CALLBACK (contacts_added_cb), br->cbc);
g_signal_connect (
book_view, "objects-removed",
G_CALLBACK (contacts_removed_cb), br->cbc);
g_signal_connect (
book_view, "objects-modified",
G_CALLBACK (contacts_modified_cb), br->cbc);
e_book_client_view_start (book_view, NULL);
book_record_set_book_view (br, book_view);
g_object_unref (book_view);
exit:
g_free (query_sexp);
book_record_unref (br);
return NULL;
}
static void
source_unset_last_credentials_required_args_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
if (!e_source_unset_last_credentials_required_arguments_finish (E_SOURCE (source_object), result, &error))
g_debug ("%s: Failed to unset last credentials required arguments for %s: %s",
G_STRFUNC, e_source_get_display_name (E_SOURCE (source_object)), error ? error->message : "Unknown error");
g_clear_error (&error);
}
static void
book_client_notify_online_cb (EClient *client,
GParamSpec *param,
BookRecord *br)
{
g_return_if_fail (E_IS_BOOK_CLIENT (client));
g_return_if_fail (br != NULL);
if ((br->online ? 1 : 0) == (e_client_is_online (client) ? 1 : 0))
return;
br->online = e_client_is_online (client);
if (br->online) {
ECalBackendContacts *cbc;
ESource *source;
cbc = g_object_ref (br->cbc);
source = g_object_ref (e_client_get_source (client));
cal_backend_contacts_remove_book_record (cbc, source);
create_book_record (cbc, source);
g_clear_object (&source);
g_clear_object (&cbc);
}
}
static void
book_client_connected_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EClient *client;
ESource *source;
GThread *thread;
BookRecord *br = user_data;
GError *error = NULL;
g_return_if_fail (br != NULL);
client = e_book_client_connect_finish (result, &error);
/* Sanity check. */
g_return_if_fail (
((client != NULL) && (error == NULL)) ||
((client == NULL) && (error != NULL)));
if (error != NULL) {
if (E_IS_BOOK_CLIENT (source_object)) {
source = e_client_get_source (E_CLIENT (source_object));
if (source)
e_source_unset_last_credentials_required_arguments (source, NULL,
source_unset_last_credentials_required_args_cb, NULL);
}
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
book_record_unref (br);
return;
}
source = e_client_get_source (client);
br->book_client = g_object_ref (client);
br->online = e_client_is_online (client);
br->notify_online_id = g_signal_connect (client, "notify::online", G_CALLBACK (book_client_notify_online_cb), br);
cal_backend_contacts_insert_book_record (br->cbc, source, br);
/* Let it consume the 'br' reference */
thread = g_thread_new (NULL, book_record_get_view_thread, br);
g_thread_unref (thread);
g_object_unref (client);
}
/* ContactRecord methods */
static ContactRecord *
contact_record_new (ECalBackendContacts *cbc,
EBookClient *book_client,
EContact *contact)
{
ContactRecord *cr = g_new0 (ContactRecord, 1);
cr->cbc = cbc;
cr->book_client = book_client;
cr->contact = contact;
cr->comp_birthday = create_birthday (cbc, contact);
cr->comp_anniversary = create_anniversary (cbc, contact);
if (cr->comp_birthday)
e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_birthday);
if (cr->comp_anniversary)
e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_anniversary);
g_object_ref (G_OBJECT (contact));
return cr;
}
static void
contact_record_free (ContactRecord *cr)
{
ECalComponentId *id;
g_object_unref (G_OBJECT (cr->contact));
/* Remove the birthday event */
if (cr->comp_birthday) {
id = e_cal_component_get_id (cr->comp_birthday);
e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_birthday, NULL);
e_cal_component_id_free (id);
g_object_unref (G_OBJECT (cr->comp_birthday));
}
/* Remove the anniversary event */
if (cr->comp_anniversary) {
id = e_cal_component_get_id (cr->comp_anniversary);
e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_anniversary, NULL);
e_cal_component_id_free (id);
g_object_unref (G_OBJECT (cr->comp_anniversary));
}
g_free (cr);
}
/* ContactRecordCB methods */
typedef struct _ContactRecordCB {
ECalBackendContacts *cbc;
ECalBackendSExp *sexp;
gboolean as_string;
GSList *result;
} ContactRecordCB;
static ContactRecordCB *
contact_record_cb_new (ECalBackendContacts *cbc,
ECalBackendSExp *sexp,
gboolean as_string)
{
ContactRecordCB *cb_data = g_new (ContactRecordCB, 1);
cb_data->cbc = cbc;
cb_data->sexp = sexp;
cb_data->as_string = as_string;
cb_data->result = NULL;
return cb_data;
}
static void
contact_record_cb_free (ContactRecordCB *cb_data,
gboolean can_free_result)
{
if (can_free_result) {
if (cb_data->as_string)
g_slist_foreach (cb_data->result, (GFunc) g_free, NULL);
g_slist_free (cb_data->result);
}
g_free (cb_data);
}
static void
contact_record_cb (gpointer key,
gpointer value,
gpointer user_data)
{
ETimezoneCache *timezone_cache;
ContactRecordCB *cb_data = user_data;
ContactRecord *record = value;
gpointer data;
timezone_cache = E_TIMEZONE_CACHE (cb_data->cbc);
if (record->comp_birthday && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_birthday, timezone_cache)) {
if (cb_data->as_string)
data = e_cal_component_get_as_string (record->comp_birthday);
else
data = record->comp_birthday;
cb_data->result = g_slist_prepend (cb_data->result, data);
}
if (record->comp_anniversary && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_anniversary, timezone_cache)) {
if (cb_data->as_string)
data = e_cal_component_get_as_string (record->comp_anniversary);
else
data = record->comp_anniversary;
cb_data->result = g_slist_prepend (cb_data->result, data);
}
}
static gboolean
ecb_contacts_watcher_filter_cb (ESourceRegistryWatcher *watcher,
ESource *source,
gpointer user_data)
{
ESourceContacts *extension;
g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CONTACTS_BACKEND);
return extension && e_source_contacts_get_include_me (extension);
}
static void
ecb_contacts_watcher_appeared_cb (ESourceRegistryWatcher *watcher,
ESource *source,
gpointer user_data)
{
ECalBackendContacts *cbcontacts = user_data;
g_return_if_fail (E_IS_SOURCE (source));
g_return_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts));
cal_backend_contacts_remove_book_record (cbcontacts, source);
create_book_record (cbcontacts, source);
}
static void
ecb_contacts_watcher_disappeared_cb (ESourceRegistryWatcher *watcher,
ESource *source,
gpointer user_data)
{
ECalBackendContacts *cbcontacts = user_data;
g_return_if_fail (E_IS_SOURCE (source));
g_return_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts));
cal_backend_contacts_remove_book_record (cbcontacts, source);
}
static gboolean
cal_backend_contacts_load_sources (gpointer user_data)
{
ECalBackendContacts *cbcontacts = user_data;
g_return_val_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts), FALSE);
e_source_registry_watcher_reclaim (cbcontacts->priv->registry_watcher);
return FALSE;
}
/************************************************************************************/
static void
contacts_modified_cb (EBookClientView *book_view,
const GSList *contacts,
gpointer user_data)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
EBookClient *book_client;
const GSList *ii;
book_client = e_book_client_view_ref_client (book_view);
if (book_client == NULL)
return;
g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
for (ii = contacts; ii; ii = ii->next) {
EContact *contact = E_CONTACT (ii->data);
const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
EContactDate *birthday, *anniversary;
/* Because this is a change of contact, then always remove old tracked data
* and if possible, add with (possibly) new values.
*/
g_hash_table_remove (cbc->priv->tracked_contacts, (gchar *) uid);
birthday = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
anniversary = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
if (birthday || anniversary) {
ContactRecord *cr = contact_record_new (cbc, book_client, contact);
g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
}
e_contact_date_free (birthday);
e_contact_date_free (anniversary);
}
g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
g_object_unref (book_client);
}
static void
contacts_added_cb (EBookClientView *book_view,
const GSList *contacts,
gpointer user_data)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
EBookClient *book_client;
const GSList *ii;
book_client = e_book_client_view_ref_client (book_view);
if (book_client == NULL)
return;
g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
/* See if any new contacts have BIRTHDAY or ANNIVERSARY fields */
for (ii = contacts; ii; ii = ii->next) {
EContact *contact = E_CONTACT (ii->data);
EContactDate *birthday, *anniversary;
birthday = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
anniversary = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
if (birthday || anniversary) {
ContactRecord *cr = contact_record_new (cbc, book_client, contact);
const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
}
e_contact_date_free (birthday);
e_contact_date_free (anniversary);
}
g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
g_object_unref (book_client);
}
static void
contacts_removed_cb (EBookClientView *book_view,
const GSList *contact_ids,
gpointer user_data)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
const GSList *ii;
g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
/* Stop tracking these */
for (ii = contact_ids; ii; ii = ii->next)
g_hash_table_remove (cbc->priv->tracked_contacts, ii->data);
g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
}
/************************************************************************************/
static ICalTime *
cdate_to_icaltime (EContactDate *cdate)
{
ICalTime *ret = i_cal_time_new_null_time ();
i_cal_time_set_year (ret, cdate->year);
i_cal_time_set_month (ret, cdate->month);
i_cal_time_set_day (ret, cdate->day);
i_cal_time_set_hour (ret, 0);
i_cal_time_set_minute (ret, 0);
i_cal_time_set_second (ret, 0);
i_cal_time_set_timezone (ret, NULL);
i_cal_time_set_is_daylight (ret, FALSE);
i_cal_time_set_is_date (ret, TRUE);
return ret;
}
static void
manage_comp_alarm_update (ECalBackendContacts *cbc,
ECalComponent *comp)
{
gchar *old_comp_str, *new_comp_str;
ECalComponent *old_comp;
g_return_if_fail (cbc != NULL);
g_return_if_fail (comp != NULL);
old_comp = e_cal_component_clone (comp);
setup_alarm (cbc, comp);
old_comp_str = e_cal_component_get_as_string (old_comp);
new_comp_str = e_cal_component_get_as_string (comp);
/* check if component changed and notify if so */
if (old_comp_str && new_comp_str && !g_str_equal (old_comp_str, new_comp_str))
e_cal_backend_notify_component_modified (E_CAL_BACKEND (cbc), old_comp, comp);
g_free (old_comp_str);
g_free (new_comp_str);
g_object_unref (old_comp);
}
static void
update_alarm_cb (gpointer key,
gpointer value,
gpointer user_data)
{
ECalBackendContacts *cbc = user_data;
ContactRecord *record = value;
g_return_if_fail (cbc != NULL);
g_return_if_fail (record != NULL);
if (record->comp_birthday)
manage_comp_alarm_update (cbc, record->comp_birthday);
if (record->comp_anniversary)
manage_comp_alarm_update (cbc, record->comp_anniversary);
}
static gboolean
update_tracked_alarms_cb (gpointer user_data)
{
ECalBackendContacts *cbc = user_data;
g_return_val_if_fail (cbc != NULL, FALSE);
g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
g_hash_table_foreach (cbc->priv->tracked_contacts, update_alarm_cb, cbc);
g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
cbc->priv->update_alarms_id = 0;
return FALSE;
}
#define BA_CONF_ENABLED "contacts-reminder-enabled"
#define BA_CONF_INTERVAL "contacts-reminder-interval"
#define BA_CONF_UNITS "contacts-reminder-units"
static void
alarm_config_changed_cb (GSettings *settings,
const gchar *key,
gpointer user_data)
{
ECalBackendContacts *cbc = user_data;
g_return_if_fail (cbc != NULL);
if (g_strcmp0 (key, BA_CONF_ENABLED) != 0 &&
g_strcmp0 (key, BA_CONF_INTERVAL) != 0 &&
g_strcmp0 (key, BA_CONF_UNITS) != 0)
return;
setup_alarm (cbc, NULL);
if (!cbc->priv->update_alarms_id)
cbc->priv->update_alarms_id = g_idle_add (update_tracked_alarms_cb, cbc);
}
/* When called with NULL, then just refresh local static variables on setup change from the user. */
static void
setup_alarm (ECalBackendContacts *cbc,
ECalComponent *comp)
{
ECalComponentAlarm *alarm;
ECalComponentAlarmTrigger *trigger;
ECalComponentText *summary;
ICalDuration *duration;
g_return_if_fail (cbc != NULL);
if (!comp || cbc->priv->alarm_interval == -1) {
gchar *str;
if (cbc->priv->alarm_interval == -1) {
/* initial setup, hook callback for changes too */
cbc->priv->notifyid = g_signal_connect (cbc->priv->settings,
"changed", G_CALLBACK (alarm_config_changed_cb), cbc);
}
cbc->priv->alarm_enabled = g_settings_get_boolean (cbc->priv->settings, BA_CONF_ENABLED);
cbc->priv->alarm_interval = g_settings_get_int (cbc->priv->settings, BA_CONF_INTERVAL);
str = g_settings_get_string (cbc->priv->settings, BA_CONF_UNITS);
if (str && !strcmp (str, "days"))
cbc->priv->alarm_units = CAL_DAYS;
else if (str && !strcmp (str, "hours"))
cbc->priv->alarm_units = CAL_HOURS;
else
cbc->priv->alarm_units = CAL_MINUTES;
g_free (str);
if (cbc->priv->alarm_interval <= 0)
cbc->priv->alarm_interval = 1;
if (!comp)
return;
}
/* ensure no alarms left */
e_cal_component_remove_all_alarms (comp);
/* do not want alarms, return */
if (!cbc->priv->alarm_enabled)
return;
alarm = e_cal_component_alarm_new ();
summary = e_cal_component_get_summary (comp);
e_cal_component_alarm_take_description (alarm, summary);
e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
duration = i_cal_duration_new_null_duration ();
i_cal_duration_set_is_neg (duration, TRUE);
switch (cbc->priv->alarm_units) {
case CAL_MINUTES:
i_cal_duration_set_minutes (duration, cbc->priv->alarm_interval);
break;
case CAL_HOURS:
i_cal_duration_set_hours (duration, cbc->priv->alarm_interval);
break;
case CAL_DAYS:
i_cal_duration_set_days (duration, cbc->priv->alarm_interval);
break;
default:
g_warning ("%s: wrong units %d\n", G_STRFUNC, cbc->priv->alarm_units);
e_cal_component_alarm_free (alarm);
return;
}
trigger = e_cal_component_alarm_trigger_new_relative (E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);
g_object_unref (duration);
e_cal_component_alarm_take_trigger (alarm, trigger);
e_cal_component_add_alarm (comp, alarm);
e_cal_component_alarm_free (alarm);
}
#undef BA_CONF_ENABLED
#undef BA_CONF_INTERVAL
#undef BA_CONF_UNITS
/* Contact -> Event creator */
static ECalComponent *
create_component (ECalBackendContacts *cbc,
const gchar *uid,
EContactDate *cdate,
const gchar *summary)
{
ECalComponent *cal_comp;
ECalComponentText *comp_summary;
ECalComponentDateTime *dt;
ICalComponent *icomp;
ICalTime *itt;
ICalRecurrence *rt;
gchar *since_year;
GSList *recur_list;
gboolean is_leap_day;
g_return_val_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbc), NULL);
if (!cdate)
return NULL;
icomp = i_cal_component_new (I_CAL_VEVENT_COMPONENT);
since_year = g_strdup_printf ("%04d", cdate->year);
e_cal_util_component_set_x_property (icomp, "X-EVOLUTION-SINCE-YEAR", since_year);
g_free (since_year);
/* Create the event object */
cal_comp = e_cal_component_new_from_icalcomponent (icomp);
/* Set uid */
d (g_message ("Creating UID: %s", uid));
e_cal_component_set_uid (cal_comp, uid);
/* Set all-day event's date from contact data */
itt = cdate_to_icaltime (cdate);
i_cal_time_normalize_inplace (itt);
is_leap_day = i_cal_time_get_day (itt) == 29 && i_cal_time_get_month (itt) == 2;
dt = e_cal_component_datetime_new_take (itt, NULL);
e_cal_component_set_dtstart (cal_comp, dt);
e_cal_component_datetime_free (dt);
itt = cdate_to_icaltime (cdate);
/* We have to add 1 day to DTEND, as it is not inclusive. */
i_cal_time_adjust (itt, 1, 0, 0, 0);
dt = e_cal_component_datetime_new_take (itt, NULL);
e_cal_component_set_dtend (cal_comp, dt);
e_cal_component_datetime_free (dt);
/* Create yearly recurrence */
rt = i_cal_recurrence_new ();
i_cal_recurrence_set_freq (rt, I_CAL_YEARLY_RECURRENCE);
i_cal_recurrence_set_interval (rt, 1);
if (is_leap_day)
i_cal_recurrence_set_by_month_day (rt, 0, -1);
recur_list = g_slist_prepend (NULL, rt);
e_cal_component_set_rrules (cal_comp, recur_list);
g_slist_free_full (recur_list, g_object_unref);
/* Create summary */
comp_summary = e_cal_component_text_new (summary, NULL);
e_cal_component_set_summary (cal_comp, comp_summary);
e_cal_component_text_free (comp_summary);
/* Set category and visibility */
if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
e_cal_component_set_categories (cal_comp, _("Anniversary"));
else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
e_cal_component_set_categories (cal_comp, _("Birthday"));
e_cal_component_set_classification (cal_comp, E_CAL_COMPONENT_CLASS_PRIVATE);
/* Birthdays/anniversaries are shown as free time */
e_cal_component_set_transparency (cal_comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT);
/* setup alarms if required */
setup_alarm (cbc, cal_comp);
/* Don't forget to call commit()! */
e_cal_component_commit_sequence (cal_comp);
return cal_comp;
}
static ECalComponent *
create_birthday (ECalBackendContacts *cbc,
EContact *contact)
{
EContactDate *cdate;
ECalComponent *cal_comp;
gchar *summary;
const gchar *name;
gchar *uid;
cdate = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
if (!name || !*name)
name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
if (!name || !*name)
name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
if (!name)
name = "";
uid = g_strdup_printf ("%s%s", (gchar *) e_contact_get_const (contact, E_CONTACT_UID), BIRTHDAY_UID_EXT);
summary = g_strdup_printf (_("Birthday: %s"), name);
cal_comp = create_component (cbc, uid, cdate, summary);
e_contact_date_free (cdate);
g_free (uid);
g_free (summary);
return cal_comp;
}
static ECalComponent *
create_anniversary (ECalBackendContacts *cbc,
EContact *contact)
{
EContactDate *cdate;
ECalComponent *cal_comp;
gchar *summary;
const gchar *name;
gchar *uid;
cdate = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
if (!name || !*name)
name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
if (!name || !*name)
name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
if (!name)
name = "";
uid = g_strdup_printf ("%s%s", (gchar *) e_contact_get_const (contact, E_CONTACT_UID), ANNIVERSARY_UID_EXT);
summary = g_strdup_printf (_("Anniversary: %s"), name);
cal_comp = create_component (cbc, uid, cdate, summary);
e_contact_date_free (cdate);
g_free (uid);
g_free (summary);
return cal_comp;
}
/************************************************************************************/
/* Calendar backend method implementations */
/* First the empty stubs */
static gchar *
e_cal_backend_contacts_get_backend_property (ECalBackend *backend,
const gchar *prop_name)
{
g_return_val_if_fail (prop_name != NULL, FALSE);
if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
return NULL;
} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
/* A contact backend has no particular email address associated
* with it (although that would be a useful feature some day).
*/
return NULL;
} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
return NULL;
}
/* Chain up to parent's method. */
return E_CAL_BACKEND_CLASS (e_cal_backend_contacts_parent_class)->impl_get_backend_property (backend, prop_name);
}
static void
e_cal_backend_contacts_get_object (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *uid,
const gchar *rid,
gchar **object,
GError **perror)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
ECalBackendContactsPrivate *priv = cbc->priv;
ContactRecord *record;
gchar *real_uid;
if (!uid) {
g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
return;
} else if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
real_uid = g_strndup (uid, strlen (uid) - strlen (ANNIVERSARY_UID_EXT));
else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
real_uid = g_strndup (uid, strlen (uid) - strlen (BIRTHDAY_UID_EXT));
else {
g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
return;
}
g_rec_mutex_lock (&priv->tracked_contacts_lock);
record = g_hash_table_lookup (priv->tracked_contacts, real_uid);
g_free (real_uid);
if (!record) {
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
return;
}
if (record->comp_birthday && g_str_has_suffix (uid, BIRTHDAY_UID_EXT)) {
*object = e_cal_component_get_as_string (record->comp_birthday);
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
d (g_message ("Return birthday: %s", *object));
return;
}
if (record->comp_anniversary && g_str_has_suffix (uid, ANNIVERSARY_UID_EXT)) {
*object = e_cal_component_get_as_string (record->comp_anniversary);
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
d (g_message ("Return anniversary: %s", *object));
return;
}
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
d (g_message ("Returning nothing for uid: %s", uid));
g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
}
static void
e_cal_backend_contacts_get_free_busy (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const GSList *users,
time_t start,
time_t end,
GSList **freebusy,
GError **perror)
{
/* Birthdays/anniversaries don't count as busy time */
ICalComponent *vfb = i_cal_component_new_vfreebusy ();
ICalTimezone *utc_zone = i_cal_timezone_get_utc_timezone ();
ICalTime *itt;
gchar *calobj;
itt = i_cal_time_new_from_timet_with_zone (start, FALSE, utc_zone);
i_cal_component_set_dtstart (vfb, itt);
g_object_unref (itt);
itt = i_cal_time_new_from_timet_with_zone (end, FALSE, utc_zone);
i_cal_component_set_dtend (vfb, itt);
g_object_unref (itt);
calobj = i_cal_component_as_ical_string (vfb);
*freebusy = g_slist_append (NULL, calobj);
g_object_unref (vfb);
}
static void
e_cal_backend_contacts_receive_objects (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *calobj,
guint32 opflags,
GError **perror)
{
g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}
static void
e_cal_backend_contacts_send_objects (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *calobj,
guint32 opflags,
GSList **users,
gchar **modified_calobj,
GError **perror)
{
*users = NULL;
*modified_calobj = NULL;
/* TODO: Investigate this */
g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}
/* Then the real implementations */
static void
e_cal_backend_contacts_notify_online_cb (ECalBackend *backend,
GParamSpec *pspec)
{
e_cal_backend_set_writable (backend, FALSE);
}
static void
e_cal_backend_contacts_open (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
GError **perror)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
ECalBackendContactsPrivate *priv = cbc->priv;
if (priv->addressbook_loaded)
return;
/* Local source is always connected. */
e_source_set_connection_status (e_backend_get_source (E_BACKEND (backend)),
E_SOURCE_CONNECTION_STATUS_CONNECTED);
priv->addressbook_loaded = TRUE;
e_cal_backend_set_writable (E_CAL_BACKEND (backend), FALSE);
e_backend_set_online (E_BACKEND (backend), TRUE);
}
/* Add_timezone handler for the file backend */
static void
e_cal_backend_contacts_add_timezone (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *tzobj,
GError **error)
{
ICalComponent *tz_comp;
ICalTimezone *zone;
tz_comp = i_cal_parser_parse_string (tzobj);
if (!tz_comp) {
g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
return;
}
if (i_cal_component_isa (tz_comp) != I_CAL_VTIMEZONE_COMPONENT) {
g_object_unref (tz_comp);
g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
return;
}
zone = i_cal_timezone_new ();
if (i_cal_timezone_set_component (zone, tz_comp))
e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (backend), zone);
g_object_unref (zone);
g_object_unref (tz_comp);
}
static void
e_cal_backend_contacts_get_object_list (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const gchar *sexp_string,
GSList **objects,
GError **perror)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
ECalBackendContactsPrivate *priv = cbc->priv;
ECalBackendSExp *sexp = e_cal_backend_sexp_new (sexp_string);
ContactRecordCB *cb_data;
if (!sexp) {
g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY));
return;
}
cb_data = contact_record_cb_new (cbc, sexp, TRUE);
g_rec_mutex_lock (&priv->tracked_contacts_lock);
g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
*objects = cb_data->result;
contact_record_cb_free (cb_data, FALSE);
}
static void
e_cal_backend_contacts_start_view (ECalBackend *backend,
EDataCalView *query)
{
ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
ECalBackendContactsPrivate *priv = cbc->priv;
ECalBackendSExp *sexp;
ContactRecordCB *cb_data;
sexp = e_data_cal_view_get_sexp (query);
if (!sexp) {
GError *error = EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY);
e_data_cal_view_notify_complete (query, error);
g_error_free (error);
return;
}
cb_data = contact_record_cb_new (cbc, sexp, FALSE);
g_rec_mutex_lock (&priv->tracked_contacts_lock);
g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
e_data_cal_view_notify_components_added (query, cb_data->result);
g_rec_mutex_unlock (&priv->tracked_contacts_lock);
contact_record_cb_free (cb_data, TRUE);
e_data_cal_view_notify_complete (query, NULL /* Success */);
}
/***********************************************************************************
*/
/* Finalize handler for the contacts backend */
static void
e_cal_backend_contacts_finalize (GObject *object)
{
ECalBackendContactsPrivate *priv;
priv = E_CAL_BACKEND_CONTACTS (object)->priv;
if (priv->update_alarms_id) {
g_source_remove (priv->update_alarms_id);
priv->update_alarms_id = 0;
}
g_hash_table_destroy (priv->addressbooks);
g_hash_table_destroy (priv->tracked_contacts);
if (priv->notifyid)
g_signal_handler_disconnect (priv->settings, priv->notifyid);
g_object_unref (priv->settings);
g_rec_mutex_clear (&priv->rec_mutex);
g_rec_mutex_clear (&priv->tracked_contacts_lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->finalize (object);
}
static void
e_cal_backend_contacts_dispose (GObject *object)
{
ECalBackendContacts *cbcontacts = E_CAL_BACKEND_CONTACTS (object);
g_clear_object (&cbcontacts->priv->registry_watcher);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->dispose (object);
}
static void
e_cal_backend_contacts_constructed (GObject *object)
{
ECalBackendContacts *cbcontacts = E_CAL_BACKEND_CONTACTS (object);
ESourceRegistry *registry;
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->constructed (object);
registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbcontacts));
cbcontacts->priv->registry_watcher = e_source_registry_watcher_new (registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
g_signal_connect (cbcontacts->priv->registry_watcher, "filter",
G_CALLBACK (ecb_contacts_watcher_filter_cb), cbcontacts);
g_signal_connect (cbcontacts->priv->registry_watcher, "appeared",
G_CALLBACK (ecb_contacts_watcher_appeared_cb), cbcontacts);
g_signal_connect (cbcontacts->priv->registry_watcher, "disappeared",
G_CALLBACK (ecb_contacts_watcher_disappeared_cb), cbcontacts);
/* Load address book sources from an idle callback
* to avoid deadlocking e_data_factory_ref_backend(). */
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, cal_backend_contacts_load_sources,
g_object_ref (object), g_object_unref);
}
/* Object initialization function for the contacts backend */
static void
e_cal_backend_contacts_init (ECalBackendContacts *cbc)
{
cbc->priv = e_cal_backend_contacts_get_instance_private (cbc);
g_rec_mutex_init (&cbc->priv->rec_mutex);
g_rec_mutex_init (&cbc->priv->tracked_contacts_lock);
cbc->priv->addressbooks = g_hash_table_new_full (
(GHashFunc) e_source_hash,
(GEqualFunc) e_source_equal,
(GDestroyNotify) g_object_unref,
(GDestroyNotify) cancel_and_unref_book_record);
cbc->priv->tracked_contacts = g_hash_table_new_full (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) contact_record_free);
cbc->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
cbc->priv->notifyid = 0;
cbc->priv->update_alarms_id = 0;
cbc->priv->alarm_enabled = FALSE;
cbc->priv->alarm_interval = -1;
cbc->priv->alarm_units = CAL_MINUTES;
g_signal_connect (
cbc, "notify::online",
G_CALLBACK (e_cal_backend_contacts_notify_online_cb), NULL);
}
static void
e_cal_backend_contacts_create_objects (ECalBackendSync *backend,
EDataCal *cal,
GCancellable *cancellable,
const GSList *calobjs,
guint32 opflags,
GSList **uids,
GSList **new_components,
GError **perror)
{
g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}
/* Class initialization function for the contacts backend */
static void
e_cal_backend_contacts_class_init (ECalBackendContactsClass *class)
{
GObjectClass *object_class;
ECalBackendClass *backend_class;
ECalBackendSyncClass *sync_class;
object_class = (GObjectClass *) class;
backend_class = (ECalBackendClass *) class;
sync_class = (ECalBackendSyncClass *) class;
object_class->finalize = e_cal_backend_contacts_finalize;
object_class->dispose = e_cal_backend_contacts_dispose;
object_class->constructed = e_cal_backend_contacts_constructed;
/* Execute one method at a time. */
backend_class->use_serial_dispatch_queue = TRUE;
backend_class->impl_get_backend_property = e_cal_backend_contacts_get_backend_property;
backend_class->impl_start_view = e_cal_backend_contacts_start_view;
sync_class->open_sync = e_cal_backend_contacts_open;
sync_class->create_objects_sync = e_cal_backend_contacts_create_objects;
sync_class->receive_objects_sync = e_cal_backend_contacts_receive_objects;
sync_class->send_objects_sync = e_cal_backend_contacts_send_objects;
sync_class->get_object_sync = e_cal_backend_contacts_get_object;
sync_class->get_object_list_sync = e_cal_backend_contacts_get_object_list;
sync_class->add_timezone_sync = e_cal_backend_contacts_add_timezone;
sync_class->get_free_busy_sync = e_cal_backend_contacts_get_free_busy;
/* Register our ESource extension. */
E_TYPE_SOURCE_CONTACTS;
}