/*
* e-server-side-source.c
*
* 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; either
* version 2 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see
*
*/
/**
* SECTION: e-server-side-source
* @include: libebackend/libebackend.h
* @short_description: A server-side data source
*
* An #EServerSideSource is an #ESource with some additional capabilities
* exclusive to the registry D-Bus service.
**/
#include "e-server-side-source.h"
#include
#include
/* Private D-Bus classes. */
#include
#define E_SERVER_SIDE_SOURCE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourcePrivate))
#define DBUS_OBJECT_PATH E_SOURCE_REGISTRY_SERVER_OBJECT_PATH "/Source"
#define PRIMARY_GROUP_NAME "Data Source"
typedef struct _AsyncContext AsyncContext;
struct _EServerSideSourcePrivate {
gpointer server; /* weak pointer */
GWeakRef oauth2_support;
GNode node;
GFile *file;
/* For comparison. */
gchar *file_contents;
gboolean allow_auth_prompt;
GType auth_session_type;
gchar *write_directory;
};
struct _AsyncContext {
EDBusSourceRemoteCreatable *remote_creatable;
EDBusSourceRemoteDeletable *remote_deletable;
EDBusSourceOAuth2Support *oauth2_support;
GDBusMethodInvocation *invocation;
};
enum {
PROP_0,
PROP_ALLOW_AUTH_PROMPT,
PROP_AUTH_SESSION_TYPE,
PROP_EXPORTED,
PROP_FILE,
PROP_OAUTH2_SUPPORT,
PROP_REMOTE_CREATABLE,
PROP_REMOTE_DELETABLE,
PROP_REMOVABLE,
PROP_SERVER,
PROP_WRITABLE,
PROP_WRITE_DIRECTORY
};
static GInitableIface *initable_parent_interface;
/* Forward Declarations */
static void e_server_side_source_initable_init
(GInitableIface *interface);
G_DEFINE_TYPE_WITH_CODE (
EServerSideSource,
e_server_side_source,
E_TYPE_SOURCE,
G_IMPLEMENT_INTERFACE (
G_TYPE_INITABLE,
e_server_side_source_initable_init))
static void
async_context_free (AsyncContext *async_context)
{
if (async_context->remote_creatable != NULL)
g_object_unref (async_context->remote_creatable);
if (async_context->remote_deletable != NULL)
g_object_unref (async_context->remote_deletable);
if (async_context->oauth2_support != NULL)
g_object_unref (async_context->oauth2_support);
if (async_context->invocation != NULL)
g_object_unref (async_context->invocation);
g_slice_free (AsyncContext, async_context);
}
static gboolean
server_side_source_parse_data (GKeyFile *key_file,
const gchar *data,
gsize length,
GError **error)
{
gboolean success;
success = g_key_file_load_from_data (
key_file, data, length, G_KEY_FILE_NONE, error);
if (!success)
return FALSE;
/* Make sure the key file has a [Data Source] group. */
if (!g_key_file_has_group (key_file, PRIMARY_GROUP_NAME)) {
g_set_error (
error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
_("Data source is missing a [%s] group"),
PRIMARY_GROUP_NAME);
return FALSE;
}
return TRUE;
}
static void
server_side_source_print_diff (ESource *source,
const gchar *old_data,
const gchar *new_data)
{
gchar **old_strv = NULL;
gchar **new_strv = NULL;
guint old_length = 0;
guint new_length = 0;
guint ii;
g_print ("Saving %s\n", e_source_get_uid (source));
if (old_data != NULL) {
old_strv = g_strsplit (old_data, "\n", 0);
old_length = g_strv_length (old_strv);
}
if (new_data != NULL) {
new_strv = g_strsplit (new_data, "\n", 0);
new_length = g_strv_length (new_strv);
}
for (ii = 0; ii < MIN (old_length, new_length); ii++) {
if (g_strcmp0 (old_strv[ii], new_strv[ii]) != 0) {
g_print (" - : %s\n", old_strv[ii]);
g_print (" + : %s\n", new_strv[ii]);
} else {
g_print (" : %s\n", old_strv[ii]);
}
}
for (; ii < old_length; ii++)
g_print (" - : %s\n", old_strv[ii]);
for (; ii < new_length; ii++)
g_print (" + : %s\n", new_strv[ii]);
g_strfreev (old_strv);
g_strfreev (new_strv);
}
static gboolean
server_side_source_traverse_cb (GNode *node,
GQueue *queue)
{
g_queue_push_tail (queue, g_object_ref (node->data));
return FALSE;
}
static gboolean
server_side_source_allow_auth_prompt_cb (EDBusSource *interface,
GDBusMethodInvocation *invocation,
EServerSideSource *source)
{
e_server_side_source_set_allow_auth_prompt (source, TRUE);
e_dbus_source_complete_allow_auth_prompt (interface, invocation);
return TRUE;
}
static gboolean
server_side_source_remove_cb (EDBusSourceRemovable *interface,
GDBusMethodInvocation *invocation,
EServerSideSource *source)
{
GError *error = NULL;
/* Note we don't need to verify the source is removable here
* since if it isn't, the remove() method won't be available.
* Descendants of the source are removed whether they export
* a remove() method or not. */
e_source_remove_sync (E_SOURCE (source), NULL, &error);
if (error != NULL)
g_dbus_method_invocation_take_error (invocation, error);
else
e_dbus_source_removable_complete_remove (
interface, invocation);
return TRUE;
}
static gboolean
server_side_source_write_cb (EDBusSourceWritable *interface,
GDBusMethodInvocation *invocation,
const gchar *data,
ESource *source)
{
GKeyFile *key_file;
GDBusObject *dbus_object;
EDBusSource *dbus_source;
GError *error = NULL;
/* Note we don't need to verify the source is writable here
* since if it isn't, the write() method won't be available. */
dbus_object = e_source_ref_dbus_object (source);
dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
/* Validate the raw data before making the changes live. */
key_file = g_key_file_new ();
server_side_source_parse_data (key_file, data, strlen (data), &error);
g_key_file_free (key_file);
/* Q: How does this trigger data being written to disk?
*
* A: Here's the sequence of events:
*
* 1) We set the EDBusSource:data property.
* 2) ESource picks up the "notify::data" signal and parses
* the raw data, which triggers an ESource:changed signal.
* 3) Our changed() method schedules an idle callback.
* 4) The idle callback calls e_source_write_sync().
* 5) e_source_write_sync() calls e_dbus_source_dup_data()
* and synchronously writes the resulting string to disk.
*/
if (error == NULL)
e_dbus_source_set_data (dbus_source, data);
if (error != NULL)
g_dbus_method_invocation_take_error (invocation, error);
else
e_dbus_source_writable_complete_write (
interface, invocation);
g_object_unref (dbus_source);
g_object_unref (dbus_object);
return TRUE;
}
/* Helper for server_side_source_remote_create_cb() */
static void
server_side_source_remote_create_done_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
ESource *source;
AsyncContext *async_context;
GError *error = NULL;
source = E_SOURCE (source_object);
async_context = (AsyncContext *) user_data;
e_source_remote_create_finish (source, result, &error);
if (error != NULL)
g_dbus_method_invocation_take_error (
async_context->invocation, error);
else
e_dbus_source_remote_creatable_complete_create (
async_context->remote_creatable,
async_context->invocation);
async_context_free (async_context);
}
static gboolean
server_side_source_remote_create_cb (EDBusSourceRemoteCreatable *interface,
GDBusMethodInvocation *invocation,
const gchar *uid,
const gchar *data,
ESource *source)
{
EServerSideSource *server_side_source;
ESourceRegistryServer *server;
AsyncContext *async_context;
ESource *scratch_source;
GDBusObject *dbus_object;
EDBusSource *dbus_source;
GKeyFile *key_file;
GFile *file;
GError *error = NULL;
/* Create a new EServerSideSource from 'uid' and 'data' but
* DO NOT add it to the ESourceRegistryServer yet. It's up
* to the ECollectionBackend whether to use source as given
* or create its own equivalent EServerSideSource, possibly
* in response to a notification from a remote server. */
/* Validate the raw data. */
key_file = g_key_file_new ();
server_side_source_parse_data (key_file, data, strlen (data), &error);
g_key_file_free (key_file);
if (error != NULL) {
g_dbus_method_invocation_take_error (invocation, error);
return TRUE;
}
server_side_source = E_SERVER_SIDE_SOURCE (source);
server = e_server_side_source_get_server (server_side_source);
file = e_server_side_source_new_user_file (uid);
scratch_source = e_server_side_source_new (server, file, &error);
g_object_unref (file);
/* Sanity check. */
g_warn_if_fail (
((scratch_source != NULL) && (error == NULL)) ||
((scratch_source == NULL) && (error != NULL)));
if (error != NULL) {
g_dbus_method_invocation_take_error (invocation, error);
return TRUE;
}
dbus_object = e_source_ref_dbus_object (scratch_source);
dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
e_dbus_source_set_data (dbus_source, data);
g_object_unref (dbus_object);
g_object_unref (dbus_source);
async_context = g_slice_new0 (AsyncContext);
async_context->remote_creatable = g_object_ref (interface);
async_context->invocation = g_object_ref (invocation);
e_source_remote_create (
source, scratch_source, NULL,
server_side_source_remote_create_done_cb,
async_context);
g_object_unref (scratch_source);
return TRUE;
}
/* Helper for server_side_source_remote_delete_cb() */
static void
server_side_source_remote_delete_done_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
ESource *source;
AsyncContext *async_context;
GError *error = NULL;
source = E_SOURCE (source_object);
async_context = (AsyncContext *) user_data;
e_source_remote_delete_finish (source, result, &error);
if (error != NULL)
g_dbus_method_invocation_take_error (
async_context->invocation, error);
else
e_dbus_source_remote_deletable_complete_delete (
async_context->remote_deletable,
async_context->invocation);
async_context_free (async_context);
}
static gboolean
server_side_source_remote_delete_cb (EDBusSourceRemoteDeletable *interface,
GDBusMethodInvocation *invocation,
ESource *source)
{
AsyncContext *async_context;
async_context = g_slice_new0 (AsyncContext);
async_context->remote_deletable = g_object_ref (interface);
async_context->invocation = g_object_ref (invocation);
e_source_remote_delete (
source, NULL,
server_side_source_remote_delete_done_cb,
async_context);
return TRUE;
}
/* Helper for server_side_source_get_access_token_cb() */
static void
server_side_source_get_access_token_done_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
ESource *source;
AsyncContext *async_context;
gchar *access_token = NULL;
gint expires_in = 0;
GError *error = NULL;
source = E_SOURCE (source_object);
async_context = (AsyncContext *) user_data;
e_source_get_oauth2_access_token_finish (
source, result, &access_token, &expires_in, &error);
/* Sanity check. */
g_return_if_fail (
((access_token != NULL) && (error == NULL)) ||
((access_token == NULL) && (error != NULL)));
if (error != NULL)
g_dbus_method_invocation_take_error (
async_context->invocation, error);
else
e_dbus_source_oauth2_support_complete_get_access_token (
async_context->oauth2_support,
async_context->invocation,
access_token, expires_in);
g_free (access_token);
async_context_free (async_context);
}
static gboolean
server_side_source_get_access_token_cb (EDBusSourceOAuth2Support *interface,
GDBusMethodInvocation *invocation,
ESource *source)
{
AsyncContext *async_context;
async_context = g_slice_new0 (AsyncContext);
async_context->oauth2_support = g_object_ref (interface);
async_context->invocation = g_object_ref (invocation);
e_source_get_oauth2_access_token (
source, NULL,
server_side_source_get_access_token_done_cb,
async_context);
return TRUE;
}
static void
server_side_source_set_file (EServerSideSource *source,
GFile *file)
{
g_return_if_fail (file == NULL || G_IS_FILE (file));
g_return_if_fail (source->priv->file == NULL);
if (file != NULL)
source->priv->file = g_object_ref (file);
}
static void
server_side_source_set_server (EServerSideSource *source,
ESourceRegistryServer *server)
{
g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
g_return_if_fail (source->priv->server == NULL);
source->priv->server = server;
g_object_add_weak_pointer (
G_OBJECT (server), &source->priv->server);
}
static void
server_side_source_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ALLOW_AUTH_PROMPT:
e_server_side_source_set_allow_auth_prompt (
E_SERVER_SIDE_SOURCE (object),
g_value_get_boolean (value));
return;
case PROP_AUTH_SESSION_TYPE:
e_server_side_source_set_auth_session_type (
E_SERVER_SIDE_SOURCE (object),
g_value_get_gtype (value));
return;
case PROP_FILE:
server_side_source_set_file (
E_SERVER_SIDE_SOURCE (object),
g_value_get_object (value));
return;
case PROP_OAUTH2_SUPPORT:
e_server_side_source_set_oauth2_support (
E_SERVER_SIDE_SOURCE (object),
g_value_get_object (value));
return;
case PROP_REMOTE_CREATABLE:
e_server_side_source_set_remote_creatable (
E_SERVER_SIDE_SOURCE (object),
g_value_get_boolean (value));
return;
case PROP_REMOTE_DELETABLE:
e_server_side_source_set_remote_deletable (
E_SERVER_SIDE_SOURCE (object),
g_value_get_boolean (value));
return;
case PROP_REMOVABLE:
e_server_side_source_set_removable (
E_SERVER_SIDE_SOURCE (object),
g_value_get_boolean (value));
return;
case PROP_SERVER:
server_side_source_set_server (
E_SERVER_SIDE_SOURCE (object),
g_value_get_object (value));
return;
case PROP_WRITABLE:
e_server_side_source_set_writable (
E_SERVER_SIDE_SOURCE (object),
g_value_get_boolean (value));
return;
case PROP_WRITE_DIRECTORY:
e_server_side_source_set_write_directory (
E_SERVER_SIDE_SOURCE (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
server_side_source_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ALLOW_AUTH_PROMPT:
g_value_set_boolean (
value,
e_server_side_source_get_allow_auth_prompt (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_AUTH_SESSION_TYPE:
g_value_set_gtype (
value,
e_server_side_source_get_auth_session_type (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_EXPORTED:
g_value_set_boolean (
value,
e_server_side_source_get_exported (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_FILE:
g_value_set_object (
value,
e_server_side_source_get_file (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_OAUTH2_SUPPORT:
g_value_take_object (
value,
e_server_side_source_ref_oauth2_support (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_REMOTE_CREATABLE:
g_value_set_boolean (
value,
e_source_get_remote_creatable (
E_SOURCE (object)));
return;
case PROP_REMOTE_DELETABLE:
g_value_set_boolean (
value,
e_source_get_remote_deletable (
E_SOURCE (object)));
return;
case PROP_REMOVABLE:
g_value_set_boolean (
value,
e_source_get_removable (
E_SOURCE (object)));
return;
case PROP_SERVER:
g_value_set_object (
value,
e_server_side_source_get_server (
E_SERVER_SIDE_SOURCE (object)));
return;
case PROP_WRITABLE:
g_value_set_boolean (
value,
e_source_get_writable (
E_SOURCE (object)));
return;
case PROP_WRITE_DIRECTORY:
g_value_set_string (
value,
e_server_side_source_get_write_directory (
E_SERVER_SIDE_SOURCE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
server_side_source_dispose (GObject *object)
{
EServerSideSourcePrivate *priv;
priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
if (priv->server != NULL) {
g_object_remove_weak_pointer (
G_OBJECT (priv->server), &priv->server);
priv->server = NULL;
}
g_weak_ref_set (&priv->oauth2_support, NULL);
if (priv->file != NULL) {
g_object_unref (priv->file);
priv->file = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_server_side_source_parent_class)->dispose (object);
}
static void
server_side_source_finalize (GObject *object)
{
EServerSideSourcePrivate *priv;
priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
g_node_unlink (&priv->node);
g_free (priv->file_contents);
g_free (priv->write_directory);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_server_side_source_parent_class)->finalize (object);
}
static void
server_side_source_changed (ESource *source)
{
GDBusObject *dbus_object;
EDBusSource *dbus_source;
gchar *old_data;
gchar *new_data;
GError *error = NULL;
/* Do not write changes to disk until the source has been exported. */
if (!e_server_side_source_get_exported (E_SERVER_SIDE_SOURCE (source)))
return;
dbus_object = e_source_ref_dbus_object (source);
dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
old_data = e_dbus_source_dup_data (dbus_source);
new_data = e_source_to_string (source, NULL);
/* Setting the "data" property triggers the ESource::changed,
* signal, which invokes this callback, which sets the "data"
* property, etc. This breaks an otherwise infinite loop. */
if (g_strcmp0 (old_data, new_data) != 0)
e_dbus_source_set_data (dbus_source, new_data);
g_free (old_data);
g_free (new_data);
g_object_unref (dbus_source);
g_object_unref (dbus_object);
/* This writes the "data" property to disk. */
e_source_write_sync (source, NULL, &error);
if (error != NULL) {
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
}
}
static gboolean
server_side_source_remove_sync (ESource *source,
GCancellable *cancellable,
GError **error)
{
EAsyncClosure *closure;
GAsyncResult *result;
gboolean success;
closure = e_async_closure_new ();
e_source_remove (
source, cancellable, e_async_closure_callback, closure);
result = e_async_closure_wait (closure);
success = e_source_remove_finish (source, result, error);
e_async_closure_free (closure);
return success;
}
static void
server_side_source_remove (ESource *source,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
EServerSideSourcePrivate *priv;
GSimpleAsyncResult *simple;
ESourceRegistryServer *server;
GQueue queue = G_QUEUE_INIT;
GList *list, *link;
GError *error = NULL;
/* XXX Yes we block here. We do this operation
* synchronously to keep the server code simple. */
priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
simple = g_simple_async_result_new (
G_OBJECT (source), callback, user_data,
server_side_source_remove);
g_simple_async_result_set_check_cancellable (simple, cancellable);
/* Collect the source and its descendants into a queue.
* Do this before unexporting so we hold references to
* all the removed sources. */
g_node_traverse (
&priv->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
(GNodeTraverseFunc) server_side_source_traverse_cb, &queue);
/* Unexport the object and its descendants. */
server = E_SOURCE_REGISTRY_SERVER (priv->server);
e_source_registry_server_remove_source (server, source);
list = g_queue_peek_head_link (&queue);
/* Delete the key file for each source in the queue. */
for (link = list; link != NULL; link = g_list_next (link)) {
EServerSideSource *child;
GFile *file;
child = E_SERVER_SIDE_SOURCE (link->data);
file = e_server_side_source_get_file (child);
if (file != NULL)
g_file_delete (file, cancellable, &error);
/* XXX Even though e_source_registry_server_remove_source()
* is called first, the object path is unexported from
* an idle callback some time after we have deleted the
* key file. That creates a small window of time where
* the file is deleted but the object is still exported.
*
* If a client calls e_source_remove() during that small
* window of time, we still want to report a successful
* removal, so disregard G_IO_ERROR_NOT_FOUND. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
g_clear_error (&error);
if (error != NULL)
goto exit;
}
exit:
while (!g_queue_is_empty (&queue))
g_object_unref (g_queue_pop_head (&queue));
if (error != NULL)
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
}
static gboolean
server_side_source_remove_finish (ESource *source,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (source),
server_side_source_remove), FALSE);
simple = G_SIMPLE_ASYNC_RESULT (result);
/* Assume success unless a GError is set. */
return !g_simple_async_result_propagate_error (simple, error);
}
static gboolean
server_side_source_write_sync (ESource *source,
GCancellable *cancellable,
GError **error)
{
EAsyncClosure *closure;
GAsyncResult *result;
gboolean success;
closure = e_async_closure_new ();
e_source_write (
source, cancellable, e_async_closure_callback, closure);
result = e_async_closure_wait (closure);
success = e_source_write_finish (source, result, error);
e_async_closure_free (closure);
return success;
}
static void
server_side_source_write (ESource *source,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
EServerSideSourcePrivate *priv;
GSimpleAsyncResult *simple;
GDBusObject *dbus_object;
EDBusSource *dbus_source;
gboolean replace_file;
const gchar *old_data;
gchar *new_data;
GError *error = NULL;
/* XXX Yes we block here. We do this operation
* synchronously to keep the server code simple. */
priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
simple = g_simple_async_result_new (
G_OBJECT (source), callback, user_data,
server_side_source_write);
g_simple_async_result_set_check_cancellable (simple, cancellable);
dbus_object = e_source_ref_dbus_object (source);
dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
old_data = priv->file_contents;
new_data = e_source_to_string (source, NULL);
/* When writing source data to disk, we always write to the
* source's specified "write-directory" even if the key file
* was originally read from a different directory. To avoid
* polluting the write directory with key files identical to
* the original, we check that the data has actually changed
* before writing a copy to disk. */
replace_file =
G_IS_FILE (priv->file) &&
(g_strcmp0 (old_data, new_data) != 0);
if (replace_file) {
GFile *file;
GFile *write_directory;
gchar *basename;
g_warn_if_fail (priv->write_directory != NULL);
basename = g_file_get_basename (priv->file);
write_directory = g_file_new_for_path (priv->write_directory);
file = g_file_get_child (write_directory, basename);
g_free (basename);
if (!g_file_equal (file, priv->file)) {
g_object_unref (priv->file);
priv->file = g_object_ref (file);
}
server_side_source_print_diff (source, old_data, new_data);
g_file_make_directory_with_parents (
write_directory, cancellable, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
g_clear_error (&error);
if (error == NULL)
g_file_replace_contents (
file, new_data, strlen (new_data),
NULL, FALSE, G_FILE_CREATE_NONE,
NULL, cancellable, &error);
if (error == NULL) {
g_free (priv->file_contents);
priv->file_contents = new_data;
new_data = NULL;
}
g_object_unref (write_directory);
g_object_unref (file);
}
g_free (new_data);
g_object_unref (dbus_source);
g_object_unref (dbus_object);
if (error != NULL)
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
}
static gboolean
server_side_source_write_finish (ESource *source,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (source),
server_side_source_write), FALSE);
simple = G_SIMPLE_ASYNC_RESULT (result);
/* Assume success unless a GError is set. */
return !g_simple_async_result_propagate_error (simple, error);
}
static gboolean
server_side_source_remote_create_sync (ESource *source,
ESource *scratch_source,
GCancellable *cancellable,
GError **error)
{
ECollectionBackend *backend;
ESourceRegistryServer *server;
EServerSideSource *server_side_source;
gboolean success;
if (!e_source_get_remote_creatable (source)) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Data source '%s' does not "
"support creating remote resources"),
e_source_get_display_name (source));
return FALSE;
}
server_side_source = E_SERVER_SIDE_SOURCE (source);
server = e_server_side_source_get_server (server_side_source);
backend = e_source_registry_server_ref_backend (server, source);
if (backend == NULL) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Data source '%s' has no collection "
"backend to create the remote resource"),
e_source_get_display_name (source));
return FALSE;
}
success = e_collection_backend_create_resource_sync (
backend, scratch_source, cancellable, error);
g_object_unref (backend);
return success;
}
static gboolean
server_side_source_remote_delete_sync (ESource *source,
GCancellable *cancellable,
GError **error)
{
ECollectionBackend *backend;
ESourceRegistryServer *server;
EServerSideSource *server_side_source;
gboolean success;
if (!e_source_get_remote_deletable (source)) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Data source '%s' does not "
"support deleting remote resources"),
e_source_get_display_name (source));
return FALSE;
}
server_side_source = E_SERVER_SIDE_SOURCE (source);
server = e_server_side_source_get_server (server_side_source);
backend = e_source_registry_server_ref_backend (server, source);
if (backend == NULL) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Data source '%s' has no collection "
"backend to delete the remote resource"),
e_source_get_display_name (source));
return FALSE;
}
success = e_collection_backend_delete_resource_sync (
backend, source, cancellable, error);
g_object_unref (backend);
return success;
}
static gboolean
server_side_source_get_oauth2_access_token_sync (ESource *source,
GCancellable *cancellable,
gchar **out_access_token,
gint *out_expires_in,
GError **error)
{
EOAuth2Support *oauth2_support;
gboolean success;
oauth2_support = e_server_side_source_ref_oauth2_support (
E_SERVER_SIDE_SOURCE (source));
if (oauth2_support == NULL) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Data source '%s' does not "
"support OAuth 2.0 authentication"),
e_source_get_display_name (source));
return FALSE;
}
success = e_oauth2_support_get_access_token_sync (
oauth2_support, source, cancellable,
out_access_token, out_expires_in, error);
g_object_unref (oauth2_support);
return success;
}
static gboolean
server_side_source_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
EServerSideSource *source;
GDBusObject *dbus_object;
EDBusSource *dbus_source;
gchar *uid;
source = E_SERVER_SIDE_SOURCE (initable);
dbus_source = e_dbus_source_skeleton_new ();
uid = e_source_dup_uid (E_SOURCE (source));
if (uid == NULL)
uid = e_uid_new ();
e_dbus_source_set_uid (dbus_source, uid);
g_free (uid);
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source);
g_object_unref (dbus_object);
g_signal_connect (
dbus_source, "handle-allow-auth-prompt",
G_CALLBACK (server_side_source_allow_auth_prompt_cb), source);
g_object_unref (dbus_source);
if (!e_server_side_source_load (source, cancellable, error))
return FALSE;
/* Chain up to parent interface's init() method. */
return initable_parent_interface->init (initable, cancellable, error);
}
static void
e_server_side_source_class_init (EServerSideSourceClass *class)
{
GObjectClass *object_class;
ESourceClass *source_class;
g_type_class_add_private (class, sizeof (EServerSideSourcePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = server_side_source_set_property;
object_class->get_property = server_side_source_get_property;
object_class->dispose = server_side_source_dispose;
object_class->finalize = server_side_source_finalize;
source_class = E_SOURCE_CLASS (class);
source_class->changed = server_side_source_changed;
source_class->remove_sync = server_side_source_remove_sync;
source_class->remove = server_side_source_remove;
source_class->remove_finish = server_side_source_remove_finish;
source_class->write_sync = server_side_source_write_sync;
source_class->write = server_side_source_write;
source_class->write_finish = server_side_source_write_finish;
source_class->remote_create_sync =
server_side_source_remote_create_sync;
source_class->remote_delete_sync =
server_side_source_remote_delete_sync;
source_class->get_oauth2_access_token_sync =
server_side_source_get_oauth2_access_token_sync;
g_object_class_install_property (
object_class,
PROP_ALLOW_AUTH_PROMPT,
g_param_spec_boolean (
"allow-auth-prompt",
"Allow Auth Prompt",
"Whether authentication sessions may "
"interrupt the user for a password",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_AUTH_SESSION_TYPE,
g_param_spec_gtype (
"auth-session-type",
"Auth Session Type",
"The type of authentication session "
"to instantiate for this source",
E_TYPE_AUTHENTICATION_SESSION,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_EXPORTED,
g_param_spec_boolean (
"exported",
"Exported",
"Whether the source has been exported over D-Bus",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_FILE,
g_param_spec_object (
"file",
"File",
"The key file for the data source",
G_TYPE_FILE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_OAUTH2_SUPPORT,
g_param_spec_object (
"oauth2-support",
"OAuth2 Support",
"The object providing OAuth 2.0 support",
E_TYPE_OAUTH2_SUPPORT,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/* This overrides the "remote-creatable" property
* in ESourceClass with a writable version. */
g_object_class_install_property (
object_class,
PROP_REMOTE_CREATABLE,
g_param_spec_boolean (
"remote-creatable",
"Remote Creatable",
"Whether the data source "
"can create remote resources",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/* This overrides the "remote-deletable" property
* in ESourceClass with a writable version. */
g_object_class_install_property (
object_class,
PROP_REMOTE_DELETABLE,
g_param_spec_boolean (
"remote-deletable",
"Remote Deletable",
"Whether the data source "
"can delete remote resources",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/* This overrides the "removable" property
* in ESourceClass with a writable version. */
g_object_class_install_property (
object_class,
PROP_REMOVABLE,
g_param_spec_boolean (
"removable",
"Removable",
"Whether the data source is removable",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SERVER,
g_param_spec_object (
"server",
"Server",
"The server to which the data source belongs",
E_TYPE_SOURCE_REGISTRY_SERVER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/* This overrides the "writable" property
* in ESourceClass with a writable version. */
g_object_class_install_property (
object_class,
PROP_WRITABLE,
g_param_spec_boolean (
"writable",
"Writable",
"Whether the data source is writable",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/* Do not use G_PARAM_CONSTRUCT. We initialize the
* property ourselves in e_server_side_source_init(). */
g_object_class_install_property (
object_class,
PROP_WRITE_DIRECTORY,
g_param_spec_string (
"write-directory",
"Write Directory",
"Directory in which to write changes to disk",
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
}
static void
e_server_side_source_initable_init (GInitableIface *interface)
{
initable_parent_interface = g_type_interface_peek_parent (interface);
interface->init = server_side_source_initable_init;
}
static void
e_server_side_source_init (EServerSideSource *source)
{
const gchar *user_dir;
source->priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
source->priv->node.data = source;
user_dir = e_server_side_source_get_user_dir ();
source->priv->write_directory = g_strdup (user_dir);
source->priv->auth_session_type = E_TYPE_AUTHENTICATION_SESSION;
}
/**
* e_server_side_source_get_user_dir:
*
* Returns the directory where user-specific data source files are stored.
*
* Returns: the user-specific data source directory
*
* Since: 3.6
**/
const gchar *
e_server_side_source_get_user_dir (void)
{
static gchar *dirname = NULL;
if (G_UNLIKELY (dirname == NULL)) {
const gchar *config_dir = e_get_user_config_dir ();
dirname = g_build_filename (config_dir, "sources", NULL);
g_mkdir_with_parents (dirname, 0700);
}
return dirname;
}
/**
* e_server_side_source_new_user_file:
* @uid: unique identifier for a data source, or %NULL
*
* Generates a unique file name for a new user-specific data source.
* If @uid is non-%NULL it will be used in the basename of the file,
* otherwise a unique basename will be generated using e_uid_new().
*
* The returned #GFile can then be passed to e_server_side_source_new().
* Unreference the #GFile with g_object_unref() when finished with it.
*
* Note the data source file itself is not created here, only its name.
*
* Returns: the #GFile for a new data source
*
* Since: 3.6
**/
GFile *
e_server_side_source_new_user_file (const gchar *uid)
{
GFile *file;
gchar *safe_uid;
gchar *basename;
gchar *filename;
const gchar *user_dir;
if (uid == NULL)
safe_uid = e_uid_new ();
else
safe_uid = g_strdup (uid);
e_filename_make_safe (safe_uid);
user_dir = e_server_side_source_get_user_dir ();
basename = g_strconcat (safe_uid, ".source", NULL);
filename = g_build_filename (user_dir, basename, NULL);
file = g_file_new_for_path (filename);
g_free (basename);
g_free (filename);
g_free (safe_uid);
return file;
}
/**
* e_server_side_source_uid_from_file:
* @file: a #GFile for a data source
* @error: return location for a #GError, or %NULL
*
* Extracts a unique identity string from the base name of @file.
* If the base name of @file is missing a '.source' extension, the
* function sets @error and returns %NULL.
*
* Returns: the unique identity string for @file, or %NULL
*
* Since: 3.6
**/
gchar *
e_server_side_source_uid_from_file (GFile *file,
GError **error)
{
gchar *basename;
gchar *uid = NULL;
g_return_val_if_fail (G_IS_FILE (file), FALSE);
basename = g_file_get_basename (file);
if (*basename == '.') {
/* ignore hidden files */
} else if (g_str_has_suffix (basename, ".source")) {
/* strlen(".source") --> 7 */
uid = g_strndup (basename, strlen (basename) - 7);
} else {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_INVALID_FILENAME,
_("File must have a '.source' extension"));
}
g_free (basename);
return uid;
}
/**
* e_server_side_source_new:
* @server: an #ESourceRegistryServer
* @file: a #GFile, or %NULL
* @error: return location for a #GError, or %NULL
*
* Creates a new #EServerSideSource which belongs to @server. If @file
* is non-%NULL and points to an existing file, the #EServerSideSource is
* initialized from the file content. If a read error occurs or the file
* contains syntax errors, the function sets @error and returns %NULL.
*
* Returns: a new #EServerSideSource, or %NULL
*
* Since: 3.6
**/
ESource *
e_server_side_source_new (ESourceRegistryServer *server,
GFile *file,
GError **error)
{
EDBusObjectSkeleton *dbus_object;
ESource *source;
gchar *uid = NULL;
g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
/* Extract a UID from the GFile, if we were given one. */
if (file != NULL) {
uid = e_server_side_source_uid_from_file (file, error);
if (uid == NULL)
return NULL;
}
/* XXX This is an awkward way of initializing the "dbus-object"
* property, but e_source_ref_dbus_object() needs to work. */
dbus_object = e_dbus_object_skeleton_new (DBUS_OBJECT_PATH);
source = g_initable_new (
E_TYPE_SERVER_SIDE_SOURCE, NULL, error,
"dbus-object", dbus_object,
"file", file, "server", server,
"uid", uid, NULL);
g_object_unref (dbus_object);
g_free (uid);
return source;
}
/**
* e_server_side_source_new_memory_only:
* @server: an #ESourceRegistryServer
* @uid: a unique identifier, or %NULL
* @error: return location for a #GError, or %NULL
*
* Creates a memory-only #EServerSideSource which belongs to @server.
* No on-disk key file is created for this data source, so it will not
* be remembered across sessions.
*
* Data source collections are often populated with memory-only data
* sources to serve as proxies for resources discovered on a remote server.
* These data sources are usually neither #EServerSideSource:writable nor
* #EServerSideSource:removable by clients, at least not directly.
*
* If an error occurs while instantiating the #EServerSideSource, the
* function sets @error and returns %NULL. Although at this time there
* are no known error conditions for memory-only data sources.
*
* Returns: a new memory-only #EServerSideSource, or %NULL
*
* Since: 3.6
**/
ESource *
e_server_side_source_new_memory_only (ESourceRegistryServer *server,
const gchar *uid,
GError **error)
{
EDBusObjectSkeleton *dbus_object;
ESource *source;
g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
/* XXX This is an awkward way of initializing the "dbus-object"
* property, but e_source_ref_dbus_object() needs to work. */
dbus_object = e_dbus_object_skeleton_new (DBUS_OBJECT_PATH);
source = g_initable_new (
E_TYPE_SERVER_SIDE_SOURCE, NULL, error,
"dbus-object", dbus_object,
"server", server, "uid", uid, NULL);
g_object_unref (dbus_object);
return source;
}
/**
* e_server_side_source_load:
* @source: an #EServerSideSource
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Reloads data source content from the file pointed to by the
* #EServerSideSource:file property.
*
* If the #EServerSideSource:file property is %NULL or the file it points
* to does not exist, the function does nothing and returns %TRUE.
*
* If a read error occurs or the file contains syntax errors, the function
* sets @error and returns %FALSE.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.6
**/
gboolean
e_server_side_source_load (EServerSideSource *source,
GCancellable *cancellable,
GError **error)
{
GDBusObject *dbus_object;
EDBusSource *dbus_source;
GKeyFile *key_file;
GFile *file;
gboolean success;
gchar *data = NULL;
gsize length;
GError *local_error = NULL;
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
file = e_server_side_source_get_file (source);
if (file != NULL)
g_file_load_contents (
file, cancellable, &data,
&length, NULL, &local_error);
/* Disregard G_IO_ERROR_NOT_FOUND and treat it as a successful load. */
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
g_error_free (local_error);
} else if (local_error != NULL) {
g_propagate_error (error, local_error);
return FALSE;
} else {
source->priv->file_contents = g_strdup (data);
}
if (data == NULL) {
/* Create the bare minimum to pass parse_data(). */
data = g_strdup_printf ("[%s]", PRIMARY_GROUP_NAME);
length = strlen (data);
}
key_file = g_key_file_new ();
success = server_side_source_parse_data (
key_file, data, length, error);
g_key_file_free (key_file);
if (!success) {
g_free (data);
return FALSE;
}
/* Update the D-Bus interface properties. */
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
e_dbus_source_set_data (dbus_source, data);
g_object_unref (dbus_source);
g_object_unref (dbus_object);
g_free (data);
return TRUE;
}
/**
* e_server_side_source_get_file:
* @source: an #EServerSideSource
*
* Returns the #GFile from which data source content is loaded and to
* which changes are saved. Note the @source may not have a #GFile.
*
* Returns: the #GFile for @source, or %NULL
*
* Since: 3.6
**/
GFile *
e_server_side_source_get_file (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
return source->priv->file;
}
/**
* e_server_side_source_get_node:
* @source: an #EServerSideSource
*
* Returns the #GNode representing the @source's hierarchical placement,
* or %NULL if @source has not been placed in the data source hierarchy.
* The data member of the #GNode points back to @source. This is an easy
* way to traverse ancestor and descendant data sources.
*
* Note that accessing other data sources this way is not thread-safe,
* and this therefore function may be replaced at some later date.
*
* Returns: a #GNode, or %NULL
*
* Since: 3.6
**/
GNode *
e_server_side_source_get_node (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
return &source->priv->node;
}
/**
* e_server_side_source_get_server:
* @source: an #EServerSideSource
*
* Returns the #ESourceRegistryServer to which @source belongs.
*
* Returns: the #ESourceRegistryServer for @source
*
* Since: 3.6
**/
ESourceRegistryServer *
e_server_side_source_get_server (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
return source->priv->server;
}
/**
* e_server_side_source_get_allow_auth_prompt:
* @source: an #EServerSideSource
*
* Returns whether an authentication prompt is allowed to be shown
* for @source. #EAuthenticationSession will honor this setting by
* dismissing the session if it can't find a valid stored password.
*
* See e_server_side_source_set_allow_auth_prompt() for further
* discussion.
*
* Returns: whether auth prompts are allowed for @source
*
* Since: 3.6
**/
gboolean
e_server_side_source_get_allow_auth_prompt (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
return source->priv->allow_auth_prompt;
}
/**
* e_server_side_source_set_allow_auth_prompt:
* @source: an #EServerSideSource
* @allow_auth_prompt: whether auth prompts are allowed for @source
*
* Sets whether an authentication prompt is allowed to be shown for @source.
* #EAuthenticationSession will honor this setting by dismissing the session
* if it can't find a valid stored password.
*
* If the user declines to provide a password for @source when prompted
* by an #EAuthenticationSession, the #ESourceRegistryServer will set this
* property to %FALSE to suppress any further prompting, which would likely
* annoy the user. However when an #ESourceRegistry instance is created by
* a client application, the first thing it does is reset this property to
* %TRUE for all registered data sources. So suppressing authentication
* prompts is only ever temporary.
*
* Since: 3.6
**/
void
e_server_side_source_set_allow_auth_prompt (EServerSideSource *source,
gboolean allow_auth_prompt)
{
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
if (source->priv->allow_auth_prompt == allow_auth_prompt)
return;
source->priv->allow_auth_prompt = allow_auth_prompt;
g_object_notify (G_OBJECT (source), "allow-auth-prompt");
}
/**
* e_server_side_source_get_auth_session_type:
* @source: an #EServerSideSource
*
* Returns the type of authentication session to use for @source,
* which will get passed to e_source_registry_server_authenticate().
*
* This defaults to the #GType for #EAuthenticationSession.
*
* Returns: the type of authentication session to use for @source
*
* Since: 3.8
**/
GType
e_server_side_source_get_auth_session_type (EServerSideSource *source)
{
g_return_val_if_fail (
E_IS_SERVER_SIDE_SOURCE (source),
E_TYPE_AUTHENTICATION_SESSION);
return source->priv->auth_session_type;
}
/**
* e_server_side_source_set_auth_session_type:
* @source: an #EServerSideSource
* @auth_session_type: the type of authentication session to use for @source
*
* Sets the type of authentication session to use for @source, which will
* get passed to e_source_registry_server_authenticate().
*
* @auth_session_type must be derived from #EAuthenticationSession, or else
* the function will reject the #GType with a runtime warning.
*
* This defaults to the #GType for #EAuthenticationSession.
*
* Since: 3.8
**/
void
e_server_side_source_set_auth_session_type (EServerSideSource *source,
GType auth_session_type)
{
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
if (!g_type_is_a (auth_session_type, E_TYPE_AUTHENTICATION_SESSION)) {
g_warning (
"Invalid auth session type '%s' for source '%s'",
g_type_name (auth_session_type),
e_source_get_display_name (E_SOURCE (source)));
return;
}
if (auth_session_type == source->priv->auth_session_type)
return;
source->priv->auth_session_type = auth_session_type;
g_object_notify (G_OBJECT (source), "auth-session-type");
}
/**
* e_server_side_source_get_exported:
* @source: an #EServerSideSource
*
* Returns whether @source has been exported over D-Bus.
*
* The function returns %FALSE after @source is initially created, %TRUE
* after passing @source to e_source_registry_add_source() (provided that
* @source's #ESource:parent is also exported), and %FALSE after passing
* @source to e_source_registry_remove_source().
*
* Returns: whether @source has been exported
*
* Since: 3.6
**/
gboolean
e_server_side_source_get_exported (EServerSideSource *source)
{
ESourceRegistryServer *server;
ESource *exported_source;
gboolean exported = FALSE;
const gchar *uid;
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
uid = e_source_get_uid (E_SOURCE (source));
server = e_server_side_source_get_server (source);
/* We're exported if we can look ourselves up in the registry. */
exported_source = e_source_registry_server_ref_source (server, uid);
if (exported_source != NULL) {
exported = TRUE;
g_object_unref (exported_source);
}
return exported;
}
/**
* e_server_side_source_get_write_directory:
* @source: an #EServerSideSource
*
* Returns the local directory path where changes to @source are written.
*
* By default, changes are written to the local directory path returned by
* e_server_side_source_get_user_dir(), but an #ECollectionBackend may wish
* to override this to use its own private cache directory for data sources
* it creates automatically.
*
* Returns: the directory where changes are written
*
* Since: 3.6
**/
const gchar *
e_server_side_source_get_write_directory (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
return source->priv->write_directory;
}
/**
* e_server_side_source_set_write_directory:
* @source: an #EServerSideSource
* @write_directory: the directory where changes are to be written
*
* Sets the local directory path where changes to @source are to be written.
*
* By default, changes are written to the local directory path returned by
* e_server_side_source_get_user_dir(), but an #ECollectionBackend may wish
* to override this to use its own private cache directory for data sources
* it creates automatically.
*
* Since: 3.6
**/
void
e_server_side_source_set_write_directory (EServerSideSource *source,
const gchar *write_directory)
{
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
g_return_if_fail (write_directory != NULL);
if (g_strcmp0 (source->priv->write_directory, write_directory) == 0)
return;
g_free (source->priv->write_directory);
source->priv->write_directory = g_strdup (write_directory);
g_object_notify (G_OBJECT (source), "write-directory");
}
/**
* e_server_side_source_set_removable:
* @source: an #EServerSideSource
* @removable: whether to export the Removable interface
*
* Sets whether to allow registry clients to remove @source and its
* descendants. If %TRUE, the Removable D-Bus interface is exported at
* the object path for @source. If %FALSE, the Removable D-Bus interface
* is unexported at the object path for @source, and any attempt by clients
* to call e_source_remove() will fail.
*
* Note this is only enforced for clients of the registry D-Bus service.
* The service itself may remove any data source at any time.
*
* Since: 3.6
**/
void
e_server_side_source_set_removable (EServerSideSource *source,
gboolean removable)
{
EDBusSourceRemovable *dbus_interface = NULL;
GDBusObject *dbus_object;
gboolean currently_removable;
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
currently_removable = e_source_get_removable (E_SOURCE (source));
if (removable == currently_removable)
return;
if (removable) {
dbus_interface =
e_dbus_source_removable_skeleton_new ();
g_signal_connect (
dbus_interface, "handle-remove",
G_CALLBACK (server_side_source_remove_cb), source);
}
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source_removable (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_interface);
g_object_unref (dbus_object);
if (dbus_interface != NULL)
g_object_unref (dbus_interface);
g_object_notify (G_OBJECT (source), "removable");
}
/**
* e_server_side_source_set_writable:
* @source: an #EServerSideSource
* @writable: whether to export the Writable interface
*
* Sets whether to allow registry clients to alter the content of @source.
* If %TRUE, the Writable D-Bus interface is exported at the object path
* for @source. If %FALSE, the Writable D-Bus interface is unexported at
* the object path for @source, and any attempt by clients to call
* e_source_write() will fail.
*
* Note this is only enforced for clients of the registry D-Bus service.
* The service itself can write to any data source at any time.
*
* Since: 3.6
**/
void
e_server_side_source_set_writable (EServerSideSource *source,
gboolean writable)
{
EDBusSourceWritable *dbus_interface = NULL;
GDBusObject *dbus_object;
gboolean currently_writable;
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
currently_writable = e_source_get_writable (E_SOURCE (source));
if (writable == currently_writable)
return;
if (writable) {
dbus_interface =
e_dbus_source_writable_skeleton_new ();
g_signal_connect (
dbus_interface, "handle-write",
G_CALLBACK (server_side_source_write_cb), source);
}
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source_writable (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_interface);
g_object_unref (dbus_object);
if (dbus_interface != NULL)
g_object_unref (dbus_interface);
g_object_notify (G_OBJECT (source), "writable");
}
/**
* e_server_side_source_set_remote_creatable:
* @source: an #EServerSideSource
* @remote_creatable: whether to export the RemoteCreatable interface
*
* Indicates whether @source can be used to create resources on a remote
* server. Typically this is only set to %TRUE for collection sources.
*
* If %TRUE, the RemoteCreatable D-Bus interface is exported at the object
* path for @source. If %FALSE, the RemoteCreatable D-Bus interface is
* unexported at the object path for @source, and any attempt by clients
* to call e_source_remote_create() will fail.
*
* Unlike the #ESource:removable and #ESource:writable properties, this
* is enforced for both clients of the registry D-Bus service and within
* the registry D-Bus service itself.
*
* Since: 3.6
**/
void
e_server_side_source_set_remote_creatable (EServerSideSource *source,
gboolean remote_creatable)
{
EDBusSourceRemoteCreatable *dbus_interface = NULL;
GDBusObject *dbus_object;
gboolean currently_remote_creatable;
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
currently_remote_creatable =
e_source_get_remote_creatable (E_SOURCE (source));
if (remote_creatable == currently_remote_creatable)
return;
if (remote_creatable) {
dbus_interface =
e_dbus_source_remote_creatable_skeleton_new ();
g_signal_connect (
dbus_interface, "handle-create",
G_CALLBACK (server_side_source_remote_create_cb),
source);
}
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source_remote_creatable (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_interface);
g_object_unref (dbus_object);
if (dbus_interface != NULL)
g_object_unref (dbus_interface);
g_object_notify (G_OBJECT (source), "remote-creatable");
}
/**
* e_server_side_source_set_remote_deletable:
* @source: an #EServerSideSource
* @remote_deletable: whether to export the RemoteDeletable interface
*
* Indicates whether @source can be used to delete resources on a remote
* server. Typically this is only set to %TRUE for sources created by an
* #ECollectionBackend to represent a remote resource.
*
* If %TRUE, the RemoteDeletable D-Bus interface is exported at the object
* path for @source. If %FALSE, the RemoteDeletable D-Bus interface is
* unexported at the object path for @source, and any attempt by clients
* to call e_source_remote_delete() will fail.
*
* Unlike the #ESource:removable and #ESource:writable properties, this
* is enforced for both clients of the registry D-Bus server and within
* the registry D-Bus service itself.
*
* Since: 3.6
**/
void
e_server_side_source_set_remote_deletable (EServerSideSource *source,
gboolean remote_deletable)
{
EDBusSourceRemoteDeletable *dbus_interface = NULL;
GDBusObject *dbus_object;
gboolean currently_remote_deletable;
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
currently_remote_deletable =
e_source_get_remote_deletable (E_SOURCE (source));
if (remote_deletable == currently_remote_deletable)
return;
if (remote_deletable) {
dbus_interface =
e_dbus_source_remote_deletable_skeleton_new ();
g_signal_connect (
dbus_interface, "handle-delete",
G_CALLBACK (server_side_source_remote_delete_cb),
source);
}
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source_remote_deletable (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_interface);
g_object_unref (dbus_object);
if (dbus_interface != NULL)
g_object_unref (dbus_interface);
g_object_notify (G_OBJECT (source), "remote-deletable");
}
/**
* e_server_side_source_ref_oauth2_support:
* @source: an #EServerSideSource
*
* Returns the object implementing the #EOAuth2SupportInterface,
* or %NULL if @source does not support OAuth 2.0 authentication.
*
* The returned #EOAuth2Support object is referenced for thread-safety.
* Unreference the object with g_object_unref() when finished with it.
*
* Returns: an #EOAuth2Support object, or %NULL
*
* Since: 3.8
**/
EOAuth2Support *
e_server_side_source_ref_oauth2_support (EServerSideSource *source)
{
g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
return g_weak_ref_get (&source->priv->oauth2_support);
}
/**
* e_server_side_source_set_oauth2_support:
* @source: an #EServerSideSource
* @oauth2_support: an #EOAuth2Support object, or %NULL
*
* Indicates whether @source supports OAuth 2.0 authentication.
*
* If @oauth2_support is non-%NULL, the OAuth2Support D-Bus interface is
* exported at the object path for @source. If @oauth2_support is %NULL,
* the OAuth2Support D-Bus interface is unexported at the object path for
* @source, and any attempt by clients to call
* e_source_get_oauth2_access_token() will fail.
*
* Requests for OAuth 2.0 access tokens are forwarded to @oauth2_support,
* which implements the #EOAuth2SupportInterface.
*
* Since: 3.8
**/
void
e_server_side_source_set_oauth2_support (EServerSideSource *source,
EOAuth2Support *oauth2_support)
{
EDBusSourceOAuth2Support *dbus_interface = NULL;
GDBusObject *dbus_object;
g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
if (oauth2_support != NULL) {
g_return_if_fail (E_IS_OAUTH2_SUPPORT (oauth2_support));
dbus_interface =
e_dbus_source_oauth2_support_skeleton_new ();
g_signal_connect (
dbus_interface, "handle-get-access-token",
G_CALLBACK (server_side_source_get_access_token_cb),
source);
}
g_weak_ref_set (&source->priv->oauth2_support, oauth2_support);
dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
e_dbus_object_skeleton_set_source_oauth2_support (
E_DBUS_OBJECT_SKELETON (dbus_object), dbus_interface);
g_object_unref (dbus_object);
if (dbus_interface != NULL)
g_object_unref (dbus_interface);
g_object_notify (G_OBJECT (source), "oauth2-support");
}