From f2ae6a9a64f2cf62690764c74de0ef216dd40674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 25 Feb 2021 12:56:56 +0100 Subject: [PATCH] Add EteSync provider Fixes #152 --- configure.ac | 15 + data/icons/meson.build | 2 + data/icons/scalable/Makefile.am | 1 + data/icons/scalable/goa-account-etesync.svg | 19 + data/icons/symbolic/Makefile.am | 1 + .../symbolic/goa-account-etesync-symbolic.svg | 13 + meson.build | 9 + meson_options.txt | 2 + po/POTFILES.in | 1 + src/goabackend/Makefile.am | 1 + src/goabackend/goaetesyncprovider.c | 944 ++++++++++++++++++ src/goabackend/goaetesyncprovider.h | 37 + src/goabackend/goaprovider.c | 4 + src/goabackend/meson.build | 4 +- 14 files changed, 1052 insertions(+), 1 deletion(-) create mode 100644 data/icons/scalable/goa-account-etesync.svg create mode 100644 data/icons/symbolic/goa-account-etesync-symbolic.svg create mode 100644 src/goabackend/goaetesyncprovider.c create mode 100644 src/goabackend/goaetesyncprovider.h diff --git a/configure.ac b/configure.ac index 11862b5e..12fcfd47 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 281c5388..3cf68cef 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 5721dd55..1092bbf7 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 00000000..a1d2c16c --- /dev/null +++ b/data/icons/scalable/goa-account-etesync.svg @@ -0,0 +1,19 @@ + + + EteSync + + + + + + + + + + + + + + + + diff --git a/data/icons/symbolic/Makefile.am b/data/icons/symbolic/Makefile.am index 1ba8100b..9a60fc2d 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 00000000..c87e0e4c --- /dev/null +++ b/data/icons/symbolic/goa-account-etesync-symbolic.svg @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/meson.build b/meson.build index c3d54c8f..ab2e9c68 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 08c65ecd..bd183883 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 279fb64d..16af3954 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 83852378..cd14c7ff 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 00000000..da689240 --- /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 00000000..5d7db7f2 --- /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 9e5bd45a..ce34652b 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 3d9dee8b..adcbf62f 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 = [ -- GitLab