1352 lines
38 KiB
C
1352 lines
38 KiB
C
/* gcal-manager.c
|
|
*
|
|
* Copyright (C) 2015 Erick Pérez Castellanos <erickpc@gnome.org>
|
|
*
|
|
* gnome-calendar is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* gnome-calendar 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 General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalManager"
|
|
|
|
#include "gcal-application.h"
|
|
#include "gcal-context.h"
|
|
#include "gcal-debug.h"
|
|
#include "gcal-manager.h"
|
|
#include "gcal-timeline.h"
|
|
#include "gcal-timeline-subscriber.h"
|
|
#include "gcal-utils.h"
|
|
|
|
#include <libedataserverui4/libedataserverui4.h>
|
|
|
|
/**
|
|
* SECTION:gcal-manager
|
|
* @short_description: The backend of GNOME Calendar
|
|
* @title:GcalManager
|
|
* @stability:unstable
|
|
*
|
|
* #GcalManager is the backend of GNOME Calendar. It sets everything
|
|
* up, connects to the Online Accounts daemon, and manages the events
|
|
* and calendars.
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
GcalEvent *event;
|
|
GcalManager *manager;
|
|
} AsyncOpsData;
|
|
|
|
typedef struct
|
|
{
|
|
gchar *event_uid;
|
|
GcalCalendar *calendar;
|
|
GcalCalendar *new_calendar;
|
|
ECalComponent *new_component;
|
|
GcalManager *manager;
|
|
} MoveEventData;
|
|
|
|
struct _GcalManager
|
|
{
|
|
GObject parent;
|
|
|
|
/**
|
|
* The list of clients we are managing.
|
|
* Each value is of type GCalStoreUnit
|
|
* And each key is the source uid
|
|
*/
|
|
GHashTable *clients;
|
|
GListStore *calendars_model;
|
|
|
|
ESourceRegistry *source_registry;
|
|
ECredentialsPrompter *credentials_prompter;
|
|
|
|
GCancellable *async_ops;
|
|
|
|
gint clients_synchronizing;
|
|
|
|
GcalTimeline *timeline;
|
|
|
|
GcalContext *context;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GcalManager, gcal_manager, G_TYPE_OBJECT)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONTEXT,
|
|
PROP_DEFAULT_CALENDAR,
|
|
PROP_SYNCHRONIZING,
|
|
NUM_PROPS
|
|
};
|
|
|
|
enum
|
|
{
|
|
CALENDAR_ADDED,
|
|
CALENDAR_CHANGED,
|
|
CALENDAR_REMOVED,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint signals[NUM_SIGNALS] = { 0, };
|
|
static GParamSpec *properties[NUM_PROPS] = { NULL, };
|
|
|
|
static void
|
|
free_async_ops_data (AsyncOpsData *data)
|
|
{
|
|
g_object_unref (data->event);
|
|
g_free (data);
|
|
}
|
|
|
|
static void
|
|
remove_source (GcalManager *self,
|
|
ESource *source)
|
|
{
|
|
g_autoptr (GcalCalendar) calendar = NULL;
|
|
guint position;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (E_IS_SOURCE (source));
|
|
|
|
calendar = g_hash_table_lookup (self->clients, source);
|
|
|
|
if (!calendar)
|
|
return;
|
|
|
|
GCAL_TRACE_MSG ("Removing calendar %s (%s)",
|
|
e_source_get_display_name (source),
|
|
e_source_get_uid (source));
|
|
|
|
g_object_ref (calendar);
|
|
|
|
gcal_timeline_remove_calendar (self->timeline, calendar);
|
|
g_hash_table_remove (self->clients, source);
|
|
|
|
g_list_store_find (self->calendars_model, calendar, &position);
|
|
g_list_store_remove (self->calendars_model, position);
|
|
|
|
g_signal_emit (self, signals[CALENDAR_REMOVED], 0, calendar);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
source_changed (GcalManager *self,
|
|
ESource *source)
|
|
{
|
|
GcalCalendar *calendar;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
calendar = g_hash_table_lookup (self->clients, source);
|
|
|
|
if (!calendar)
|
|
GCAL_RETURN ();
|
|
|
|
g_signal_emit (self, signals[CALENDAR_CHANGED], 0, calendar);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
on_client_refreshed (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GcalManager *self = GCAL_MANAGER (user_data);
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (e_client_refresh_finish (E_CLIENT (source_object), result, &error))
|
|
{
|
|
ESource *source = e_client_get_source (E_CLIENT (source_object));
|
|
/* FIXME: add notification to UI */
|
|
g_debug ("Client of source: %s refreshed successfully", e_source_get_uid (source));
|
|
}
|
|
else
|
|
{
|
|
/* FIXME: do something when there was some error */
|
|
g_warning ("Error synchronizing client");
|
|
}
|
|
|
|
self->clients_synchronizing--;
|
|
if (self->clients_synchronizing == 0)
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYNCHRONIZING]);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static gint
|
|
sort_calendar_by_name_cb (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
GcalCalendar *calendar_a = GCAL_CALENDAR ((gpointer) a);
|
|
GcalCalendar *calendar_b = GCAL_CALENDAR ((gpointer) b);
|
|
g_autofree gchar *parent_name_a = NULL;
|
|
g_autofree gchar *parent_name_b = NULL;
|
|
GcalManager *self;
|
|
gint retval;
|
|
|
|
retval = g_utf8_collate (gcal_calendar_get_name (calendar_a), gcal_calendar_get_name (calendar_b));
|
|
if (retval != 0)
|
|
return retval;
|
|
|
|
self = GCAL_MANAGER (user_data);
|
|
get_source_parent_name_color (self, gcal_calendar_get_source (calendar_a), &parent_name_a, NULL);
|
|
get_source_parent_name_color (self, gcal_calendar_get_source (calendar_b), &parent_name_b, NULL);
|
|
|
|
return g_utf8_collate (parent_name_a, parent_name_b);
|
|
}
|
|
|
|
static void
|
|
on_calendar_name_changed_cb (GcalCalendar *calendar,
|
|
GParamSpec *pspec,
|
|
GcalManager *self)
|
|
{
|
|
g_list_store_sort (self->calendars_model, sort_calendar_by_name_cb, self);
|
|
}
|
|
|
|
static void
|
|
on_calendar_created_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr (ESource) default_source = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
ESourceRefresh *refresh_extension;
|
|
ESourceOffline *offline_extension;
|
|
GcalCalendar *calendar;
|
|
GcalManager *self;
|
|
ECalClient *client;
|
|
ESource *source;
|
|
gboolean visible;
|
|
|
|
self = GCAL_MANAGER (user_data);
|
|
calendar = gcal_calendar_new_finish (result, &error);
|
|
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
|
!g_error_matches (error, GCAL_CALENDAR_ERROR, GCAL_CALENDAR_ERROR_NOT_CALENDAR))
|
|
{
|
|
g_warning ("Failed to open/connect to calendar: %s", error->message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
g_debug ("Source %s (%s) connected",
|
|
gcal_calendar_get_name (calendar),
|
|
gcal_calendar_get_id (calendar));
|
|
|
|
visible = gcal_calendar_get_visible (calendar);
|
|
client = gcal_calendar_get_client (calendar);
|
|
source = gcal_calendar_get_source (calendar);
|
|
|
|
g_hash_table_insert (self->clients, g_object_ref (source), calendar);
|
|
g_list_store_insert_sorted (self->calendars_model, calendar, sort_calendar_by_name_cb, self);
|
|
g_signal_connect (calendar, "notify::name", G_CALLBACK (on_calendar_name_changed_cb), self);
|
|
|
|
gcal_timeline_add_calendar (self->timeline, calendar);
|
|
|
|
/* refresh client when it's added */
|
|
if (visible && e_client_check_refresh_supported (E_CLIENT (client)))
|
|
{
|
|
e_client_refresh (E_CLIENT (client), NULL, on_client_refreshed, user_data);
|
|
|
|
self->clients_synchronizing++;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYNCHRONIZING]);
|
|
}
|
|
|
|
/* Cache all the online calendars, so the user can see them offline */
|
|
offline_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_OFFLINE);
|
|
e_source_offline_set_stay_synchronized (offline_extension, TRUE);
|
|
|
|
/* And also make sure the source is periodically updated */
|
|
refresh_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_REFRESH);
|
|
e_source_refresh_set_enabled (refresh_extension, TRUE);
|
|
e_source_refresh_set_interval_minutes (refresh_extension, 30);
|
|
|
|
e_source_registry_commit_source (self->source_registry,
|
|
source,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
g_signal_emit (self, signals[CALENDAR_ADDED], 0, calendar);
|
|
|
|
default_source = e_source_registry_ref_default_calendar (self->source_registry);
|
|
if (default_source == source)
|
|
g_object_notify (G_OBJECT (self->source_registry), "default-calendar");
|
|
}
|
|
|
|
static void
|
|
load_source (GcalManager *self,
|
|
ESource *source)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
if (g_hash_table_contains (self->clients, source))
|
|
{
|
|
g_warning ("%s: Skipping already loaded source: %s",
|
|
G_STRFUNC,
|
|
e_source_get_uid (source));
|
|
return;
|
|
}
|
|
|
|
gcal_calendar_new (source, self->async_ops, on_calendar_created_cb, self);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static gboolean
|
|
transform_e_source_to_gcal_calendar_cb (GBinding *binding,
|
|
const GValue *from_value,
|
|
GValue *to_value,
|
|
gpointer user_data)
|
|
{
|
|
GcalCalendar *calendar;
|
|
GcalManager *self;
|
|
ESource *source;
|
|
|
|
self = GCAL_MANAGER (user_data);
|
|
source = g_value_get_object (from_value);
|
|
|
|
calendar = g_hash_table_lookup (self->clients, source);
|
|
g_value_set_object (to_value, calendar);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_event_created (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
AsyncOpsData *data;
|
|
ECalClient *client;
|
|
gchar *new_uid;
|
|
GError *error;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
data = (AsyncOpsData*) user_data;
|
|
client = E_CAL_CLIENT (source_object);
|
|
new_uid = NULL;
|
|
error = NULL;
|
|
|
|
if (!e_cal_client_create_object_finish (client, result, &new_uid, &error))
|
|
{
|
|
/* Some error */
|
|
g_warning ("Error creating object: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
{
|
|
g_object_ref (data->event);
|
|
gcal_manager_set_default_calendar (data->manager, gcal_event_get_calendar (data->event));
|
|
g_debug ("Event: %s created successfully", new_uid);
|
|
}
|
|
|
|
g_free (new_uid);
|
|
free_async_ops_data (data);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* on_component_updated:
|
|
* @source_object: #ECalClient source
|
|
* @result: result of the operation
|
|
* @user_data: manager instance
|
|
*
|
|
* Called when an component is modified. Currently, it only checks for
|
|
* error, but a more sophisticated implementation will come in no time.
|
|
*
|
|
**/
|
|
static void
|
|
on_event_updated (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (! e_cal_client_modify_object_finish (E_CAL_CLIENT (source_object),
|
|
result,
|
|
&error))
|
|
{
|
|
g_warning ("Error updating component: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
g_object_unref (E_CAL_COMPONENT (user_data));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* on_event_removed:
|
|
* @source_object: #ECalClient source
|
|
* @result: result of the operation
|
|
* @user_data: an #ECalComponent
|
|
*
|
|
* Called when an component is removed. Currently, it only checks for
|
|
* error, but a more sophisticated implementation will come in no time.*
|
|
*
|
|
**/
|
|
static void
|
|
on_event_removed (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
ECalClient *client;
|
|
GcalEvent *event;
|
|
GError *error;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
client = E_CAL_CLIENT (source_object);
|
|
event = user_data;
|
|
error = NULL;
|
|
|
|
e_cal_client_remove_object_finish (client, result, &error);
|
|
|
|
if (error)
|
|
{
|
|
/* FIXME: Notify the user somehow */
|
|
g_warning ("Error removing event: %s", error->message);
|
|
g_error_free (error);
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
g_object_unref (event);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
show_source_error (const gchar *where,
|
|
const gchar *what,
|
|
ESource *source,
|
|
const GError *error)
|
|
{
|
|
if (!error || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
return;
|
|
|
|
/* TODO Show the error in UI, somehow */
|
|
g_warning ("%s: %s '%s': %s", where, what, e_source_get_display_name (source), error->message);
|
|
}
|
|
|
|
static void
|
|
source_invoke_authenticate_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
ESource *source = E_SOURCE (source_object);
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (!e_source_invoke_authenticate_finish (source, result, &error) &&
|
|
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
show_source_error (G_STRFUNC, "Failed to invoke authenticate", source, error);
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
source_trust_prompt_done_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
ETrustPromptResponse response = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
|
|
ESource *source = E_SOURCE (source_object);
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (!e_trust_prompt_run_for_source_finish (source, result, &response, &error))
|
|
{
|
|
show_source_error (G_STRFUNC, "Failed to prompt for trust for", source, error);
|
|
}
|
|
else if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
|
|
response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY)
|
|
{
|
|
/* Use NULL credentials to reuse those from the last time. */
|
|
e_source_invoke_authenticate (source, NULL, NULL /* cancellable */, source_invoke_authenticate_cb, NULL);
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
source_credentials_required_cb (ESourceRegistry *registry,
|
|
ESource *source,
|
|
ESourceCredentialsReason reason,
|
|
const gchar *certificate_pem,
|
|
GTlsCertificateFlags certificate_errors,
|
|
const GError *op_error,
|
|
GcalManager *self)
|
|
{
|
|
ECredentialsPrompter *credentials_prompter;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
|
|
credentials_prompter = self->credentials_prompter;
|
|
|
|
if (e_credentials_prompter_get_auto_prompt_disabled_for (credentials_prompter, source))
|
|
return;
|
|
|
|
if (reason == E_SOURCE_CREDENTIALS_REASON_SSL_FAILED)
|
|
{
|
|
e_trust_prompt_run_for_source (e_credentials_prompter_get_dialog_parent (credentials_prompter),
|
|
source,
|
|
certificate_pem,
|
|
certificate_errors,
|
|
op_error ? op_error->message : NULL,
|
|
TRUE /* allow_source_save */,
|
|
NULL /* cancellable */,
|
|
source_trust_prompt_done_cb,
|
|
NULL);
|
|
}
|
|
else if (reason == E_SOURCE_CREDENTIALS_REASON_ERROR && op_error)
|
|
{
|
|
show_source_error (G_STRFUNC, "Failed to authenticate", source, op_error);
|
|
}
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
source_get_last_credentials_required_arguments_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GcalManager *self = user_data;
|
|
ESource *source;
|
|
ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
|
|
gchar *certificate_pem = NULL;
|
|
GTlsCertificateFlags certificate_errors = 0;
|
|
GError *op_error = NULL;
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (E_IS_SOURCE (source_object));
|
|
|
|
source = E_SOURCE (source_object);
|
|
|
|
if (!e_source_get_last_credentials_required_arguments_finish (source, result, &reason,
|
|
&certificate_pem, &certificate_errors, &op_error, &error))
|
|
{
|
|
/* Can be cancelled only if the manager is disposing/disposed */
|
|
if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
show_source_error (G_STRFUNC, "Failed to get last credentials required arguments for", source, error);
|
|
|
|
g_clear_error (&error);
|
|
return;
|
|
}
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
|
|
if (reason != E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
|
|
source_credentials_required_cb (NULL, source, reason, certificate_pem, certificate_errors, op_error, self);
|
|
|
|
g_free (certificate_pem);
|
|
g_clear_error (&op_error);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_manager_finalize (GObject *object)
|
|
{
|
|
GcalManager *self =GCAL_MANAGER (object);
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_clear_object (&self->timeline);
|
|
|
|
if (self->context)
|
|
{
|
|
g_object_remove_weak_pointer (G_OBJECT (self->context), (gpointer *)&self->context);
|
|
self->context = NULL;
|
|
}
|
|
|
|
g_clear_object (&self->calendars_model);
|
|
g_clear_pointer (&self->clients, g_hash_table_destroy);
|
|
|
|
G_OBJECT_CLASS (gcal_manager_parent_class)->finalize (object);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_manager_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalManager *self = GCAL_MANAGER (object);
|
|
|
|
GCAL_ENTRY;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
g_assert (self->context == NULL);
|
|
self->context = g_value_get_object (value);
|
|
g_object_add_weak_pointer (G_OBJECT (self->context), (gpointer *)&self->context);
|
|
break;
|
|
|
|
case PROP_DEFAULT_CALENDAR:
|
|
gcal_manager_set_default_calendar (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_SYNCHRONIZING:
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_manager_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalManager *self = GCAL_MANAGER (object);
|
|
|
|
GCAL_ENTRY;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
g_value_set_object (value, self->context);
|
|
break;
|
|
|
|
case PROP_DEFAULT_CALENDAR:
|
|
g_value_set_object (value, gcal_manager_get_default_calendar (self));
|
|
break;
|
|
|
|
case PROP_SYNCHRONIZING:
|
|
g_value_set_boolean (value, self->clients_synchronizing != 0);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_manager_class_init (GcalManagerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = gcal_manager_finalize;
|
|
object_class->set_property = gcal_manager_set_property;
|
|
object_class->get_property = gcal_manager_get_property;
|
|
|
|
/**
|
|
* GcalManager:context:
|
|
*
|
|
* The #GcalContext.
|
|
*/
|
|
properties[PROP_CONTEXT] = g_param_spec_object ("context",
|
|
"Data context",
|
|
"Data context",
|
|
GCAL_TYPE_CONTEXT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GcalManager:default-calendar:
|
|
*
|
|
* The default calendar.
|
|
*/
|
|
properties[PROP_DEFAULT_CALENDAR] = g_param_spec_object ("default-calendar",
|
|
"Default calendar",
|
|
"The default calendar",
|
|
GCAL_TYPE_CALENDAR,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GcalManager:refreshing:
|
|
*
|
|
* Whether there are any sources refreshing or not.
|
|
*/
|
|
properties[PROP_SYNCHRONIZING] = g_param_spec_boolean ("synchronizing",
|
|
"Synchronizing",
|
|
"Whether there are any sources synchronizing or not",
|
|
FALSE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, NUM_PROPS, properties);
|
|
|
|
/* signals */
|
|
signals[CALENDAR_ADDED] = g_signal_new ("calendar-added",
|
|
GCAL_TYPE_MANAGER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_CALENDAR);
|
|
|
|
signals[CALENDAR_CHANGED] = g_signal_new ("calendar-changed",
|
|
GCAL_TYPE_MANAGER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_CALENDAR);
|
|
|
|
signals[CALENDAR_REMOVED] = g_signal_new ("calendar-removed",
|
|
GCAL_TYPE_MANAGER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_CALENDAR);
|
|
}
|
|
|
|
static void
|
|
gcal_manager_init (GcalManager *self)
|
|
{
|
|
self->calendars_model = g_list_store_new (GCAL_TYPE_CALENDAR);
|
|
}
|
|
|
|
/* Public API */
|
|
/**
|
|
* gcal_manager_new:
|
|
* @context: a #GcalContext
|
|
*
|
|
* Creates a new #GcalManager.
|
|
*
|
|
* Returns: (transfer full): a newly created #GcalManager
|
|
*/
|
|
GcalManager*
|
|
gcal_manager_new (GcalContext *context)
|
|
{
|
|
return g_object_new (GCAL_TYPE_MANAGER,
|
|
"context", context,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_source:
|
|
* @self: a #GcalManager
|
|
* @uid: the unique identifier of the source
|
|
*
|
|
* Retrieve a source according to it's UID. The source
|
|
* is referenced for thread-safety and must be unreferenced
|
|
* after user.
|
|
*
|
|
* Returns: (nullable)(transfer full): an #ESource, or %NULL.
|
|
*/
|
|
ESource*
|
|
gcal_manager_get_source (GcalManager *self,
|
|
const gchar *uid)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
return e_source_registry_ref_source (self->source_registry, uid);
|
|
}
|
|
|
|
GList*
|
|
gcal_manager_get_calendars (GcalManager *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
return g_hash_table_get_values (self->clients);
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_calendars_model:
|
|
* @self: a #GcalManager
|
|
*
|
|
* Retrieves a model with all available #GcalCalendar. This is useful
|
|
* for binding to combo rows.
|
|
*
|
|
* Returns: (transfer none): a #GListModel with all available #GcalCalendar
|
|
*/
|
|
GListModel*
|
|
gcal_manager_get_calendars_model (GcalManager *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
return G_LIST_MODEL (self->calendars_model);
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_default_calendar:
|
|
* @self: a #GcalManager
|
|
*
|
|
* Returns: (transfer none): a #GcalCalendar.
|
|
*/
|
|
GcalCalendar*
|
|
gcal_manager_get_default_calendar (GcalManager *self)
|
|
{
|
|
g_autoptr (ESource) default_source = NULL;
|
|
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
default_source = e_source_registry_ref_default_calendar (self->source_registry);
|
|
return g_hash_table_lookup (self->clients, default_source);
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_set_default_calendar:
|
|
* @self: a #GcalManager
|
|
* @calendar: the new default calendar.
|
|
*
|
|
* Sets the default calendar.
|
|
*/
|
|
void
|
|
gcal_manager_set_default_calendar (GcalManager *self,
|
|
GcalCalendar *calendar)
|
|
{
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
|
|
if (calendar == gcal_manager_get_default_calendar (self))
|
|
return;
|
|
|
|
e_source_registry_set_default_calendar (self->source_registry,
|
|
gcal_calendar_get_source (calendar));
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_timeline:
|
|
* @self: a #GcalManager
|
|
*
|
|
* Retrieves the default timeline of GNOME Calendar.
|
|
*
|
|
* Returns: (transfer none): a #GcalTimeline
|
|
*/
|
|
GcalTimeline*
|
|
gcal_manager_get_timeline (GcalManager *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
return self->timeline;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_add_source:
|
|
* @self: a #GcalManager
|
|
* @base_uri: URI defining the ESourceGroup the client will belongs
|
|
* @relative_uri: URI, relative to the base URI
|
|
* @color: a string representing a color parseable for gdk_rgba_parse
|
|
*
|
|
* Add a new calendar by its URI.
|
|
* The calendar is enabled by default
|
|
*
|
|
* Returns: (nullable)(transfer full): unique identifier of calendar's source, or
|
|
* %NULL.
|
|
*/
|
|
gchar*
|
|
gcal_manager_add_source (GcalManager *self,
|
|
const gchar *name,
|
|
const gchar *backend,
|
|
const gchar *color)
|
|
{
|
|
ESource *source;
|
|
ESourceCalendar *extension;
|
|
GError *error;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
source = e_source_new (NULL, NULL, NULL);
|
|
extension = E_SOURCE_CALENDAR (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
|
|
|
|
g_object_set (extension,
|
|
"backend-name", backend,
|
|
"color", color,
|
|
NULL);
|
|
|
|
e_source_set_display_name (source, name);
|
|
|
|
error = NULL;
|
|
e_source_registry_commit_source_sync (self->source_registry,
|
|
source,
|
|
NULL,
|
|
&error);
|
|
if (error)
|
|
{
|
|
g_warning ("Failed to store calendar configuration: %s", error->message);
|
|
g_object_unref (source);
|
|
g_clear_error (&error);
|
|
|
|
GCAL_RETURN (NULL);
|
|
}
|
|
|
|
load_source (self, source);
|
|
|
|
GCAL_RETURN (e_source_dup_uid (source));
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_save_source:
|
|
* @self: a #GcalManager
|
|
* @source: the target ESource
|
|
*
|
|
* Commit the given ESource.
|
|
*/
|
|
void
|
|
gcal_manager_save_source (GcalManager *self,
|
|
ESource *source)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (E_IS_SOURCE (source));
|
|
|
|
e_source_registry_commit_source_sync (self->source_registry, source, NULL, &error);
|
|
|
|
if (error)
|
|
{
|
|
/* FIXME: Notify the user somehow */
|
|
g_warning ("Error saving source: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_refresh:
|
|
* @self: a #GcalManager
|
|
*
|
|
* Forces a full refresh and synchronization of all available
|
|
* calendars.
|
|
*/
|
|
void
|
|
gcal_manager_refresh (GcalManager *self)
|
|
{
|
|
GList *clients;
|
|
GList *l;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
|
|
clients = g_hash_table_get_values (self->clients);
|
|
|
|
/* refresh clients */
|
|
for (l = clients; l != NULL; l = l->next)
|
|
{
|
|
GcalCalendar *calendar = l->data;
|
|
EClient *client;
|
|
|
|
client = E_CLIENT (gcal_calendar_get_client (calendar));
|
|
|
|
if (!e_client_check_refresh_supported (client))
|
|
continue;
|
|
|
|
e_client_refresh (client,
|
|
NULL,
|
|
on_client_refreshed,
|
|
self);
|
|
|
|
self->clients_synchronizing++;
|
|
}
|
|
|
|
g_list_free (clients);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYNCHRONIZING]);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_create_event:
|
|
* @self: a #GcalManager
|
|
* @event: a #GcalEvent
|
|
*
|
|
* Creates @event.
|
|
*/
|
|
void
|
|
gcal_manager_create_event (GcalManager *self,
|
|
GcalEvent *event)
|
|
{
|
|
ICalComponent *new_event_icalcomp;
|
|
ECalComponent *component;
|
|
GcalCalendar *calendar;
|
|
AsyncOpsData *data;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (GCAL_IS_EVENT (event));
|
|
|
|
component = gcal_event_get_component (event);
|
|
calendar = gcal_event_get_calendar (event);
|
|
|
|
new_event_icalcomp = e_cal_component_get_icalcomponent (component);
|
|
|
|
data = g_new0 (AsyncOpsData, 1);
|
|
data->event = g_object_ref (event);
|
|
data->manager = self;
|
|
|
|
e_cal_client_create_object (gcal_calendar_get_client (calendar),
|
|
new_event_icalcomp,
|
|
E_CAL_OPERATION_FLAG_NONE,
|
|
self->async_ops,
|
|
on_event_created,
|
|
data);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_update_event:
|
|
* @self: a #GcalManager
|
|
* @event: a #GcalEvent
|
|
* @mod: an #GcalRecurrenceModType
|
|
*
|
|
* Saves all changes made to @event persistently.
|
|
*/
|
|
void
|
|
gcal_manager_update_event (GcalManager *self,
|
|
GcalEvent *event,
|
|
GcalRecurrenceModType mod)
|
|
{
|
|
ECalComponent *component;
|
|
GcalCalendar *calendar;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (GCAL_IS_EVENT (event));
|
|
|
|
calendar = gcal_event_get_calendar (event);
|
|
component = gcal_event_get_component (event);
|
|
|
|
e_cal_client_modify_object (gcal_calendar_get_client (calendar),
|
|
e_cal_component_get_icalcomponent (component),
|
|
(ECalObjModType) mod,
|
|
E_CAL_OPERATION_FLAG_NONE,
|
|
NULL,
|
|
on_event_updated,
|
|
g_object_ref (component));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_remove_event:
|
|
* @self: a #GcalManager
|
|
* @event: a #GcalEvent
|
|
* @mod: an #GcalRecurrenceModType
|
|
*
|
|
* Deletes @event.
|
|
*/
|
|
void
|
|
gcal_manager_remove_event (GcalManager *self,
|
|
GcalEvent *event,
|
|
GcalRecurrenceModType mod)
|
|
{
|
|
ECalComponent *component;
|
|
GcalCalendar *calendar;
|
|
gchar *rid;
|
|
const gchar *uid;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (GCAL_IS_EVENT (event));
|
|
|
|
component = gcal_event_get_component (event);
|
|
calendar = gcal_event_get_calendar (event);
|
|
rid = NULL;
|
|
|
|
uid = e_cal_component_get_uid (component);
|
|
|
|
if (gcal_event_has_recurrence (event))
|
|
rid = e_cal_component_get_recurid_as_string (component);
|
|
|
|
e_cal_client_remove_object (gcal_calendar_get_client (calendar),
|
|
uid,
|
|
mod == GCAL_RECURRENCE_MOD_ALL ? NULL : rid,
|
|
(ECalObjModType) mod,
|
|
E_CAL_OPERATION_FLAG_NONE,
|
|
self->async_ops,
|
|
on_event_removed,
|
|
g_object_ref (event));
|
|
|
|
g_free (rid);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_move_event_to_source:
|
|
* @self: a #GcalManager
|
|
* @event: a #GcalEvent
|
|
* @dest: the destination calendar
|
|
*
|
|
* Moves @event to @dest calendar. This is a fail-safe operation:
|
|
* worst case, the user will have two duplicated events, and we
|
|
* guarantee to never loose any data.
|
|
*/
|
|
void
|
|
gcal_manager_move_event_to_source (GcalManager *self,
|
|
GcalEvent *event,
|
|
ESource *dest)
|
|
{
|
|
ECalComponent *ecomponent;
|
|
ECalComponent *clone;
|
|
ICalComponent *comp;
|
|
GcalCalendar *calendar;
|
|
ECalComponentId *id;
|
|
GError *error;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_if_fail (GCAL_IS_MANAGER (self));
|
|
g_return_if_fail (GCAL_IS_EVENT (event));
|
|
g_return_if_fail (E_IS_SOURCE (dest));
|
|
|
|
error = NULL;
|
|
|
|
/* First, try to create the component on the destination source */
|
|
calendar = g_hash_table_lookup (self->clients, dest);
|
|
|
|
ecomponent = gcal_event_get_component (event);
|
|
clone = e_cal_component_clone (ecomponent);
|
|
comp = e_cal_component_get_icalcomponent (clone);
|
|
|
|
g_assert (clone != NULL);
|
|
|
|
e_cal_client_create_object_sync (gcal_calendar_get_client (calendar),
|
|
comp,
|
|
E_CAL_OPERATION_FLAG_NONE,
|
|
NULL,
|
|
NULL,
|
|
&error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error moving source: %s", error->message);
|
|
g_clear_error (&error);
|
|
GCAL_EXIT;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* After we carefully confirm that a clone of the component was
|
|
* created, try to remove the old component. Data loss it the last
|
|
* thing we want to happen here.
|
|
*/
|
|
calendar = gcal_event_get_calendar (event);
|
|
|
|
id = e_cal_component_get_id (ecomponent);
|
|
|
|
e_cal_client_remove_object_sync (gcal_calendar_get_client (calendar),
|
|
e_cal_component_id_get_uid (id),
|
|
e_cal_component_id_get_rid (id),
|
|
E_CAL_OBJ_MOD_THIS,
|
|
E_CAL_OPERATION_FLAG_NONE,
|
|
self->async_ops,
|
|
&error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error moving source: %s", error->message);
|
|
g_clear_error (&error);
|
|
GCAL_EXIT;
|
|
return;
|
|
}
|
|
|
|
e_cal_component_id_free (id);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_events:
|
|
* @self: a #GcalManager
|
|
* @start_date: the start of the dete range
|
|
* @end_date: the end of the dete range
|
|
*
|
|
* Returns a list with #GcalEvent objects owned by the caller,
|
|
* the list and the objects. The components inside the list are
|
|
* owned by the caller as well.
|
|
*
|
|
* Returns: (nullable)(transfer full)(content-type GcalEvent):a #GList
|
|
*/
|
|
GPtrArray*
|
|
gcal_manager_get_events (GcalManager *self,
|
|
GDateTime *start_date,
|
|
GDateTime *end_date)
|
|
{
|
|
g_autoptr (GPtrArray) events_at_range = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL);
|
|
|
|
events_at_range = gcal_timeline_get_events_at_range (self->timeline, start_date, end_date);
|
|
|
|
GCAL_RETURN (g_steal_pointer (&events_at_range));
|
|
}
|
|
|
|
/**
|
|
* gcal_manager_get_synchronizing:
|
|
* @self: a #GcalManager
|
|
*
|
|
* Retrieves whether @self is refreshing the calendars or not.
|
|
*
|
|
* Returns: %TRUE if any calendars are being synchronizing.
|
|
*/
|
|
gboolean
|
|
gcal_manager_get_synchronizing (GcalManager *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MANAGER (self), FALSE);
|
|
|
|
return self->clients_synchronizing != 0;
|
|
}
|
|
|
|
void
|
|
gcal_manager_startup (GcalManager *self)
|
|
{
|
|
ESourceCredentialsProvider *credentials_provider;
|
|
GList *sources, *l;
|
|
GError *error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
self->timeline = gcal_timeline_new (self->context);
|
|
self->clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
|
|
(GEqualFunc) e_source_equal,
|
|
g_object_unref,
|
|
g_object_unref);
|
|
|
|
/* reading sources and schedule its connecting */
|
|
self->source_registry = e_source_registry_new_sync (NULL, &error);
|
|
|
|
if (!self->source_registry)
|
|
{
|
|
g_warning ("Failed to access calendar configuration: %s", error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
g_object_bind_property_full (self->source_registry,
|
|
"default-calendar",
|
|
self,
|
|
"default-calendar",
|
|
G_BINDING_DEFAULT,
|
|
transform_e_source_to_gcal_calendar_cb,
|
|
NULL,
|
|
self,
|
|
NULL);
|
|
|
|
self->credentials_prompter = e_credentials_prompter_new (self->source_registry);
|
|
|
|
/* First disable credentials prompt for all but calendar sources... */
|
|
sources = e_source_registry_list_sources (self->source_registry, NULL);
|
|
|
|
for (l = sources; l != NULL; l = g_list_next (l))
|
|
{
|
|
ESource *source = E_SOURCE (l->data);
|
|
|
|
/* Mark for skip also currently disabled sources */
|
|
if (!e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
|
|
!e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION))
|
|
{
|
|
e_credentials_prompter_set_auto_prompt_disabled_for (self->credentials_prompter, source, TRUE);
|
|
}
|
|
else
|
|
{
|
|
e_source_get_last_credentials_required_arguments (source,
|
|
NULL,
|
|
source_get_last_credentials_required_arguments_cb,
|
|
self);
|
|
}
|
|
}
|
|
|
|
g_list_free_full (sources, g_object_unref);
|
|
|
|
credentials_provider = e_credentials_prompter_get_provider (self->credentials_prompter);
|
|
|
|
/* ...then enable credentials prompt for credential source of the calendar sources,
|
|
which can be a collection source. */
|
|
sources = e_source_registry_list_sources (self->source_registry, E_SOURCE_EXTENSION_CALENDAR);
|
|
|
|
for (l = sources; l != NULL; l = g_list_next (l))
|
|
{
|
|
ESource *source, *cred_source;
|
|
|
|
source = l->data;
|
|
cred_source = e_source_credentials_provider_ref_credentials_source (credentials_provider, source);
|
|
|
|
if (cred_source && !e_source_equal (source, cred_source))
|
|
{
|
|
e_credentials_prompter_set_auto_prompt_disabled_for (self->credentials_prompter, cred_source, FALSE);
|
|
|
|
/* Only consider SSL errors */
|
|
if (e_source_get_connection_status (cred_source) != E_SOURCE_CONNECTION_STATUS_SSL_FAILED)
|
|
continue;
|
|
|
|
e_source_get_last_credentials_required_arguments (cred_source,
|
|
NULL,
|
|
source_get_last_credentials_required_arguments_cb,
|
|
self);
|
|
}
|
|
|
|
g_clear_object (&cred_source);
|
|
}
|
|
|
|
g_list_free_full (sources, g_object_unref);
|
|
|
|
/* The eds_credentials_prompter responses to REQUIRED and REJECTED reasons,
|
|
the SSL_FAILED should be handled elsewhere. */
|
|
g_signal_connect_object (self->source_registry, "credentials-required", G_CALLBACK (source_credentials_required_cb), self, 0);
|
|
|
|
e_credentials_prompter_process_awaiting_credentials (self->credentials_prompter);
|
|
|
|
g_signal_connect_object (self->source_registry, "source-added", G_CALLBACK (load_source), self, G_CONNECT_SWAPPED);
|
|
g_signal_connect_object (self->source_registry, "source-removed", G_CALLBACK (remove_source), self, G_CONNECT_SWAPPED);
|
|
g_signal_connect_object (self->source_registry, "source-changed", G_CALLBACK (source_changed), self, G_CONNECT_SWAPPED);
|
|
|
|
sources = e_source_registry_list_enabled (self->source_registry, E_SOURCE_EXTENSION_CALENDAR);
|
|
|
|
for (l = sources; l != NULL; l = l->next)
|
|
load_source (self, l->data);
|
|
|
|
g_list_free (sources);
|
|
|
|
GCAL_EXIT;
|
|
}
|