diff --git a/configure.ac b/configure.ac
index 11862b5ec01b9eb62ef83da4cc4b9f758f0f0079..12fcfd471887f3eb9cf76992efd1081c13a86912 100644
--- a/configure.ac
+++ b/configure.ac
@@ -140,6 +140,10 @@ if test "$enable_backend" != "no"; then
PKG_CHECK_MODULES(LIBXML, [libxml-2.0])
AC_SUBST(LIBXML_CFLAGS)
AC_SUBST(LIBXML_LIBS)
+
+ PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.52])
+ AC_SUBST(GMODULE_CFLAGS)
+ AC_SUBST(GMODULE_LIBS)
fi
AC_ARG_ENABLE([inspector],
@@ -159,6 +163,16 @@ AC_DEFINE_UNQUOTED(GOA_TEMPLATE_FILE, ["$with_template_file"], [Path to the temp
# service providers
#
+# EteSync
+AC_DEFINE(GOA_ETESYNC_NAME, ["etesync"], [ProviderType and extension point name])
+AC_ARG_ENABLE([etesync],
+ [AS_HELP_STRING([--enable-etesync], [Enable EteSync provider])],
+ [],
+ [enable_etesync=yes])
+if test "$enable_etesync" != "no"; then
+ AC_DEFINE(GOA_ETESYNC_ENABLED, 1, [Enable EteSync data provider])
+fi
+
# Microsoft Exchange
AC_DEFINE(GOA_EXCHANGE_NAME, ["exchange"], [ProviderType and extension point name])
AC_ARG_ENABLE([exchange],
@@ -565,6 +579,7 @@ echo "
introspection: ${found_introspection}
template file: ${with_template_file}
+ EteSync provider: ${enable_etesync}
Fedora Account System provider: ${enable_fedora}
Flickr provider: ${enable_flickr} (OAuth 1.0, key:${with_flickr_consumer_key} secret:${with_flickr_consumer_secret})
Foursquare provider: ${enable_foursquare} (id:${with_foursquare_client_id})
diff --git a/data/icons/meson.build b/data/icons/meson.build
index 281c53880d2627f6a12ed55e59e59a5a0c0a2725..3cf68cef0883f2cab3ead89ffd3a9656dd33f886 100644
--- a/data/icons/meson.build
+++ b/data/icons/meson.build
@@ -1,5 +1,6 @@
icon_scalable_data = [
'goa-account.svg',
+ 'goa-account-etesync.svg',
'goa-account-exchange.svg',
'goa-account-facebook.svg',
'goa-account-fedora.svg',
@@ -13,6 +14,7 @@ icon_scalable_data = [
icon_symbolic_data = [
'goa-account-symbolic.svg',
+ 'goa-account-etesync-symbolic.svg',
'goa-account-exchange-symbolic.svg',
'goa-account-facebook-symbolic.svg',
'goa-account-flickr-symbolic.svg',
diff --git a/data/icons/scalable/Makefile.am b/data/icons/scalable/Makefile.am
index 5721dd55de0093ae4e2396dd0b7b70e1c4e0b1d8..1092bbf79bcef1044d7ec1def542b5414065219d 100644
--- a/data/icons/scalable/Makefile.am
+++ b/data/icons/scalable/Makefile.am
@@ -3,6 +3,7 @@ NULL =
icondir = $(datadir)/icons/hicolor/scalable/apps
icon_DATA = \
goa-account.svg \
+ goa-account-etesync.svg \
goa-account-exchange.svg \
goa-account-facebook.svg \
goa-account-fedora.svg \
diff --git a/data/icons/scalable/goa-account-etesync.svg b/data/icons/scalable/goa-account-etesync.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a1d2c16c1c31a2204adac5e564a822de24fcb65d
--- /dev/null
+++ b/data/icons/scalable/goa-account-etesync.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/data/icons/symbolic/Makefile.am b/data/icons/symbolic/Makefile.am
index 1ba8100b6632781ee9e641022d36ec2c0351752f..9a60fc2d7191561bc6e8451a9b12a574e4a2e7f3 100644
--- a/data/icons/symbolic/Makefile.am
+++ b/data/icons/symbolic/Makefile.am
@@ -3,6 +3,7 @@ NULL =
icondir = $(datadir)/icons/hicolor/symbolic/apps
icon_DATA = \
goa-account-symbolic.svg \
+ goa-account-etesync-symbolic.svg \
goa-account-exchange-symbolic.svg \
goa-account-facebook-symbolic.svg \
goa-account-flickr-symbolic.svg \
diff --git a/data/icons/symbolic/goa-account-etesync-symbolic.svg b/data/icons/symbolic/goa-account-etesync-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c87e0e4ce12cc87e6ddd5652d75226ce468ff47b
--- /dev/null
+++ b/data/icons/symbolic/goa-account-etesync-symbolic.svg
@@ -0,0 +1,13 @@
+
+
diff --git a/meson.build b/meson.build
index c3d54c8f2519d683fa514d0add558645b3513e00..ab2e9c6863db05b45d8d6da8d2495507ec950125 100644
--- a/meson.build
+++ b/meson.build
@@ -58,6 +58,8 @@ config_h.set('GOA_MAJOR_VERSION', goa_major_version)
config_h.set('GOA_MINOR_VERSION', goa_minor_version)
config_h.set('GOA_MICRO_VERSION', goa_micro_version)
+config_h.set_quoted('GOA_LIBDIR', goa_libdir)
+
compiler_flags = []
if goa_buildtype.contains('debug')
@@ -104,6 +106,7 @@ if enable_goabackend
libxml_dep = dependency('libxml-2.0')
rest_dep = dependency('rest-0.7')
webkit_gtk_dep = dependency('webkit2gtk-4.0', version: '>= 2.26.0')
+ gmodule_dep = dependency('gmodule-2.0', version: glib_req_version)
endif
config_h.set('GOA_BACKEND_ENABLED', enable_goabackend)
@@ -115,6 +118,11 @@ config_h.set_quoted('GOA_TEMPLATE_FILE', template_file)
enable_vapi = get_option('vapi')
+# EteSync
+config_h.set_quoted('GOA_ETESYNC_NAME', 'etesync')
+enable_etesync = get_option('etesync')
+config_h.set('GOA_ETESYNC_ENABLED', enable_etesync)
+
# Microsoft Exchange
config_h.set_quoted('GOA_EXCHANGE_NAME', 'exchange')
enable_exchange = get_option('exchange')
@@ -308,6 +316,7 @@ output += ' goabackend: ' + enable_goabackend.to_stri
output += ' inspector: ' + enable_inspector.to_string() + '\n'
output += ' introspection: ' + enable_introspection.to_string() + '\n'
output += ' template file: ' + template_file + '\n\n'
+output += ' EteSync provider: ' + enable_etesync.to_string() + '\n'
output += ' Facebook provider: ' + enable_facebook.to_string()
if enable_facebook
output += ' (OAuth 2.0, id:@0@)'.format(facebook_client_id)
diff --git a/meson_options.txt b/meson_options.txt
index 08c65ecd270af4687b1b4b2cbc2c5f3afc99016e..bd183883dd6e9cece73d569cf24b5d88b24d041d 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,6 +1,8 @@
option('goabackend', type: 'boolean', value: true, description: 'Enable goabackend library')
option('inspector', type: 'boolean', value: false, description: 'Enable a WebKitWebInspector for the embedded web view')
+option('etesync', type: 'boolean', value: true, description: 'Enable EteSync provider')
+
option('exchange', type: 'boolean', value: true, description: 'Enable Microsoft Exchange provider')
option('facebook', type: 'boolean', value: true, description: 'Enable Facebook provider')
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 279fb64dde000de47c4c9452154891bc284c93b6..16af395448c50d20cd994fc5ffd33cd0b1de8fdf 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,7 @@
# Please keep this file sorted alphabetically.
data/org.gnome.online-accounts.gschema.xml
src/daemon/goadaemon.c
+src/goabackend/goaetesyncprovider.c
src/goabackend/goaewsclient.c
src/goabackend/goaexchangeprovider.c
src/goabackend/goafacebookprovider.c
diff --git a/src/goabackend/Makefile.am b/src/goabackend/Makefile.am
index 838523788731ae52e19e367148291df137ec648f..cd14c7ff13b42616ec74877074f8e20b21df5108 100644
--- a/src/goabackend/Makefile.am
+++ b/src/goabackend/Makefile.am
@@ -77,6 +77,7 @@ libgoa_backend_1_0_la_SOURCES = \
goarestproxy.h goarestproxy.c \
goasmtpauth.h goasmtpauth.c \
goasouplogger.h goasouplogger.c \
+ goaetesyncprovider.h goaetesyncprovider.c \
goamailclient.h goamailclient.c \
goaexchangeprovider.h goaexchangeprovider.c \
goaoauthprovider.h goaoauthprovider.c \
diff --git a/src/goabackend/goaetesyncprovider.c b/src/goabackend/goaetesyncprovider.c
new file mode 100644
index 0000000000000000000000000000000000000000..da689240e9148fbd99769a3e1093082ef164d861
--- /dev/null
+++ b/src/goabackend/goaetesyncprovider.c
@@ -0,0 +1,944 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2021 Kévin Commaille
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+
+#include "config.h"
+
+#include
+
+#include
+#include
+
+#include "goaprovider.h"
+#include "goaetesyncprovider.h"
+#include "goaobjectskeletonutils.h"
+#include "goautils.h"
+
+struct _GoaEtesyncProvider
+{
+ GoaProvider parent_instance;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GoaEtesyncProvider, goa_etesync_provider, GOA_TYPE_PROVIDER,
+ goa_provider_ensure_extension_points_registered ();
+ g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ GOA_ETESYNC_NAME,
+ 0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+static const gchar *DEFAULT_SERVER_URL = "https://api.etebase.com/";
+static const gchar *DEFAULT_SERVER_HOST = "etebase.com";
+
+static const gchar *
+get_provider_type (GoaProvider *provider)
+{
+ return GOA_ETESYNC_NAME;
+}
+
+static gchar *
+get_provider_name (GoaProvider *provider, GoaObject *object)
+{
+ return g_strdup(_("EteSync"));
+}
+
+static GoaProviderGroup
+get_provider_group (GoaProvider *provider)
+{
+ return GOA_PROVIDER_GROUP_BRANDED;
+}
+
+static GoaProviderFeatures
+get_provider_features (GoaProvider *provider)
+{
+ return GOA_PROVIDER_FEATURE_BRANDED |
+ GOA_PROVIDER_FEATURE_CALENDAR |
+ GOA_PROVIDER_FEATURE_CONTACTS;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+static gboolean on_handle_get_password (GoaPasswordBased *interface,
+ GDBusMethodInvocation *invocation,
+ const gchar *id,
+ gpointer user_data);
+
+static gboolean
+build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+ GKeyFile *key_file,
+ const gchar *group,
+ GDBusConnection *connection,
+ gboolean just_added,
+ GError **error)
+{
+ GoaAccount *account = NULL;
+ GoaPasswordBased *password_based = NULL;
+ gboolean calendar_enabled;
+ gboolean contacts_enabled;
+ gboolean ret = FALSE;
+ gchar *server = NULL;
+
+ /* Chain up */
+ if (!GOA_PROVIDER_CLASS (goa_etesync_provider_parent_class)->build_object (provider,
+ object,
+ key_file,
+ group,
+ connection,
+ just_added,
+ error))
+ goto out;
+
+ password_based = goa_object_get_password_based (GOA_OBJECT (object));
+ if (password_based == NULL)
+ {
+ password_based = goa_password_based_skeleton_new ();
+ /* Ensure D-Bus method invocations run in their own thread */
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (password_based),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+ goa_object_skeleton_set_password_based (object, password_based);
+ g_signal_connect (password_based,
+ "handle-get-password",
+ G_CALLBACK (on_handle_get_password),
+ NULL);
+ }
+
+ account = goa_object_get_account (GOA_OBJECT (object));
+ server = g_key_file_get_string (key_file, group, "Server", NULL);
+
+ /* Calendar */
+ calendar_enabled = g_key_file_get_boolean (key_file, group, "CalendarEnabled", NULL);
+ goa_object_skeleton_attach_calendar (object, server, calendar_enabled, FALSE);
+
+ /* Contacts */
+ contacts_enabled = g_key_file_get_boolean (key_file, group, "ContactsEnabled", NULL);
+ goa_object_skeleton_attach_contacts (object, server, contacts_enabled, FALSE);
+
+ if (just_added)
+ {
+ goa_account_set_calendar_disabled (account, !calendar_enabled);
+ goa_account_set_contacts_disabled (account, !contacts_enabled);
+
+ g_signal_connect (account,
+ "notify::calendar-disabled",
+ G_CALLBACK (goa_util_account_notify_property_cb),
+ (gpointer) "CalendarEnabled");
+ g_signal_connect (account,
+ "notify::contacts-disabled",
+ G_CALLBACK (goa_util_account_notify_property_cb),
+ (gpointer) "ContactsEnabled");
+ }
+
+ ret = TRUE;
+
+ out:
+ g_clear_object (&password_based);
+ g_free (server);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+typedef gboolean (* EEtesyncUtilCheckCredentialsFunc) (const gchar *username,
+ const gchar *password,
+ const gchar *server_url,
+ GError **error);
+
+static gchar *get_e_etesync_module_path (void)
+{
+ gchar *dir, *path;
+
+ dir = g_strconcat(GOA_LIBDIR, "/evolution-etesync", NULL);
+ path = g_module_build_path (dir, "evolution-etesync");
+
+ g_free(dir);
+ return path;
+}
+
+static gboolean has_e_etesync_module (GError **error)
+{
+ GModule *module;
+ gchar *module_path;
+ gboolean ret = FALSE;
+
+ module_path = get_e_etesync_module_path();
+
+ if (!g_module_supported())
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED,
+ _("Cannot load modules dynamically"));
+ goto out;
+ }
+
+ module = g_module_open (module_path, G_MODULE_BIND_LAZY);
+ if (!module)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED,
+ _("Can't find libevolution-etesync, please install evolution-etesync"));
+ g_debug("%s", g_module_error ());
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_free(module_path);
+
+ return ret;
+}
+
+static gboolean etesync_check_credentials_sync (const gchar *username,
+ const gchar *password,
+ const gchar *server,
+ GError **error)
+{
+ EEtesyncUtilCheckCredentialsFunc e_etesync_util_check_credentials;
+ GModule *module;
+ gchar *module_path;
+ gboolean ret = FALSE;
+
+ module_path = get_e_etesync_module_path();
+
+ module = g_module_open (module_path, G_MODULE_BIND_LAZY);
+ if (!module)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED,
+ "%s", g_module_error ());
+ g_free(module_path);
+ return FALSE;
+ }
+
+ if (!g_module_symbol (module,
+ "e_etesync_util_check_credentials",
+ (gpointer *)&e_etesync_util_check_credentials))
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED,
+ "%s", g_module_error ());
+ goto out;
+ }
+
+ if (e_etesync_util_check_credentials == NULL)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED,
+ "symbol e_etesync_util_check_credentials is NULL");
+ goto out;
+ }
+
+ ret = e_etesync_util_check_credentials (username, password, server, error);
+
+ out:
+ g_free(module_path);
+ return ret;
+}
+
+static gboolean
+ensure_credentials_sync (GoaProvider *provider,
+ GoaObject *object,
+ gint *out_expires_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gchar *username = NULL;
+ gchar *password = NULL;
+ gchar *server = NULL;
+
+ if (!goa_utils_get_credentials (provider, object, "password", &username, &password, cancellable, error))
+ {
+ if (error != NULL)
+ {
+ (*error)->domain = GOA_ERROR;
+ (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+ }
+ goto out;
+ }
+
+ server = goa_util_lookup_keyfile_string (object, "Server");
+
+ if (!etesync_check_credentials_sync(username, password, server, error)) {
+ goto out;
+ }
+
+ if (out_expires_in != NULL)
+ *out_expires_in = 0;
+
+ ret = TRUE;
+
+ out:
+ g_free (username);
+ g_free (password);
+ g_free (server);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GCancellable *cancellable;
+
+ GtkDialog *dialog;
+ GMainLoop *loop;
+
+ GtkWidget *cluebar;
+ GtkWidget *cluebar_label;
+ GtkWidget *connect_button;
+ GtkWidget *progress_grid;
+
+ GtkWidget *username;
+ GtkWidget *password;
+
+ GtkWidget *expander;
+ GtkWidget *server;
+
+ gchar *account_object_path;
+
+ GError *error;
+} AddAccountData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget *grid,
+ gint row,
+ const gchar *text,
+ GtkWidget **out_entry)
+{
+ GtkStyleContext *context;
+ GtkWidget *label;
+ GtkWidget *entry;
+
+ label = gtk_label_new_with_mnemonic (text);
+ context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
+ gtk_widget_set_halign (label, GTK_ALIGN_END);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
+
+ entry = gtk_entry_new ();
+ gtk_widget_set_hexpand (entry, TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_grid_attach (GTK_GRID (grid), entry, 1, row, 3, 1);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ if (out_entry != NULL)
+ *out_entry = entry;
+}
+
+static void
+on_username_or_password_changed (GtkEditable *editable, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ gboolean can_add = FALSE;
+
+ can_add = gtk_entry_get_text_length (GTK_ENTRY (data->username)) != 0
+ && gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
+
+ gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+}
+
+static void
+create_account_details_ui (GoaProvider *provider,
+ GtkDialog *dialog,
+ GtkBox *vbox,
+ gboolean new_account,
+ gboolean is_template,
+ AddAccountData *data)
+{
+ GtkWidget *grid0;
+ GtkWidget *grid1;
+ GtkWidget *label;
+ GtkWidget *spinner;
+ gint row;
+ gint width;
+
+ goa_utils_set_dialog_title (provider, dialog, new_account);
+
+ grid0 = gtk_grid_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (grid0), 5);
+ gtk_widget_set_margin_bottom (grid0, 6);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (grid0), GTK_ORIENTATION_VERTICAL);
+ gtk_grid_set_row_spacing (GTK_GRID (grid0), 12);
+ gtk_container_add (GTK_CONTAINER (vbox), grid0);
+
+ data->cluebar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
+ gtk_widget_set_hexpand (data->cluebar, TRUE);
+ gtk_widget_set_no_show_all (data->cluebar, TRUE);
+ gtk_container_add (GTK_CONTAINER (grid0), data->cluebar);
+
+ data->cluebar_label = gtk_label_new ("");
+ gtk_label_set_line_wrap (GTK_LABEL (data->cluebar_label), TRUE);
+ gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data->cluebar))),
+ data->cluebar_label);
+
+ grid1 = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
+ gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
+ gtk_container_add (GTK_CONTAINER (grid0), grid1);
+
+ row = 0;
+ add_entry (grid1, row++, _("User_name"), &data->username);
+ add_entry (grid1, row++, _("_Password"), &data->password);
+ if (new_account)
+ {
+ data->expander = gtk_expander_new_with_mnemonic (_("_Custom"));
+ gtk_expander_set_expanded (GTK_EXPANDER (data->expander), FALSE);
+ gtk_expander_set_resize_toplevel (GTK_EXPANDER (data->expander), TRUE);
+ gtk_container_add (GTK_CONTAINER (grid0), data->expander);
+
+ grid1 = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
+ gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
+ gtk_container_add (GTK_CONTAINER (data->expander), grid1);
+
+ row = 0;
+ add_entry (grid1, row++, _("_Server"), &data->server);
+ }
+
+ gtk_entry_set_visibility (GTK_ENTRY (data->password), FALSE);
+
+ if (new_account)
+ gtk_widget_grab_focus (data->username);
+ else
+ gtk_widget_grab_focus (data->password);
+
+ g_signal_connect (data->username, "changed", G_CALLBACK (on_username_or_password_changed), data);
+ g_signal_connect (data->password, "changed", G_CALLBACK (on_username_or_password_changed), data);
+
+ gtk_dialog_add_button (data->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+ data->connect_button = gtk_dialog_add_button (data->dialog, _("C_onnect"), GTK_RESPONSE_OK);
+ gtk_dialog_set_default_response (data->dialog, GTK_RESPONSE_OK);
+ gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, FALSE);
+
+ data->progress_grid = gtk_grid_new ();
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (data->progress_grid), GTK_ORIENTATION_HORIZONTAL);
+ gtk_grid_set_column_spacing (GTK_GRID (data->progress_grid), 3);
+ gtk_container_add (GTK_CONTAINER (grid0), data->progress_grid);
+
+ spinner = gtk_spinner_new ();
+ gtk_widget_set_opacity (spinner, 0.0);
+ gtk_widget_set_size_request (spinner, 20, 20);
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ gtk_container_add (GTK_CONTAINER (data->progress_grid), spinner);
+
+ label = gtk_label_new (_("Connecting…"));
+ gtk_widget_set_opacity (label, 0.0);
+ gtk_container_add (GTK_CONTAINER (data->progress_grid), label);
+
+ if (new_account)
+ {
+ gtk_window_get_size (GTK_WINDOW (data->dialog), &width, NULL);
+ gtk_window_set_default_size (GTK_WINDOW (data->dialog), width, -1);
+ }
+ else
+ {
+ GtkWindow *parent;
+
+ /* Keep in sync with GoaPanelAddAccountDialog in
+ * gnome-control-center.
+ */
+ parent = gtk_window_get_transient_for (GTK_WINDOW (data->dialog));
+ if (parent != NULL)
+ {
+ gtk_window_get_size (parent, &width, NULL);
+ gtk_window_set_default_size (GTK_WINDOW (data->dialog), (gint) (0.5 * width), -1);
+ }
+ }
+}
+
+static void
+show_progress_ui (GtkContainer *container, gboolean progress)
+{
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (container);
+ for (l = children; l != NULL; l = l->next)
+ {
+ GtkWidget *widget = GTK_WIDGET (l->data);
+ gdouble opacity;
+
+ opacity = progress ? 1.0 : 0.0;
+ gtk_widget_set_opacity (widget, opacity);
+ }
+
+ g_list_free (children);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ goa_manager_call_add_account_finish (manager,
+ &data->account_object_path,
+ res,
+ &data->error);
+ g_main_loop_quit (data->loop);
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+
+ if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_DELETE_EVENT)
+ g_cancellable_cancel (data->cancellable);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaObject *
+add_account (GoaProvider *provider,
+ GoaClient *client,
+ GtkDialog *dialog,
+ GtkBox *vbox,
+ GError **error)
+{
+ AddAccountData data;
+ GVariantBuilder credentials;
+ GVariantBuilder details;
+ GoaObject *ret = NULL;
+ const gchar *password;
+ const gchar *username;
+ const gchar *server;
+ const gchar *provider_type;
+ GUri *uri = NULL;
+ gchar *presentation_identity = NULL;
+ gint response;
+ gboolean is_e_etesync_installed = FALSE;
+
+ memset (&data, 0, sizeof (AddAccountData));
+ data.cancellable = g_cancellable_new ();
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.dialog = dialog;
+ data.error = NULL;
+
+ create_account_details_ui (provider, dialog, vbox, TRUE, FALSE, &data);
+ gtk_widget_show_all (GTK_WIDGET (vbox));
+ g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+ is_e_etesync_installed = has_e_etesync_module(&data.error);
+
+ if (!is_e_etesync_installed)
+ {
+ gchar *markup;
+
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ gtk_widget_set_sensitive (data.username, FALSE);
+ gtk_widget_set_sensitive (data.password, FALSE);
+ gtk_widget_set_sensitive (data.expander, FALSE);
+ gtk_widget_set_sensitive (data.server, FALSE);
+
+ markup = g_strdup_printf ("%s:\n%s",
+ _("Error checking for EteSync support"),
+ data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ }
+
+ etesync_again:
+ response = gtk_dialog_run (dialog);
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_set_error (&data.error,
+ GOA_ERROR,
+ GOA_ERROR_DIALOG_DISMISSED,
+ _("Dialog was dismissed"));
+ goto out;
+ }
+
+ username = gtk_entry_get_text (GTK_ENTRY (data.username));
+ password = gtk_entry_get_text (GTK_ENTRY (data.password));
+ server = gtk_entry_get_text (GTK_ENTRY (data.server));
+
+ if (server != NULL && server[0] != '\0')
+ uri = g_uri_parse (server, G_URI_FLAGS_NONE, NULL);
+ else
+ server = DEFAULT_SERVER_URL;
+
+ if (uri == NULL || g_strcmp0(server, DEFAULT_SERVER_URL) == 0)
+ presentation_identity = g_strconcat (username, "@", DEFAULT_SERVER_HOST, NULL);
+ else
+ {
+ presentation_identity = g_strconcat (username, "@", g_uri_get_host(uri), NULL);
+ g_uri_unref (uri);
+ }
+
+ /* See if there's already an account of this type with the
+ * given identity
+ */
+ provider_type = goa_provider_get_provider_type (provider);
+ if (!goa_utils_check_duplicate (client,
+ username,
+ presentation_identity,
+ provider_type,
+ (GoaPeekInterfaceFunc) goa_object_peek_password_based,
+ &data.error))
+ goto out;
+
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE);
+
+ etesync_check_credentials_sync(username, password, server, &data.error);
+
+ gtk_widget_set_sensitive (data.connect_button, TRUE);
+ show_progress_ui (GTK_CONTAINER (data.progress_grid), FALSE);
+
+ if (data.error != NULL)
+ {
+ gchar *markup;
+
+ gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+
+ markup = g_strdup_printf ("%s:\n%s",
+ _("Error connecting to EteSync server"),
+ data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+
+ goto etesync_again;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+
+ g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+
+ g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&details, "{ss}", "CalendarEnabled", "true");
+ g_variant_builder_add (&details, "{ss}", "ContactsEnabled", "true");
+ g_variant_builder_add (&details, "{ss}", "Server", server);
+
+ /* OK, everything is dandy, add the account */
+ /* we want the GoaClient to update before this method returns (so it
+ * can create a proxy for the new object) so run the mainloop while
+ * waiting for this to complete
+ */
+ goa_manager_call_add_account (goa_client_get_manager (client),
+ goa_provider_get_provider_type (provider),
+ username,
+ presentation_identity,
+ g_variant_builder_end (&credentials),
+ g_variant_builder_end (&details),
+ NULL, /* GCancellable* */
+ (GAsyncReadyCallback) add_account_cb,
+ &data);
+ g_main_loop_run (data.loop);
+ if (data.error != NULL)
+ goto out;
+
+ ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+ data.account_object_path));
+
+ out:
+ /* We might have an object even when data.error is set.
+ * eg., if we failed to store the credentials in the keyring.
+ */
+ if (data.error != NULL)
+ g_propagate_error (error, data.error);
+ else
+ g_assert (ret != NULL);
+
+ g_signal_handlers_disconnect_by_func (dialog, dialog_response_cb, &data);
+
+ g_free (presentation_identity);
+ g_free (data.account_object_path);
+ g_clear_pointer (&data.loop, g_main_loop_unref);
+ g_clear_object (&data.cancellable);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider *provider,
+ GoaClient *client,
+ GoaObject *object,
+ GtkWindow *parent,
+ GError **error)
+{
+ AddAccountData data;
+ GVariantBuilder credentials;
+ GoaAccount *account;
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ gboolean is_template = FALSE;
+ gboolean ret = FALSE;
+ const gchar *password;
+ const gchar *username;
+ gchar *server = NULL;
+ gint response;
+ gboolean is_e_etesync_installed = FALSE;
+
+ g_return_val_if_fail (GOA_IS_ETESYNC_PROVIDER (provider), FALSE);
+ g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ dialog = gtk_dialog_new_with_buttons (NULL,
+ parent,
+ GTK_DIALOG_MODAL
+ | GTK_DIALOG_DESTROY_WITH_PARENT
+ | GTK_DIALOG_USE_HEADER_BAR,
+ NULL,
+ NULL);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_box_set_spacing (GTK_BOX (vbox), 12);
+
+ memset (&data, 0, sizeof (AddAccountData));
+ data.cancellable = g_cancellable_new ();
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.dialog = GTK_DIALOG (dialog);
+ data.error = NULL;
+
+ account = goa_object_peek_account (object);
+ username = goa_account_get_identity (account);
+ if (username == NULL || username[0] == '\0')
+ is_template = TRUE;
+
+ create_account_details_ui (provider, GTK_DIALOG (dialog), GTK_BOX (vbox), FALSE, is_template, &data);
+
+ server = goa_util_lookup_keyfile_string (object, "Server");
+ gtk_entry_set_text (GTK_ENTRY (data.server), server);
+ gtk_editable_set_editable (GTK_EDITABLE (data.server), FALSE);
+
+ if (!is_template)
+ {
+ gtk_entry_set_text (GTK_ENTRY (data.username), username);
+ gtk_editable_set_editable (GTK_EDITABLE (data.username), FALSE);
+ }
+
+ gtk_widget_show_all (dialog);
+ g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+ is_e_etesync_installed = has_e_etesync_module(&data.error);
+
+ if (!is_e_etesync_installed)
+ {
+ gchar *markup;
+
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ gtk_widget_set_sensitive (data.username, FALSE);
+ gtk_widget_set_sensitive (data.password, FALSE);
+ gtk_widget_set_sensitive (data.expander, FALSE);
+ gtk_widget_set_sensitive (data.server, FALSE);
+
+ markup = g_strdup_printf ("%s:\n%s",
+ _("Error checking for EteSync support"),
+ data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ }
+
+ etesync_again:
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_set_error (&data.error,
+ GOA_ERROR,
+ GOA_ERROR_DIALOG_DISMISSED,
+ _("Dialog was dismissed"));
+ goto out;
+ }
+
+ if (is_template)
+ username = gtk_entry_get_text (GTK_ENTRY (data.username));
+
+ password = gtk_entry_get_text (GTK_ENTRY (data.password));
+
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE);
+
+ etesync_check_credentials_sync(username, password, server, &data.error);
+
+ gtk_widget_set_sensitive (data.connect_button, TRUE);
+ show_progress_ui (GTK_CONTAINER (data.progress_grid), FALSE);
+
+ if (data.error != NULL)
+ {
+ gchar *markup;
+
+ markup = g_strdup_printf ("%s:\n%s",
+ _("Error connecting to EteSync server"),
+ data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ goto etesync_again;
+ }
+
+ /* TODO: run in worker thread */
+ g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+
+ if (is_template)
+ {
+ GVariantBuilder details;
+ GoaManager *manager;
+ const gchar *id;
+ const gchar *provider_type;
+ GUri *uri = NULL;
+ gchar *presentation_identity = NULL;
+
+ manager = goa_client_get_manager (client);
+ id = goa_account_get_id (account);
+ provider_type = goa_provider_get_provider_type (provider);
+
+ uri = g_uri_parse (server, G_URI_FLAGS_NONE, NULL);
+
+ if (uri == NULL || g_strcmp0(server, DEFAULT_SERVER_URL) == 0)
+ presentation_identity = g_strconcat (username, "@", DEFAULT_SERVER_HOST, NULL);
+ else
+ presentation_identity = g_strconcat (username, "@", g_uri_get_host(uri), NULL);
+ g_free(uri);
+
+ g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&details, "{ss}", "Id", id);
+
+ goa_manager_call_add_account (manager,
+ provider_type,
+ username,
+ presentation_identity,
+ g_variant_builder_end (&credentials),
+ g_variant_builder_end (&details),
+ NULL, /* GCancellable* */
+ (GAsyncReadyCallback) add_account_cb,
+ &data);
+ g_free (presentation_identity);
+
+ g_main_loop_run (data.loop);
+ if (data.error != NULL)
+ goto out;
+ }
+ else
+ {
+ if (!goa_utils_store_credentials_for_object_sync (provider,
+ object,
+ g_variant_builder_end (&credentials),
+ NULL, /* GCancellable */
+ &data.error))
+ goto out;
+ }
+
+ goa_account_call_ensure_credentials (account,
+ NULL, /* GCancellable */
+ NULL, NULL); /* callback, user_data */
+
+ ret = TRUE;
+
+ out:
+ if (data.error != NULL)
+ g_propagate_error (error, data.error);
+
+ gtk_widget_destroy (dialog);
+ g_free (server);
+ g_free (data.account_object_path);
+ g_clear_pointer (&data.loop, g_main_loop_unref);
+ g_clear_object (&data.cancellable);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_etesync_provider_init (GoaEtesyncProvider *self)
+{
+}
+
+static void
+goa_etesync_provider_class_init (GoaEtesyncProviderClass *klass)
+{
+ GoaProviderClass *provider_class;
+
+ provider_class = GOA_PROVIDER_CLASS (klass);
+ provider_class->get_provider_type = get_provider_type;
+ provider_class->get_provider_name = get_provider_name;
+ provider_class->get_provider_group = get_provider_group;
+ provider_class->get_provider_features = get_provider_features;
+ provider_class->add_account = add_account;
+ provider_class->refresh_account = refresh_account;
+ provider_class->build_object = build_object;
+ provider_class->ensure_credentials_sync = ensure_credentials_sync;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_handle_get_password (GoaPasswordBased *interface,
+ GDBusMethodInvocation *invocation,
+ const gchar *id, /* unused */
+ gpointer user_data)
+{
+ GoaObject *object;
+ GoaAccount *account;
+ GoaProvider *provider;
+ GError *error;
+ const gchar *account_id;
+ const gchar *method_name;
+ const gchar *provider_type;
+ gchar *password = NULL;
+
+ /* TODO: maybe log what app is requesting access */
+
+ object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+ account = goa_object_peek_account (object);
+ account_id = goa_account_get_id (account);
+ provider_type = goa_account_get_provider_type (account);
+ method_name = g_dbus_method_invocation_get_method_name (invocation);
+
+ provider = goa_provider_get_for_provider_type (provider_type);
+
+ error = NULL;
+ if (!goa_utils_get_credentials (provider, object, "password", NULL, &password, NULL, &error))
+ {
+ g_dbus_method_invocation_take_error (invocation, error);
+ goto out;
+ }
+
+ goa_password_based_complete_get_password (interface, invocation, password);
+
+ out:
+ g_free (password);
+ g_object_unref (provider);
+ return TRUE; /* invocation was handled */
+}
diff --git a/src/goabackend/goaetesyncprovider.h b/src/goabackend/goaetesyncprovider.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d7db7f22147d377efbb1591cfbb374aead2d6e6
--- /dev/null
+++ b/src/goabackend/goaetesyncprovider.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2021 Kévin Commaille
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __GOA_ETESYNC_PROVIDER_H__
+#define __GOA_ETESYNC_PROVIDER_H__
+
+#include
+
+#include "goaprovider-priv.h"
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_ETESYNC_PROVIDER (goa_etesync_provider_get_type ())
+G_DECLARE_FINAL_TYPE (GoaEtesyncProvider, goa_etesync_provider, GOA, ETESYNC_PROVIDER, GoaProvider);
+
+G_END_DECLS
+
+#endif /* __GOA_ETESYNC_PROVIDER_H__ */
diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
index 9e5bd45a4ba0205cdbf987c68a9430749b2f0aa7..ce34652bc0bc51e2ea067fca48483daf7b159519 100644
--- a/src/goabackend/goaprovider.c
+++ b/src/goabackend/goaprovider.c
@@ -22,6 +22,7 @@
#include "goaprovider.h"
#include "goaprovider-priv.h"
+#include "goaetesyncprovider.h"
#include "goaexchangeprovider.h"
#include "goagoogleprovider.h"
#include "goafacebookprovider.h"
@@ -941,6 +942,9 @@ static struct
#ifdef GOA_OWNCLOUD_ENABLED
{ GOA_OWNCLOUD_NAME, goa_owncloud_provider_get_type },
#endif
+#ifdef GOA_ETESYNC_ENABLED
+ { GOA_ETESYNC_NAME, goa_etesync_provider_get_type },
+#endif
#ifdef GOA_FACEBOOK_ENABLED
{ GOA_FACEBOOK_NAME, goa_facebook_provider_get_type },
#endif
diff --git a/src/goabackend/meson.build b/src/goabackend/meson.build
index 3d9dee8ba2c9b15d98a3babe87a993047bcf9c47..adcbf62fe44d5fea178db229a21f752eb9095e33 100644
--- a/src/goabackend/meson.build
+++ b/src/goabackend/meson.build
@@ -6,6 +6,7 @@ libgoa_backend_sources = files(
'goaoauthprovider.c',
'goabackendinit.c',
'goadlnaservermanager.c',
+ 'goaetesyncprovider.c',
'goaewsclient.c',
'goaexchangeprovider.c',
'goafacebookprovider.c',
@@ -94,7 +95,8 @@ deps = [
libsoup_dep,
libxml_dep,
rest_dep,
- webkit_gtk_dep
+ webkit_gtk_dep,
+ gmodule_dep
]
cflags = [