[evolution-data-server] I#165 - Add backend to access Nextcloud Notes
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] I#165 - Add backend to access Nextcloud Notes
- Date: Fri, 6 Mar 2020 08:05:35 +0000 (UTC)
commit ae8c55886bc2dfda2bae3c4324d922276d087be7
Author: Milan Crha <mcrha redhat com>
Date: Fri Mar 6 09:07:37 2020 +0100
I#165 - Add backend to access Nextcloud Notes
Closes https://gitlab.gnome.org/GNOME/evolution-data-server/issues/165
po/POTFILES.in | 2 +
src/calendar/backends/CMakeLists.txt | 1 +
src/calendar/backends/webdav-notes/CMakeLists.txt | 47 +
.../e-cal-backend-webdav-notes-factory.c | 71 +
.../webdav-notes/e-cal-backend-webdav-notes.c | 1432 ++++++++++++++++++++
.../webdav-notes/e-cal-backend-webdav-notes.h | 60 +
src/calendar/libecal/e-cal-util.h | 11 +
src/libebackend/e-webdav-collection-backend.c | 11 +-
src/libedataserver/e-webdav-discover.c | 56 +-
src/libedataserver/e-webdav-discover.h | 1 +
src/libedataserver/e-webdav-session.c | 47 +-
src/libedataserver/e-webdav-session.h | 6 +-
src/libedataserverui/e-webdav-discover-widget.c | 2 +-
.../evolution-source-registry/CMakeLists.txt | 1 +
.../builtin/webdav-notes-stub.source.in | 5 +
.../evolution-source-registry-resource.xml | 1 +
16 files changed, 1725 insertions(+), 29 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 98382fb63..d69bd9931 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -31,6 +31,7 @@ src/calendar/backends/contacts/e-cal-backend-contacts.c
src/calendar/backends/file/e-cal-backend-file.c
src/calendar/backends/http/e-cal-backend-http.c
src/calendar/backends/weather/e-cal-backend-weather.c
+src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.c
src/calendar/libecal/e-cal-client.c
src/calendar/libecal/e-cal-client-view.c
src/calendar/libecal/e-cal-component.c
@@ -241,6 +242,7 @@ src/services/evolution-calendar-factory/evolution-calendar-factory.c
[type: gettext/ini]src/services/evolution-source-registry/builtin/vfolder.source.in
[type: gettext/ini]src/services/evolution-source-registry/builtin/weather-stub.source.in
[type: gettext/ini]src/services/evolution-source-registry/builtin/webcal-stub.source.in
+[type: gettext/ini]src/services/evolution-source-registry/builtin/webdav-notes-stub.source.in
src/services/evolution-source-registry/evolution-source-registry.c
src/services/evolution-user-prompter/evolution-user-prompter.c
src/services/evolution-user-prompter/prompt-user-gtk.c
diff --git a/src/calendar/backends/CMakeLists.txt b/src/calendar/backends/CMakeLists.txt
index 56b8c3836..0de3a6d18 100644
--- a/src/calendar/backends/CMakeLists.txt
+++ b/src/calendar/backends/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory(caldav)
add_subdirectory(contacts)
add_subdirectory(file)
add_subdirectory(http)
+add_subdirectory(webdav-notes)
if(HAVE_LIBGDATA)
add_subdirectory(gtasks)
diff --git a/src/calendar/backends/webdav-notes/CMakeLists.txt
b/src/calendar/backends/webdav-notes/CMakeLists.txt
new file mode 100644
index 000000000..4a1ae855b
--- /dev/null
+++ b/src/calendar/backends/webdav-notes/CMakeLists.txt
@@ -0,0 +1,47 @@
+set(DEPENDENCIES
+ ebackend
+ ecal
+ edataserver
+ edata-cal
+)
+
+set(SOURCES
+ e-cal-backend-webdav-notes-factory.c
+ e-cal-backend-webdav-notes.c
+ e-cal-backend-webdav-notes.h
+)
+
+add_library(ecalbackendwebdavnotes MODULE
+ ${SOURCES}
+)
+
+add_dependencies(ecalbackendwebdavnotes
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(ecalbackendwebdavnotes PRIVATE
+ -DG_LOG_DOMAIN=\"e-cal-backend-webdav-notes\"
+ -DBACKENDDIR=\"${ecal_backenddir}\"
+)
+
+target_compile_options(ecalbackendwebdavnotes PUBLIC
+ ${CALENDAR_CFLAGS}
+)
+
+target_include_directories(ecalbackendwebdavnotes PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/calendar
+ ${CMAKE_SOURCE_DIR}/src/calendar
+ ${CALENDAR_INCLUDE_DIRS}
+)
+
+target_link_libraries(ecalbackendwebdavnotes
+ ${DEPENDENCIES}
+ ${CALENDAR_LDFLAGS}
+)
+
+install(TARGETS ecalbackendwebdavnotes
+ DESTINATION ${ecal_backenddir}
+)
diff --git a/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes-factory.c
b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes-factory.c
new file mode 100644
index 000000000..b38f0c85a
--- /dev/null
+++ b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes-factory.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "e-cal-backend-webdav-notes.h"
+
+typedef ECalBackendFactory ECalBackendWebDAVNotesFactory;
+typedef ECalBackendFactoryClass ECalBackendWebDAVNotesFactoryClass;
+
+static EModule *e_module;
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_cal_backend_webdav_notes_factory_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (ECalBackendWebDAVNotesFactory, e_cal_backend_webdav_notes_factory,
E_TYPE_CAL_BACKEND_FACTORY)
+
+static void
+e_cal_backend_webdav_notes_factory_class_init (ECalBackendFactoryClass *class)
+{
+ EBackendFactoryClass *backend_factory_class;
+
+ backend_factory_class = E_BACKEND_FACTORY_CLASS (class);
+ backend_factory_class->e_module = e_module;
+ backend_factory_class->share_subprocess = TRUE;
+
+ class->factory_name = "webdav-notes";
+ class->component_kind = I_CAL_VJOURNAL_COMPONENT;
+ class->backend_type = E_TYPE_CAL_BACKEND_WEBDAV_NOTES;
+}
+
+static void
+e_cal_backend_webdav_notes_factory_class_finalize (ECalBackendFactoryClass *class)
+{
+}
+
+static void
+e_cal_backend_webdav_notes_factory_init (ECalBackendFactory *factory)
+{
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ e_module = E_MODULE (type_module);
+
+ e_cal_backend_webdav_notes_factory_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+ e_module = NULL;
+}
diff --git a/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.c
b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.c
new file mode 100644
index 000000000..a4d2bd01d
--- /dev/null
+++ b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.c
@@ -0,0 +1,1432 @@
+/*
+ * Copyright (C) 2020 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-cal-backend-webdav-notes.h"
+
+#define E_WEBDAV_NOTES_X_ETAG "X-EVOLUTION-WEBDAV-NOTES-ETAG"
+
+#define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
+#define ECC_ERROR_EX(_code, _msg) e_cal_client_error_create (_code, _msg)
+
+struct _ECalBackendWebDAVNotesPrivate {
+ /* The main WebDAV session */
+ EWebDAVSession *webdav;
+ GMutex webdav_lock;
+
+ gboolean etag_supported;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ECalBackendWebDAVNotes, e_cal_backend_webdav_notes, E_TYPE_CAL_META_BACKEND)
+
+static EWebDAVSession *
+ecb_webdav_notes_ref_session (ECalBackendWebDAVNotes *cbnotes)
+{
+ EWebDAVSession *webdav;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (cbnotes), NULL);
+
+ g_mutex_lock (&cbnotes->priv->webdav_lock);
+ if (cbnotes->priv->webdav)
+ webdav = g_object_ref (cbnotes->priv->webdav);
+ else
+ webdav = NULL;
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+
+ return webdav;
+}
+
+static gboolean
+ecb_webdav_notes_connect_sync (ECalMetaBackend *meta_backend,
+ const ENamedParameters *credentials,
+ ESourceAuthenticationResult *out_auth_result,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ GHashTable *capabilities = NULL, *allows = NULL;
+ ESource *source;
+ gboolean success, is_writable = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (out_auth_result != NULL, FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+
+ g_mutex_lock (&cbnotes->priv->webdav_lock);
+ if (cbnotes->priv->webdav) {
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+ return TRUE;
+ }
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+
+ source = e_backend_get_source (E_BACKEND (meta_backend));
+
+ webdav = e_webdav_session_new (source);
+
+ e_soup_session_setup_logging (E_SOUP_SESSION (webdav), g_getenv ("WEBDAV_NOTES_DEBUG"));
+
+ e_binding_bind_property (
+ cbnotes, "proxy-resolver",
+ webdav, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
+
+ /* This is just to make it similar to the CalDAV backend. */
+ cbnotes->priv->etag_supported = TRUE;
+
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
+
+ e_soup_session_set_credentials (E_SOUP_SESSION (webdav), credentials);
+
+ success = e_webdav_session_options_sync (webdav, NULL,
+ &capabilities, &allows, cancellable, &local_error);
+
+ if (success && !g_cancellable_is_cancelled (cancellable)) {
+ GSList *privileges = NULL, *link;
+
+ /* Ignore any errors here */
+ if (e_webdav_session_get_current_user_privilege_set_sync (webdav, NULL, &privileges,
cancellable, NULL)) {
+ for (link = privileges; link && !is_writable; link = g_slist_next (link)) {
+ EWebDAVPrivilege *privilege = link->data;
+
+ if (privilege) {
+ is_writable =
+ privilege->hint == E_WEBDAV_PRIVILEGE_HINT_WRITE ||
+ privilege->hint == E_WEBDAV_PRIVILEGE_HINT_WRITE_CONTENT ||
+ privilege->hint == E_WEBDAV_PRIVILEGE_HINT_ALL;
+ }
+ }
+
+ g_slist_free_full (privileges, e_webdav_privilege_free);
+ } else {
+ is_writable = allows && (
+ g_hash_table_contains (allows, SOUP_METHOD_PUT) ||
+ g_hash_table_contains (allows, SOUP_METHOD_POST) ||
+ g_hash_table_contains (allows, SOUP_METHOD_DELETE));
+ }
+ }
+
+ if (success) {
+ e_cal_backend_set_writable (E_CAL_BACKEND (cbnotes), is_writable);
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
+ }
+
+ if (success) {
+ gchar *ctag = NULL;
+
+ /* Some servers, notably Google, allow OPTIONS when not
+ authorized (aka without credentials), thus try something
+ more aggressive, just in case.
+
+ The 'getctag' extension is not required, thuch check
+ for unauthorized error only. */
+ if (!e_webdav_session_getctag_sync (webdav, NULL, &ctag, cancellable, &local_error) &&
+ g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+ success = FALSE;
+ } else {
+ g_clear_error (&local_error);
+ }
+
+ g_free (ctag);
+ }
+
+ if (!success) {
+ gboolean credentials_empty;
+ gboolean is_ssl_error;
+
+ credentials_empty = (!credentials || !e_named_parameters_count (credentials)) &&
+ e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (webdav));
+ is_ssl_error = g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED);
+
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
+
+ /* because evolution knows only G_IO_ERROR_CANCELLED */
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
+ local_error->domain = G_IO_ERROR;
+ local_error->code = G_IO_ERROR_CANCELLED;
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN) &&
credentials_empty) {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+ if (credentials_empty)
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
+ else
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED;
+ } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) ||
+ (!e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (webdav))
&&
+ g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))) {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED;
+ } else if (!local_error) {
+ g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unknown error"));
+ }
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ }
+
+ if (is_ssl_error) {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
+
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_SSL_FAILED);
+ e_soup_session_get_ssl_error_details (E_SOUP_SESSION (webdav), out_certificate_pem,
out_certificate_errors);
+ } else {
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ }
+ }
+
+ if (capabilities)
+ g_hash_table_destroy (capabilities);
+ if (allows)
+ g_hash_table_destroy (allows);
+
+ if (success && !g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ g_mutex_lock (&cbnotes->priv->webdav_lock);
+ cbnotes->priv->webdav = webdav;
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+ } else {
+ if (success) {
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ success = FALSE;
+ }
+
+ g_clear_object (&webdav);
+ }
+
+ return success;
+}
+
+static gboolean
+ecb_webdav_notes_disconnect_sync (ECalMetaBackend *meta_backend,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ ESource *source;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+
+ g_mutex_lock (&cbnotes->priv->webdav_lock);
+ if (cbnotes->priv->webdav)
+ soup_session_abort (SOUP_SESSION (cbnotes->priv->webdav));
+
+ g_clear_object (&cbnotes->priv->webdav);
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+
+ source = e_backend_get_source (E_BACKEND (meta_backend));
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+
+ return TRUE;
+}
+
+typedef struct _WebDAVNotesChangesData {
+ GSList **out_modified_objects;
+ GSList **out_removed_objects;
+ GHashTable *known_items; /* gchar *href ~> ECalMetaBackendInfo * */
+} WebDAVNotesChangesData;
+
+static gboolean
+ecb_webdav_notes_search_changes_cb (ECalCache *cal_cache,
+ const gchar *uid,
+ const gchar *rid,
+ const gchar *revision,
+ const gchar *object,
+ const gchar *extra,
+ guint32 custom_flags,
+ EOfflineState offline_state,
+ gpointer user_data)
+{
+ WebDAVNotesChangesData *ccd = user_data;
+
+ g_return_val_if_fail (ccd != NULL, FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+
+ /* The 'extra' can be NULL for added components in offline mode */
+ if (((extra && *extra) || offline_state != E_OFFLINE_STATE_LOCALLY_CREATED) && (!rid || !*rid)) {
+ ECalMetaBackendInfo *nfo;
+
+ nfo = (extra && *extra) ? g_hash_table_lookup (ccd->known_items, extra) : NULL;
+ if (nfo) {
+ if (g_strcmp0 (revision, nfo->revision) == 0) {
+ g_hash_table_remove (ccd->known_items, extra);
+ } else {
+ if (!nfo->uid || !*(nfo->uid)) {
+ g_free (nfo->uid);
+ nfo->uid = g_strdup (uid);
+ }
+
+ *(ccd->out_modified_objects) = g_slist_prepend (*(ccd->out_modified_objects),
+ e_cal_meta_backend_info_copy (nfo));
+
+ g_hash_table_remove (ccd->known_items, extra);
+ }
+ } else {
+ *(ccd->out_removed_objects) = g_slist_prepend (*(ccd->out_removed_objects),
+ e_cal_meta_backend_info_new (uid, revision, object, extra));
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+ecb_webdav_notes_check_credentials_error (ECalBackendWebDAVNotes *cbnotes,
+ EWebDAVSession *webdav,
+ GError *op_error)
+{
+ g_return_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (cbnotes));
+
+ if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) && webdav) {
+ op_error->domain = E_CLIENT_ERROR;
+ op_error->code = E_CLIENT_ERROR_TLS_NOT_AVAILABLE;
+ } else if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+ g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
+ gboolean was_forbidden = g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN);
+
+ op_error->domain = E_CLIENT_ERROR;
+ op_error->code = E_CLIENT_ERROR_AUTHENTICATION_REQUIRED;
+
+ if (webdav) {
+ ENamedParameters *credentials;
+ gboolean empty_credentials;
+
+ credentials = e_soup_session_dup_credentials (E_SOUP_SESSION (webdav));
+ empty_credentials = !credentials || !e_named_parameters_count (credentials);
+ e_named_parameters_free (credentials);
+
+ if (!empty_credentials) {
+ if (was_forbidden) {
+ if (e_webdav_session_get_last_dav_error_is_permission (webdav)) {
+ op_error->code = E_CLIENT_ERROR_PERMISSION_DENIED;
+ g_free (op_error->message);
+ op_error->message = g_strdup (e_client_error_to_string
(op_error->code));
+ } else {
+ /* To avoid credentials prompt */
+ op_error->code = E_CLIENT_ERROR_OTHER_ERROR;
+ }
+ } else {
+ op_error->code = E_CLIENT_ERROR_AUTHENTICATION_FAILED;
+ }
+ }
+ }
+ }
+}
+
+static gboolean
+ecb_webdav_notes_getetag_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ const gchar *href,
+ guint status_code,
+ gpointer user_data)
+{
+ if (!xpath_prop_prefix)
+ return TRUE;
+
+ if (status_code == SOUP_STATUS_OK) {
+ gchar **out_etag = user_data;
+ gchar *etag;
+
+ g_return_val_if_fail (out_etag != NULL, FALSE);
+
+ etag = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:getetag", xpath_prop_prefix);
+
+ if (etag && *etag) {
+ *out_etag = e_webdav_session_util_maybe_dequote (etag);
+ } else {
+ g_free (etag);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ecb_webdav_notes_getetag_sync (EWebDAVSession *webdav,
+ const gchar *uri,
+ gchar **out_etag,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EXmlDocument *xml;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+ g_return_val_if_fail (out_etag != NULL, FALSE);
+
+ *out_etag = NULL;
+
+ xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
+ g_return_val_if_fail (xml != NULL, FALSE);
+
+ e_xml_document_start_element (xml, NULL, "prop");
+ e_xml_document_add_empty_element (xml, NULL, "getetag");
+ e_xml_document_end_element (xml); /* prop */
+
+ success = e_webdav_session_propfind_sync (webdav, uri, E_WEBDAV_DEPTH_THIS, xml,
+ ecb_webdav_notes_getetag_cb, out_etag, cancellable, error);
+
+ g_object_unref (xml);
+
+ return success && *out_etag != NULL;
+}
+
+static ICalComponent *
+ecb_webdav_notes_new_icomp (glong creation_date,
+ glong last_modified,
+ const gchar *uid,
+ const gchar *revision,
+ const gchar *summary,
+ const gchar *description)
+{
+ ICalComponent *icomp;
+ ICalTime *itt;
+ glong tt;
+
+ icomp = i_cal_component_new_vjournal ();
+
+ if (creation_date > 0)
+ tt = creation_date;
+ else if (last_modified > 0)
+ tt = last_modified;
+ else
+ tt = (time_t) time (NULL);
+
+ itt = i_cal_time_new_from_timet_with_zone ((time_t) tt, FALSE, i_cal_timezone_get_utc_timezone ());
+ i_cal_component_take_property (icomp, i_cal_property_new_created (itt));
+ g_object_unref (itt);
+
+ if (last_modified > 0)
+ tt = last_modified;
+ else
+ tt = (time_t) time (NULL);
+
+ itt = i_cal_time_new_from_timet_with_zone ((time_t) tt, FALSE, i_cal_timezone_get_utc_timezone ());
+ i_cal_component_take_property (icomp, i_cal_property_new_lastmodified (itt));
+ g_object_unref (itt);
+
+ i_cal_component_set_uid (icomp, uid);
+
+ if (summary && g_str_has_suffix (summary, ".txt")) {
+ gchar *tmp;
+
+ tmp = g_strndup (summary, strlen (summary) - 4);
+ i_cal_component_set_summary (icomp, tmp);
+ g_free (tmp);
+ } else if (summary && *summary) {
+ i_cal_component_set_summary (icomp, summary);
+ }
+
+ if (description)
+ i_cal_component_set_description (icomp, description);
+
+ e_cal_util_component_set_x_property (icomp, E_WEBDAV_NOTES_X_ETAG, revision);
+
+ return icomp;
+}
+
+static gboolean
+ecb_webdav_notes_get_objects_sync (EWebDAVSession *webdav,
+ GHashTable *resources_hash, /* gchar *href ~> EWebDAVResource * */
+ GSList *infos, /* ECalMetaBackendInfo * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *link;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+
+ for (link = infos; success && link; link = g_slist_next (link)) {
+ ECalMetaBackendInfo *nfo = link->data;
+ gchar *etag = NULL, *bytes = NULL;
+
+ if (!nfo)
+ continue;
+
+ success = e_webdav_session_get_data_sync (webdav, nfo->extra, NULL, &etag, &bytes, NULL,
cancellable, error);
+
+ if (success) {
+ EWebDAVResource *resource;
+
+ resource = g_hash_table_lookup (resources_hash, nfo->extra);
+
+ if (resource) {
+ ICalComponent *icomp;
+
+ if (g_strcmp0 (nfo->revision, etag) != 0) {
+ g_free (nfo->revision);
+ nfo->revision = etag;
+ etag = NULL;
+ }
+
+ icomp = ecb_webdav_notes_new_icomp (resource->creation_date,
+ resource->last_modified,
+ nfo->uid,
+ nfo->revision,
+ resource->display_name,
+ bytes);
+
+ g_warn_if_fail (nfo->object == NULL);
+
+ nfo->object = i_cal_component_as_ical_string (icomp);
+
+ g_object_unref (icomp);
+ } else { /* !resource */
+ g_warn_if_reached ();
+ }
+ }
+
+ g_free (etag);
+ g_free (bytes);
+ }
+
+ return success;
+}
+
+static gchar *
+ecb_webdav_notes_href_to_uid (const gchar *href)
+{
+ const gchar *filename;
+
+ if (!href || !*href)
+ return NULL;
+
+ filename = strrchr (href, '/');
+
+ if (filename && filename[1])
+ return g_uri_unescape_string (filename + 1, NULL);
+
+ return g_uri_unescape_string (href, NULL);
+}
+
+static gboolean
+ecb_webdav_notes_get_changes_sync (ECalMetaBackend *meta_backend,
+ const gchar *last_sync_tag,
+ gboolean is_repeat,
+ gchar **out_new_sync_tag,
+ gboolean *out_repeat,
+ GSList **out_created_objects,
+ GSList **out_modified_objects,
+ GSList **out_removed_objects,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ GHashTable *known_items; /* gchar *href ~> ECalMetaBackendInfo * */
+ GHashTable *resources_hash; /* gchar *href ~> EWebDAVResource *, both borrowed from 'resources'
GSList */
+ GHashTableIter iter;
+ GSList *resources = NULL, *link;
+ gpointer key = NULL, value = NULL;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (out_new_sync_tag, FALSE);
+ g_return_val_if_fail (out_created_objects, FALSE);
+ g_return_val_if_fail (out_modified_objects, FALSE);
+ g_return_val_if_fail (out_removed_objects, FALSE);
+
+ *out_new_sync_tag = NULL;
+ *out_created_objects = NULL;
+ *out_modified_objects = NULL;
+ *out_removed_objects = NULL;
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ if (cbnotes->priv->etag_supported) {
+ gchar *new_sync_tag = NULL;
+
+ success = ecb_webdav_notes_getetag_sync (webdav, NULL, &new_sync_tag, cancellable, NULL);
+ if (!success) {
+ cbnotes->priv->etag_supported = g_cancellable_set_error_if_cancelled (cancellable,
error);
+ if (cbnotes->priv->etag_supported || !webdav) {
+ g_clear_object (&webdav);
+ return FALSE;
+ }
+ } else if (new_sync_tag && last_sync_tag && g_strcmp0 (last_sync_tag, new_sync_tag) == 0) {
+ *out_new_sync_tag = new_sync_tag;
+ g_clear_object (&webdav);
+ return TRUE;
+ }
+
+ *out_new_sync_tag = new_sync_tag;
+ }
+
+ known_items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, e_cal_meta_backend_info_free);
+ resources_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ success = e_webdav_session_list_sync (webdav, NULL, E_WEBDAV_DEPTH_THIS_AND_CHILDREN,
+ E_WEBDAV_LIST_ETAG | E_WEBDAV_LIST_DISPLAY_NAME | E_WEBDAV_LIST_CREATION_DATE |
E_WEBDAV_LIST_LAST_MODIFIED,
+ &resources, cancellable, &local_error);
+
+ if (success) {
+ ECalCache *cal_cache;
+ WebDAVNotesChangesData ccd;
+
+ for (link = resources; link; link = g_slist_next (link)) {
+ EWebDAVResource *resource = link->data;
+
+ if (resource && resource->kind == E_WEBDAV_RESOURCE_KIND_RESOURCE && resource->href
&& g_str_has_suffix (resource->href, ".txt")) {
+ gchar *filename = ecb_webdav_notes_href_to_uid (resource->href);
+
+ g_hash_table_insert (known_items, g_strdup (resource->href),
+ e_cal_meta_backend_info_new (filename, resource->etag, NULL,
resource->href));
+
+ g_hash_table_insert (resources_hash, resource->href, resource);
+
+ g_free (filename);
+ }
+ }
+
+ ccd.out_modified_objects = out_modified_objects;
+ ccd.out_removed_objects = out_removed_objects;
+ ccd.known_items = known_items;
+
+ cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+
+ success = e_cal_cache_search_with_callback (cal_cache, NULL,
ecb_webdav_notes_search_changes_cb, &ccd, cancellable, &local_error);
+
+ g_clear_object (&cal_cache);
+ }
+
+ if (success) {
+ g_hash_table_iter_init (&iter, known_items);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ *out_created_objects = g_slist_prepend (*out_created_objects,
e_cal_meta_backend_info_copy (value));
+ }
+ }
+
+ g_hash_table_destroy (known_items);
+
+ if (success && (*out_created_objects || *out_modified_objects)) {
+ success = ecb_webdav_notes_get_objects_sync (webdav, resources_hash, *out_created_objects,
cancellable, &local_error);
+ success = success && ecb_webdav_notes_get_objects_sync (webdav, resources_hash,
*out_modified_objects, cancellable, &local_error);
+ }
+
+ if (local_error) {
+ ecb_webdav_notes_check_credentials_error (cbnotes, webdav, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ g_slist_free_full (resources, e_webdav_resource_free);
+ g_hash_table_destroy (resources_hash);
+ g_clear_object (&webdav);
+
+ return success;
+}
+
+static gboolean
+ecb_webdav_notes_list_existing_sync (ECalMetaBackend *meta_backend,
+ gchar **out_new_sync_tag,
+ GSList **out_existing_objects,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ GSList *resources = NULL;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (out_existing_objects != NULL, FALSE);
+
+ *out_existing_objects = NULL;
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ success = e_webdav_session_list_sync (webdav, NULL, E_WEBDAV_DEPTH_THIS_AND_CHILDREN,
E_WEBDAV_LIST_ETAG, &resources, cancellable, &local_error);
+
+ if (success) {
+ GSList *link;
+
+ for (link = resources; link; link = g_slist_next (link)) {
+ EWebDAVResource *resource = link->data;
+
+ if (resource && resource->kind == E_WEBDAV_RESOURCE_KIND_RESOURCE && resource->href
&& g_str_has_suffix (resource->href, ".txt")) {
+ gchar *filename = ecb_webdav_notes_href_to_uid (resource->href);
+
+ *out_existing_objects = g_slist_prepend (*out_existing_objects,
+ e_cal_meta_backend_info_new (filename, resource->etag, NULL,
resource->href));
+
+ g_free (filename);
+ }
+ }
+
+ *out_existing_objects = g_slist_reverse (*out_existing_objects);
+ }
+
+ if (local_error) {
+ ecb_webdav_notes_check_credentials_error (cbnotes, webdav, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ g_slist_free_full (resources, e_webdav_resource_free);
+ g_clear_object (&webdav);
+
+ return success;
+}
+
+static gchar *
+ecb_webdav_notes_uid_to_uri (ECalBackendWebDAVNotes *cbnotes,
+ const gchar *uid)
+{
+ ESourceWebdav *webdav_extension;
+ SoupURI *soup_uri;
+ gchar *uri, *tmp, *filename, *uid_hash = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (cbnotes), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ webdav_extension = e_source_get_extension (e_backend_get_source (E_BACKEND (cbnotes)),
E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+ g_return_val_if_fail (soup_uri != NULL, NULL);
+
+ /* UIDs with forward slashes can cause trouble, because the destination server can
+ consider them as a path delimiter. Double-encode the URL doesn't always work,
+ thus rather cause a mismatch between stored UID and its href on the server. */
+ if (strchr (uid, '/')) {
+ uid_hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, uid, -1);
+
+ if (uid_hash)
+ uid = uid_hash;
+ }
+
+ filename = soup_uri_encode (uid, NULL);
+
+ if (soup_uri->path) {
+ gchar *slash = strrchr (soup_uri->path, '/');
+
+ if (slash && !slash[1])
+ *slash = '\0';
+ }
+
+ soup_uri_set_user (soup_uri, NULL);
+ soup_uri_set_password (soup_uri, NULL);
+
+ tmp = g_strconcat (soup_uri->path && *soup_uri->path ? soup_uri->path : "", "/", filename, NULL);
+ soup_uri_set_path (soup_uri, tmp);
+ g_free (tmp);
+
+ uri = soup_uri_to_string (soup_uri, FALSE);
+
+ soup_uri_free (soup_uri);
+ g_free (filename);
+ g_free (uid_hash);
+
+ return uri;
+}
+
+static void
+ecb_webdav_notes_store_component_etag (ICalComponent *icomp,
+ const gchar *etag)
+{
+ ICalComponent *subcomp;
+
+ g_return_if_fail (icomp != NULL);
+ g_return_if_fail (etag != NULL);
+
+ e_cal_util_component_set_x_property (icomp, E_WEBDAV_NOTES_X_ETAG, etag);
+
+ for (subcomp = i_cal_component_get_first_component (icomp, I_CAL_ANY_COMPONENT);
+ subcomp;
+ g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp,
I_CAL_ANY_COMPONENT)) {
+ ICalComponentKind kind = i_cal_component_isa (subcomp);
+
+ if (kind == I_CAL_VJOURNAL_COMPONENT) {
+ e_cal_util_component_set_x_property (subcomp, E_WEBDAV_NOTES_X_ETAG, etag);
+ }
+ }
+}
+
+static gboolean
+ecb_webdav_notes_load_component_sync (ECalMetaBackend *meta_backend,
+ const gchar *uid,
+ const gchar *extra,
+ ICalComponent **out_component,
+ gchar **out_extra,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ EWebDAVResource *use_resource = NULL;
+ gchar *uri = NULL, *href = NULL, *etag = NULL, *bytes = NULL;
+ gsize length = -1;
+ gboolean success = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_component != NULL, FALSE);
+ g_return_val_if_fail (out_extra != NULL, FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+
+ /* When called immediately after save and the server didn't change the component,
+ then the 'extra' contains "href" + "\n" + "vCalendar", to avoid unneeded GET
+ from the server. */
+ if (extra && *extra) {
+ const gchar *newline;
+
+ newline = strchr (extra, '\n');
+ if (newline && newline[1] && newline != extra) {
+ ICalComponent *vcalendar;
+
+ vcalendar = i_cal_component_new_from_string (newline + 1);
+ if (vcalendar) {
+ *out_extra = g_strndup (extra, newline - extra);
+ *out_component = vcalendar;
+
+ return TRUE;
+ }
+ }
+ }
+
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ if (extra && *extra) {
+ uri = g_strdup (extra);
+
+ success = e_webdav_session_get_data_sync (webdav, uri, &href, &etag, &bytes, &length,
cancellable, &local_error);
+
+ if (!success) {
+ g_free (uri);
+ uri = NULL;
+ }
+ }
+
+ if (!success && cbnotes->priv->etag_supported) {
+ gchar *new_sync_tag = NULL;
+
+ if (ecb_webdav_notes_getetag_sync (webdav, NULL, &new_sync_tag, cancellable, NULL) &&
new_sync_tag) {
+ gchar *last_sync_tag;
+
+ last_sync_tag = e_cal_meta_backend_dup_sync_tag (meta_backend);
+
+ /* The calendar didn't change, thus the component cannot be there */
+ if (g_strcmp0 (last_sync_tag, new_sync_tag) == 0) {
+ g_clear_error (&local_error);
+ g_clear_object (&webdav);
+ g_free (last_sync_tag);
+ g_free (new_sync_tag);
+
+ g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
+
+ return FALSE;
+ }
+
+ g_free (last_sync_tag);
+ }
+
+ g_free (new_sync_tag);
+ }
+
+ if (!success) {
+ uri = ecb_webdav_notes_uid_to_uri (cbnotes, uid);
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ g_clear_error (&local_error);
+
+ success = e_webdav_session_get_data_sync (webdav, uri, &href, &etag, &bytes, &length,
cancellable, &local_error);
+ }
+
+ if (success) {
+ GSList *resources = NULL;
+
+ success = e_webdav_session_list_sync (webdav, href, E_WEBDAV_DEPTH_THIS,
+ E_WEBDAV_LIST_DISPLAY_NAME | E_WEBDAV_LIST_CREATION_DATE |
E_WEBDAV_LIST_LAST_MODIFIED,
+ &resources, cancellable, &local_error);
+
+ if (success) {
+ GSList *link;
+
+ for (link = resources; link; link = g_slist_next (link)) {
+ EWebDAVResource *resource = link->data;
+
+ if (resource && resource->kind == E_WEBDAV_RESOURCE_KIND_RESOURCE &&
g_strcmp0 (resource->href, href) == 0) {
+ use_resource = resource;
+ link->data = NULL;
+ break;
+ }
+ }
+
+ g_slist_free_full (resources, e_webdav_resource_free);
+
+ success = use_resource != NULL;
+
+ if (!success)
+ local_error = ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND);
+ }
+ }
+
+ if (success) {
+ *out_component = NULL;
+
+ if (href && etag && length != ((gsize) -1)) {
+ ICalComponent *icomp;
+ gchar *filename = ecb_webdav_notes_href_to_uid (use_resource->href);
+
+ icomp = ecb_webdav_notes_new_icomp (use_resource->creation_date,
+ use_resource->last_modified,
+ filename,
+ etag,
+ use_resource->display_name,
+ bytes);
+
+ g_free (filename);
+
+ if (icomp) {
+ ecb_webdav_notes_store_component_etag (icomp, etag);
+
+ *out_component = icomp;
+ }
+ }
+
+ if (!*out_component) {
+ success = FALSE;
+
+ if (!href)
+ g_propagate_error (&local_error, ECC_ERROR_EX
(E_CAL_CLIENT_ERROR_INVALID_OBJECT, _("Server didn’t return object’s href")));
+ else if (!etag)
+ g_propagate_error (&local_error, ECC_ERROR_EX
(E_CAL_CLIENT_ERROR_INVALID_OBJECT, _("Server didn’t return object’s ETag")));
+ else
+ g_propagate_error (&local_error, ECC_ERROR
(E_CAL_CLIENT_ERROR_INVALID_OBJECT));
+ } else if (out_extra) {
+ *out_extra = g_strdup (href);
+ }
+ }
+
+ e_webdav_resource_free (use_resource);
+ g_free (uri);
+ g_free (href);
+ g_free (etag);
+ g_free (bytes);
+
+ if (local_error) {
+ ecb_webdav_notes_check_credentials_error (cbnotes, webdav, local_error);
+
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ local_error->domain = E_CAL_CLIENT_ERROR;
+ local_error->code = E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND;
+ }
+
+ g_propagate_error (error, local_error);
+ }
+
+ g_clear_object (&webdav);
+
+ return success;
+}
+
+/* A rough code to mimic what Nextcloud does, it's not accurate, but it's close
+ enough to work similarly. It skips leading whitespaces and uses up to the first
+ 100 letters of the first non-empty line as the base file name. */
+static gchar *
+ecb_webdav_notes_construct_base_filename (const gchar *description)
+{
+ GString *base_filename;
+ gunichar uchr;
+ gboolean add_space = FALSE;
+
+ if (!description || !*description || !g_utf8_validate (description, -1, NULL))
+ return g_strdup (_("New note"));
+
+ base_filename = g_string_sized_new (102);
+
+ while (uchr = g_utf8_get_char (description), g_unichar_isspace (uchr))
+ description = g_utf8_next_char (description);
+
+ while (uchr = g_utf8_get_char (description), uchr && uchr != '\r' && uchr != '\n') {
+ if (g_unichar_isspace (uchr)) {
+ add_space = TRUE;
+ } else if ((uchr >> 8) != 0 || !strchr ("\"/\\?:*|", (uchr & 0xFF))) {
+ if (base_filename->len >= 98)
+ break;
+
+ if (add_space) {
+ g_string_append_c (base_filename, ' ');
+ add_space = FALSE;
+ }
+
+ g_string_append_unichar (base_filename, uchr);
+
+ if (base_filename->len >= 100)
+ break;
+ }
+
+ description = g_utf8_next_char (description);
+ }
+
+ if (!base_filename->len)
+ g_string_append (base_filename, _("New note"));
+
+ return g_string_free (base_filename, FALSE);
+}
+
+static gboolean
+ecb_webdav_notes_save_component_sync (ECalMetaBackend *meta_backend,
+ gboolean overwrite_existing,
+ EConflictResolution conflict_resolution,
+ const GSList *instances,
+ const gchar *extra,
+ guint32 opflags,
+ gchar **out_new_uid,
+ gchar **out_new_extra,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ ICalComponent *icomp;
+ gchar *href = NULL, *etag = NULL;
+ const gchar *description = NULL, *uid = NULL;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (instances != NULL, FALSE);
+ g_return_val_if_fail (instances->data != NULL, FALSE);
+ g_return_val_if_fail (instances->next == NULL, FALSE);
+ g_return_val_if_fail (out_new_uid, FALSE);
+ g_return_val_if_fail (out_new_extra, FALSE);
+
+ icomp = e_cal_component_get_icalcomponent (instances->data);
+ g_return_val_if_fail (i_cal_component_isa (icomp) == I_CAL_VJOURNAL_COMPONENT, FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+
+ description = i_cal_component_get_description (icomp);
+ etag = e_cal_util_component_dup_x_property (icomp, E_WEBDAV_NOTES_X_ETAG);
+ uid = i_cal_component_get_uid (icomp);
+
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ if (uid && (!overwrite_existing || (extra && *extra))) {
+ gchar *new_extra = NULL, *new_etag = NULL;
+ gchar *base_filename, *expected_filename;
+ gboolean force_write = FALSE, new_filename = FALSE;
+ guint counter = 1;
+
+ base_filename = ecb_webdav_notes_construct_base_filename (description);
+ expected_filename = g_strconcat (base_filename, ".txt", NULL);
+
+ if (overwrite_existing) {
+ switch (conflict_resolution) {
+ case E_CONFLICT_RESOLUTION_FAIL:
+ case E_CONFLICT_RESOLUTION_USE_NEWER:
+ case E_CONFLICT_RESOLUTION_KEEP_SERVER:
+ case E_CONFLICT_RESOLUTION_WRITE_COPY:
+ break;
+ case E_CONFLICT_RESOLUTION_KEEP_LOCAL:
+ force_write = TRUE;
+ break;
+ }
+ }
+
+ new_filename = g_strcmp0 (expected_filename, uid) != 0;
+
+ /* Checkw whether the saved file on the server is already with the "(nnn)" suffix */
+ if (new_filename && expected_filename && uid &&
+ g_str_has_suffix (uid, ").txt") &&
+ g_ascii_strncasecmp (uid, expected_filename, strlen (expected_filename) - 4 /* strlen
(".txt") */) == 0) {
+ gint ii = strlen (expected_filename) - 4;
+
+ if (uid[ii] == ' ' && uid[ii + 1] == '(') {
+ ii += 2;
+
+ while (uid[ii] >= '0' && uid[ii] <= '9')
+ ii++;
+
+ if (g_strcmp0 (uid + ii, ").txt") == 0)
+ new_filename = FALSE;
+ }
+ }
+
+ if (!extra || !*extra || new_filename) {
+ href = ecb_webdav_notes_uid_to_uri (cbnotes, expected_filename);
+ uid = expected_filename;
+ force_write = FALSE;
+ }
+
+ do {
+ if (!counter)
+ break;
+
+ g_clear_error (&local_error);
+
+ if (counter > 1) {
+ g_free (expected_filename);
+ expected_filename = g_strdup_printf ("%s (%u).txt", base_filename, counter);
+
+ g_free (href);
+ href = ecb_webdav_notes_uid_to_uri (cbnotes, expected_filename);
+
+ uid = expected_filename;
+ }
+
+ success = e_webdav_session_put_data_sync (webdav, (!new_filename && extra && *extra)
? extra : href,
+ force_write ? "" : (overwrite_existing && !new_filename) ? etag : NULL,
"text/plain; charset=\"utf-8\"",
+ description ? description : "", -1, &new_extra, &new_etag, cancellable,
&local_error);
+
+ counter++;
+ } while (!success && new_filename && !g_cancellable_is_cancelled (cancellable) &&
+ g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_PRECONDITION_FAILED));
+
+ if (success && new_filename && extra && *extra) {
+ /* The name on the server changed, remove the old file */
+ e_webdav_session_delete_sync (webdav, extra, E_WEBDAV_DEPTH_THIS, etag, cancellable,
NULL);
+ }
+
+ if (success) {
+ /* Only if both are returned and it's not a weak ETag */
+ if (new_extra && *new_extra && new_etag && *new_etag &&
+ g_ascii_strncasecmp (new_etag, "W/", 2) != 0) {
+ ICalComponent *icomp_copy;
+ gchar *tmp = NULL, *ical_string;
+ glong now = (glong) time (NULL);
+
+ if (g_str_has_suffix (uid, ".txt"))
+ tmp = g_strndup (uid, strlen (uid) - 4);
+
+ icomp_copy = ecb_webdav_notes_new_icomp (now, now, uid, new_etag,
+ tmp ? tmp : uid, description);
+
+ g_free (tmp);
+
+ ical_string = i_cal_component_as_ical_string (icomp_copy);
+
+ /* Encodes the href and the component into one string, which
+ will be decoded in the load function */
+ tmp = g_strconcat (new_extra, "\n", ical_string, NULL);
+ g_free (new_extra);
+ new_extra = tmp;
+
+ g_object_unref (icomp_copy);
+ g_free (ical_string);
+ }
+
+ /* To read the component back, either from the new_extra
+ or from the server, because the server could change it */
+ *out_new_uid = g_strdup (uid);
+
+ if (out_new_extra)
+ *out_new_extra = new_extra;
+ else
+ g_free (new_extra);
+ }
+
+ g_free (base_filename);
+ g_free (expected_filename);
+ g_free (new_etag);
+ } else if (uid) {
+ success = FALSE;
+ g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT, _("Missing
information about component URL, local cache is possibly incomplete or broken. Remove it, please.")));
+ } else {
+ success = FALSE;
+ g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
+ }
+
+ g_free (href);
+ g_free (etag);
+
+ if (local_error) {
+ ecb_webdav_notes_check_credentials_error (cbnotes, webdav, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ g_clear_object (&webdav);
+
+ return success;
+}
+
+static gboolean
+ecb_webdav_notes_remove_component_sync (ECalMetaBackend *meta_backend,
+ EConflictResolution conflict_resolution,
+ const gchar *uid,
+ const gchar *extra,
+ const gchar *object,
+ guint32 opflags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ ICalComponent *icomp;
+ gchar *etag = NULL;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+
+ if (!extra || !*extra) {
+ g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
+ return FALSE;
+ }
+
+ icomp = i_cal_component_new_from_string (object);
+ if (!icomp) {
+ g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
+ return FALSE;
+ }
+
+ if (conflict_resolution == E_CONFLICT_RESOLUTION_FAIL)
+ etag = e_cal_util_component_dup_x_property (icomp, E_WEBDAV_NOTES_X_ETAG);
+
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ success = e_webdav_session_delete_sync (webdav, extra,
+ NULL, etag, cancellable, &local_error);
+
+ g_object_unref (icomp);
+ g_free (etag);
+
+ /* Ignore not found errors, this was a delete and the resource is gone.
+ It can be that it had been deleted on the server by other application. */
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ g_clear_error (&local_error);
+ success = TRUE;
+ }
+
+ if (local_error) {
+ ecb_webdav_notes_check_credentials_error (cbnotes, webdav, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ g_clear_object (&webdav);
+
+ return success;
+}
+
+static gboolean
+ecb_webdav_notes_get_ssl_error_details (ECalMetaBackend *meta_backend,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors)
+{
+ ECalBackendWebDAVNotes *cbnotes;
+ EWebDAVSession *webdav;
+ gboolean res;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (meta_backend), FALSE);
+
+ cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (meta_backend);
+ webdav = ecb_webdav_notes_ref_session (cbnotes);
+
+ if (!webdav)
+ return FALSE;
+
+ res = e_soup_session_get_ssl_error_details (E_SOUP_SESSION (webdav), out_certificate_pem,
out_certificate_errors);
+
+ g_clear_object (&webdav);
+
+ return res;
+}
+
+static gchar *
+ecb_webdav_notes_get_usermail (ECalBackendWebDAVNotes *cbnotes)
+{
+ ESource *source;
+ ESourceAuthentication *auth_extension;
+ ESourceWebdav *webdav_extension;
+ const gchar *extension_name;
+ gchar *usermail;
+ gchar *username;
+ gchar *res = NULL;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (cbnotes), NULL);
+
+ source = e_backend_get_source (E_BACKEND (cbnotes));
+
+ extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
+ webdav_extension = e_source_get_extension (source, extension_name);
+
+ /* This will never return an empty string. */
+ usermail = e_source_webdav_dup_email_address (webdav_extension);
+
+ if (usermail)
+ return usermail;
+
+ extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+ auth_extension = e_source_get_extension (source, extension_name);
+ username = e_source_authentication_dup_user (auth_extension);
+
+ if (username && strchr (username, '@') && strrchr (username, '.') > strchr (username, '@')) {
+ res = username;
+ username = NULL;
+ }
+
+ g_free (username);
+
+ return res;
+}
+
+static gchar *
+ecb_webdav_notes_get_backend_property (ECalBackend *backend,
+ const gchar *prop_name)
+{
+ g_return_val_if_fail (prop_name != NULL, NULL);
+
+ if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+ return g_strjoin (",",
+ e_cal_meta_backend_get_capabilities (E_CAL_META_BACKEND (backend)),
+ E_CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED,
+ E_CAL_STATIC_CAPABILITY_SIMPLE_MEMO,
+ NULL);
+ } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
+ g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
+ return ecb_webdav_notes_get_usermail (E_CAL_BACKEND_WEBDAV_NOTES (backend));
+ }
+
+ /* Chain up to parent's method. */
+ return E_CAL_BACKEND_CLASS (e_cal_backend_webdav_notes_parent_class)->impl_get_backend_property
(backend, prop_name);
+}
+
+static void
+ecb_webdav_notes_notify_property_changed_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ ECalBackendWebDAVNotes *cbnotes = user_data;
+ ECalBackend *cal_backend;
+ gboolean email_address_changed;
+ gchar *value;
+
+ g_return_if_fail (E_IS_CAL_BACKEND_WEBDAV_NOTES (cbnotes));
+
+ cal_backend = E_CAL_BACKEND (cbnotes);
+
+ email_address_changed = param && g_strcmp0 (param->name, "email-address") == 0;
+
+ if (email_address_changed) {
+ value = ecb_webdav_notes_get_backend_property (cal_backend,
E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS);
+ e_cal_backend_notify_property_changed (cal_backend, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS,
value);
+ e_cal_backend_notify_property_changed (cal_backend,
E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, value);
+ g_free (value);
+ }
+}
+
+static gchar *
+ecb_webdav_notes_dup_component_revision_cb (ECalCache *cal_cache,
+ ICalComponent *icomp)
+{
+ g_return_val_if_fail (icomp != NULL, NULL);
+
+ return e_cal_util_component_dup_x_property (icomp, E_WEBDAV_NOTES_X_ETAG);
+}
+
+static void
+e_cal_backend_webdav_notes_constructed (GObject *object)
+{
+ ECalBackendWebDAVNotes *cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (object);
+ ECalCache *cal_cache;
+ ESource *source;
+ ESourceWebdav *webdav_extension;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_webdav_notes_parent_class)->constructed (object);
+
+ cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cbnotes));
+
+ g_signal_connect (cal_cache, "dup-component-revision",
+ G_CALLBACK (ecb_webdav_notes_dup_component_revision_cb), NULL);
+
+ g_clear_object (&cal_cache);
+
+ source = e_backend_get_source (E_BACKEND (cbnotes));
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+
+ g_signal_connect_object (webdav_extension, "notify::email-address",
+ G_CALLBACK (ecb_webdav_notes_notify_property_changed_cb), cbnotes, 0);
+}
+
+static void
+e_cal_backend_webdav_notes_dispose (GObject *object)
+{
+ ECalBackendWebDAVNotes *cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (object);
+
+ g_mutex_lock (&cbnotes->priv->webdav_lock);
+ g_clear_object (&cbnotes->priv->webdav);
+ g_mutex_unlock (&cbnotes->priv->webdav_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_webdav_notes_parent_class)->dispose (object);
+}
+
+static void
+e_cal_backend_webdav_notes_finalize (GObject *object)
+{
+ ECalBackendWebDAVNotes *cbnotes = E_CAL_BACKEND_WEBDAV_NOTES (object);
+
+ g_mutex_clear (&cbnotes->priv->webdav_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_webdav_notes_parent_class)->finalize (object);
+}
+
+static void
+e_cal_backend_webdav_notes_init (ECalBackendWebDAVNotes *cbnotes)
+{
+ cbnotes->priv = e_cal_backend_webdav_notes_get_instance_private (cbnotes);
+
+ g_mutex_init (&cbnotes->priv->webdav_lock);
+}
+
+static void
+e_cal_backend_webdav_notes_class_init (ECalBackendWebDAVNotesClass *klass)
+{
+ GObjectClass *object_class;
+ ECalBackendClass *cal_backend_class;
+ ECalMetaBackendClass *cal_meta_backend_class;
+
+ cal_meta_backend_class = E_CAL_META_BACKEND_CLASS (klass);
+ cal_meta_backend_class->connect_sync = ecb_webdav_notes_connect_sync;
+ cal_meta_backend_class->disconnect_sync = ecb_webdav_notes_disconnect_sync;
+ cal_meta_backend_class->get_changes_sync = ecb_webdav_notes_get_changes_sync;
+ cal_meta_backend_class->list_existing_sync = ecb_webdav_notes_list_existing_sync;
+ cal_meta_backend_class->load_component_sync = ecb_webdav_notes_load_component_sync;
+ cal_meta_backend_class->save_component_sync = ecb_webdav_notes_save_component_sync;
+ cal_meta_backend_class->remove_component_sync = ecb_webdav_notes_remove_component_sync;
+ cal_meta_backend_class->get_ssl_error_details = ecb_webdav_notes_get_ssl_error_details;
+
+ cal_backend_class = E_CAL_BACKEND_CLASS (klass);
+ cal_backend_class->impl_get_backend_property = ecb_webdav_notes_get_backend_property;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = e_cal_backend_webdav_notes_constructed;
+ object_class->dispose = e_cal_backend_webdav_notes_dispose;
+ object_class->finalize = e_cal_backend_webdav_notes_finalize;
+}
diff --git a/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.h
b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.h
new file mode 100644
index 000000000..814acb281
--- /dev/null
+++ b/src/calendar/backends/webdav-notes/e-cal-backend-webdav-notes.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 Red Hat (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef E_CAL_BACKEND_WEBDAV_NOTES_H
+#define E_CAL_BACKEND_WEBDAV_NOTES_H
+
+#include <libedata-cal/libedata-cal.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_BACKEND_WEBDAV_NOTES \
+ (e_cal_backend_webdav_notes_get_type ())
+#define E_CAL_BACKEND_WEBDAV_NOTES(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_BACKEND_WEBDAV_NOTES, ECalBackendWebDAVNotes))
+#define E_CAL_BACKEND_WEBDAV_NOTES_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_BACKEND_WEBDAV_NOTES, ECalBackendWebDAVNotesClass))
+#define E_IS_CAL_BACKEND_WEBDAV_NOTES(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_BACKEND_WEBDAV_NOTES))
+#define E_IS_CAL_BACKEND_WEBDAV_NOTES_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_BACKEND_WEBDAV_NOTES))
+#define E_CAL_BACKEND_WEBDAV_NOTES_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_BACKEND_WEBDAV_NOTES, ECalBackendWebDAVNotesClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalBackendWebDAVNotes ECalBackendWebDAVNotes;
+typedef struct _ECalBackendWebDAVNotesClass ECalBackendWebDAVNotesClass;
+typedef struct _ECalBackendWebDAVNotesPrivate ECalBackendWebDAVNotesPrivate;
+
+struct _ECalBackendWebDAVNotes {
+ ECalMetaBackend parent;
+ ECalBackendWebDAVNotesPrivate *priv;
+};
+
+struct _ECalBackendWebDAVNotesClass {
+ ECalMetaBackendClass parent_class;
+};
+
+GType e_cal_backend_webdav_notes_get_type (void);
+
+G_END_DECLS
+
+#endif /* E_CAL_BACKEND_WEBDAV_NOTES_H */
diff --git a/src/calendar/libecal/e-cal-util.h b/src/calendar/libecal/e-cal-util.h
index e9f66f1d2..6fcb2172c 100644
--- a/src/calendar/libecal/e-cal-util.h
+++ b/src/calendar/libecal/e-cal-util.h
@@ -199,6 +199,17 @@ G_BEGIN_DECLS
**/
#define E_CAL_STATIC_CAPABILITY_TASK_HANDLE_RECUR "task-handle-recur"
+/**
+ * E_CAL_STATIC_CAPABILITY_SIMPLE_MEMO:
+ *
+ * When the capability is set, the backend handles only simple memos,
+ * which means it stores only memo description. The summary can be changed
+ * by the backend, if needed.
+ *
+ * Since: 3.38
+ **/
+#define E_CAL_STATIC_CAPABILITY_SIMPLE_MEMO "simple-memo"
+
struct _ECalClient;
ICalComponent * e_cal_util_new_top_level (void);
diff --git a/src/libebackend/e-webdav-collection-backend.c b/src/libebackend/e-webdav-collection-backend.c
index 19bfc03e1..9fad3b253 100644
--- a/src/libebackend/e-webdav-collection-backend.c
+++ b/src/libebackend/e-webdav-collection-backend.c
@@ -129,6 +129,11 @@ webdav_collection_add_found_source (ECollectionBackend *collection,
provider = "caldav";
identity_prefix = "tasks";
break;
+ case E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES:
+ backend_name = E_SOURCE_EXTENSION_MEMO_LIST;
+ provider = "webdav-notes";
+ identity_prefix = "notes";
+ break;
default:
g_warn_if_reached ();
return;
@@ -547,13 +552,15 @@ e_webdav_collection_backend_discover_sync (EWebDAVCollectionBackend *webdav_back
if (e_source_collection_get_calendar_enabled (collection_extension) && calendar_url &&
!g_cancellable_is_cancelled (cancellable) &&
e_webdav_discover_sources_full_sync (source, calendar_url,
- E_WEBDAV_DISCOVER_SUPPORTS_EVENTS | E_WEBDAV_DISCOVER_SUPPORTS_MEMOS |
E_WEBDAV_DISCOVER_SUPPORTS_TASKS | E_WEBDAV_DISCOVER_SUPPORTS_CALENDAR_AUTO_SCHEDULE,
+ E_WEBDAV_DISCOVER_SUPPORTS_EVENTS | E_WEBDAV_DISCOVER_SUPPORTS_MEMOS |
E_WEBDAV_DISCOVER_SUPPORTS_TASKS |
+ E_WEBDAV_DISCOVER_SUPPORTS_CALENDAR_AUTO_SCHEDULE | E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES,
credentials, (EWebDAVDiscoverRefSourceFunc) e_source_registry_server_ref_source, server,
out_certificate_pem, out_certificate_errors, &discovered_sources, NULL, cancellable,
&local_error)) {
EWebDAVDiscoverSupports source_types[] = {
E_WEBDAV_DISCOVER_SUPPORTS_EVENTS,
E_WEBDAV_DISCOVER_SUPPORTS_MEMOS,
- E_WEBDAV_DISCOVER_SUPPORTS_TASKS
+ E_WEBDAV_DISCOVER_SUPPORTS_TASKS,
+ E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES
};
webdav_collection_process_discovered_sources (collection, discovered_sources, known_sources,
source_types, G_N_ELEMENTS (source_types));
diff --git a/src/libedataserver/e-webdav-discover.c b/src/libedataserver/e-webdav-discover.c
index e6d1faaa9..204923f3a 100644
--- a/src/libedataserver/e-webdav-discover.c
+++ b/src/libedataserver/e-webdav-discover.c
@@ -71,7 +71,8 @@ e_webdav_discover_split_resources (WebDAVDiscoverData *wdd,
if (resource && (
resource->kind == E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK ||
resource->kind == E_WEBDAV_RESOURCE_KIND_CALENDAR ||
- resource->kind == E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR)) {
+ resource->kind == E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR ||
+ resource->kind == E_WEBDAV_RESOURCE_KIND_WEBDAV_NOTES)) {
EWebDAVDiscoveredSource *discovered;
if ((wdd->only_supports & (~CUSTOM_SUPPORTS_FLAGS)) !=
E_WEBDAV_DISCOVER_SUPPORTS_NONE &&
@@ -297,6 +298,27 @@ e_webdav_discover_traverse_propfind_response_cb (EWebDAVSession *webdav,
else
g_clear_error (&local_error);
}
+
+ if (((wdd->only_supports & (~CUSTOM_SUPPORTS_FLAGS)) == E_WEBDAV_DISCOVER_SUPPORTS_NONE ||
+ (wdd->only_supports & E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES) != 0) &&
+ (g_str_has_suffix (href, "/Notes") ||
+ g_str_has_suffix (href, "/Notes/")) &&
+ !e_webdav_discovery_already_discovered (href, wdd->calendars) &&
+ e_xml_xpath_eval_exists (xpath_ctx, "%s/D:resourcetype/D:collection", xpath_prop_prefix))
{
+ GSList *resources = NULL;
+
+ resources = g_slist_prepend (NULL,
+ e_webdav_resource_new (E_WEBDAV_RESOURCE_KIND_WEBDAV_NOTES,
+ E_WEBDAV_RESOURCE_SUPPORTS_WEBDAV_NOTES, href, NULL,
+ _("Notes"),
+ NULL, 0, 0, 0, NULL, NULL));
+
+ e_webdav_discover_split_resources (wdd, resources);
+
+ g_slist_free_full (resources, e_webdav_resource_free);
+
+ g_hash_table_insert (wdd->covered_hrefs, g_strdup (href), GINT_TO_POINTER (2));
+ }
}
return TRUE;
@@ -919,6 +941,38 @@ e_webdav_discover_sources_full_sync (ESource *source,
wdd.error = NULL;
}
+ if (!g_cancellable_is_cancelled (cancellable) &&
+ ((only_supports & (~CUSTOM_SUPPORTS_FLAGS)) == E_WEBDAV_DISCOVER_SUPPORTS_NONE ||
+ (only_supports & (E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES)) != 0) &&
+ (!soup_uri_get_path (soup_uri) || !strstr (soup_uri_get_path (soup_uri),
"/.well-known/"))) {
+ gchar *saved_path;
+ GError *local_error_2nd = NULL;
+
+ saved_path = g_strdup (soup_uri_get_path (soup_uri));
+
+ soup_uri_set_path (soup_uri, "/.well-known/webdav/Notes/");
+
+ uri = soup_uri_to_string (soup_uri, FALSE);
+
+ wdd.error = &local_error_2nd;
+ wdd.only_supports = E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES;
+ g_hash_table_remove_all (wdd.covered_hrefs);
+
+ success = (uri && *uri && e_webdav_discover_propfind_uri_sync (webdav, &wdd, uri,
FALSE)) || success;
+
+ g_free (uri);
+
+ soup_uri_set_path (soup_uri, saved_path);
+ g_free (saved_path);
+
+ if (e_webdav_discover_maybe_replace_auth_error (&local_error, &local_error_2nd))
+ success = FALSE;
+
+ g_clear_error (&local_error_2nd);
+
+ wdd.error = NULL;
+ }
+
if (!g_cancellable_is_cancelled (cancellable) && !wdd.addressbooks &&
((only_supports & (~CUSTOM_SUPPORTS_FLAGS)) == E_WEBDAV_DISCOVER_SUPPORTS_NONE ||
(only_supports & (E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS)) != 0) &&
diff --git a/src/libedataserver/e-webdav-discover.h b/src/libedataserver/e-webdav-discover.h
index 623df891b..a7d4cb22b 100644
--- a/src/libedataserver/e-webdav-discover.h
+++ b/src/libedataserver/e-webdav-discover.h
@@ -35,6 +35,7 @@ typedef enum {
E_WEBDAV_DISCOVER_SUPPORTS_EVENTS = E_WEBDAV_RESOURCE_SUPPORTS_EVENTS,
E_WEBDAV_DISCOVER_SUPPORTS_MEMOS = E_WEBDAV_RESOURCE_SUPPORTS_MEMOS,
E_WEBDAV_DISCOVER_SUPPORTS_TASKS = E_WEBDAV_RESOURCE_SUPPORTS_TASKS,
+ E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES = E_WEBDAV_RESOURCE_SUPPORTS_WEBDAV_NOTES,
E_WEBDAV_DISCOVER_SUPPORTS_CALENDAR_AUTO_SCHEDULE = E_WEBDAV_RESOURCE_SUPPORTS_LAST << 1,
E_WEBDAV_DISCOVER_SUPPORTS_SUBSCRIBED_ICALENDAR = E_WEBDAV_DISCOVER_SUPPORTS_CALENDAR_AUTO_SCHEDULE
<< 1
} EWebDAVDiscoverSupports;
diff --git a/src/libedataserver/e-webdav-session.c b/src/libedataserver/e-webdav-session.c
index 1117e451c..d71f6ccc8 100644
--- a/src/libedataserver/e-webdav-session.c
+++ b/src/libedataserver/e-webdav-session.c
@@ -860,7 +860,8 @@ e_webdav_session_replace_with_detailed_error_internal (EWebDAVSession *webdav,
gboolean ignore_multistatus,
const gchar *prefix,
GError **inout_error,
- gboolean can_change_last_dav_error_code)
+ gboolean can_change_last_dav_error_code,
+ gboolean skip_text_on_success)
{
SoupMessage *message;
GByteArray byte_array = { 0 };
@@ -912,7 +913,7 @@ e_webdav_session_replace_with_detailed_error_internal (EWebDAVSession *webdav,
}
content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
- if (content_type && (
+ if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL
(status_code))) && (
(g_ascii_strcasecmp (content_type, "application/xml") == 0 ||
g_ascii_strcasecmp (content_type, "text/xml") == 0))) {
xmlDocPtr doc;
@@ -969,10 +970,10 @@ e_webdav_session_replace_with_detailed_error_internal (EWebDAVSession *webdav,
xmlXPathFreeContext (xpath_ctx);
xmlFreeDoc (doc);
}
- } else if (content_type &&
+ } else if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL
(status_code))) &&
g_ascii_strcasecmp (content_type, "text/plain") == 0) {
detail_text = g_strndup ((const gchar *) byte_array.data, byte_array.len);
- } else if (content_type &&
+ } else if (content_type && (!skip_text_on_success || (status_code && !SOUP_STATUS_IS_SUCCESSFUL
(status_code))) &&
g_ascii_strcasecmp (content_type, "text/html") == 0) {
SoupURI *soup_uri;
gchar *uri = NULL;
@@ -1088,7 +1089,7 @@ e_webdav_session_replace_with_detailed_error (EWebDAVSession *webdav,
const gchar *prefix,
GError **inout_error)
{
- return e_webdav_session_replace_with_detailed_error_internal (webdav, request, response_data,
ignore_multistatus, prefix, inout_error, FALSE);
+ return e_webdav_session_replace_with_detailed_error_internal (webdav, request, response_data,
ignore_multistatus, prefix, inout_error, FALSE, FALSE);
}
/**
@@ -1335,7 +1336,7 @@ e_webdav_session_post_with_content_type_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to post data"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to post data"), error, TRUE, FALSE) &&
bytes != NULL;
if (success) {
@@ -1483,7 +1484,7 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to get properties"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to get properties"), error, TRUE, FALSE) &&
bytes != NULL;
if (success)
@@ -1559,7 +1560,7 @@ e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to update properties"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to update properties"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -1665,7 +1666,7 @@ e_webdav_session_report_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to issue REPORT"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to issue REPORT"), error, TRUE, FALSE) &&
bytes != NULL;
if (success && func && message->status_code == SOUP_STATUS_MULTI_STATUS)
@@ -1726,7 +1727,7 @@ e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create collection"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create collection"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -1831,7 +1832,7 @@ e_webdav_session_mkcol_addressbook_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create address book"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create address book"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -1993,7 +1994,7 @@ e_webdav_session_mkcalendar_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create calendar"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to create calendar"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -2127,7 +2128,7 @@ e_webdav_session_get_sync (EWebDAVSession *webdav,
tmp_bytes.data = buffer;
tmp_bytes.len = nread;
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav,
request, &tmp_bytes, FALSE, _("Failed to read resource"), error, TRUE);
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav,
request, &tmp_bytes, FALSE, _("Failed to read resource"), error, TRUE, TRUE);
if (!success)
break;
}
@@ -2138,7 +2139,7 @@ e_webdav_session_get_sync (EWebDAVSession *webdav,
}
if (success && first_chunk) {
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request,
NULL, FALSE, _("Failed to read resource"), error, TRUE);
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request,
NULL, FALSE, _("Failed to read resource"), error, TRUE, TRUE);
} else if (success && !first_chunk && log_level == SOUP_LOGGER_LOG_BODY) {
fprintf (stdout, "\n");
fflush (stdout);
@@ -2452,7 +2453,7 @@ e_webdav_session_put_sync (EWebDAVSession *webdav,
g_signal_handler_disconnect (message, wrote_headers_id);
g_signal_handler_disconnect (message, wrote_chunk_id);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to put data"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to put data"), error, TRUE, FALSE) &&
bytes != NULL;
if (cwd.wrote_any && cwd.log_level == SOUP_LOGGER_LOG_BODY) {
@@ -2598,7 +2599,7 @@ e_webdav_session_put_data_sync (EWebDAVSession *webdav,
ret_bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, ret_bytes, FALSE,
_("Failed to put data"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, ret_bytes, FALSE,
_("Failed to put data"), error, TRUE, FALSE) &&
ret_bytes != NULL;
if (success) {
@@ -2700,7 +2701,7 @@ e_webdav_session_delete_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to delete resource"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to delete resource"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -2769,7 +2770,7 @@ e_webdav_session_copy_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to copy resource"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to copy resource"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -2833,7 +2834,7 @@ e_webdav_session_move_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to move resource"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to move resource"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -2940,7 +2941,7 @@ e_webdav_session_lock_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to lock resource"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to lock resource"), error, TRUE, FALSE) &&
bytes != NULL;
if (success && out_xml_response) {
@@ -3050,7 +3051,7 @@ e_webdav_session_refresh_lock_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to refresh lock"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to refresh lock"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -3111,7 +3112,7 @@ e_webdav_session_unlock_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to unlock"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, FALSE,
_("Failed to unlock"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
@@ -4227,7 +4228,7 @@ e_webdav_session_acl_sync (EWebDAVSession *webdav,
bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable,
error);
- success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to get access control list"), error, TRUE) &&
+ success = !e_webdav_session_replace_with_detailed_error_internal (webdav, request, bytes, TRUE,
_("Failed to get access control list"), error, TRUE, FALSE) &&
bytes != NULL;
if (bytes)
diff --git a/src/libedataserver/e-webdav-session.h b/src/libedataserver/e-webdav-session.h
index 0345e437e..418d30c0c 100644
--- a/src/libedataserver/e-webdav-session.h
+++ b/src/libedataserver/e-webdav-session.h
@@ -88,7 +88,8 @@ typedef enum {
E_WEBDAV_RESOURCE_KIND_PRINCIPAL,
E_WEBDAV_RESOURCE_KIND_COLLECTION,
E_WEBDAV_RESOURCE_KIND_RESOURCE,
- E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR
+ E_WEBDAV_RESOURCE_KIND_SUBSCRIBED_ICALENDAR,
+ E_WEBDAV_RESOURCE_KIND_WEBDAV_NOTES
} EWebDAVResourceKind;
typedef enum {
@@ -99,7 +100,8 @@ typedef enum {
E_WEBDAV_RESOURCE_SUPPORTS_TASKS = 1 << 3,
E_WEBDAV_RESOURCE_SUPPORTS_FREEBUSY = 1 << 4,
E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE = 1 << 5,
- E_WEBDAV_RESOURCE_SUPPORTS_LAST = E_WEBDAV_RESOURCE_SUPPORTS_TIMEZONE
+ E_WEBDAV_RESOURCE_SUPPORTS_WEBDAV_NOTES = 1 << 6,
+ E_WEBDAV_RESOURCE_SUPPORTS_LAST = E_WEBDAV_RESOURCE_SUPPORTS_WEBDAV_NOTES
} EWebDAVResourceSupports;
typedef struct _EWebDAVResource {
diff --git a/src/libedataserverui/e-webdav-discover-widget.c b/src/libedataserverui/e-webdav-discover-widget.c
index 916cd6d68..fbe2f6260 100644
--- a/src/libedataserverui/e-webdav-discover-widget.c
+++ b/src/libedataserverui/e-webdav-discover-widget.c
@@ -523,7 +523,7 @@ e_webdav_discover_content_fill_discovered_sources (GtkTreeView *tree_view,
addbit (E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS, C_("WebDAVDiscover", "Contacts"));
addbit (E_WEBDAV_DISCOVER_SUPPORTS_EVENTS, C_("WebDAVDiscover", "Events"));
- addbit (E_WEBDAV_DISCOVER_SUPPORTS_MEMOS, C_("WebDAVDiscover", "Memos"));
+ addbit (E_WEBDAV_DISCOVER_SUPPORTS_MEMOS | E_WEBDAV_DISCOVER_SUPPORTS_WEBDAV_NOTES,
C_("WebDAVDiscover", "Memos"));
addbit (E_WEBDAV_DISCOVER_SUPPORTS_TASKS, C_("WebDAVDiscover", "Tasks"));
#undef addbit
diff --git a/src/services/evolution-source-registry/CMakeLists.txt
b/src/services/evolution-source-registry/CMakeLists.txt
index a6462a4d0..1dfd24c50 100644
--- a/src/services/evolution-source-registry/CMakeLists.txt
+++ b/src/services/evolution-source-registry/CMakeLists.txt
@@ -30,6 +30,7 @@ set(builtin_sources_files
local-stub.source
weather-stub.source
webcal-stub.source
+ webdav-notes-stub.source
birthdays.source
local.source
sendmail.source
diff --git a/src/services/evolution-source-registry/builtin/webdav-notes-stub.source.in
b/src/services/evolution-source-registry/builtin/webdav-notes-stub.source.in
new file mode 100644
index 000000000..01a3b0135
--- /dev/null
+++ b/src/services/evolution-source-registry/builtin/webdav-notes-stub.source.in
@@ -0,0 +1,5 @@
+
+[Data Source]
+_DisplayName=WebDAV Notes
+Enabled=true
+Parent=
diff --git a/src/services/evolution-source-registry/evolution-source-registry-resource.xml
b/src/services/evolution-source-registry/evolution-source-registry-resource.xml
index 828dd2a64..409aa7062 100644
--- a/src/services/evolution-source-registry/evolution-source-registry-resource.xml
+++ b/src/services/evolution-source-registry/evolution-source-registry-resource.xml
@@ -9,6 +9,7 @@
<file>local-stub.source</file>
<file>weather-stub.source</file>
<file>webcal-stub.source</file>
+ <file>webdav-notes-stub.source</file>
</gresource>
<gresource prefix="/org/gnome/evolution-data-server/rw-sources">
<file>birthdays.source</file>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]