Files
evolution/calendar/gui/comp-util.c
T
Milan Crha 8752647eca Make Calendar, Memos and Tasks views non-UI-blocking
The Calendar, Memos and Tasks views use to do D-Bus calls to
the backends on the main (UI) thread, which could result in UI
freezes, until the operation was done on the backend (and server)
side. This commit fixes that by invoking the operations in
a dedicated thread. It has few additional advantages too:
- operations can be cancelled
- proper error reporting to a user
- less code duplication between the views for common operations

There had been fixed some performance issues when selecting/unselecting
sources in the source selector as well.
2014-10-06 12:33:03 +02:00

1301 lines
36 KiB
C

/*
* Evolution calendar - Utilities for manipulating ECalComponent objects
*
* This program 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 program 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 General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*
* Authors:
* Federico Mena-Quintero <federico@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <time.h>
#include "calendar-config.h"
#include "comp-util.h"
#include "e-calendar-view.h"
#include "dialogs/delete-comp.h"
#include "shell/e-shell-window.h"
#include "shell/e-shell-view.h"
/**
* cal_comp_util_add_exdate:
* @comp: A calendar component object.
* @itt: Time for the exception.
*
* Adds an exception date to the current list of EXDATE properties in a calendar
* component object.
**/
void
cal_comp_util_add_exdate (ECalComponent *comp,
time_t t,
icaltimezone *zone)
{
GSList *list;
ECalComponentDateTime *cdt;
g_return_if_fail (comp != NULL);
g_return_if_fail (E_IS_CAL_COMPONENT (comp));
e_cal_component_get_exdate_list (comp, &list);
cdt = g_new (ECalComponentDateTime, 1);
cdt->value = g_new (struct icaltimetype, 1);
*cdt->value = icaltime_from_timet_with_zone (t, FALSE, zone);
cdt->tzid = g_strdup (icaltimezone_get_tzid (zone));
list = g_slist_append (list, cdt);
e_cal_component_set_exdate_list (comp, list);
e_cal_component_free_exdate_list (list);
}
/* Returns TRUE if the TZIDs are equivalent, i.e. both NULL or the same. */
static gboolean
e_cal_component_compare_tzid (const gchar *tzid1,
const gchar *tzid2)
{
gboolean retval = TRUE;
if (tzid1) {
if (!tzid2 || strcmp (tzid1, tzid2))
retval = FALSE;
} else {
if (tzid2)
retval = FALSE;
}
return retval;
}
/**
* cal_comp_util_compare_event_timezones:
* @comp: A calendar component object.
* @client: A #ECalClient.
*
* Checks if the component uses the given timezone for both the start and
* the end time, or if the UTC offsets of the start and end times are the same
* as in the given zone.
*
* Returns: TRUE if the component's start and end time are at the same UTC
* offset in the given timezone.
**/
gboolean
cal_comp_util_compare_event_timezones (ECalComponent *comp,
ECalClient *client,
icaltimezone *zone)
{
ECalComponentDateTime start_datetime, end_datetime;
const gchar *tzid;
gboolean retval = FALSE;
icaltimezone *start_zone = NULL;
icaltimezone *end_zone = NULL;
gint offset1, offset2;
tzid = icaltimezone_get_tzid (zone);
e_cal_component_get_dtstart (comp, &start_datetime);
e_cal_component_get_dtend (comp, &end_datetime);
/* If either the DTSTART or the DTEND is a DATE value, we return TRUE.
* Maybe if one was a DATE-TIME we should check that, but that should
* not happen often. */
if ((start_datetime.value && start_datetime.value->is_date)
|| (end_datetime.value && end_datetime.value->is_date)) {
retval = TRUE;
goto out;
}
/* If the event uses UTC for DTSTART & DTEND, return TRUE. Outlook
* will send single events as UTC, so we don't want to mark all of
* these. */
if ((!start_datetime.value || start_datetime.value->is_utc)
&& (!end_datetime.value || end_datetime.value->is_utc)) {
retval = TRUE;
goto out;
}
/* If the event uses floating time for DTSTART & DTEND, return TRUE.
* Imported vCalendar files will use floating times, so we don't want
* to mark all of these. */
if (!start_datetime.tzid && !end_datetime.tzid) {
retval = TRUE;
goto out;
}
/* FIXME: DURATION may be used instead. */
if (e_cal_component_compare_tzid (tzid, start_datetime.tzid)
&& e_cal_component_compare_tzid (tzid, end_datetime.tzid)) {
/* If both TZIDs are the same as the given zone's TZID, then
* we know the timezones are the same so we return TRUE. */
retval = TRUE;
} else {
/* If the TZIDs differ, we have to compare the UTC offsets
* of the start and end times, using their own timezones and
* the given timezone. */
e_cal_client_get_timezone_sync (
client, start_datetime.tzid, &start_zone, NULL, NULL);
if (start_zone == NULL)
goto out;
if (start_datetime.value) {
offset1 = icaltimezone_get_utc_offset (
start_zone,
start_datetime.value,
NULL);
offset2 = icaltimezone_get_utc_offset (
zone,
start_datetime.value,
NULL);
if (offset1 != offset2)
goto out;
}
e_cal_client_get_timezone_sync (
client, end_datetime.tzid, &end_zone, NULL, NULL);
if (end_zone == NULL)
goto out;
if (end_datetime.value) {
offset1 = icaltimezone_get_utc_offset (
end_zone,
end_datetime.value,
NULL);
offset2 = icaltimezone_get_utc_offset (
zone,
end_datetime.value,
NULL);
if (offset1 != offset2)
goto out;
}
retval = TRUE;
}
out:
e_cal_component_free_datetime (&start_datetime);
e_cal_component_free_datetime (&end_datetime);
return retval;
}
/**
* cal_comp_is_on_server_sync:
* @comp: an #ECalComponent
* @client: an #ECalClient
* @cancellable: (allow none): a #GCancellable
* @error: (out): (allow none): a #GError
*
* Checks whether @client contains @comp. A "No Such Object" error is not
* propagated to the caller, any other errors are.
*
* Returns: #TRUE, when the @client contains @comp, #FALSE when not or on error.
* The @error is not set when the @client doesn't contain the @comp.
**/
gboolean
cal_comp_is_on_server_sync (ECalComponent *comp,
ECalClient *client,
GCancellable *cancellable,
GError **error)
{
const gchar *uid;
gchar *rid = NULL;
icalcomponent *icalcomp = NULL;
GError *local_error = NULL;
g_return_val_if_fail (comp != NULL, FALSE);
g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
g_return_val_if_fail (client != NULL, FALSE);
g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
/* See if the component is on the server. If it is not, then it likely
* means that the appointment is new, only in the day view, and we
* haven't added it yet to the server. In that case, we don't need to
* confirm and we can just delete the event. Otherwise, we ask
* the user.
*/
e_cal_component_get_uid (comp, &uid);
/* TODO We should not be checking for this here. But since
* e_cal_util_construct_instance does not create the instances
* of all day events, so we default to old behaviour. */
if (e_cal_client_check_recurrences_no_master (client)) {
rid = e_cal_component_get_recurid_as_string (comp);
}
if (e_cal_client_get_object_sync (client, uid, rid, &icalcomp, cancellable, &local_error) &&
icalcomp != NULL) {
icalcomponent_free (icalcomp);
g_free (rid);
return TRUE;
}
if (g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND))
g_clear_error (&local_error);
else
g_propagate_error (error, local_error);
g_free (rid);
return FALSE;
}
/**
* cal_comp_is_icalcomp_on_server_sync:
* The same as cal_comp_is_on_server_sync(), only the component parameter is
* icalcomponent, not the ECalComponent.
**/
gboolean
cal_comp_is_icalcomp_on_server_sync (icalcomponent *icalcomp,
ECalClient *client,
GCancellable *cancellable,
GError **error)
{
gboolean on_server;
ECalComponent *comp;
if (!icalcomp || !client || !icalcomponent_get_uid (icalcomp))
return FALSE;
comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icalcomp));
if (!comp)
return FALSE;
on_server = cal_comp_is_on_server_sync (comp, client, cancellable, error);
g_object_unref (comp);
return on_server;
}
/**
* cal_comp_event_new_with_defaults_sync:
*
* Creates a new VEVENT component and adds any default alarms to it as set in
* the program's configuration values, but only if not the all_day event.
*
* Return value: A newly-created calendar component.
**/
ECalComponent *
cal_comp_event_new_with_defaults_sync (ECalClient *client,
gboolean all_day,
gboolean use_default_reminder,
gint default_reminder_interval,
EDurationType default_reminder_units,
GCancellable *cancellable,
GError **error)
{
icalcomponent *icalcomp = NULL;
ECalComponent *comp;
ECalComponentAlarm *alarm;
icalproperty *icalprop;
ECalComponentAlarmTrigger trigger;
if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
return NULL;
if (icalcomp == NULL)
icalcomp = icalcomponent_new (ICAL_VEVENT_COMPONENT);
comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
icalcomponent_free (icalcomp);
e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
}
if (all_day || !use_default_reminder)
return comp;
alarm = e_cal_component_alarm_new ();
/* We don't set the description of the alarm; we'll copy it from the
* summary when it gets committed to the server. For that, we add a
* X-EVOLUTION-NEEDS-DESCRIPTION property to the alarm's component.
*/
icalcomp = e_cal_component_alarm_get_icalcomponent (alarm);
icalprop = icalproperty_new_x ("1");
icalproperty_set_x_name (icalprop, "X-EVOLUTION-NEEDS-DESCRIPTION");
icalcomponent_add_property (icalcomp, icalprop);
e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
memset (&trigger.u.rel_duration, 0, sizeof (trigger.u.rel_duration));
trigger.u.rel_duration.is_neg = TRUE;
switch (default_reminder_units) {
case E_DURATION_MINUTES:
trigger.u.rel_duration.minutes = default_reminder_interval;
break;
case E_DURATION_HOURS:
trigger.u.rel_duration.hours = default_reminder_interval;
break;
case E_DURATION_DAYS:
trigger.u.rel_duration.days = default_reminder_interval;
break;
default:
g_warning ("wrong units %d\n", default_reminder_units);
}
e_cal_component_alarm_set_trigger (alarm, trigger);
e_cal_component_add_alarm (comp, alarm);
e_cal_component_alarm_free (alarm);
return comp;
}
ECalComponent *
cal_comp_event_new_with_current_time_sync (ECalClient *client,
gboolean all_day,
gboolean use_default_reminder,
gint default_reminder_interval,
EDurationType default_reminder_units,
GCancellable *cancellable,
GError **error)
{
ECalComponent *comp;
struct icaltimetype itt;
ECalComponentDateTime dt;
icaltimezone *zone;
comp = cal_comp_event_new_with_defaults_sync (
client, all_day, use_default_reminder,
default_reminder_interval, default_reminder_units,
cancellable, error);
if (!comp)
return NULL;
zone = calendar_config_get_icaltimezone ();
if (all_day) {
itt = icaltime_from_timet_with_zone (time (NULL), 1, zone);
dt.value = &itt;
dt.tzid = icaltimezone_get_tzid (zone);
e_cal_component_set_dtstart (comp, &dt);
e_cal_component_set_dtend (comp, &dt);
} else {
itt = icaltime_current_time_with_zone (zone);
icaltime_adjust (&itt, 0, 1, -itt.minute, -itt.second);
dt.value = &itt;
dt.tzid = icaltimezone_get_tzid (zone);
e_cal_component_set_dtstart (comp, &dt);
icaltime_adjust (&itt, 0, 1, 0, 0);
e_cal_component_set_dtend (comp, &dt);
}
return comp;
}
ECalComponent *
cal_comp_task_new_with_defaults_sync (ECalClient *client,
GCancellable *cancellable,
GError **error)
{
ECalComponent *comp;
icalcomponent *icalcomp = NULL;
if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
return NULL;
if (icalcomp == NULL)
icalcomp = icalcomponent_new (ICAL_VTODO_COMPONENT);
comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
icalcomponent_free (icalcomp);
e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
}
return comp;
}
ECalComponent *
cal_comp_memo_new_with_defaults_sync (ECalClient *client,
GCancellable *cancellable,
GError **error)
{
ECalComponent *comp;
icalcomponent *icalcomp = NULL;
if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
return NULL;
if (icalcomp == NULL)
icalcomp = icalcomponent_new (ICAL_VJOURNAL_COMPONENT);
comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
icalcomponent_free (icalcomp);
e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
}
return comp;
}
void
cal_comp_update_time_by_active_window (ECalComponent *comp,
EShell *shell)
{
GtkWindow *window;
g_return_if_fail (comp != NULL);
g_return_if_fail (shell != NULL);
window = e_shell_get_active_window (shell);
if (E_IS_SHELL_WINDOW (window)) {
EShellWindow *shell_window;
const gchar *active_view;
shell_window = E_SHELL_WINDOW (window);
active_view = e_shell_window_get_active_view (shell_window);
if (g_strcmp0 (active_view, "calendar") == 0) {
EShellContent *shell_content;
EShellView *shell_view;
ECalendarView *cal_view;
time_t start = 0, end = 0;
icaltimezone *zone;
struct icaltimetype itt;
icalcomponent *icalcomp;
icalproperty *prop;
shell_view = e_shell_window_peek_shell_view (shell_window, "calendar");
g_return_if_fail (shell_view != NULL);
cal_view = NULL;
shell_content = e_shell_view_get_shell_content (shell_view);
g_object_get (shell_content, "current-view", &cal_view, NULL);
g_return_if_fail (cal_view != NULL);
g_return_if_fail (e_calendar_view_get_visible_time_range (cal_view, &start, &end));
zone = e_cal_model_get_timezone (e_calendar_view_get_model (cal_view));
itt = icaltime_from_timet_with_zone (start, FALSE, zone);
icalcomp = e_cal_component_get_icalcomponent (comp);
prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
if (prop) {
icalproperty_set_dtstart (prop, itt);
} else {
prop = icalproperty_new_dtstart (itt);
icalcomponent_add_property (icalcomp, prop);
}
e_cal_component_rescan (comp);
g_clear_object (&cal_view);
}
}
}
/**
* cal_comp_util_get_n_icons:
* @comp: A calendar component object.
* @pixbufs: List of pixbufs to use. Can be NULL.
*
* Get the number of icons owned by the component.
* Each member of pixmaps should be freed with g_object_unref
* and the list itself should be freed too.
*
* Returns: the number of icons owned by the component.
**/
gint
cal_comp_util_get_n_icons (ECalComponent *comp,
GSList **pixbufs)
{
GSList *categories_list, *elem;
gint num_icons = 0;
g_return_val_if_fail (comp != NULL, 0);
g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), 0);
e_cal_component_get_categories_list (comp, &categories_list);
for (elem = categories_list; elem; elem = elem->next) {
const gchar *category;
GdkPixbuf *pixbuf = NULL;
category = elem->data;
if (e_categories_config_get_icon_for (category, &pixbuf)) {
if (!pixbuf)
continue;
num_icons++;
if (pixbufs) {
*pixbufs = g_slist_append (*pixbufs, pixbuf);
} else {
g_object_unref (pixbuf);
}
}
}
e_cal_component_free_categories_list (categories_list);
return num_icons;
}
/**
* cal_comp_selection_set_string_list
* @data: Selection data, where to put list of strings.
* @str_list: List of strings. (Each element is of type const gchar *.)
*
* Stores list of strings into selection target data. Use
* cal_comp_selection_get_string_list() to get this list from target data.
**/
void
cal_comp_selection_set_string_list (GtkSelectionData *data,
GSList *str_list)
{
/* format is "str1\0str2\0...strN\0" */
GSList *p;
GByteArray *array;
GdkAtom target;
g_return_if_fail (data != NULL);
if (!str_list)
return;
array = g_byte_array_new ();
for (p = str_list; p; p = p->next) {
const guint8 *c = p->data;
if (c)
g_byte_array_append (array, c, strlen ((const gchar *) c) + 1);
}
target = gtk_selection_data_get_target (data);
gtk_selection_data_set (data, target, 8, array->data, array->len);
g_byte_array_free (array, TRUE);
}
/**
* cal_comp_selection_get_string_list
* @data: Selection data, where to put list of strings.
*
* Converts data from selection to list of strings. Data should be assigned
* to selection data with cal_comp_selection_set_string_list().
* Each string in newly created list should be freed by g_free().
* List itself should be freed by g_slist_free().
*
* Returns: Newly allocated #GSList of strings.
**/
GSList *
cal_comp_selection_get_string_list (GtkSelectionData *selection_data)
{
/* format is "str1\0str2\0...strN\0" */
gchar *inptr, *inend;
GSList *list;
const guchar *data;
gint length;
g_return_val_if_fail (selection_data != NULL, NULL);
data = gtk_selection_data_get_data (selection_data);
length = gtk_selection_data_get_length (selection_data);
list = NULL;
inptr = (gchar *) data;
inend = (gchar *) (data + length);
while (inptr < inend) {
gchar *start = inptr;
while (inptr < inend && *inptr)
inptr++;
list = g_slist_prepend (list, g_strndup (start, inptr - start));
inptr++;
}
return list;
}
static void
datetime_to_zone (ECalClient *client,
ECalComponentDateTime *date,
const gchar *tzid)
{
icaltimezone *from, *to;
g_return_if_fail (date != NULL);
if (date->tzid == NULL || tzid == NULL ||
date->tzid == tzid || g_str_equal (date->tzid, tzid))
return;
from = icaltimezone_get_builtin_timezone_from_tzid (date->tzid);
if (!from) {
GError *error = NULL;
e_cal_client_get_timezone_sync (
client, date->tzid, &from, NULL, &error);
if (error != NULL) {
g_warning (
"%s: Could not get timezone '%s' from server: %s",
G_STRFUNC, date->tzid ? date->tzid : "",
error->message);
g_error_free (error);
}
}
to = icaltimezone_get_builtin_timezone_from_tzid (tzid);
if (!to) {
/* do not check failure here, maybe the zone is not available there */
e_cal_client_get_timezone_sync (client, tzid, &to, NULL, NULL);
}
icaltimezone_convert_time (date->value, from, to);
date->tzid = tzid;
}
/**
* cal_comp_set_dtstart_with_oldzone:
* @client: ECalClient structure, to retrieve timezone from, when required.
* @comp: Component, where make the change.
* @pdate: Value, to change to.
*
* Changes 'dtstart' of the component, but converts time to the old timezone.
**/
void
cal_comp_set_dtstart_with_oldzone (ECalClient *client,
ECalComponent *comp,
const ECalComponentDateTime *pdate)
{
ECalComponentDateTime olddate, date;
g_return_if_fail (comp != NULL);
g_return_if_fail (pdate != NULL);
e_cal_component_get_dtstart (comp, &olddate);
date = *pdate;
datetime_to_zone (client, &date, olddate.tzid);
e_cal_component_set_dtstart (comp, &date);
e_cal_component_free_datetime (&olddate);
}
/**
* cal_comp_set_dtend_with_oldzone:
* @client: ECalClient structure, to retrieve timezone from, when required.
* @comp: Component, where make the change.
* @pdate: Value, to change to.
*
* Changes 'dtend' of the component, but converts time to the old timezone.
**/
void
cal_comp_set_dtend_with_oldzone (ECalClient *client,
ECalComponent *comp,
const ECalComponentDateTime *pdate)
{
ECalComponentDateTime olddate, date;
g_return_if_fail (comp != NULL);
g_return_if_fail (pdate != NULL);
e_cal_component_get_dtend (comp, &olddate);
date = *pdate;
datetime_to_zone (client, &date, olddate.tzid);
e_cal_component_set_dtend (comp, &date);
e_cal_component_free_datetime (&olddate);
}
gboolean
comp_util_sanitize_recurrence_master_sync (ECalComponent *comp,
ECalClient *client,
GCancellable *cancellable,
GError **error)
{
ECalComponent *master = NULL;
icalcomponent *icalcomp = NULL;
ECalComponentRange rid;
ECalComponentDateTime sdt;
const gchar *uid;
/* Get the master component */
e_cal_component_get_uid (comp, &uid);
if (!e_cal_client_get_object_sync (client, uid, NULL, &icalcomp, cancellable, error))
return FALSE;
master = e_cal_component_new_from_icalcomponent (icalcomp);
if (!master) {
g_warn_if_reached ();
return FALSE;
}
/* Compare recur id and start date */
e_cal_component_get_recurid (comp, &rid);
e_cal_component_get_dtstart (comp, &sdt);
if (rid.datetime.value && sdt.value &&
icaltime_compare_date_only (
*rid.datetime.value, *sdt.value) == 0) {
ECalComponentDateTime msdt, medt, edt;
gint *sequence;
e_cal_component_get_dtstart (master, &msdt);
e_cal_component_get_dtend (master, &medt);
e_cal_component_get_dtend (comp, &edt);
if (!msdt.value || !medt.value || !edt.value) {
g_warn_if_reached ();
e_cal_component_free_datetime (&msdt);
e_cal_component_free_datetime (&medt);
e_cal_component_free_datetime (&edt);
e_cal_component_free_datetime (&sdt);
e_cal_component_free_range (&rid);
g_object_unref (master);
return FALSE;
}
sdt.value->year = msdt.value->year;
sdt.value->month = msdt.value->month;
sdt.value->day = msdt.value->day;
edt.value->year = medt.value->year;
edt.value->month = medt.value->month;
edt.value->day = medt.value->day;
e_cal_component_set_dtstart (comp, &sdt);
e_cal_component_set_dtend (comp, &edt);
e_cal_component_get_sequence (master, &sequence);
e_cal_component_set_sequence (comp, sequence);
e_cal_component_free_datetime (&msdt);
e_cal_component_free_datetime (&medt);
e_cal_component_free_datetime (&edt);
}
e_cal_component_free_datetime (&sdt);
e_cal_component_free_range (&rid);
e_cal_component_set_recurid (comp, NULL);
g_object_unref (master);
return TRUE;
}
gchar *
icalcomp_suggest_filename (icalcomponent *icalcomp,
const gchar *default_name)
{
icalproperty *prop;
const gchar *summary = NULL;
if (!icalcomp)
return g_strconcat (default_name, ".ics", NULL);
prop = icalcomponent_get_first_property (icalcomp, ICAL_SUMMARY_PROPERTY);
if (prop)
summary = icalproperty_get_summary (prop);
if (!summary || !*summary)
summary = default_name;
return g_strconcat (summary, ".ics", NULL);
}
void
cal_comp_get_instance_times (ECalClient *client,
icalcomponent *icalcomp,
const icaltimezone *default_zone,
time_t *instance_start,
gboolean *start_is_date,
time_t *instance_end,
gboolean *end_is_date,
GCancellable *cancellable)
{
struct icaltimetype start_time, end_time;
const icaltimezone *zone = default_zone;
g_return_if_fail (E_IS_CAL_CLIENT (client));
g_return_if_fail (icalcomp != NULL);
g_return_if_fail (instance_start != NULL);
g_return_if_fail (instance_end != NULL);
start_time = icalcomponent_get_dtstart (icalcomp);
end_time = icalcomponent_get_dtend (icalcomp);
if (start_time.zone) {
zone = start_time.zone;
} else {
icalparameter *param = NULL;
icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
if (prop) {
param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
if (param) {
const gchar *tzid = NULL;
icaltimezone *st_zone = NULL;
tzid = icalparameter_get_tzid (param);
if (tzid)
e_cal_client_get_timezone_sync (client, tzid, &st_zone, cancellable, NULL);
if (st_zone)
zone = st_zone;
}
}
}
*instance_start = icaltime_as_timet_with_zone (start_time, zone);
if (start_is_date)
*start_is_date = start_time.is_date;
if (end_time.zone) {
zone = end_time.zone;
} else {
icalparameter *param = NULL;
icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
if (prop) {
param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
if (param) {
const gchar *tzid = NULL;
icaltimezone *end_zone = NULL;
tzid = icalparameter_get_tzid (param);
if (tzid)
e_cal_client_get_timezone_sync (client, tzid, &end_zone, cancellable, NULL);
if (end_zone)
zone = end_zone;
}
}
}
*instance_end = icaltime_as_timet_with_zone (end_time, zone);
if (end_is_date)
*end_is_date = end_time.is_date;
}
time_t
cal_comp_gdate_to_timet (const GDate *date,
const icaltimezone *with_zone)
{
struct tm tm;
struct icaltimetype tt;
g_return_val_if_fail (date != NULL, (time_t) -1);
g_return_val_if_fail (g_date_valid (date), (time_t) -1);
g_date_to_struct_tm (date, &tm);
tt = tm_to_icaltimetype (&tm, TRUE);
if (with_zone)
return icaltime_as_timet_with_zone (tt, with_zone);
return icaltime_as_timet (tt);
}
typedef struct _AsyncContext {
ECalClient *src_client;
icalcomponent *icalcomp_clone;
gboolean do_copy;
} AsyncContext;
struct ForeachTzidData
{
ECalClient *source_client;
ECalClient *destination_client;
GCancellable *cancellable;
GError **error;
gboolean success;
};
static void
async_context_free (AsyncContext *async_context)
{
if (async_context->src_client)
g_object_unref (async_context->src_client);
if (async_context->icalcomp_clone)
icalcomponent_free (async_context->icalcomp_clone);
g_slice_free (AsyncContext, async_context);
}
static void
add_timezone_to_cal_cb (icalparameter *param,
gpointer data)
{
struct ForeachTzidData *ftd = data;
icaltimezone *tz = NULL;
const gchar *tzid;
g_return_if_fail (ftd != NULL);
g_return_if_fail (ftd->source_client != NULL);
g_return_if_fail (ftd->destination_client != NULL);
if (!ftd->success)
return;
if (ftd->cancellable && g_cancellable_is_cancelled (ftd->cancellable)) {
ftd->success = FALSE;
return;
}
tzid = icalparameter_get_tzid (param);
if (!tzid || !*tzid)
return;
if (e_cal_client_get_timezone_sync (ftd->source_client, tzid, &tz, ftd->cancellable, NULL) && tz)
ftd->success = e_cal_client_add_timezone_sync (
ftd->destination_client, tz, ftd->cancellable, ftd->error);
}
/* Helper for cal_comp_transfer_item_to() */
static void
cal_comp_transfer_item_to_thread (GSimpleAsyncResult *simple,
GObject *source_object,
GCancellable *cancellable)
{
AsyncContext *async_context;
GError *local_error = NULL;
async_context = g_simple_async_result_get_op_res_gpointer (simple);
cal_comp_transfer_item_to_sync (
async_context->src_client,
E_CAL_CLIENT (source_object),
async_context->icalcomp_clone,
async_context->do_copy,
cancellable, &local_error);
if (local_error != NULL)
g_simple_async_result_take_error (simple, local_error);
}
void
cal_comp_transfer_item_to (ECalClient *src_client,
ECalClient *dest_client,
icalcomponent *icalcomp_vcal,
gboolean do_copy,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
AsyncContext *async_context;
g_return_if_fail (E_IS_CAL_CLIENT (src_client));
g_return_if_fail (E_IS_CAL_CLIENT (dest_client));
g_return_if_fail (icalcomp_vcal != NULL);
async_context = g_slice_new0 (AsyncContext);
async_context->src_client = g_object_ref (src_client);
async_context->icalcomp_clone = icalcomponent_new_clone (icalcomp_vcal);
async_context->do_copy = do_copy;
simple = g_simple_async_result_new (
G_OBJECT (dest_client), callback, user_data,
cal_comp_transfer_item_to);
g_simple_async_result_set_check_cancellable (simple, cancellable);
g_simple_async_result_set_op_res_gpointer (
simple, async_context, (GDestroyNotify) async_context_free);
g_simple_async_result_run_in_thread (
simple, cal_comp_transfer_item_to_thread,
G_PRIORITY_DEFAULT, cancellable);
g_object_unref (simple);
}
gboolean
cal_comp_transfer_item_to_finish (ECalClient *client,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (
g_simple_async_result_is_valid (result, G_OBJECT (client), cal_comp_transfer_item_to),
FALSE);
simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
return TRUE;
}
gboolean
cal_comp_transfer_item_to_sync (ECalClient *src_client,
ECalClient *dest_client,
icalcomponent *icalcomp_vcal,
gboolean do_copy,
GCancellable *cancellable,
GError **error)
{
icalcomponent *icalcomp;
icalcomponent *icalcomp_event, *subcomp;
icalcomponent_kind icalcomp_kind;
const gchar *uid;
gchar *new_uid = NULL;
struct ForeachTzidData ftd;
ECalClientSourceType source_type;
GHashTable *processed_uids;
gboolean success = FALSE;
g_return_val_if_fail (E_IS_CAL_CLIENT (src_client), FALSE);
g_return_val_if_fail (E_IS_CAL_CLIENT (dest_client), FALSE);
g_return_val_if_fail (icalcomp_vcal != NULL, FALSE);
icalcomp_event = icalcomponent_get_inner (icalcomp_vcal);
g_return_val_if_fail (icalcomp_event != NULL, FALSE);
source_type = e_cal_client_get_source_type (src_client);
switch (source_type) {
case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
icalcomp_kind = ICAL_VEVENT_COMPONENT;
break;
case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
icalcomp_kind = ICAL_VTODO_COMPONENT;
break;
case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
icalcomp_kind = ICAL_VJOURNAL_COMPONENT;
break;
default:
g_return_val_if_reached (FALSE);
}
processed_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
icalcomp_event = icalcomponent_get_first_component (icalcomp_vcal, icalcomp_kind);
/*
* This check should be removed in the near future.
* We should be able to work properly with multiselection, which means that we always
* will receive a component with subcomponents.
*/
if (icalcomp_event == NULL)
icalcomp_event = icalcomp_vcal;
for (;
icalcomp_event;
icalcomp_event = icalcomponent_get_next_component (icalcomp_vcal, icalcomp_kind)) {
GError *local_error = NULL;
uid = icalcomponent_get_uid (icalcomp_event);
if (g_hash_table_lookup (processed_uids, uid))
continue;
success = e_cal_client_get_object_sync (dest_client, uid, NULL, &icalcomp, cancellable, &local_error);
if (success) {
success = e_cal_client_modify_object_sync (
dest_client, icalcomp_event, CALOBJ_MOD_ALL, cancellable, error);
icalcomponent_free (icalcomp);
if (!success)
goto exit;
if (!do_copy) {
ECalObjModType mod_type = CALOBJ_MOD_THIS;
/* Remove the item from the source calendar. */
if (e_cal_util_component_is_instance (icalcomp_event) ||
e_cal_util_component_has_recurrences (icalcomp_event))
mod_type = CALOBJ_MOD_ALL;
success = e_cal_client_remove_object_sync (
src_client, uid, NULL, mod_type, cancellable, error);
if (!success)
goto exit;
}
continue;
} else if (local_error != NULL && !g_error_matches (
local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND)) {
g_propagate_error (error, local_error);
goto exit;
} else {
g_clear_error (&local_error);
}
if (e_cal_util_component_is_instance (icalcomp_event)) {
GSList *ecalcomps = NULL, *eiter;
ECalComponent *comp;
success = e_cal_client_get_objects_for_uid_sync (src_client, uid, &ecalcomps, cancellable, error);
if (!success)
goto exit;
if (ecalcomps && !ecalcomps->next) {
/* only one component, no need for a vCalendar list */
comp = ecalcomps->data;
icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
} else {
icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
for (eiter = ecalcomps; eiter; eiter = g_slist_next (eiter)) {
comp = eiter->data;
icalcomponent_add_component (
icalcomp,
icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
}
}
e_cal_client_free_ecalcomp_slist (ecalcomps);
} else {
icalcomp = icalcomponent_new_clone (icalcomp_event);
}
if (do_copy) {
/* Change the UID to avoid problems with duplicated UID */
new_uid = e_cal_component_gen_uid ();
if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
/* in case of a vCalendar, the component might have detached instances,
* thus change the UID on all of the subcomponents of it */
for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
subcomp;
subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
icalcomponent_set_uid (subcomp, new_uid);
}
} else {
icalcomponent_set_uid (icalcomp, new_uid);
}
g_free (new_uid);
new_uid = NULL;
}
ftd.source_client = src_client;
ftd.destination_client = dest_client;
ftd.cancellable = cancellable;
ftd.error = error;
ftd.success = TRUE;
if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
/* in case of a vCalendar, the component might have detached instances,
* thus check timezones on all of the subcomponents of it */
for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
subcomp && ftd.success;
subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
icalcomponent_foreach_tzid (subcomp, add_timezone_to_cal_cb, &ftd);
}
} else {
icalcomponent_foreach_tzid (icalcomp, add_timezone_to_cal_cb, &ftd);
}
if (!ftd.success) {
success = FALSE;
goto exit;
}
if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
gboolean did_add = FALSE;
/* in case of a vCalendar, the component might have detached instances,
* thus add the master object first, and then all of the subcomponents of it */
for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
subcomp && !did_add;
subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
if (icaltime_is_null_time (icalcomponent_get_recurrenceid (subcomp))) {
did_add = TRUE;
success = e_cal_client_create_object_sync (
dest_client, subcomp,
&new_uid, cancellable, error);
g_free (new_uid);
}
}
if (!success) {
icalcomponent_free (icalcomp);
goto exit;
}
/* deal with detached instances */
for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
subcomp && success;
subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
if (!icaltime_is_null_time (icalcomponent_get_recurrenceid (subcomp))) {
if (did_add) {
success = e_cal_client_modify_object_sync (
dest_client, subcomp,
CALOBJ_MOD_THIS, cancellable, error);
} else {
/* just in case there are only detached instances and no master object */
did_add = TRUE;
success = e_cal_client_create_object_sync (
dest_client, subcomp,
&new_uid, cancellable, error);
g_free (new_uid);
}
}
}
} else {
success = e_cal_client_create_object_sync (dest_client, icalcomp, &new_uid, cancellable, error);
g_free (new_uid);
}
icalcomponent_free (icalcomp);
if (!success)
goto exit;
if (!do_copy) {
ECalObjModType mod_type = CALOBJ_MOD_THIS;
/* Remove the item from the source calendar. */
if (e_cal_util_component_is_instance (icalcomp_event) ||
e_cal_util_component_has_recurrences (icalcomp_event))
mod_type = CALOBJ_MOD_ALL;
success = e_cal_client_remove_object_sync (src_client, uid, NULL, mod_type, cancellable, error);
if (!success)
goto exit;
}
g_hash_table_insert (processed_uids, g_strdup (uid), GINT_TO_POINTER (1));
}
exit:
g_hash_table_destroy (processed_uids);
return success;
}