Files
federated-calendar/src/gcal-manager.c
T
Georges Basile Stavracas Neto 6283d9d025 application: update event colors when agenda changes
When we change a source (aka agenda) color, we have to
restart Calendar in order to see it in action.

After the work on GcalEvent, events are now able to track
the agenda's color changes, and the event widget updates
itself in response to that.

The last step was simple notifying GcalApplication when an
agenda changes, so it knows when to recreate the color schemes.
2016-02-14 12:51:32 -02:00

1625 lines
50 KiB
C

/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* 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/>.
*/
#include "gcal-manager.h"
#include "gcal-utils.h"
#include <libedataserverui/libedataserverui.h>
typedef struct
{
GcalEvent *event;
GcalManager *manager;
} AsyncOpsData;
typedef struct
{
ECalDataModelSubscriber *subscriber;
gchar *query;
guint sources_left;
gboolean passed_start;
gboolean search_done;
} ViewStateData;
typedef struct
{
ECalClient *client;
gboolean enabled;
gboolean connected;
} GcalManagerUnit;
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;
ESourceRegistry *source_registry;
ECredentialsPrompter *credentials_prompter;
ECalDataModel *e_data_model;
ECalDataModel *search_data_model;
ECalDataModel *shell_search_data_model;
ViewStateData *search_view_data;
GCancellable *async_ops;
GoaClient *goa_client;
/* state flags */
gboolean goa_client_ready;
gchar **disabled_sources;
gint sources_at_launch;
/* timezone */
icaltimezone *system_timezone;
/* property */
GSettings *settings;
};
struct _MoveEventData
{
gchar *event_uid;
GcalManagerUnit *unit;
GcalManagerUnit *new_unit;
ECalComponent *new_component;
GcalManager *manager;
};
typedef struct _MoveEventData MoveEventData;
enum
{
PROP_0,
PROP_SETTINGS,
};
enum
{
SOURCE_ACTIVATED,
SOURCE_ADDED,
SOURCE_CHANGED,
SOURCE_REMOVED,
SOURCE_ENABLED,
LOAD_COMPLETED,
QUERY_COMPLETED,
GOA_CLIENT_READY,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS] = { 0, };
static void free_async_ops_data (AsyncOpsData *data);
static void free_unit_data (GcalManagerUnit *data);
static void source_changed (GcalManager *manager,
ESource *source);
static void load_source (GcalManager *manager,
ESource *source);
static void on_client_connected (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void on_client_readonly_changed (EClient *client,
GParamSpec *pspec,
gpointer user_data);
static void on_client_refreshed (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void on_event_created (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void on_event_updated (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void on_event_removed (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void remove_source (GcalManager *manager,
ESource *source);
/* class_init && init vfuncs */
static void gcal_manager_constructed (GObject *object);
static void gcal_manager_finalize (GObject *object);
static void gcal_manager_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gcal_manager_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (GcalManager, gcal_manager, G_TYPE_OBJECT)
/* -- start: threading related code provided by Milan Crha */
typedef struct {
EThreadJobFunc func;
gpointer user_data;
GDestroyNotify free_user_data;
GCancellable *cancellable;
GError *error;
} ThreadJobData;
static void
thread_job_data_free (gpointer ptr)
{
ThreadJobData *tjd = ptr;
if (tjd != NULL)
{
/* This should go to UI with more info/description,
* if it is not G_IOI_ERROR_CANCELLED */
if (tjd->error != NULL && !g_error_matches (tjd->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Job failed: %s\n", tjd->error->message);
if (tjd->free_user_data != NULL)
tjd->free_user_data (tjd->user_data);
g_clear_object (&tjd->cancellable);
g_clear_error (&tjd->error);
g_free (tjd);
}
}
static gpointer
thread_job_thread (gpointer user_data)
{
ThreadJobData *tjd = user_data;
g_return_val_if_fail (tjd != NULL, NULL);
if (tjd->func != NULL)
tjd->func (tjd->user_data, tjd->cancellable, &tjd->error);
thread_job_data_free (tjd);
return g_thread_self ();
}
static GCancellable *
submit_thread_job (EThreadJobFunc func,
gpointer user_data,
GDestroyNotify free_user_data)
{
ThreadJobData *tjd;
GThread *thread;
GCancellable *cancellable;
cancellable = g_cancellable_new ();
tjd = g_new0 (ThreadJobData, 1);
tjd->func = func;
tjd->user_data = user_data;
tjd->free_user_data = free_user_data;
/* user should be able to cancel this cancellable somehow */
tjd->cancellable = g_object_ref (cancellable);
tjd->error = NULL;
thread = g_thread_try_new (NULL,
thread_job_thread, tjd,
&tjd->error);
if (thread != NULL)
{
g_thread_unref (thread);
}
else
{
thread_job_data_free (tjd);
g_clear_object (&cancellable);
}
return cancellable;
}
/* -- end: threading related code provided by Milan Crha -- */
static void
free_async_ops_data (AsyncOpsData *data)
{
g_object_unref (data->event);
g_free (data);
}
static void
free_unit_data (GcalManagerUnit *data)
{
g_object_unref (data->client);
g_free (data);
}
static gboolean
gather_events (ECalDataModel *data_model,
ECalClient *client,
const ECalComponentId *id,
ECalComponent *comp,
time_t instance_start,
time_t instance_end,
gpointer user_data)
{
GList **result = user_data;
GcalEvent *event;
event = gcal_event_new (e_client_get_source (E_CLIENT (client)), comp);
*result = g_list_append (*result, event);/* FIXME: add me sorted */
return TRUE;
}
static void
source_changed (GcalManager *manager,
ESource *source)
{
if (g_hash_table_lookup (manager->clients, source) != NULL &&
e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
{
g_signal_emit (manager, signals[SOURCE_CHANGED], 0, source);
}
}
/**
* load_source:
* @manager: Manager instance
* @source: Loaded source
*
* Create @GcalManagerUnit data, add it to internal hash of sources.
* Open/connect to calendars available
*
**/
static void
load_source (GcalManager *manager,
ESource *source)
{
if (g_hash_table_lookup (manager->clients, source) == NULL &&
e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
{
/* NULL: because maybe the operation cannot be really cancelled */
e_cal_client_connect (source,
E_CAL_CLIENT_SOURCE_TYPE_EVENTS, 1, NULL,
on_client_connected,
manager);
}
else
{
g_warning ("%s: Skipping already loaded source: %s",
G_STRFUNC,
e_source_get_uid (source));
}
}
static void
on_client_connected (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GcalManager *manager;
ECalClient *client;
GError *error;
ESource *source;
GcalManagerUnit *unit;
manager = GCAL_MANAGER (user_data);
source = e_client_get_source (E_CLIENT (source_object));
manager->sources_at_launch--;
if (manager->sources_at_launch == 0)
g_signal_emit (user_data, signals[LOAD_COMPLETED], 0);
error = NULL;
client = E_CAL_CLIENT (e_cal_client_connect_finish (result, &error));
if (error == NULL)
{
unit = g_new0 (GcalManagerUnit, 1);
unit->connected = TRUE;
unit->client = g_object_ref (client);
g_hash_table_insert (manager->clients, source, unit);
g_debug ("Source %s (%s) connected",
e_source_get_display_name (source),
e_source_get_uid (source));
/* notify the readonly property */
g_signal_connect (client, "notify::readonly", G_CALLBACK (on_client_readonly_changed), user_data);
if (g_strv_contains ((const gchar * const *) manager->disabled_sources, e_source_get_uid (source)))
{
unit->enabled = FALSE;
}
else
{
unit->enabled = TRUE;
e_cal_data_model_add_client (manager->e_data_model, client);
e_cal_data_model_add_client (manager->search_data_model, client);
if (manager->shell_search_data_model != NULL)
e_cal_data_model_add_client (manager->shell_search_data_model, client);
}
/* refresh client when it's added */
if (unit->enabled && e_client_check_refresh_supported (E_CLIENT (client)))
{
e_client_refresh (E_CLIENT (client), NULL, on_client_refreshed, user_data);
}
g_signal_emit (GCAL_MANAGER (user_data), signals[SOURCE_ADDED], 0, source, unit->enabled);
g_clear_object (&client);
}
else
{
/* in any other case, remove it*/
remove_source (GCAL_MANAGER (user_data), source);
g_warning ("%s: Failed to open/connect '%s': %s",
G_STRFUNC,
e_source_get_display_name (source),
error->message);
g_object_unref (source);
g_error_free (error);
return;
}
}
static void
on_client_readonly_changed (EClient *client,
GParamSpec *pspec,
gpointer user_data)
{
GcalManager *manager;
ESource *source;
GcalManagerUnit *unit;
manager = GCAL_MANAGER (user_data);
source = e_client_get_source (client);
unit = g_hash_table_lookup (manager->clients, source);
if (unit && unit->enabled)
g_signal_emit (GCAL_MANAGER (user_data), signals[SOURCE_ACTIVATED], 0, source, !e_client_is_readonly (client));
}
static void
on_client_refreshed (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GError *error;
error = NULL;
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 succesfully", e_source_get_uid (source));
}
else
{
/* FIXME: do something when there was some error */
g_warning ("Error synchronizing client");
}
}
static void
on_event_created (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
AsyncOpsData *data;
ECalClient *client;
gchar *new_uid;
GError *error;
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);
}
g_debug ("Event: %s created successfully", new_uid);
g_free (new_uid);
free_async_ops_data (data);
}
/**
* on_component_updated:
* @source_object: {@link 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;
error = NULL;
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));
}
/**
* on_event_removed:
* @source_object: {@link ECalClient} source
* @result: result of the operation
* @user_data: an {@link 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;
GError *error;
client = E_CAL_CLIENT (source_object);
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);
}
g_object_unref (user_data);
}
void
remove_source (GcalManager *manager,
ESource *source)
{
GcalManagerUnit *unit;
g_return_if_fail (GCAL_IS_MANAGER (manager));
g_return_if_fail (E_IS_SOURCE (source));
e_cal_data_model_remove_client (manager->e_data_model,
e_source_get_uid (source));
e_cal_data_model_remove_client (manager->search_data_model,
e_source_get_uid (source));
unit = g_hash_table_lookup (manager->clients, source);
if (unit && unit->client)
g_signal_handlers_disconnect_by_data (unit->client, manager);
g_hash_table_remove (manager->clients, source);
g_signal_emit (manager, signals[SOURCE_REMOVED], 0, source);
}
static void
model_state_changed (GcalManager *manager,
ECalClientView *view,
ECalDataModelViewState state,
guint percent,
const gchar *message,
const GError *error,
ECalDataModel *data_model)
{
gchar *filter = e_cal_data_model_dup_filter (data_model);
if (state == E_CAL_DATA_MODEL_VIEW_STATE_START && g_strcmp0 (manager->search_view_data->query, filter) == 0)
{
manager->search_view_data->passed_start = TRUE;
goto out;
}
if (manager->search_view_data->passed_start && state == E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE &&
g_strcmp0 (manager->search_view_data->query, filter) == 0)
{
manager->search_view_data->sources_left--;
manager->search_view_data->search_done = (manager->search_view_data->sources_left == 0);
if (manager->search_view_data->search_done)
g_signal_emit (manager, signals[QUERY_COMPLETED], 0);
}
out:
g_free (filter);
return;
}
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;
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);
}
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;
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);
}
static void
source_credentials_required_cb (ESourceRegistry *registry,
ESource *source,
ESourceCredentialsReason reason,
const gchar *certificate_pem,
GTlsCertificateFlags certificate_errors,
const GError *op_error,
GcalManager *manager)
{
ECredentialsPrompter *credentials_prompter;
g_return_if_fail (GCAL_IS_MANAGER (manager));
credentials_prompter = manager->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);
}
}
static void
source_get_last_credentials_required_arguments_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GcalManager *manager = 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;
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 (manager));
if (reason != E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
source_credentials_required_cb (NULL, source, reason, certificate_pem, certificate_errors, op_error, manager);
g_free (certificate_pem);
g_clear_error (&op_error);
}
static void
gcal_manager_class_init (GcalManagerClass *klass)
{
G_OBJECT_CLASS (klass)->constructed = gcal_manager_constructed;
G_OBJECT_CLASS (klass)->finalize = gcal_manager_finalize;
G_OBJECT_CLASS (klass)->set_property = gcal_manager_set_property;
G_OBJECT_CLASS (klass)->get_property = gcal_manager_get_property;
/* properties */
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SETTINGS,
g_param_spec_object ("settings",
"Application settings",
"The settings of the application passed down from GcalApplication",
G_TYPE_SETTINGS,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
/* signals */
signals[SOURCE_ACTIVATED] = g_signal_new ("source-activated", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
signals[SOURCE_ADDED] = g_signal_new ("source-added", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
signals[SOURCE_CHANGED] = g_signal_new ("source-changed", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, E_TYPE_SOURCE);
signals[SOURCE_REMOVED] = g_signal_new ("source-removed", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_POINTER);
signals[SOURCE_ENABLED] = g_signal_new ("source-enabled", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, E_TYPE_SOURCE, G_TYPE_BOOLEAN);
signals[LOAD_COMPLETED] = g_signal_new ("load-completed", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[QUERY_COMPLETED] = g_signal_new ("query-completed", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[GOA_CLIENT_READY] = g_signal_new ("goa-client-ready", GCAL_TYPE_MANAGER, G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, GOA_TYPE_CLIENT);
}
static void
gcal_manager_client_ready_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GcalManager *manager = GCAL_MANAGER (user_data);
GError *error = NULL;
manager->goa_client = goa_client_new_finish (result, &error);
manager->goa_client_ready = TRUE;
g_signal_emit (user_data, signals[GOA_CLIENT_READY], 0, manager->goa_client);
if (error != NULL)
{
g_warning ("%s: Error retrieving GoaClient: %s",
G_STRFUNC,
error->message);
g_error_free (error);
}
}
static void
gcal_manager_init (GcalManager *self)
{
;
}
static void
gcal_manager_constructed (GObject *object)
{
GcalManager *manager;
GList *sources, *l;
GError *error = NULL;
ESourceCredentialsProvider *credentials_provider;
G_OBJECT_CLASS (gcal_manager_parent_class)->constructed (object);
manager = GCAL_MANAGER (object);
manager->disabled_sources = g_settings_get_strv (manager->settings, "disabled-sources");
manager->system_timezone = e_cal_util_get_system_timezone ();
manager->clients = g_hash_table_new_full ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal,
g_object_unref, (GDestroyNotify) free_unit_data);
/* load GOA client */
goa_client_new (NULL, // we won't really cancel it
(GAsyncReadyCallback) gcal_manager_client_ready_cb,
object);
/* reading sources and schedule its connecting */
manager->source_registry = e_source_registry_new_sync (NULL, &error);
if (manager->source_registry == NULL)
{
g_warning ("Failed to access calendar configuration: %s", error->message);
g_error_free (error);
return;
}
manager->credentials_prompter = e_credentials_prompter_new (manager->source_registry);
/* First disable credentials prompt for all but calendar sources... */
sources = e_source_registry_list_sources (manager->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 (manager->credentials_prompter, source, TRUE);
else
e_source_get_last_credentials_required_arguments (source, NULL,
source_get_last_credentials_required_arguments_cb, object);
}
g_list_free_full (sources, g_object_unref);
credentials_provider = e_credentials_prompter_get_provider (manager->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 (manager->source_registry, E_SOURCE_EXTENSION_CALENDAR);
for (l = sources; l != NULL; l = g_list_next (l))
{
ESource *source = E_SOURCE (l->data), *cred_source;
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 (manager->credentials_prompter, cred_source, FALSE);
if (e_source_get_connection_status (cred_source) == E_SOURCE_CONNECTION_STATUS_SSL_FAILED)
{
e_source_get_last_credentials_required_arguments (cred_source, NULL,
source_get_last_credentials_required_arguments_cb, object);
}
}
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 (manager->source_registry, "credentials-required", G_CALLBACK (source_credentials_required_cb), object);
e_credentials_prompter_process_awaiting_credentials (manager->credentials_prompter);
g_signal_connect_swapped (manager->source_registry, "source-added", G_CALLBACK (load_source), object);
g_signal_connect_swapped (manager->source_registry, "source-removed", G_CALLBACK (remove_source), object);
g_signal_connect_swapped (manager->source_registry, "source-changed", G_CALLBACK (source_changed), object);
/* create data model */
manager->e_data_model = e_cal_data_model_new (submit_thread_job);
manager->search_data_model = e_cal_data_model_new (submit_thread_job);
e_cal_data_model_set_expand_recurrences (manager->e_data_model, TRUE);
e_cal_data_model_set_timezone (manager->e_data_model, manager->system_timezone);
e_cal_data_model_set_expand_recurrences (manager->search_data_model, TRUE);
e_cal_data_model_set_timezone (manager->search_data_model, manager->system_timezone);
sources = e_source_registry_list_enabled (manager->source_registry, E_SOURCE_EXTENSION_CALENDAR);
manager->sources_at_launch = g_list_length (sources);
for (l = sources; l != NULL; l = l->next)
load_source (GCAL_MANAGER (object), l->data);
g_list_free (sources);
}
static void
gcal_manager_finalize (GObject *object)
{
GcalManager *manager =GCAL_MANAGER (object);
g_clear_object (&manager->settings);
g_clear_object (&manager->goa_client);
g_clear_object (&manager->e_data_model);
g_clear_object (&manager->search_data_model);
g_clear_object (&manager->shell_search_data_model);
g_strfreev (manager->disabled_sources);
if (manager->search_view_data != NULL)
{
g_free (manager->search_view_data->query);
g_free (manager->search_view_data);
}
g_hash_table_destroy (manager->clients);
}
static void
gcal_manager_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GcalManager *self = GCAL_MANAGER (object);
switch (property_id)
{
case PROP_SETTINGS:
if (g_set_object (&self->settings, g_value_get_object (value)))
g_object_notify (object, "settings");
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
gcal_manager_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GcalManager *self = GCAL_MANAGER (object);
switch (property_id)
{
case PROP_SETTINGS:
g_value_set_object (value, self->settings);
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
/* Public API */
/**
* gcal_manager_new_with_settings:
* @settings:
*
* Since: 0.0.1
* Return value: the new #GcalManager
* Returns: (transfer full):
**/
GcalManager*
gcal_manager_new_with_settings (GSettings *settings)
{
return GCAL_MANAGER (g_object_new (GCAL_TYPE_MANAGER, "settings", settings, NULL));
}
/**
* gcal_manager_get_source:
* @manager:
*
* Retrieve a source according to it's UID. The source
* is referenced for thread-safety and must be unreferenced
* after user.
*
* Returns: (Transfer full) an {@link ESource}, or NULL.
**/
ESource*
gcal_manager_get_source (GcalManager *manager,
const gchar *uid)
{
return e_source_registry_ref_source (manager->source_registry, uid);
}
/**
* gcal_manager_get_sources:
* @manager:
*
* Retrieve a list of the enabled sources used in the application.
*
* Returns: (Transfer full) a {@link GList} object to be freed with g_list_free()
**/
GList*
gcal_manager_get_sources (GcalManager *manager)
{
GHashTableIter iter;
gpointer key, value;
GList *aux = NULL;
g_hash_table_iter_init (&iter, manager->clients);
while (g_hash_table_iter_next (&iter, &key, &value))
{
GcalManagerUnit *unit = value;
if (!unit->enabled)
continue;
aux = g_list_append (aux, key);
}
return aux;
}
/**
* gcal_manager_get_sources_connected:
* @manager:
*
* Returns a {@link GList} with every source connected on the app, whether they are enabled or not.
*
* Returns: (Transfer full) a {@link GList} object to be freed with g_list_free()
**/
GList*
gcal_manager_get_sources_connected (GcalManager *manager)
{
return g_hash_table_get_keys (manager->clients);
}
/**
* gcal_manager_get_default_source:
* @manager: App singleton {@link GcalManager} instance
*
* Returns: (Transfer full): an {@link ESource} object. Free with g_object_unref().
**/
ESource*
gcal_manager_get_default_source (GcalManager *manager)
{
return e_source_registry_ref_default_calendar (manager->source_registry);
}
/**
* gcal_manager_set_default_source:
* @manager: App singleton {@link GcalManager} instance
* @source: the new default source.
*
* Returns:
**/
void
gcal_manager_set_default_source (GcalManager *manager,
ESource *source)
{
e_source_registry_set_default_calendar (manager->source_registry, source);
}
icaltimezone*
gcal_manager_get_system_timezone (GcalManager *manager)
{
return manager->system_timezone;
}
void
gcal_manager_setup_shell_search (GcalManager *manager,
ECalDataModelSubscriber *subscriber)
{
if (manager->shell_search_data_model == NULL)
{
manager->shell_search_data_model = e_cal_data_model_new (submit_thread_job);
g_signal_connect_swapped (manager->shell_search_data_model, "view-state-changed", G_CALLBACK (model_state_changed), manager);
e_cal_data_model_set_expand_recurrences (manager->shell_search_data_model, TRUE);
e_cal_data_model_set_timezone (manager->shell_search_data_model, manager->system_timezone);
manager->search_view_data = g_new0 (ViewStateData, 1);
manager->search_view_data->subscriber = subscriber;
}
}
/**
* gcal_manager_set_shell_search_query:
* @manager: A #GcalManager instance
* @query: query string (an s-exp)
*
* Set the query terms of the #ECalDataModel used in the shell search
**/
void
gcal_manager_set_shell_search_query (GcalManager *manager,
const gchar *query)
{
manager->search_view_data->passed_start = FALSE;
manager->search_view_data->search_done = FALSE;
manager->search_view_data->sources_left = g_hash_table_size (manager->clients) - g_strv_length (manager->disabled_sources);
if (manager->search_view_data->query != NULL)
g_free (manager->search_view_data->query);
manager->search_view_data->query = g_strdup (query);
e_cal_data_model_set_filter (manager->shell_search_data_model, query);
}
void
gcal_manager_set_shell_search_subscriber (GcalManager *manager,
ECalDataModelSubscriber *subscriber,
time_t range_start,
time_t range_end)
{
e_cal_data_model_subscribe (manager->shell_search_data_model, subscriber, range_start, range_end);
}
gboolean
gcal_manager_shell_search_done (GcalManager *manager)
{
return manager->search_view_data->search_done;
}
GList*
gcal_manager_get_shell_search_events (GcalManager *manager)
{
time_t range_start, range_end;
GList *list = NULL;
e_cal_data_model_get_subscriber_range (manager->shell_search_data_model, manager->search_view_data->subscriber,
&range_start, &range_end);
e_cal_data_model_foreach_component (manager->shell_search_data_model, range_start, range_end, gather_events, &list);
return list;
}
void
gcal_manager_set_subscriber (GcalManager *manager,
ECalDataModelSubscriber *subscriber,
time_t range_start,
time_t range_end)
{
e_cal_data_model_subscribe (manager->e_data_model,
subscriber,
range_start, range_end);
}
void
gcal_manager_set_search_subscriber (GcalManager *manager,
ECalDataModelSubscriber *subscriber,
time_t range_start,
time_t range_end)
{
e_cal_data_model_subscribe (manager->search_data_model,
subscriber,
range_start, range_end);
}
/**
* gcal_manager_set_query:
* @manager: A #GcalManager instance
* @query: (nullable): query terms or %NULL
*
* Set the query terms of the #ECalDataModel or clear it if %NULL is
* passed
*
**/
void
gcal_manager_set_query (GcalManager *manager,
const gchar *query)
{
e_cal_data_model_set_filter (manager->search_data_model,
query != NULL ? query : "#t");
}
/**
* gcal_manager_query_client_data:
*
* Queries for a specific data field of
* the {@link ECalClient}.
*
* Returns: (Transfer none) a string representing the retrieved value, or NULL.
*/
gchar*
gcal_manager_query_client_data (GcalManager *manager,
ESource *source,
const gchar *field)
{
GcalManagerUnit *unit;
gchar *out;
unit = g_hash_table_lookup (manager->clients, source);
if (unit == NULL)
return NULL;
g_object_get (unit->client, field, &out, NULL);
return out;
}
/**
* gcal_manager_add_source:
* @manager: 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
*
* Return value: The UID of calendar's source, NULL in other case
* Returns: (transfer full):
*
* Since: 0.0.1
*/
gchar*
gcal_manager_add_source (GcalManager *manager,
const gchar *name,
const gchar *backend,
const gchar *color)
{
ESource *source;
ESourceCalendar *extension;
GError *error;
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 (manager->source_registry,
source,
NULL,
&error);
if (error)
{
g_warning ("Failed to store calendar configuration: %s",
error->message);
g_error_free (error);
g_object_unref (source);
return NULL;
}
load_source (manager, source);
return e_source_dup_uid (source);
}
/**
* gcal_manager_enable_source:
* @manager: a #GcalManager
* @source: the target ESource
*
* Enable the given ESource.
*/
void
gcal_manager_enable_source (GcalManager *manager,
ESource *source)
{
GcalManagerUnit *unit;
gchar **new_disabled_sources;
gint i;
unit = g_hash_table_lookup (manager->clients, source);
if (unit->enabled)
return;
unit->enabled = TRUE;
e_cal_data_model_add_client (manager->e_data_model, unit->client);
e_cal_data_model_add_client (manager->search_data_model, unit->client);
if (manager->shell_search_data_model != NULL)
e_cal_data_model_add_client (manager->shell_search_data_model, unit->client);
/* remove source's uid from disabled_sources array */
new_disabled_sources = g_new0 (gchar*, g_strv_length (manager->disabled_sources));
for (i = 0; i < g_strv_length (manager->disabled_sources); i++)
{
if (g_strcmp0 (manager->disabled_sources[i], e_source_get_uid (source)) == 0)
continue;
new_disabled_sources[i] = g_strdup (manager->disabled_sources[i]);
}
g_strfreev (manager->disabled_sources);
manager->disabled_sources = new_disabled_sources;
g_signal_emit (manager, signals[SOURCE_ENABLED], 0, source, TRUE);
/* sync settings value */
g_settings_set_strv (manager->settings, "disabled-sources", (const gchar * const *) manager->disabled_sources);
}
/**
* gcal_manager_disable_source:
* @manager: a #GcalManager
* @source: the target ESource
*
* Disable the given ESource.
*/
void
gcal_manager_disable_source (GcalManager *manager,
ESource *source)
{
GcalManagerUnit *unit;
gchar **new_disabled_sources;
gint i;
const gchar *source_uid;
unit = g_hash_table_lookup (manager->clients, source);
if (!unit->enabled)
return;
source_uid = e_source_get_uid (source);
unit->enabled = FALSE;
e_cal_data_model_remove_client (manager->e_data_model, source_uid);
e_cal_data_model_remove_client (manager->search_data_model, source_uid);
if (manager->shell_search_data_model != NULL)
e_cal_data_model_remove_client (manager->shell_search_data_model, source_uid);
/* add source's uid from disabled_sources array */
new_disabled_sources = g_new0 (gchar*, g_strv_length (manager->disabled_sources) + 2);
for (i = 0; i < g_strv_length (manager->disabled_sources); i++)
new_disabled_sources[i] = g_strdup (manager->disabled_sources[i]);
new_disabled_sources[g_strv_length (manager->disabled_sources)] = g_strdup (source_uid);
g_strfreev (manager->disabled_sources);
manager->disabled_sources = new_disabled_sources;
g_signal_emit (manager, signals[SOURCE_ENABLED], 0, source, FALSE);
/* sync settings value */
g_settings_set_strv (manager->settings, "disabled-sources", (const gchar * const *) manager->disabled_sources);
}
/**
* gcal_manager_save_source:
* @manager: a #GcalManager
* @source: the target ESource
*
* Commit the given ESource.
*/
void
gcal_manager_save_source (GcalManager *manager,
ESource *source)
{
GError *error;
error = NULL;
e_source_registry_commit_source_sync (manager->source_registry, source, NULL, &error);
if (error != NULL)
{
/* FIXME: Notify the user somehow */
g_warning ("Error saving source: %s", error->message);
g_error_free (error);
}
}
gboolean
gcal_manager_source_enabled (GcalManager *manager,
ESource *source)
{
GcalManagerUnit *unit;
unit = g_hash_table_lookup (manager->clients, source);
if (unit == NULL)
return FALSE;
return unit->enabled;
}
void
gcal_manager_refresh (GcalManager *manager)
{
GList *clients;
GList *l;
clients = g_hash_table_get_values (manager->clients);
/* refresh clients */
for (l = clients; l != NULL; l = l->next)
{
GcalManagerUnit *unit = l->data;
if (!unit->connected || ! e_client_check_refresh_supported (E_CLIENT (unit->client)))
continue;
e_client_refresh (E_CLIENT (unit->client),
NULL,
on_client_refreshed,
manager);
}
g_list_free (clients);
}
gboolean
gcal_manager_is_client_writable (GcalManager *manager,
ESource *source)
{
GcalManagerUnit *unit;
unit = g_hash_table_lookup (manager->clients, source);
if (unit == NULL)
return FALSE;
return unit->connected && !e_client_is_readonly (E_CLIENT (unit->client));
}
void
gcal_manager_create_event (GcalManager *manager,
GcalEvent *event)
{
GcalManagerUnit *unit;
icalcomponent *new_event_icalcomp;
ECalComponent *component;
AsyncOpsData *data;
ESource *source;
source = gcal_event_get_source (event);
component = gcal_event_get_component (event);
unit = g_hash_table_lookup (manager->clients, source);
new_event_icalcomp = e_cal_component_get_icalcomponent (component);
data = g_new0 (AsyncOpsData, 1);
data->event = g_object_ref (event);
data->manager = manager;
e_cal_client_create_object (unit->client,
new_event_icalcomp,
manager->async_ops,
on_event_created,
data);
}
void
gcal_manager_update_event (GcalManager *manager,
GcalEvent *event)
{
GcalManagerUnit *unit;
ECalComponent *component;
unit = g_hash_table_lookup (manager->clients, gcal_event_get_source (event));
component = gcal_event_get_component (event);
/*
* While we're updating the event, we don't want the component
* to be destroyed, so take a reference of the component while
* we're performing the update on it.
*/
g_object_ref (component);
e_cal_client_modify_object (unit->client,
e_cal_component_get_icalcomponent (component),
E_CAL_OBJ_MOD_THIS,
NULL,
on_event_updated,
component);
}
void
gcal_manager_remove_event (GcalManager *manager,
GcalEvent *event)
{
GcalManagerUnit *unit;
ECalComponentId *id;
ECalComponent *component;
component = gcal_event_get_component (event);
unit = g_hash_table_lookup (manager->clients, gcal_event_get_source (event));
id = e_cal_component_get_id (component);
/*
* While we're removing the event, we don't want the component
* to be destroyed, so take a reference of the component while
* we're deleting it.
*/
g_object_ref (component);
e_cal_client_remove_object (unit->client,
id->uid,
id->rid,
E_CAL_OBJ_MOD_THIS,
manager->async_ops,
on_event_removed,
component);
e_cal_component_free_id (id);
}
void
gcal_manager_move_event_to_source (GcalManager *manager,
GcalEvent *event,
ESource *dest)
{
ECalComponent *ecomponent;
ECalComponent *clone;
icalcomponent *comp;
GcalManagerUnit *unit;
ECalComponentId *id;
GError *error;
g_return_if_fail (GCAL_IS_MANAGER (manager));
error = NULL;
/* First, try to create the component on the destination source */
unit = g_hash_table_lookup (manager->clients, dest);
ecomponent = gcal_event_get_component (event);
clone = e_cal_component_clone (ecomponent);
comp = e_cal_component_get_icalcomponent (clone);
e_cal_client_create_object_sync (unit->client,
comp,
NULL,
NULL,
&error);
if (error)
{
g_warning ("Error moving source: %s", error->message);
g_clear_error (&error);
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.
*/
unit = g_hash_table_lookup (manager->clients, gcal_event_get_source (event));
id = e_cal_component_get_id (ecomponent);
e_cal_client_remove_object_sync (unit->client,
id->uid,
id->rid,
E_CAL_OBJ_MOD_THIS,
manager->async_ops,
&error);
if (error)
{
g_warning ("Error moving source: %s", error->message);
g_clear_error (&error);
return;
}
e_cal_component_free_id (id);
}
/**
* gcal_manager_get_events:
*
* Returns a list with {@link GcalEvent} objects owned by the caller, the list and the objects.
* The components inside the list are owned by the caller as well. So, don't ref the component.
*
* Returns: An {@link GList} object
*/
GList*
gcal_manager_get_events (GcalManager *manager,
icaltimetype *start_date,
icaltimetype *end_date)
{
time_t range_start, range_end;
GList *list = NULL;
range_start = icaltime_as_timet_with_zone (*start_date, manager->system_timezone);
range_end = icaltime_as_timet_with_zone (*end_date, manager->system_timezone);
e_cal_data_model_foreach_component (manager->e_data_model, range_start, range_end, gather_events, &list);
return list;
}
gboolean
gcal_manager_load_completed (GcalManager *manager)
{
return manager->sources_at_launch == 0;
}
GcalEvent*
gcal_manager_get_event_from_shell_search (GcalManager *manager,
const gchar *uuid)
{
GcalEvent *new_event;
GList *l, *list;
time_t range_start, range_end;
list = NULL;
new_event = NULL;
e_cal_data_model_get_subscriber_range (manager->shell_search_data_model, manager->search_view_data->subscriber,
&range_start, &range_end);
e_cal_data_model_foreach_component (manager->shell_search_data_model, range_start, range_end, gather_events, &list);
for (l = list; l != NULL; l = g_list_next (l))
{
GcalEvent *event;
event = l->data;
if (g_strcmp0 (gcal_event_get_uid (event), uuid) == 0)
{
new_event = event;
}
g_object_unref (event);
}
g_list_free (list);
return new_event;
}
gboolean
gcal_manager_is_goa_client_ready (GcalManager *manager)
{
g_return_val_if_fail (GCAL_IS_MANAGER (manager), FALSE);
return manager->goa_client_ready;
}
GoaClient*
gcal_manager_get_goa_client (GcalManager *manager)
{
g_return_val_if_fail (GCAL_IS_MANAGER (manager), FALSE);
return manager->goa_client;
}