[evolution-data-server/wip/offline-cache] Make EBookBackendWebDAV derive from EBookMetaBackend
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/wip/offline-cache] Make EBookBackendWebDAV derive from EBookMetaBackend
- Date: Fri, 12 May 2017 10:47:52 +0000 (UTC)
commit 3795e05a2461e2acb5e9744b394414a67c5c03b2
Author: Milan Crha <mcrha redhat com>
Date: Fri May 12 12:46:46 2017 +0200
Make EBookBackendWebDAV derive from EBookMetaBackend
.../backends/webdav/e-book-backend-webdav.c | 2390 +++++++-------------
.../backends/webdav/e-book-backend-webdav.h | 23 +-
src/addressbook/libedata-book/e-book-cache.c | 58 +-
src/addressbook/libedata-book/e-book-cache.h | 13 +-
.../libedata-book/e-book-meta-backend.c | 71 +-
.../backends/caldav/e-cal-backend-caldav.c | 82 +-
src/calendar/libedata-cal/e-cal-meta-backend.c | 72 +-
7 files changed, 1014 insertions(+), 1695 deletions(-)
---
diff --git a/src/addressbook/backends/webdav/e-book-backend-webdav.c
b/src/addressbook/backends/webdav/e-book-backend-webdav.c
index 7deda66..a4617d9 100644
--- a/src/addressbook/backends/webdav/e-book-backend-webdav.c
+++ b/src/addressbook/backends/webdav/e-book-backend-webdav.c
@@ -1,6 +1,6 @@
-/* e-book-backend-webdav.c - Webdav contact backend.
- *
+/*
* Copyright (C) 2008 Matthias Braun <matze braunis de>
+ * Copyright (C) 2017 Red Hat, Inc. (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
@@ -17,12 +17,6 @@
* Authors: Matthias Braun <matze braunis de>
*/
-/*
- * Implementation notes:
- * We use the DavResource URIs as UID in the evolution contact
- * ETags are saved in the WEBDAV_CONTACT_ETAG field so we know which cached contacts
- * are outdated.
- */
#include "evolution-data-server-config.h"
#include <stdio.h>
@@ -30,84 +24,36 @@
#include <string.h>
#include <glib/gi18n-lib.h>
+#include "libedataserver/libedataserver.h"
+
#include "e-book-backend-webdav.h"
-#include <libsoup/soup.h>
-
-#include <libxml/parser.h>
-#include <libxml/xmlreader.h>
-#include <libxml/xpath.h>
-#include <libxml/xpathInternals.h>
-
-#define E_BOOK_BACKEND_WEBDAV_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE \
- ((obj), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebdavPrivate))
-
-#define USERAGENT "Evolution/" VERSION
-#define WEBDAV_CLOSURE_NAME "EBookBackendWebdav.BookView::closure"
-#define WEBDAV_CTAG_KEY "WEBDAV_CTAG"
-#define WEBDAV_CACHE_VERSION_KEY "WEBDAV_CACHE_VERSION"
-#define WEBDAV_CACHE_VERSION "2"
-#define WEBDAV_CONTACT_ETAG "X-EVOLUTION-WEBDAV-ETAG"
-#define WEBDAV_CONTACT_HREF "X-EVOLUTION-WEBDAV-HREF"
-
-G_DEFINE_TYPE (EBookBackendWebdav, e_book_backend_webdav, E_TYPE_BOOK_BACKEND)
-
-struct _EBookBackendWebdavPrivate {
- gboolean marked_for_offline;
- SoupSession *session;
- gchar *uri;
- gchar *username;
- gchar *password;
- gboolean supports_getctag;
- gint64 last_server_test_us; /* real-time, in microseconds, when the last server test
- for changes had been made, when the server doesn't support ctag */
-
- GMutex cache_lock;
- GMutex update_lock;
- EBookBackendCache *cache;
-};
+#define E_WEBDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget
request */
-typedef struct {
- EBookBackendWebdav *webdav;
- GThread *thread;
- EFlag *running;
-} WebdavBackendSearchClosure;
+#define E_WEBDAV_X_ETAG "X-EVOLUTION-WEBDAV-ETAG"
-static void
-webdav_debug_setup (SoupSession *session)
-{
- const gchar *debug_str;
- SoupLogger *logger;
- SoupLoggerLogLevel level;
-
- g_return_if_fail (session != NULL);
-
- debug_str = g_getenv ("WEBDAV_DEBUG");
- if (!debug_str || !*debug_str)
- return;
-
- if (g_ascii_strcasecmp (debug_str, "all") == 0)
- level = SOUP_LOGGER_LOG_BODY;
- else if (g_ascii_strcasecmp (debug_str, "headers") == 0)
- level = SOUP_LOGGER_LOG_HEADERS;
- else
- level = SOUP_LOGGER_LOG_MINIMAL;
-
- logger = soup_logger_new (level, 100 * 1024 * 1024);
- soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
- g_object_unref (logger);
-}
+#define EDB_ERROR(_code) e_data_book_create_error (_code, NULL)
+#define EDB_ERROR_EX(_code, _msg) e_data_book_create_error (_code, _msg)
+
+struct _EBookBackendWebDAVPrivate {
+ /* The main WebDAV session */
+ EWebDAVSession *webdav;
+
+ /* support for 'getctag' extension */
+ gboolean ctag_supported;
+};
+
+G_DEFINE_TYPE (EBookBackendWebDAV, e_book_backend_webdav, E_TYPE_BOOK_META_BACKEND)
static void
-webdav_contact_set_etag (EContact *contact,
- const gchar *etag)
+ebb_webdav_set_contact_etag (EContact *contact,
+ const gchar *etag)
{
EVCardAttribute *attr;
g_return_if_fail (E_IS_CONTACT (contact));
- attr = e_vcard_get_attribute (E_VCARD (contact), WEBDAV_CONTACT_ETAG);
+ attr = e_vcard_get_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
if (attr) {
e_vcard_attribute_remove_values (attr);
@@ -119,20 +65,20 @@ webdav_contact_set_etag (EContact *contact,
} else if (etag) {
e_vcard_append_attribute_with_value (
E_VCARD (contact),
- e_vcard_attribute_new (NULL, WEBDAV_CONTACT_ETAG),
+ e_vcard_attribute_new (NULL, E_WEBDAV_X_ETAG),
etag);
}
}
static gchar *
-webdav_contact_get_etag (EContact *contact)
+ebb_webdav_dup_contact_etag (EContact *contact)
{
EVCardAttribute *attr;
GList *v = NULL;
g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
- attr = e_vcard_get_attribute (E_VCARD (contact), WEBDAV_CONTACT_ETAG);
+ attr = e_vcard_get_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
if (attr)
v = e_vcard_attribute_get_values (attr);
@@ -140,1850 +86,1052 @@ webdav_contact_get_etag (EContact *contact)
return ((v && v->data) ? g_strstrip (g_strdup (v->data)) : NULL);
}
-static void
-webdav_contact_set_href (EContact *contact,
- const gchar *href)
-{
- EVCardAttribute *attr;
-
- g_return_if_fail (E_IS_CONTACT (contact));
-
- attr = e_vcard_get_attribute (E_VCARD (contact), WEBDAV_CONTACT_HREF);
-
- if (attr) {
- e_vcard_attribute_remove_values (attr);
- if (href) {
- e_vcard_attribute_add_value (attr, href);
- } else {
- e_vcard_remove_attribute (E_VCARD (contact), attr);
- }
- } else if (href) {
- e_vcard_append_attribute_with_value (
- E_VCARD (contact),
- e_vcard_attribute_new (NULL, WEBDAV_CONTACT_HREF),
- href);
- }
-}
-
-static gchar *
-webdav_contact_get_href (EContact *contact)
+static gboolean
+ebb_webdav_connect_sync (EBookMetaBackend *meta_backend,
+ const ENamedParameters *credentials,
+ ESourceAuthenticationResult *out_auth_result,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
{
- EVCardAttribute *attr;
- GList *v = NULL;
+ EBookBackendWebDAV *bbdav;
+ GHashTable *capabilities = NULL, *allows = NULL;
+ ESource *source;
+ gboolean success;
+ GError *local_error = NULL;
- g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+ g_return_val_if_fail (out_auth_result != NULL, FALSE);
- attr = e_vcard_get_attribute (E_VCARD (contact), WEBDAV_CONTACT_HREF);
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- if (attr)
- v = e_vcard_attribute_get_values (attr);
+ if (bbdav->priv->webdav)
+ return TRUE;
- return ((v && v->data) ? g_strstrip (g_strdup (v->data)) : NULL);
-}
+ source = e_backend_get_source (E_BACKEND (meta_backend));
-static void
-closure_destroy (WebdavBackendSearchClosure *closure)
-{
- e_flag_free (closure->running);
- if (closure->thread)
- g_thread_unref (closure->thread);
- g_free (closure);
-}
+ bbdav->priv->webdav = e_webdav_session_new (source);
-static WebdavBackendSearchClosure *
-init_closure (EDataBookView *book_view,
- EBookBackendWebdav *webdav)
-{
- WebdavBackendSearchClosure *closure = g_new (WebdavBackendSearchClosure, 1);
+ e_soup_session_setup_logging (E_SOUP_SESSION (bbdav->priv->webdav), g_getenv ("WEBDAV_DEBUG"));
- closure->webdav = webdav;
- closure->thread = NULL;
- closure->running = e_flag_new ();
+ e_binding_bind_property (
+ bbdav, "proxy-resolver",
+ bbdav->priv->webdav, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
- g_object_set_data_full (
- G_OBJECT (book_view), WEBDAV_CLOSURE_NAME,
- closure, (GDestroyNotify) closure_destroy);
+ /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
+ bbdav->priv->ctag_supported = TRUE;
- return closure;
-}
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
-static WebdavBackendSearchClosure *
-get_closure (EDataBookView *book_view)
-{
- return g_object_get_data (G_OBJECT (book_view), WEBDAV_CLOSURE_NAME);
-}
+ e_soup_session_set_credentials (E_SOUP_SESSION (bbdav->priv->webdav), credentials);
-static guint
-send_and_handle_ssl (EBookBackendWebdav *webdav,
- SoupMessage *message,
- GCancellable *cancellable)
-{
- guint status_code;
+ success = e_webdav_session_options_sync (bbdav->priv->webdav, NULL,
+ &capabilities, &allows, cancellable, &local_error);
- e_soup_ssl_trust_connect (message, e_backend_get_source (E_BACKEND (webdav)));
+ /* iCloud and Google servers can return "404 Not Found" when issued OPTIONS on the addressbook
collection */
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ ESourceWebdav *webdav_extension;
+ SoupURI *soup_uri;
- status_code = soup_session_send_message (webdav->priv->session, message);
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+ if (soup_uri) {
+ if (soup_uri->host && soup_uri->path && *soup_uri->path &&
+ e_util_utf8_strstrcase (soup_uri->host, ".icloud.com")) {
+ /* Try parent directory */
+ gchar *path;
+ gint len = strlen (soup_uri->path);
- if (SOUP_STATUS_IS_SUCCESSFUL (status_code))
- e_backend_ensure_source_status_connected (E_BACKEND (webdav));
+ if (soup_uri->path[len - 1] == '/')
+ soup_uri->path[len - 1] = '\0';
- return status_code;
-}
+ path = g_path_get_dirname (soup_uri->path);
+ if (path && g_str_has_prefix (soup_uri->path, path)) {
+ gchar *uri;
-static EContact *
-download_contact (EBookBackendWebdav *webdav,
- const gchar *uri,
- GCancellable *cancellable)
-{
- SoupMessage *message;
- const gchar *etag;
- EContact *contact;
- guint status;
-
- message = soup_message_new (SOUP_METHOD_GET, uri);
- soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
- soup_message_headers_append (message->request_headers, "Connection", "close");
-
- status = send_and_handle_ssl (webdav, message, cancellable);
- if (status != 200) {
- g_warning ("Couldn't load '%s' (http status %d)", uri, status);
- g_object_unref (message);
- return NULL;
- }
+ soup_uri_set_path (soup_uri, path);
- if (message->response_body == NULL) {
- g_message ("no response body after requesting '%s'", uri);
- g_object_unref (message);
- return NULL;
- }
+ uri = soup_uri_to_string (soup_uri, FALSE);
+ if (uri) {
+ g_clear_error (&local_error);
- if (message->response_body->length <= 11 || 0 != g_ascii_strncasecmp ((const gchar *)
message->response_body->data, "BEGIN:VCARD", 11)) {
- g_object_unref (message);
- return NULL;
- }
+ success = e_webdav_session_options_sync (bbdav->priv->webdav,
uri,
+ &capabilities, &allows, cancellable, &local_error);
+ }
- etag = soup_message_headers_get_list (message->response_headers, "ETag");
+ g_free (uri);
+ }
- /* we use our URI as UID */
- contact = e_contact_new_from_vcard (message->response_body->data);
- if (contact == NULL) {
- g_warning ("Invalid vcard at '%s'", uri);
- g_object_unref (message);
- return NULL;
- }
+ g_free (path);
+ } else if (soup_uri->host && e_util_utf8_strstrcase (soup_uri->host,
".googleusercontent.com")) {
+ g_clear_error (&local_error);
+ success = TRUE;
- webdav_contact_set_href (contact, uri);
- /* the etag is remembered in the WEBDAV_CONTACT_ETAG field */
- if (etag != NULL) {
- webdav_contact_set_etag (contact, etag);
- }
+ /* Google's WebDAV doesn't like OPTIONS, hard-code it */
+ capabilities = g_hash_table_new_full (camel_strcase_hash,
camel_strcase_equal, g_free, NULL);
+ g_hash_table_insert (capabilities, g_strdup
(E_WEBDAV_CAPABILITY_ADDRESSBOOK), GINT_TO_POINTER (1));
- g_object_unref (message);
- return contact;
-}
+ allows = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal,
g_free, NULL);
+ g_hash_table_insert (allows, g_strdup (SOUP_METHOD_PUT), GINT_TO_POINTER (1));
+ }
-static guint
-upload_contact (EBookBackendWebdav *webdav,
- const gchar *uri,
- EContact *contact,
- gchar **reason,
- GCancellable *cancellable)
-{
- ESource *source;
- ESourceWebdav *webdav_extension;
- SoupMessage *message;
- gchar *etag;
- const gchar *new_etag, *redir_uri;
- gchar *request;
- guint status;
- gboolean avoid_ifmatch;
- const gchar *extension_name;
-
- g_return_val_if_fail (uri != NULL, SOUP_STATUS_BAD_REQUEST);
-
- source = e_backend_get_source (E_BACKEND (webdav));
-
- extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
- webdav_extension = e_source_get_extension (source, extension_name);
-
- message = soup_message_new (SOUP_METHOD_PUT, uri);
- soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
- soup_message_headers_append (message->request_headers, "Connection", "close");
-
- avoid_ifmatch = e_source_webdav_get_avoid_ifmatch (webdav_extension);
-
- /* some servers (like apache < 2.2.8) don't handle If-Match, correctly so
- * we can leave it out */
- if (!avoid_ifmatch) {
- /* only override if etag is still the same on the server */
- etag = webdav_contact_get_etag (contact);
- if (etag == NULL) {
- soup_message_headers_append (
- message->request_headers,
- "If-None-Match", "*");
- } else if (etag[0] == 'W' && etag[1] == '/') {
- g_warning ("we only have a weak ETag, don't use If-Match synchronisation");
- } else {
- soup_message_headers_append (
- message->request_headers,
- "If-Match", etag);
+ soup_uri_free (soup_uri);
}
-
- g_free (etag);
}
- /* Remove the stored ETag, before saving to the server */
- webdav_contact_set_etag (contact, NULL);
-
- request = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
- soup_message_set_request (
- message, "text/vcard", SOUP_MEMORY_TEMPORARY,
- request, strlen (request));
+ if (success) {
+ ESourceWebdav *webdav_extension;
+ EBookCache *book_cache;
+ SoupURI *soup_uri;
+ gboolean is_writable;
+ gboolean addressbook;
- status = send_and_handle_ssl (webdav, message, cancellable);
- new_etag = soup_message_headers_get_list (message->response_headers, "ETag");
+ webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+ book_cache = e_book_meta_backend_ref_cache (meta_backend);
- redir_uri = soup_message_headers_get_list (message->response_headers, "Location");
+ /* The POST added for FastMail servers, which doesn't advertise PUT on collections. */
+ 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));
- /* set UID and WEBDAV_CONTACT_ETAG fields */
- webdav_contact_set_etag (contact, new_etag);
- if (redir_uri && *redir_uri) {
- if (!strstr (redir_uri, "://")) {
- /* it's a relative URI */
- SoupURI *suri = soup_uri_new (uri);
- gchar *full_uri;
+ addressbook = capabilities && g_hash_table_contains (capabilities,
E_WEBDAV_CAPABILITY_ADDRESSBOOK);
- if (*redir_uri != '/' && *redir_uri != '\\') {
- gchar *slashed_path = g_strconcat ("/", redir_uri, NULL);
+ if (addressbook) {
+ e_book_backend_set_writable (E_BOOK_BACKEND (bbdav), is_writable);
- soup_uri_set_path (suri, slashed_path);
- g_free (slashed_path);
- } else {
- soup_uri_set_path (suri, redir_uri);
- }
- full_uri = soup_uri_to_string (suri, FALSE);
-
- webdav_contact_set_href (contact, full_uri);
-
- g_free (full_uri);
- soup_uri_free (suri);
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
} else {
- webdav_contact_set_href (contact, redir_uri);
- }
- } else {
- webdav_contact_set_href (contact, uri);
- }
+ gchar *uri;
- if (reason != NULL) {
- const gchar *phrase;
+ uri = soup_uri_to_string (soup_uri, FALSE);
- phrase = message->reason_phrase;
- if (phrase == NULL)
- phrase = soup_status_get_phrase (message->status_code);
- if (phrase == NULL)
- phrase = _("Unknown error");
-
- *reason = g_strdup (phrase);
- }
+ success = FALSE;
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Given URL ā%sā doesn't reference WebDAV address book"), uri);
- g_object_unref (message);
- g_free (request);
+ g_free (uri);
- return status;
-}
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ }
-static gboolean
-webdav_handle_auth_request (EBookBackendWebdav *webdav,
- GError **error)
-{
- EBookBackendWebdavPrivate *priv = webdav->priv;
-
- if (priv->username != NULL) {
- g_free (priv->username);
- priv->username = NULL;
- g_free (priv->password);
- priv->password = NULL;
-
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_AUTHENTICATION_FAILED,
- e_client_error_to_string (
- E_CLIENT_ERROR_AUTHENTICATION_FAILED));
- } else {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_AUTHENTICATION_REQUIRED,
- e_client_error_to_string (
- E_CLIENT_ERROR_AUTHENTICATION_REQUIRED));
+ g_clear_object (&book_cache);
+ soup_uri_free (soup_uri);
}
- return FALSE;
-}
-
-static guint
-delete_contact (EBookBackendWebdav *webdav,
- const gchar *uri,
- GCancellable *cancellable)
-{
- SoupMessage *message;
- guint status;
-
- message = soup_message_new (SOUP_METHOD_DELETE, uri);
- soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
- soup_message_headers_append (message->request_headers, "Connection", "close");
-
- status = send_and_handle_ssl (webdav, message, cancellable);
- g_object_unref (message);
-
- return status;
-}
-
-typedef struct parser_strings_t {
- const xmlChar *multistatus;
- const xmlChar *dav;
- const xmlChar *href;
- const xmlChar *response;
- const xmlChar *propstat;
- const xmlChar *prop;
- const xmlChar *getetag;
-} parser_strings_t;
-
-typedef struct response_element_t response_element_t;
-struct response_element_t {
- xmlChar *href;
- xmlChar *etag;
- response_element_t *next;
-};
-
-static response_element_t *
-parse_response_tag (const parser_strings_t *strings,
- xmlTextReaderPtr reader)
-{
- xmlChar *href = NULL;
- xmlChar *etag = NULL;
- gint depth = xmlTextReaderDepth (reader);
- response_element_t *element;
-
- while (xmlTextReaderRead (reader) == 1 && xmlTextReaderDepth (reader) > depth) {
- const xmlChar *tag_name;
- if (xmlTextReaderNodeType (reader) != XML_READER_TYPE_ELEMENT)
- continue;
+ if (success) {
+ gchar *ctag = NULL;
- if (xmlTextReaderConstNamespaceUri (reader) != strings->dav)
- continue;
+ /* Some servers, notably Google, allow OPTIONS when not
+ authorized (aka without credentials), thus try something
+ more aggressive, just in case.
- tag_name = xmlTextReaderConstLocalName (reader);
- if (tag_name == strings->href) {
- if (href != NULL) {
- /* multiple href elements?!? */
- xmlFree (href);
- }
- href = xmlTextReaderReadString (reader);
- } else if (tag_name == strings->propstat) {
- /* find <propstat><prop><getetag> hierarchy */
- gint depth2 = xmlTextReaderDepth (reader);
- while (xmlTextReaderRead (reader) == 1 && xmlTextReaderDepth (reader) > depth2) {
- gint depth3;
- if (xmlTextReaderNodeType (reader) != XML_READER_TYPE_ELEMENT)
- continue;
-
- if (xmlTextReaderConstNamespaceUri (reader) != strings->dav
- || xmlTextReaderConstLocalName (reader) != strings->prop)
- continue;
-
- depth3 = xmlTextReaderDepth (reader);
- while (xmlTextReaderRead (reader) == 1 && xmlTextReaderDepth (reader) >
depth3) {
- if (xmlTextReaderNodeType (reader) != XML_READER_TYPE_ELEMENT)
- continue;
-
- if (xmlTextReaderConstNamespaceUri (reader) != strings->dav
- || xmlTextReaderConstLocalName (reader)
- != strings->getetag)
- continue;
-
- if (etag != NULL) {
- /* multiple etag elements?!? */
- xmlFree (etag);
- }
- etag = xmlTextReaderReadString (reader);
- }
- }
+ The 'getctag' extension is not required, thuch check
+ for unauthorized error only. */
+ if (!e_webdav_session_getctag_sync (bbdav->priv->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);
}
- }
-
- if (href == NULL) {
- g_warning ("webdav returned response element without href");
- return NULL;
- }
-
- /* append element to list */
- element = g_malloc (sizeof (element[0]));
- element->href = href;
- element->etag = etag;
- return element;
-}
-
-static response_element_t *
-parse_propfind_response (xmlTextReaderPtr reader)
-{
- parser_strings_t strings;
- response_element_t *elements;
-
- /* get internalized versions of some strings to avoid strcmp while
- * parsing */
- strings.multistatus = xmlTextReaderConstString (reader, BAD_CAST "multistatus");
- strings.dav = xmlTextReaderConstString (reader, BAD_CAST "DAV:");
- strings.href = xmlTextReaderConstString (reader, BAD_CAST "href");
- strings.response = xmlTextReaderConstString (reader, BAD_CAST "response");
- strings.propstat = xmlTextReaderConstString (reader, BAD_CAST "propstat");
- strings.prop = xmlTextReaderConstString (reader, BAD_CAST "prop");
- strings.getetag = xmlTextReaderConstString (reader, BAD_CAST "getetag");
-
- while (xmlTextReaderRead (reader) == 1 && xmlTextReaderNodeType (reader) != XML_READER_TYPE_ELEMENT) {
- }
- if (xmlTextReaderConstLocalName (reader) != strings.multistatus
- || xmlTextReaderConstNamespaceUri (reader) != strings.dav) {
- g_warning ("webdav PROPFIND result is not <DAV:multistatus>");
- return NULL;
+ g_free (ctag);
}
- elements = NULL;
-
- /* parse all DAV:response tags */
- while (xmlTextReaderRead (reader) == 1 && xmlTextReaderDepth (reader) > 0) {
- response_element_t *element;
-
- if (xmlTextReaderNodeType (reader) != XML_READER_TYPE_ELEMENT)
- continue;
-
- if (xmlTextReaderConstLocalName (reader) != strings.response
- || xmlTextReaderConstNamespaceUri (reader) != strings.dav)
- continue;
-
- element = parse_response_tag (&strings, reader);
- if (element == NULL)
- continue;
-
- element->next = elements;
- elements = element;
- }
+ if (success) {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+ } else {
+ gboolean credentials_empty;
+ gboolean is_ssl_error;
+
+ credentials_empty = !credentials || !e_named_parameters_count (credentials);
+ 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 (local_error) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ } else {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unknown error"));
+ }
- return elements;
-}
+ if (is_ssl_error) {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
-static SoupMessage *
-send_propfind (EBookBackendWebdav *webdav,
- GCancellable *cancellable,
- GError **error)
-{
- SoupMessage *message;
- EBookBackendWebdavPrivate *priv = webdav->priv;
- const gchar *request =
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
- "<propfind xmlns=\"DAV:\"><prop><getetag/></prop></propfind>";
-
- message = soup_message_new (SOUP_METHOD_PROPFIND, priv->uri);
- if (!message) {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Malformed URI: %s"),
priv->uri);
- return NULL;
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_SSL_FAILED);
+ e_soup_session_get_ssl_error_details (E_SOUP_SESSION (bbdav->priv->webdav),
out_certificate_pem, out_certificate_errors);
+ } else {
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ }
}
- soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
- soup_message_headers_append (message->request_headers, "Connection", "close");
- soup_message_headers_append (message->request_headers, "Depth", "1");
- soup_message_set_request (
- message, "text/xml", SOUP_MEMORY_TEMPORARY,
- (gchar *) request, strlen (request));
+ if (capabilities)
+ g_hash_table_destroy (capabilities);
+ if (allows)
+ g_hash_table_destroy (allows);
- send_and_handle_ssl (webdav, message, cancellable);
+ if (!success)
+ g_clear_object (&bbdav->priv->webdav);
- return message;
+ return success;
}
-static xmlXPathObjectPtr
-xpath_eval (xmlXPathContextPtr ctx,
- const gchar *format,
- ...)
+static gboolean
+ebb_webdav_disconnect_sync (EBookMetaBackend *meta_backend,
+ GCancellable *cancellable,
+ GError **error)
{
- xmlXPathObjectPtr result;
- va_list args;
- gchar *expr;
+ EBookBackendWebDAV *bbdav;
+ ESource *source;
- if (ctx == NULL) {
- return NULL;
- }
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
- va_start (args, format);
- expr = g_strdup_vprintf (format, args);
- va_end (args);
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
- g_free (expr);
+ if (bbdav->priv->webdav)
+ soup_session_abort (SOUP_SESSION (bbdav->priv->webdav));
- if (result == NULL) {
- return NULL;
- }
+ g_clear_object (&bbdav->priv->webdav);
- if (result->type == XPATH_NODESET &&
- xmlXPathNodeSetIsEmpty (result->nodesetval)) {
- xmlXPathFreeObject (result);
- return NULL;
- }
+ source = e_backend_get_source (E_BACKEND (meta_backend));
+ e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
- return result;
+ return TRUE;
}
-static gchar *
-xp_object_get_string (xmlXPathObjectPtr result)
+static void
+ebb_webdav_update_nfo_with_contact (EBookMetaBackendInfo *nfo,
+ EContact *contact,
+ const gchar *etag)
{
- gchar *ret = NULL;
+ const gchar *uid;
- if (result == NULL)
- return ret;
+ g_return_if_fail (nfo != NULL);
+ g_return_if_fail (E_IS_CONTACT (contact));
- if (result->type == XPATH_STRING) {
- ret = g_strdup ((gchar *) result->stringval);
- }
+ uid = e_contact_get_const (contact, E_CONTACT_UID);
- xmlXPathFreeObject (result);
- return ret;
-}
+ if (!etag || !*etag)
+ etag = nfo->revision;
-static guint
-xp_object_get_status (xmlXPathObjectPtr result)
-{
- gboolean res;
- guint ret = 0;
+ ebb_webdav_set_contact_etag (contact, etag);
- if (result == NULL)
- return ret;
+ g_warn_if_fail (nfo->object == NULL);
+ nfo->object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
- if (result->type == XPATH_STRING) {
- res = soup_headers_parse_status_line ((gchar *) result->stringval, NULL, &ret, NULL);
- if (!res) {
- ret = 0;
- }
+ if (!nfo->uid || !*(nfo->uid)) {
+ g_free (nfo->uid);
+ nfo->uid = g_strdup (uid);
}
- xmlXPathFreeObject (result);
- return ret;
+ if (g_strcmp0 (etag, nfo->revision) != 0) {
+ gchar *copy = g_strdup (etag);
+
+ g_free (nfo->revision);
+ nfo->revision = copy;
+ }
}
static gboolean
-check_addressbook_changed (EBookBackendWebdav *webdav,
- gchar **new_ctag,
- GCancellable *cancellable)
+ebb_webdav_multiget_response_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ const gchar *href,
+ guint status_code,
+ gpointer user_data)
{
- gboolean res = TRUE;
- const gchar *request = "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind
xmlns=\"DAV:\"><prop><getctag/></prop></propfind>";
- EBookBackendWebdavPrivate *priv;
- SoupMessage *message;
+ GSList **from_link = user_data;
- g_return_val_if_fail (webdav != NULL, TRUE);
- g_return_val_if_fail (new_ctag != NULL, TRUE);
+ g_return_val_if_fail (from_link != NULL, FALSE);
- *new_ctag = NULL;
- priv = webdav->priv;
+ if (!xpath_prop_prefix) {
+ e_xml_xpath_context_register_namespaces (xpath_ctx, "C", E_WEBDAV_NS_CARDDAV, NULL);
+ } else if (status_code == SOUP_STATUS_OK) {
+ gchar *address_data, *etag;
- if (!priv->supports_getctag) {
- gint64 real_time_us = g_get_real_time ();
+ g_return_val_if_fail (href != NULL, FALSE);
- /* Fifteen minutes in microseconds */
- if (real_time_us - priv->last_server_test_us < 15 * 60 * 1000 * 1000)
- return FALSE;
+ address_data = e_xml_xpath_eval_as_string (xpath_ctx, "%s/C:address-data", xpath_prop_prefix);
+ etag = e_webdav_session_util_maybe_dequote (e_xml_xpath_eval_as_string (xpath_ctx,
"%s/D:getetag", xpath_prop_prefix));
- priv->last_server_test_us = real_time_us;
+ if (address_data) {
+ EContact *contact;
- return TRUE;
- }
+ contact = e_contact_new_from_vcard (address_data);
+ if (contact) {
+ const gchar *uid;
- priv->supports_getctag = FALSE;
+ uid = e_contact_get_const (contact, E_CONTACT_UID);
+ if (uid) {
+ GSList *link;
- message = soup_message_new (SOUP_METHOD_PROPFIND, priv->uri);
- if (!message)
- return TRUE;
+ for (link = *from_link; link; link = g_slist_next (link)) {
+ EBookMetaBackendInfo *nfo = link->data;
- soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
- soup_message_headers_append (message->request_headers, "Connection", "close");
- soup_message_headers_append (message->request_headers, "Depth", "0");
- soup_message_set_request (message, "text/xml", SOUP_MEMORY_TEMPORARY, (gchar *) request, strlen
(request));
- send_and_handle_ssl (webdav, message, cancellable);
-
- if (message->status_code == 207 && message->response_body) {
- xmlDocPtr xml;
-
- xml = xmlReadMemory (message->response_body->data, message->response_body->length, NULL,
NULL, XML_PARSE_NOWARNING);
- if (xml) {
- const gchar *GETCTAG_XPATH_STATUS =
"string(/D:multistatus/D:response/D:propstat/D:prop/D:getctag/../../D:status)";
- const gchar *GETCTAG_XPATH_VALUE =
"string(/D:multistatus/D:response/D:propstat/D:prop/D:getctag)";
- xmlXPathContextPtr xpctx;
-
- xpctx = xmlXPathNewContext (xml);
- xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
-
- if (xp_object_get_status (xpath_eval (xpctx, GETCTAG_XPATH_STATUS)) == 200) {
- gchar *txt = xp_object_get_string (xpath_eval (xpctx, GETCTAG_XPATH_VALUE));
- const gchar *stored_version;
- gboolean old_version;
-
- g_mutex_lock (&priv->cache_lock);
- stored_version = e_file_cache_get_object (E_FILE_CACHE (priv->cache),
WEBDAV_CACHE_VERSION_KEY);
-
- /* The ETag was moved from REV to its own attribute, thus
- * if the cache version is too low, update it. */
- old_version = !stored_version || atoi (stored_version) < atoi
(WEBDAV_CACHE_VERSION);
- g_mutex_unlock (&priv->cache_lock);
-
- if (txt && *txt) {
- gint len = strlen (txt);
-
- if (*txt == '\"' && len > 2 && txt[len - 1] == '\"') {
- /* dequote */
- *new_ctag = g_strndup (txt + 1, len - 2);
- } else {
- *new_ctag = txt;
- txt = NULL;
- }
+ if (!nfo)
+ continue;
- if (*new_ctag) {
- const gchar *my_ctag;
+ if (g_strcmp0 (nfo->extra, href) == 0) {
+ /* If the server returns data in the same order as it
had been requested,
+ then this speeds up lookup for the matching
object. */
+ if (link == *from_link)
+ *from_link = g_slist_next (*from_link);
- g_mutex_lock (&priv->cache_lock);
- my_ctag = e_file_cache_get_object (E_FILE_CACHE
(priv->cache), WEBDAV_CTAG_KEY);
- res = old_version || !my_ctag || !g_str_equal (my_ctag,
*new_ctag);
+ ebb_webdav_update_nfo_with_contact (nfo, contact,
etag);
- priv->supports_getctag = TRUE;
- g_mutex_unlock (&priv->cache_lock);
+ break;
+ }
}
}
- g_free (txt);
-
- if (old_version) {
- g_mutex_lock (&priv->cache_lock);
-
- if (!e_file_cache_replace_object (E_FILE_CACHE (priv->cache),
- WEBDAV_CACHE_VERSION_KEY,
- WEBDAV_CACHE_VERSION))
- e_file_cache_add_object (
- E_FILE_CACHE (priv->cache),
- WEBDAV_CACHE_VERSION_KEY,
- WEBDAV_CACHE_VERSION);
-
- g_mutex_unlock (&priv->cache_lock);
- }
+ g_object_unref (contact);
}
-
- xmlXPathFreeContext (xpctx);
- xmlFreeDoc (xml);
}
- }
-
- g_object_unref (message);
-
- return res;
-}
-static void
-remove_unknown_contacts_cb (gpointer href,
- gpointer pcontact,
- gpointer pwebdav)
-{
- EContact *contact = pcontact;
- EBookBackendWebdav *webdav = pwebdav;
- const gchar *uid;
+ g_free (address_data);
+ g_free (etag);
+ }
- uid = e_contact_get_const (contact, E_CONTACT_UID);
- if (uid && e_book_backend_cache_remove_contact (webdav->priv->cache, uid))
- e_book_backend_notify_remove ((EBookBackend *) webdav, uid);
+ return TRUE;
}
static gboolean
-download_contacts (EBookBackendWebdav *webdav,
- EFlag *running,
- EDataBookView *book_view,
- gboolean force,
- GCancellable *cancellable,
- GError **error)
+ebb_webdav_multiget_from_sets_sync (EBookBackendWebDAV *bbdav,
+ GSList **in_link,
+ GSList **set2,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendWebdavPrivate *priv = webdav->priv;
- EBookBackend *book_backend;
- SoupMessage *message;
- guint status;
- xmlTextReaderPtr reader;
- response_element_t *elements;
- response_element_t *element;
- response_element_t *next;
- gint count;
- gint i;
- gchar *new_ctag = NULL;
- GHashTable *href_to_contact;
- GList *cached_contacts, *iter;
-
- g_mutex_lock (&priv->update_lock);
-
- if (!force && !check_addressbook_changed (webdav, &new_ctag, cancellable)) {
- g_free (new_ctag);
- g_mutex_unlock (&priv->update_lock);
- return TRUE;
- }
+ EXmlDocument *xml;
+ gint left_to_go = E_WEBDAV_MAX_MULTIGET_AMOUNT;
+ GSList *link;
+ gboolean success = TRUE;
- book_backend = E_BOOK_BACKEND (webdav);
+ g_return_val_if_fail (in_link != NULL, FALSE);
+ g_return_val_if_fail (*in_link != NULL, FALSE);
+ g_return_val_if_fail (set2 != NULL, FALSE);
- if (book_view != NULL) {
- e_data_book_view_notify_progress (book_view, -1,
- _("Loading Addressbook summary..."));
- }
+ xml = e_xml_document_new (E_WEBDAV_NS_CARDDAV, "addressbook-multiget");
+ g_return_val_if_fail (xml != NULL, FALSE);
- message = send_propfind (webdav, cancellable, error);
- if (!message) {
- g_free (new_ctag);
- if (book_view)
- e_data_book_view_notify_progress (book_view, -1, NULL);
- g_mutex_unlock (&priv->update_lock);
- return FALSE;
- }
+ e_xml_document_add_namespaces (xml, "D", E_WEBDAV_NS_DAV, NULL);
- status = message->status_code;
-
- if (status == SOUP_STATUS_UNAUTHORIZED ||
- status == SOUP_STATUS_PROXY_UNAUTHORIZED ||
- status == SOUP_STATUS_FORBIDDEN) {
- g_object_unref (message);
- g_free (new_ctag);
- if (book_view)
- e_data_book_view_notify_progress (book_view, -1, NULL);
- g_mutex_unlock (&priv->update_lock);
- return webdav_handle_auth_request (webdav, error);
- }
- if (status != 207) {
- g_set_error (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("PROPFIND on webdav failed with HTTP status %d (%s)"),
- status,
- message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
- (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase
(message->status_code) : _("Unknown error")));
+ e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
+ e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "getetag");
+ e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "address-data");
+ e_xml_document_end_element (xml); /* prop */
- g_object_unref (message);
- g_free (new_ctag);
+ link = *in_link;
- if (book_view)
- e_data_book_view_notify_progress (book_view, -1, NULL);
+ while (link && left_to_go > 0) {
+ EBookMetaBackendInfo *nfo = link->data;
+ SoupURI *suri;
+ gchar *path = NULL;
- g_mutex_unlock (&priv->update_lock);
+ link = g_slist_next (link);
+ if (!link) {
+ link = *set2;
+ *set2 = NULL;
+ }
- return FALSE;
- }
- if (message->response_body == NULL) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("No response body in webdav PROPFIND result"));
+ if (!nfo)
+ continue;
- g_object_unref (message);
- g_free (new_ctag);
+ left_to_go--;
- if (book_view)
- e_data_book_view_notify_progress (book_view, -1, NULL);
+ suri = soup_uri_new (nfo->extra);
+ if (suri) {
+ path = soup_uri_to_string (suri, TRUE);
+ soup_uri_free (suri);
+ }
- g_mutex_unlock (&priv->update_lock);
+ e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "href");
+ e_xml_document_write_string (xml, path ? path : nfo->extra);
+ e_xml_document_end_element (xml); /* href */
- return FALSE;
+ g_free (path);
}
- /* parse response */
- reader = xmlReaderForMemory (
- message->response_body->data,
- message->response_body->length, NULL, NULL,
- XML_PARSE_NOWARNING);
+ if (left_to_go != E_WEBDAV_MAX_MULTIGET_AMOUNT && success) {
+ GSList *from_link = *in_link;
- elements = parse_propfind_response (reader);
-
- /* count contacts */
- count = 0;
- for (element = elements; element != NULL; element = element->next) {
- ++count;
+ success = e_webdav_session_report_sync (bbdav->priv->webdav, NULL, NULL, xml,
+ ebb_webdav_multiget_response_cb, &from_link, NULL, NULL, cancellable, error);
}
- href_to_contact = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- g_mutex_lock (&priv->cache_lock);
- e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache));
- cached_contacts = e_book_backend_cache_get_contacts (priv->cache, NULL);
- for (iter = cached_contacts; iter; iter = g_list_next (iter)) {
- EContact *contact = iter->data;
- gchar *href;
-
- if (!contact)
- continue;
+ g_object_unref (xml);
- href = webdav_contact_get_href (contact);
+ *in_link = link;
- if (href)
- g_hash_table_insert (href_to_contact, href, g_object_ref (contact));
- }
- g_list_free_full (cached_contacts, g_object_unref);
- g_mutex_unlock (&priv->cache_lock);
-
- /* download contacts */
- i = 0;
- for (element = elements; element != NULL; element = element->next, ++i) {
- const gchar *uri;
- const gchar *etag;
- EContact *contact;
- gchar *complete_uri, *stored_etag;
-
- /* stop downloading if search was aborted */
- if (running != NULL && !e_flag_is_set (running))
- break;
-
- if (book_view != NULL) {
- gfloat percent = 100.0 / count * i;
- gchar buf[100];
- snprintf (buf, sizeof (buf), _("Loading Contacts (%d%%)"), (gint) percent);
- e_data_book_view_notify_progress (book_view, -1, buf);
- }
+ return success;
+}
- /* skip collections */
- uri = (const gchar *) element->href;
- if (uri[strlen (uri) - 1] == '/')
- continue;
+static gboolean
+ebb_webdav_get_contact_items_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ const gchar *href,
+ guint status_code,
+ gpointer user_data)
+{
+ GHashTable *known_items = user_data; /* gchar *href ~> EBookMetaBackendInfo * */
- /* uri might be relative, construct complete one */
- if (uri[0] == '/') {
- SoupURI *soup_uri = soup_uri_new (priv->uri);
- g_free (soup_uri->path);
- soup_uri->path = g_strdup (uri);
+ g_return_val_if_fail (xpath_ctx != NULL, FALSE);
+ g_return_val_if_fail (known_items != NULL, FALSE);
- complete_uri = soup_uri_to_string (soup_uri, FALSE);
- soup_uri_free (soup_uri);
- } else {
- complete_uri = g_strdup (uri);
- }
+ if (xpath_prop_prefix &&
+ status_code == SOUP_STATUS_OK) {
+ EBookMetaBackendInfo *nfo;
+ gchar *etag;
- etag = (const gchar *) element->etag;
+ g_return_val_if_fail (href != NULL, FALSE);
- contact = g_hash_table_lookup (href_to_contact, complete_uri);
- if (contact) {
- g_object_ref (contact);
- g_hash_table_remove (href_to_contact, complete_uri);
- stored_etag = webdav_contact_get_etag (contact);
- } else {
- stored_etag = NULL;
+ /* Skip collection resource, if returned by the server (like iCloud.com does) */
+ if (g_str_has_suffix (href, "/") ||
+ (request_uri && request_uri->path && g_str_has_suffix (href, request_uri->path))) {
+ return TRUE;
}
+ etag = e_webdav_session_util_maybe_dequote (e_xml_xpath_eval_as_string (xpath_ctx,
"%s/D:getetag", xpath_prop_prefix));
+ /* Return 'TRUE' to not stop on faulty data from the server */
+ g_return_val_if_fail (etag != NULL, TRUE);
- /* download contact if it is not cached or its ETag changed */
- if (contact == NULL || etag == NULL || !stored_etag ||
- strcmp (stored_etag, etag) != 0) {
- if (contact != NULL)
- g_object_unref (contact);
- contact = download_contact (webdav, complete_uri, cancellable);
- if (contact != NULL) {
- const gchar *uid;
+ /* UID is unknown at this moment */
+ nfo = e_book_meta_backend_info_new ("", etag, NULL, href);
- uid = e_contact_get_const (contact, E_CONTACT_UID);
-
- g_mutex_lock (&priv->cache_lock);
- if (e_book_backend_cache_remove_contact (priv->cache, uid))
- e_book_backend_notify_remove (book_backend, uid);
- e_book_backend_cache_add_contact (priv->cache, contact);
- g_mutex_unlock (&priv->cache_lock);
- e_book_backend_notify_update (book_backend, contact);
- }
- }
+ g_free (etag);
+ g_return_val_if_fail (nfo != NULL, FALSE);
- if (contact != NULL)
- g_object_unref (contact);
- g_free (complete_uri);
- g_free (stored_etag);
+ g_hash_table_insert (known_items, g_strdup (href), nfo);
}
- /* free element list */
- for (element = elements; element != NULL; element = next) {
- next = element->next;
+ return TRUE;
+}
- xmlFree (element->href);
- xmlFree (element->etag);
- g_free (element);
- }
+typedef struct _WebDAVChangesData {
+ GSList **out_modified_objects;
+ GSList **out_removed_objects;
+ GHashTable *known_items; /* gchar *href ~> EBookMetaBackendInfo * */
+} WebDAVChangesData;
- xmlFreeTextReader (reader);
- g_object_unref (message);
+static gboolean
+ebb_webdav_search_changes_cb (EBookCache *book_cache,
+ const gchar *uid,
+ const gchar *revision,
+ const gchar *object,
+ const gchar *extra,
+ EOfflineState offline_state,
+ gpointer user_data)
+{
+ WebDAVChangesData *ccd = user_data;
- if (new_ctag) {
- g_mutex_lock (&priv->cache_lock);
- if (!e_file_cache_replace_object (E_FILE_CACHE (priv->cache), WEBDAV_CTAG_KEY, new_ctag))
- e_file_cache_add_object (E_FILE_CACHE (priv->cache), WEBDAV_CTAG_KEY, new_ctag);
- g_mutex_unlock (&priv->cache_lock);
- }
- g_free (new_ctag);
+ g_return_val_if_fail (ccd != NULL, FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
- if (book_view)
- e_data_book_view_notify_progress (book_view, -1, NULL);
+ /* Can be NULL for added components in offline mode */
+ if (extra && *extra) {
+ EBookMetaBackendInfo *nfo;
- g_mutex_lock (&priv->cache_lock);
+ nfo = g_hash_table_lookup (ccd->known_items, extra);
+ 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);
+ }
- if (!g_cancellable_is_cancelled (cancellable) &&
- (!running || e_flag_is_set (running))) {
- /* clean-up the cache only if it wasn't cancelled during the work */
- g_hash_table_foreach (href_to_contact, remove_unknown_contacts_cb, webdav);
- }
- e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache));
- g_mutex_unlock (&priv->cache_lock);
- g_mutex_unlock (&priv->update_lock);
+ *(ccd->out_modified_objects) = g_slist_prepend (*(ccd->out_modified_objects),
+ e_book_meta_backend_info_copy (nfo));
- g_hash_table_destroy (href_to_contact);
+ g_hash_table_remove (ccd->known_items, extra);
+ }
+ } else {
+ *(ccd->out_removed_objects) = g_slist_prepend (*(ccd->out_removed_objects),
+ e_book_meta_backend_info_new (uid, revision, object, extra));
+ }
+ }
return TRUE;
}
-static gpointer
-book_view_thread (gpointer data)
+static gboolean
+ebb_webdav_get_changes_sync (EBookMetaBackend *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)
{
- EDataBookView *book_view = data;
- WebdavBackendSearchClosure *closure = get_closure (book_view);
- EBookBackendWebdav *webdav = closure->webdav;
-
- e_flag_set (closure->running);
-
- /* ref the book view because it'll be removed and unrefed when/if
- * it's stopped */
- g_object_ref (book_view);
-
- download_contacts (webdav, closure->running, book_view, FALSE, NULL, NULL);
-
- g_object_unref (book_view);
-
- return NULL;
-}
+ EBookBackendWebDAV *bbdav;
+ EXmlDocument *xml;
+ GHashTable *known_items; /* gchar *href ~> EBookMetaBackendInfo * */
+ GHashTableIter iter;
+ gpointer key = NULL, value = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (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;
+
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
+
+ if (bbdav->priv->ctag_supported) {
+ gchar *new_sync_tag = NULL;
+
+ success = e_webdav_session_getctag_sync (bbdav->priv->webdav, NULL, &new_sync_tag,
cancellable, NULL);
+ if (!success) {
+ bbdav->priv->ctag_supported = g_cancellable_set_error_if_cancelled (cancellable,
error);
+ if (bbdav->priv->ctag_supported || !bbdav->priv->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;
+ return TRUE;
+ }
-static void
-e_book_backend_webdav_start_view (EBookBackend *backend,
- EDataBookView *book_view)
-{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- EBookBackendWebdavPrivate *priv = webdav->priv;
- EBookBackendSExp *sexp;
- const gchar *query;
- GList *contacts;
- GList *l;
-
- sexp = e_data_book_view_get_sexp (book_view);
- query = e_book_backend_sexp_text (sexp);
-
- g_mutex_lock (&priv->cache_lock);
- contacts = e_book_backend_cache_get_contacts (priv->cache, query);
- g_mutex_unlock (&priv->cache_lock);
-
- for (l = contacts; l != NULL; l = g_list_next (l)) {
- EContact *contact = l->data;
- e_data_book_view_notify_update (book_view, contact);
- g_object_unref (contact);
+ *out_new_sync_tag = new_sync_tag;
}
- g_list_free (contacts);
- /* this way the UI is notified about cached contacts immediately,
- * and the update thread notifies about possible changes only */
- e_data_book_view_notify_complete (book_view, NULL /* Success */);
+ xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
+ g_return_val_if_fail (xml != NULL, FALSE);
- if (e_backend_get_online (E_BACKEND (backend))) {
- WebdavBackendSearchClosure *closure;
+ e_xml_document_start_element (xml, NULL, "prop");
+ e_xml_document_add_empty_element (xml, NULL, "getetag");
+ e_xml_document_end_element (xml); /* prop */
- closure = init_closure (
- book_view, E_BOOK_BACKEND_WEBDAV (backend));
+ known_items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, e_book_meta_backend_info_free);
- closure->thread = g_thread_new (
- NULL, book_view_thread, book_view);
+ success = e_webdav_session_propfind_sync (bbdav->priv->webdav, NULL,
E_WEBDAV_DEPTH_THIS_AND_CHILDREN, xml,
+ ebb_webdav_get_contact_items_cb, known_items, cancellable, error);
- e_flag_wait (closure->running);
- }
-}
+ g_object_unref (xml);
-static void
-e_book_backend_webdav_stop_view (EBookBackend *backend,
- EDataBookView *book_view)
-{
- WebdavBackendSearchClosure *closure;
- gboolean need_join;
+ if (success) {
+ EBookCache *book_cache;
+ WebDAVChangesData ccd;
- if (!e_backend_get_online (E_BACKEND (backend)))
- return;
+ ccd.out_modified_objects = out_modified_objects;
+ ccd.out_removed_objects = out_removed_objects;
+ ccd.known_items = known_items;
- closure = get_closure (book_view);
- if (closure == NULL)
- return;
+ book_cache = e_book_meta_backend_ref_cache (meta_backend);
- need_join = e_flag_is_set (closure->running);
- e_flag_clear (closure->running);
+ success = e_book_cache_search_with_callback (book_cache, NULL, ebb_webdav_search_changes_cb,
&ccd, cancellable, error);
- if (need_join) {
- g_thread_join (closure->thread);
- closure->thread = NULL;
+ g_clear_object (&book_cache);
}
-}
-/** authentication callback for libsoup */
-static void
-soup_authenticate (SoupSession *session,
- SoupMessage *message,
- SoupAuth *auth,
- gboolean retrying,
- gpointer data)
-{
- EBookBackendWebdav *webdav = data;
- EBookBackendWebdavPrivate *priv = webdav->priv;
-
- if (retrying)
- return;
-
- if (!priv->username || !*priv->username || !priv->password)
- soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
- else
- soup_auth_authenticate (auth, priv->username, priv->password);
-}
-
-static void
-e_book_backend_webdav_notify_online_cb (EBookBackend *backend,
- GParamSpec *pspec)
-{
- gboolean online;
+ if (!success) {
+ g_hash_table_destroy (known_items);
+ return FALSE;
+ }
- /* set_mode is called before the backend is loaded */
- if (!e_book_backend_is_opened (backend))
- return;
+ 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_book_meta_backend_info_copy
(value));
+ }
- /* XXX Could just use a property binding for this.
- * EBackend:online --> EBookBackend:writable */
- online = e_backend_get_online (E_BACKEND (backend));
- e_book_backend_set_writable (backend, online);
-}
+ g_hash_table_destroy (known_items);
-static void
-book_backend_webdav_dispose (GObject *object)
-{
- EBookBackendWebdavPrivate *priv;
+ if (*out_created_objects || *out_modified_objects) {
+ GSList *link, *set2 = *out_modified_objects;
- priv = E_BOOK_BACKEND_WEBDAV_GET_PRIVATE (object);
+ if (*out_created_objects) {
+ link = *out_created_objects;
+ } else {
+ link = set2;
+ set2 = NULL;
+ }
- g_clear_object (&priv->session);
- g_clear_object (&priv->cache);
+ do {
+ success = ebb_webdav_multiget_from_sets_sync (bbdav, &link, &set2, cancellable,
error);
+ } while (success && link);
+ }
- /* Chain up to parent's dispose() method. */
- G_OBJECT_CLASS (e_book_backend_webdav_parent_class)->dispose (object);
+ return success;
}
-static void
-book_backend_webdav_finalize (GObject *object)
+static gboolean
+ebb_webdav_extract_existing_cb (EWebDAVSession *webdav,
+ xmlXPathContextPtr xpath_ctx,
+ const gchar *xpath_prop_prefix,
+ const SoupURI *request_uri,
+ const gchar *href,
+ guint status_code,
+ gpointer user_data)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (object);
- EBookBackendWebdavPrivate *priv = webdav->priv;
+ GSList **out_existing_objects = user_data;
- g_free (priv->uri);
- g_free (priv->username);
- g_free (priv->password);
+ g_return_val_if_fail (out_existing_objects != NULL, FALSE);
- g_mutex_clear (&priv->cache_lock);
- g_mutex_clear (&priv->update_lock);
+ if (!xpath_prop_prefix) {
+ e_xml_xpath_context_register_namespaces (xpath_ctx, "C", E_WEBDAV_NS_CARDDAV, NULL);
+ } else if (status_code == SOUP_STATUS_OK) {
+ gchar *etag;
+ gchar *address_data;
- /* Chain up to parent's finalize() method. */
- G_OBJECT_CLASS (e_book_backend_webdav_parent_class)->finalize (object);
-}
+ g_return_val_if_fail (href != NULL, FALSE);
-static gchar *
-book_backend_webdav_get_backend_property (EBookBackend *backend,
- const gchar *prop_name)
-{
- g_return_val_if_fail (prop_name != NULL, NULL);
+ etag = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:getetag", xpath_prop_prefix);
+ address_data = e_xml_xpath_eval_as_string (xpath_ctx, "%s/C:address-data", xpath_prop_prefix);
- if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
- return g_strdup ("net,do-initial-query,contact-lists,refresh-supported");
+ if (address_data) {
+ EContact *contact;
- } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
- return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
+ contact = e_contact_new_from_vcard (address_data);
+ if (contact) {
+ const gchar *uid;
- } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
- GString *fields;
- gint ii;
+ uid = e_contact_get_const (contact, E_CONTACT_UID);
- fields = g_string_sized_new (1024);
+ if (uid) {
+ etag = e_webdav_session_util_maybe_dequote (etag);
+ *out_existing_objects = g_slist_prepend (*out_existing_objects,
+ e_book_meta_backend_info_new (uid, etag, NULL, href));
+ }
- /* we support everything */
- for (ii = 1; ii < E_CONTACT_FIELD_LAST; ii++) {
- if (fields->len > 0)
- g_string_append_c (fields, ',');
- g_string_append (fields, e_contact_field_name (ii));
+ g_object_unref (contact);
+ }
}
- return g_string_free (fields, FALSE);
+ g_free (address_data);
+ g_free (etag);
}
- /* Chain up to parent's get_backend_property() method. */
- return E_BOOK_BACKEND_CLASS (e_book_backend_webdav_parent_class)->
- get_backend_property (backend, prop_name);
+ return TRUE;
}
static gboolean
-book_backend_webdav_test_can_connect (EBookBackendWebdav *webdav,
- gchar **out_certificate_pem,
- GTlsCertificateFlags *out_certificate_errors,
- GCancellable *cancellable,
- GError **error)
+ebb_webdav_list_existing_sync (EBookMetaBackend *meta_backend,
+ gchar **out_new_sync_tag,
+ GSList **out_existing_objects,
+ GCancellable *cancellable,
+ GError **error)
{
- SoupMessage *message;
- gboolean res = FALSE;
+ EBookBackendWebDAV *bbdav;
+ EXmlDocument *xml;
+ gboolean success;
- g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (webdav), FALSE);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+ g_return_val_if_fail (out_existing_objects != NULL, FALSE);
- /* Send a PROPFIND to test whether user/password is correct. */
- message = send_propfind (webdav, cancellable, error);
- if (!message)
- return FALSE;
+ *out_existing_objects = NULL;
- switch (message->status_code) {
- case SOUP_STATUS_OK:
- case SOUP_STATUS_MULTI_STATUS:
- res = TRUE;
- break;
-
- case SOUP_STATUS_UNAUTHORIZED:
- case SOUP_STATUS_PROXY_UNAUTHORIZED:
- g_free (webdav->priv->username);
- webdav->priv->username = NULL;
- g_free (webdav->priv->password);
- webdav->priv->password = NULL;
- g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED,
- e_client_error_to_string (E_CLIENT_ERROR_AUTHENTICATION_FAILED));
- break;
-
- case SOUP_STATUS_FORBIDDEN:
- g_free (webdav->priv->username);
- webdav->priv->username = NULL;
- g_free (webdav->priv->password);
- webdav->priv->password = NULL;
- g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_REQUIRED,
- e_client_error_to_string (E_CLIENT_ERROR_AUTHENTICATION_REQUIRED));
- break;
-
- case SOUP_STATUS_SSL_FAILED:
- if (out_certificate_pem && out_certificate_errors) {
- GTlsCertificate *certificate = NULL;
-
- g_object_get (G_OBJECT (message),
- "tls-certificate", &certificate,
- "tls-errors", out_certificate_errors,
- NULL);
-
- if (certificate) {
- g_object_get (certificate, "certificate-pem", out_certificate_pem,
NULL);
- g_object_unref (certificate);
- }
- }
-
- g_set_error_literal (
- error, SOUP_HTTP_ERROR,
- message->status_code,
- message->reason_phrase);
- break;
-
- default:
- g_set_error_literal (
- error, SOUP_HTTP_ERROR,
- message->status_code,
- message->reason_phrase);
- break;
- }
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- g_object_unref (message);
+ xml = e_xml_document_new (E_WEBDAV_NS_CARDDAV, "addressbook-query");
+ g_return_val_if_fail (xml != NULL, FALSE);
- return res;
-}
-
-static gboolean
-book_backend_webdav_open_sync (EBookBackend *backend,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- ESourceAuthentication *auth_extension;
- ESourceOffline *offline_extension;
- ESourceWebdav *webdav_extension;
- ESource *source;
- const gchar *extension_name;
- const gchar *cache_dir;
- gchar *filename;
- SoupSession *session;
- SoupURI *suri;
- gboolean success = TRUE;
-
- /* will try fetch ctag for the first time, if it fails then sets this to FALSE */
- webdav->priv->supports_getctag = TRUE;
-
- source = e_backend_get_source (E_BACKEND (backend));
- cache_dir = e_book_backend_get_cache_dir (backend);
-
- extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
- auth_extension = e_source_get_extension (source, extension_name);
-
- extension_name = E_SOURCE_EXTENSION_OFFLINE;
- offline_extension = e_source_get_extension (source, extension_name);
-
- extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
- webdav_extension = e_source_get_extension (source, extension_name);
-
- webdav->priv->marked_for_offline =
- e_source_offline_get_stay_synchronized (offline_extension);
-
- if (!e_backend_get_online (E_BACKEND (backend)) &&
- !webdav->priv->marked_for_offline ) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE,
- e_client_error_to_string (
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE));
- return FALSE;
- }
+ e_xml_document_add_namespaces (xml, "D", E_WEBDAV_NS_DAV, NULL);
- suri = e_source_webdav_dup_soup_uri (webdav_extension);
-
- webdav->priv->uri = soup_uri_to_string (suri, FALSE);
- if (!webdav->priv->uri || !*webdav->priv->uri) {
- g_free (webdav->priv->uri);
- webdav->priv->uri = NULL;
- soup_uri_free (suri);
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("Cannot transform SoupURI to string"));
- return FALSE;
- }
+ e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
+ e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "getetag");
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CARDDAV, "address-data");
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CARDDAV, "prop");
+ e_xml_document_add_attribute (xml, NULL, "name", "VERSION");
+ e_xml_document_end_element (xml); /* prop / VERSION */
+ e_xml_document_start_element (xml, E_WEBDAV_NS_CARDDAV, "prop");
+ e_xml_document_add_attribute (xml, NULL, "name", "UID");
+ e_xml_document_end_element (xml); /* prop / UID */
+ e_xml_document_end_element (xml); /* address-data */
+ e_xml_document_end_element (xml); /* prop */
- g_mutex_lock (&webdav->priv->cache_lock);
+ success = e_webdav_session_report_sync (bbdav->priv->webdav, NULL, E_WEBDAV_DEPTH_THIS, xml,
+ ebb_webdav_extract_existing_cb, out_existing_objects, NULL, NULL, cancellable, error);
- /* make sure the uri ends with a forward slash */
- if (webdav->priv->uri[strlen (webdav->priv->uri) - 1] != '/') {
- gchar *tmp = webdav->priv->uri;
- webdav->priv->uri = g_strconcat (tmp, "/", NULL);
- g_free (tmp);
- }
+ g_object_unref (xml);
- if (!webdav->priv->cache) {
- filename = g_build_filename (cache_dir, "cache.xml", NULL);
- webdav->priv->cache = e_book_backend_cache_new (filename);
- g_free (filename);
- }
- g_mutex_unlock (&webdav->priv->cache_lock);
+ if (success)
+ *out_existing_objects = g_slist_reverse (*out_existing_objects);
- session = soup_session_sync_new ();
- g_object_set (
- session,
- SOUP_SESSION_TIMEOUT, 90,
- SOUP_SESSION_SSL_STRICT, TRUE,
- SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
- SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
- NULL);
-
- e_binding_bind_property (
- backend, "proxy-resolver",
- session, "proxy-resolver",
- G_BINDING_SYNC_CREATE);
-
- e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
-
- g_signal_connect (
- session, "authenticate",
- G_CALLBACK (soup_authenticate), webdav);
-
- webdav->priv->session = session;
- webdav_debug_setup (webdav->priv->session);
+ return success;
+}
- e_backend_set_online (E_BACKEND (backend), TRUE);
- e_book_backend_set_writable (backend, TRUE);
+static gchar *
+ebb_webdav_uid_to_uri (EBookBackendWebDAV *bbdav,
+ const gchar *uid,
+ const gchar *extension)
+{
+ ESourceWebdav *webdav_extension;
+ SoupURI *soup_uri;
+ gchar *uri, *tmp, *filename;
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (bbdav), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
- if (e_source_authentication_required (auth_extension)) {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ webdav_extension = e_source_get_extension (e_backend_get_source (E_BACKEND (bbdav)),
E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+ g_return_val_if_fail (soup_uri != NULL, NULL);
- success = e_backend_credentials_required_sync (E_BACKEND (backend),
- E_SOURCE_CREDENTIALS_REASON_REQUIRED, NULL, 0, NULL,
- cancellable, error);
+ if (extension) {
+ tmp = g_strconcat (uid, extension, NULL);
+ filename = soup_uri_encode (tmp, NULL);
+ g_free (tmp);
} else {
- gchar *certificate_pem = NULL;
- GTlsCertificateFlags certificate_errors = 0;
- GError *local_error = NULL;
-
- success = book_backend_webdav_test_can_connect (webdav, &certificate_pem,
&certificate_errors, cancellable, &local_error);
- if (!success && !g_cancellable_is_cancelled (cancellable)) {
- ESourceCredentialsReason reason;
- GError *local_error2 = NULL;
-
- if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
- reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
- e_source_set_connection_status (source,
E_SOURCE_CONNECTION_STATUS_SSL_FAILED);
- } else if (g_error_matches (local_error, E_CLIENT_ERROR,
E_CLIENT_ERROR_AUTHENTICATION_FAILED) ||
- g_error_matches (local_error, E_CLIENT_ERROR,
E_CLIENT_ERROR_AUTHENTICATION_REQUIRED)) {
- reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
- } else {
- reason = E_SOURCE_CREDENTIALS_REASON_ERROR;
- }
-
- if (!e_backend_credentials_required_sync (E_BACKEND (backend), reason,
certificate_pem, certificate_errors,
- local_error, cancellable, &local_error2)) {
- g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC,
local_error2 ? local_error2->message : "Unknown error");
- }
-
- if (!local_error2 && g_error_matches (local_error, SOUP_HTTP_ERROR,
SOUP_STATUS_SSL_FAILED)) {
- /* These cerificate errors are treated through the authentication */
- g_clear_error (&local_error);
- } else {
- g_propagate_error (error, local_error);
- local_error = NULL;
- }
-
- g_clear_error (&local_error2);
- } else {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
- }
-
- g_free (certificate_pem);
-
- if (local_error)
- g_propagate_error (error, local_error);
+ filename = soup_uri_encode (uid, NULL);
}
- soup_uri_free (suri);
+ if (soup_uri->path) {
+ gchar *slash = strrchr (soup_uri->path, '/');
- return success;
-}
+ if (slash && !slash[1])
+ *slash = '\0';
+ }
-static gboolean
-webdav_can_use_uid (const gchar *uid)
-{
- const gchar *ptr;
+ soup_uri_set_user (soup_uri, NULL);
+ soup_uri_set_password (soup_uri, NULL);
- if (!uid || !*uid)
- return FALSE;
+ tmp = g_strconcat (soup_uri->path && *soup_uri->path ? soup_uri->path : "", "/", filename, NULL);
+ soup_uri_set_path (soup_uri, tmp);
+ g_free (tmp);
- for (ptr = uid; *ptr; ptr++) {
- if ((*ptr >= 'a' && *ptr <= 'z') ||
- (*ptr >= 'A' && *ptr <= 'Z') ||
- (*ptr >= '0' && *ptr <= '9') ||
- strchr (".-@", *ptr) != NULL)
- continue;
+ uri = soup_uri_to_string (soup_uri, FALSE);
- return FALSE;
- }
+ soup_uri_free (soup_uri);
+ g_free (filename);
- return TRUE;
+ return uri;
}
static gboolean
-book_backend_webdav_create_contacts_sync (EBookBackend *backend,
- const gchar * const *vcards,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
+ebb_webdav_load_contact_sync (EBookMetaBackend *meta_backend,
+ const gchar *uid,
+ const gchar *extra,
+ EContact **out_contact,
+ gchar **out_extra,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- EContact *contact;
- gchar *uid, *href;
- const gchar *orig_uid;
- guint status;
- gchar *status_reason = NULL, *stored_etag;
-
- /* We make the assumption that the vCard list we're passed is
- * always exactly one element long, since we haven't specified
- * "bulk-adds" in our static capability list. This is because
- * there is no way to roll back changes in case of an error. */
- if (g_strv_length ((gchar **) vcards) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk additions"));
- return FALSE;
- }
-
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_REPOSITORY_OFFLINE,
- e_client_error_to_string (
- E_CLIENT_ERROR_REPOSITORY_OFFLINE));
- return FALSE;
- }
+ EBookBackendWebDAV *bbdav;
+ gchar *uri = NULL, *href = NULL, *etag = NULL, *bytes = NULL;
+ gsize length = -1;
+ gboolean success = FALSE;
+ GError *local_error = NULL;
- contact = e_contact_new_from_vcard (vcards[0]);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_contact != NULL, FALSE);
- orig_uid = e_contact_get_const (contact, E_CONTACT_UID);
- if (orig_uid && *orig_uid && webdav_can_use_uid (orig_uid) && !e_book_backend_cache_check_contact
(webdav->priv->cache, orig_uid)) {
- uid = g_strdup (orig_uid);
- } else {
- uid = NULL;
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- do {
- g_free (uid);
+ if (extra && *extra) {
+ uri = g_strdup (extra);
- /* do 3 random() calls to construct a unique ID... poor way but should be
- * good enough for us */
- uid = g_strdup_printf ("%08X-%08X-%08X", g_random_int (), g_random_int (),
g_random_int ());
+ success = e_webdav_session_get_data_sync (bbdav->priv->webdav, uri, &href, &etag, &bytes,
&length, cancellable, &local_error);
- } while (e_book_backend_cache_check_contact (webdav->priv->cache, uid) &&
- !g_cancellable_is_cancelled (cancellable));
-
- e_contact_set (contact, E_CONTACT_UID, uid);
+ if (!success) {
+ g_free (uri);
+ uri = NULL;
+ }
}
- href = g_strconcat (webdav->priv->uri, uid, ".vcf", NULL);
+ if (!success) {
+ uri = ebb_webdav_uid_to_uri (bbdav, uid, ".vcf");
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ g_clear_error (&local_error);
- /* kill WEBDAV_CONTACT_ETAG field (might have been set by some other backend) */
- webdav_contact_set_href (contact, NULL);
- webdav_contact_set_etag (contact, NULL);
+ success = e_webdav_session_get_data_sync (bbdav->priv->webdav, uri, &href, &etag, &bytes,
&length, cancellable, &local_error);
+ if (!success && !g_cancellable_is_cancelled (cancellable) &&
+ g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ g_free (uri);
+ uri = ebb_webdav_uid_to_uri (bbdav, uid, NULL);
- status = upload_contact (webdav, href, contact, &status_reason, cancellable);
- g_free (href);
+ if (uri) {
+ g_clear_error (&local_error);
- if (status != 201 && status != 204) {
- g_object_unref (contact);
- if (status == 401 || status == 407) {
- webdav_handle_auth_request (webdav, error);
- } else {
- g_set_error (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("Create resource ā%sā failed with HTTP status %d (%s)"),
- uid, status, status_reason);
+ success = e_webdav_session_get_data_sync (bbdav->priv->webdav, uri, &href,
&etag, &bytes, &length, cancellable, &local_error);
+ }
}
- g_free (uid);
- g_free (status_reason);
- return FALSE;
}
- g_free (status_reason);
- g_free (uid);
+ if (success) {
+ *out_contact = NULL;
- /* PUT request didn't return an etag? try downloading to get one */
- stored_etag = webdav_contact_get_etag (contact);
- if (!stored_etag) {
- gchar *href;
- EContact *new_contact = NULL;
+ if (href && etag && bytes && length != ((gsize) -1)) {
+ EContact *contact;
- href = webdav_contact_get_href (contact);
- if (href) {
- new_contact = download_contact (webdav, href, cancellable);
- g_free (href);
+ contact = e_contact_new_from_vcard (bytes);
+ if (contact) {
+ ebb_webdav_set_contact_etag (contact, etag);
+ *out_contact = contact;
+ }
}
- g_object_unref (contact);
-
- if (new_contact == NULL) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- e_client_error_to_string (
- E_CLIENT_ERROR_OTHER_ERROR));
- return FALSE;
+ if (!*out_contact) {
+ success = FALSE;
+ g_propagate_error (&local_error, EDB_ERROR_EX (E_DATA_BOOK_STATUS_OTHER_ERROR,
_("Received object is not a valid vCard")));
}
- contact = new_contact;
- } else {
- g_free (stored_etag);
}
- g_mutex_lock (&webdav->priv->cache_lock);
- e_book_backend_cache_add_contact (webdav->priv->cache, contact);
- g_mutex_unlock (&webdav->priv->cache_lock);
-
- g_queue_push_tail (out_contacts, g_object_ref (contact));
+ g_free (uri);
+ g_free (href);
+ g_free (etag);
+ g_free (bytes);
- g_object_unref (contact);
+ if (local_error)
+ g_propagate_error (error, local_error);
- return TRUE;
+ return success;
}
static gboolean
-book_backend_webdav_modify_contacts_sync (EBookBackend *backend,
- const gchar * const *vcards,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
+ebb_webdav_save_contact_sync (EBookMetaBackend *meta_backend,
+ gboolean overwrite_existing,
+ EConflictResolution conflict_resolution,
+ /* const */ EContact *contact,
+ const gchar *extra,
+ gchar **out_new_uid,
+ gchar **out_new_extra,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- EContact *contact;
- const gchar *uid;
- gchar *href, *etag;
- guint status;
- gchar *status_reason = NULL;
-
- /* We make the assumption that the vCard list we're passed is
- * always exactly one element long, since we haven't specified
- * "bulk-modifies" in our static capability list. This is because
- * there is no clean way to roll back changes in case of an error. */
- if (g_strv_length ((gchar **) vcards) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk modifications"));
- return FALSE;
- }
+ EBookBackendWebDAV *bbdav;
+ gchar *href = NULL, *etag = NULL, *uid = NULL;
+ gchar *vcard_string = NULL;
+ gboolean success;
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_REPOSITORY_OFFLINE,
- e_client_error_to_string (
- E_CLIENT_ERROR_REPOSITORY_OFFLINE));
- return FALSE;
- }
-
- /* modify contact */
- contact = e_contact_new_from_vcard (vcards[0]);
- href = webdav_contact_get_href (contact);
- status = upload_contact (webdav, href, contact, &status_reason, cancellable);
- g_free (href);
- if (status != 200 && status != 201 && status != 204) {
- g_object_unref (contact);
- if (status == 401 || status == 407) {
- webdav_handle_auth_request (webdav, error);
- g_free (status_reason);
- return FALSE;
- }
- /* data changed on server while we were editing */
- if (status == 412) {
- /* too bad no special error code in evolution for this... */
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("Contact on server changed -> not modifying"));
- g_free (status_reason);
- return FALSE;
- }
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+ g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
+ g_return_val_if_fail (out_new_uid, FALSE);
+ g_return_val_if_fail (out_new_extra, FALSE);
- g_set_error (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("Modify contact failed with HTTP status %d (%s)"),
- status, status_reason);
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- g_free (status_reason);
- return FALSE;
- }
+ uid = e_contact_get (contact, E_CONTACT_UID);
+ etag = ebb_webdav_dup_contact_etag (contact);
- g_free (status_reason);
+ ebb_webdav_set_contact_etag (contact, NULL);
- uid = e_contact_get_const (contact, E_CONTACT_UID);
- g_mutex_lock (&webdav->priv->cache_lock);
- e_book_backend_cache_remove_contact (webdav->priv->cache, uid);
+ vcard_string = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
- etag = webdav_contact_get_etag (contact);
+ if (uid && vcard_string && (!overwrite_existing || (extra && *extra))) {
+ gboolean force_write = FALSE;
- /* PUT request didn't return an etag? try downloading to get one */
- if (etag == NULL || (etag[0] == 'W' && etag[1] == '/')) {
- EContact *new_contact = NULL;
+ if (!extra || !*extra)
+ href = ebb_webdav_uid_to_uri (bbdav, uid, ".vcf");
- href = webdav_contact_get_href (contact);
- if (href) {
- new_contact = download_contact (webdav, href, cancellable);
- g_free (href);
+ 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;
+ }
}
- if (new_contact != NULL) {
- g_object_unref (contact);
- contact = new_contact;
- }
+ success = e_webdav_session_put_data_sync (bbdav->priv->webdav, (extra && *extra) ? extra :
href,
+ force_write ? "" : overwrite_existing ? etag : NULL, E_WEBDAV_CONTENT_TYPE_VCARD,
+ vcard_string, -1, out_new_extra, NULL, cancellable, error);
+
+ /* To read the component back, because server can change it */
+ if (success)
+ *out_new_uid = g_strdup (uid);
+ } else {
+ success = FALSE;
+ g_propagate_error (error, EDB_ERROR_EX (E_DATA_BOOK_STATUS_OTHER_ERROR, _("Object to save is
not a valid vCard")));
}
+ g_free (vcard_string);
+ g_free (href);
g_free (etag);
+ g_free (uid);
- e_book_backend_cache_add_contact (webdav->priv->cache, contact);
- g_mutex_unlock (&webdav->priv->cache_lock);
-
- g_queue_push_tail (out_contacts, g_object_ref (contact));
-
- g_object_unref (contact);
-
- return TRUE;
+ return success;
}
static gboolean
-book_backend_webdav_remove_contacts_sync (EBookBackend *backend,
- const gchar * const *uids,
- GCancellable *cancellable,
- GError **error)
+ebb_webdav_remove_contact_sync (EBookMetaBackend *meta_backend,
+ EConflictResolution conflict_resolution,
+ const gchar *uid,
+ const gchar *extra,
+ const gchar *object,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
+ EBookBackendWebDAV *bbdav;
EContact *contact;
- gchar *href;
- guint status;
-
- /* We make the assumption that the ID list we're passed is
- * always exactly one element long, since we haven't specified
- * "bulk-removes" in our static capability list. */
- if (g_strv_length ((gchar **) uids) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk removals"));
- return FALSE;
- }
-
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_REPOSITORY_OFFLINE,
- e_client_error_to_string (
- E_CLIENT_ERROR_REPOSITORY_OFFLINE));
- return FALSE;
- }
+ gchar *etag = NULL;
+ gboolean success;
+ GError *local_error = NULL;
- g_mutex_lock (&webdav->priv->cache_lock);
- contact = e_book_backend_cache_get_contact (webdav->priv->cache, uids[0]);
- g_mutex_unlock (&webdav->priv->cache_lock);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
- if (!contact) {
- g_set_error_literal (
- error, E_BOOK_CLIENT_ERROR,
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
- e_book_client_error_to_string (
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
- return FALSE;
- }
+ bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
- href = webdav_contact_get_href (contact);
- if (!href) {
- g_object_unref (contact);
- g_set_error (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("DELETE failed with HTTP status %d"), SOUP_STATUS_MALFORMED);
+ if (!extra || !*extra) {
+ g_propagate_error (error, EDB_ERROR (E_DATA_BOOK_STATUS_INVALID_ARG));
return FALSE;
}
- status = delete_contact (webdav, href, cancellable);
-
- g_object_unref (contact);
- g_free (href);
-
- if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
- if (status == 401 || status == 407) {
- webdav_handle_auth_request (webdav, error);
- } else {
- g_set_error (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OTHER_ERROR,
- _("DELETE failed with HTTP status %d"), status);
- }
+ contact = e_contact_new_from_vcard (object);
+ if (!contact) {
+ g_propagate_error (error, EDB_ERROR (E_DATA_BOOK_STATUS_INVALID_ARG));
return FALSE;
}
- g_mutex_lock (&webdav->priv->cache_lock);
- e_book_backend_cache_remove_contact (webdav->priv->cache, uids[0]);
- g_mutex_unlock (&webdav->priv->cache_lock);
-
- return TRUE;
-}
-
-static EContact *
-book_backend_webdav_get_contact_sync (EBookBackend *backend,
- const gchar *uid,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- EContact *contact;
+ if (conflict_resolution == E_CONFLICT_RESOLUTION_FAIL)
+ etag = ebb_webdav_dup_contact_etag (contact);
- g_mutex_lock (&webdav->priv->cache_lock);
- contact = e_book_backend_cache_get_contact (
- webdav->priv->cache, uid);
- g_mutex_unlock (&webdav->priv->cache_lock);
+ success = e_webdav_session_delete_sync (bbdav->priv->webdav, extra,
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
- if (contact && e_backend_get_online (E_BACKEND (backend))) {
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
gchar *href;
- href = webdav_contact_get_href (contact);
- g_object_unref (contact);
-
+ href = ebb_webdav_uid_to_uri (bbdav, uid, ".vcf");
if (href) {
- contact = download_contact (webdav, href, cancellable);
+ g_clear_error (&local_error);
+ success = e_webdav_session_delete_sync (bbdav->priv->webdav, href,
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
+
g_free (href);
- } else {
- contact = NULL;
}
- /* update cache as we possibly have changes */
- if (contact != NULL) {
- g_mutex_lock (&webdav->priv->cache_lock);
- e_book_backend_cache_remove_contact (
- webdav->priv->cache, uid);
- e_book_backend_cache_add_contact (
- webdav->priv->cache, contact);
- g_mutex_unlock (&webdav->priv->cache_lock);
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ href = ebb_webdav_uid_to_uri (bbdav, uid, NULL);
+ if (href) {
+ g_clear_error (&local_error);
+ success = e_webdav_session_delete_sync (bbdav->priv->webdav, href,
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
+
+ g_free (href);
+ }
}
}
- if (contact == NULL) {
- g_set_error_literal (
- error, E_BOOK_CLIENT_ERROR,
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
- e_book_client_error_to_string (
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
- return FALSE;
- }
+ g_object_unref (contact);
+ g_free (etag);
- return contact;
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
}
-static gboolean
-book_backend_webdav_get_contact_list_sync (EBookBackend *backend,
- const gchar *query,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
+static gchar *
+ebb_webdav_get_backend_property (EBookBackend *book_backend,
+ const gchar *prop_name)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- GList *contact_list;
-
- if (e_backend_get_online (E_BACKEND (backend)) &&
- e_source_get_connection_status (e_backend_get_source (E_BACKEND (backend))) ==
E_SOURCE_CONNECTION_STATUS_CONNECTED) {
- /* make sure the cache is up to date */
- if (!download_contacts (webdav, NULL, NULL, FALSE, cancellable, error))
- return FALSE;
- }
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (book_backend), NULL);
+ g_return_val_if_fail (prop_name != NULL, NULL);
- /* answer query from cache */
- g_mutex_lock (&webdav->priv->cache_lock);
- contact_list = e_book_backend_cache_get_contacts (
- webdav->priv->cache, query);
- g_mutex_unlock (&webdav->priv->cache_lock);
-
- /* This appends contact_list to out_contacts, one element at a
- * time, since GLib lacks something like g_queue_append_list().
- *
- * XXX Would be better if e_book_backend_cache_get_contacts()
- * took an output GQueue instead of returning a GList. */
- while (contact_list != NULL) {
- GList *link = contact_list;
- contact_list = g_list_remove_link (contact_list, link);
- g_queue_push_tail_link (out_contacts, link);
+ if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+ return g_strjoin (",",
+ "net",
+ "do-initial-query",
+ "contact-lists",
+ e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)),
+ NULL);
}
- return TRUE;
+ /* Chain up to parent's method. */
+ return E_BOOK_BACKEND_CLASS (e_book_backend_webdav_parent_class)->get_backend_property (book_backend,
prop_name);
}
-static ESourceAuthenticationResult
-book_backend_webdav_authenticate_sync (EBackend *backend,
- const ENamedParameters *credentials,
- gchar **out_certificate_pem,
- GTlsCertificateFlags *out_certificate_errors,
- GCancellable *cancellable,
- GError **error)
+static gchar *
+ebb_webdav_dup_contact_revision_cb (EBookCache *book_cache,
+ EContact *contact)
{
- EBookBackendWebdav *webdav = E_BOOK_BACKEND_WEBDAV (backend);
- ESourceAuthentication *auth_extension;
- ESourceAuthenticationResult result;
- ESource *source;
- const gchar *username;
- GError *local_error = NULL;
+ g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
- source = e_backend_get_source (backend);
- auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+ return ebb_webdav_dup_contact_etag (contact);
+}
- g_free (webdav->priv->username);
- webdav->priv->username = NULL;
+static void
+e_book_backend_webdav_constructed (GObject *object)
+{
+ EBookBackendWebDAV *bbdav = E_BOOK_BACKEND_WEBDAV (object);
+ EBookCache *book_cache;
- g_free (webdav->priv->password);
- webdav->priv->password = g_strdup (e_named_parameters_get (credentials,
E_SOURCE_CREDENTIAL_PASSWORD));
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_webdav_parent_class)->constructed (object);
- username = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_USERNAME);
- if (username && *username) {
- webdav->priv->username = g_strdup (username);
- } else {
- webdav->priv->username = e_source_authentication_dup_user (auth_extension);
- }
+ book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (bbdav));
- if (book_backend_webdav_test_can_connect (webdav, out_certificate_pem, out_certificate_errors,
cancellable, &local_error)) {
- result = E_SOURCE_AUTHENTICATION_ACCEPTED;
- } else if (g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED) ||
- g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_REQUIRED)) {
- if (!e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_PASSWORD) ||
- g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_REQUIRED))
- result = E_SOURCE_AUTHENTICATION_REQUIRED;
- else
- result = E_SOURCE_AUTHENTICATION_REJECTED;
- g_clear_error (&local_error);
- } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
- result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
- g_propagate_error (error, local_error);
- } else {
- result = E_SOURCE_AUTHENTICATION_ERROR;
- g_propagate_error (error, local_error);
- }
+ g_signal_connect (book_cache, "dup-contact-revision",
+ G_CALLBACK (ebb_webdav_dup_contact_revision_cb), NULL);
- return result;
+ g_clear_object (&book_cache);
}
-static gboolean
-e_book_backend_webdav_refresh_sync (EBookBackend *book_backend,
- GCancellable *cancellable,
- GError **error)
+static void
+e_book_backend_webdav_dispose (GObject *object)
{
- EBackend *backend;
-
- g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (book_backend), FALSE);
+ EBookBackendWebDAV *bbdav = E_BOOK_BACKEND_WEBDAV (object);
- backend = E_BACKEND (book_backend);
+ g_clear_object (&bbdav->priv->webdav);
- if (!e_backend_get_online (backend) &&
- e_backend_is_destination_reachable (backend, cancellable, NULL)) {
- e_backend_set_online (backend, TRUE);
- }
-
- if (e_backend_get_online (backend) && !g_cancellable_is_cancelled (cancellable)) {
- return download_contacts (E_BOOK_BACKEND_WEBDAV (book_backend), NULL, NULL, TRUE,
cancellable, error);
- }
-
- return TRUE;
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_webdav_parent_class)->dispose (object);
}
static void
-e_book_backend_webdav_class_init (EBookBackendWebdavClass *class)
+e_book_backend_webdav_init (EBookBackendWebDAV *bbdav)
{
- GObjectClass *object_class;
- EBackendClass *backend_class;
- EBookBackendClass *book_backend_class;
-
- g_type_class_add_private (class, sizeof (EBookBackendWebdavPrivate));
-
- object_class = G_OBJECT_CLASS (class);
- object_class->dispose = book_backend_webdav_dispose;
- object_class->finalize = book_backend_webdav_finalize;
-
- backend_class = E_BACKEND_CLASS (class);
- backend_class->authenticate_sync = book_backend_webdav_authenticate_sync;
-
- book_backend_class = E_BOOK_BACKEND_CLASS (class);
- book_backend_class->get_backend_property = book_backend_webdav_get_backend_property;
- book_backend_class->open_sync = book_backend_webdav_open_sync;
- book_backend_class->create_contacts_sync = book_backend_webdav_create_contacts_sync;
- book_backend_class->modify_contacts_sync = book_backend_webdav_modify_contacts_sync;
- book_backend_class->remove_contacts_sync = book_backend_webdav_remove_contacts_sync;
- book_backend_class->get_contact_sync = book_backend_webdav_get_contact_sync;
- book_backend_class->get_contact_list_sync = book_backend_webdav_get_contact_list_sync;
- book_backend_class->start_view = e_book_backend_webdav_start_view;
- book_backend_class->stop_view = e_book_backend_webdav_stop_view;
- book_backend_class->refresh_sync = e_book_backend_webdav_refresh_sync;
+ bbdav->priv = G_TYPE_INSTANCE_GET_PRIVATE (bbdav, E_TYPE_BOOK_BACKEND_WEBDAV,
EBookBackendWebDAVPrivate);
}
static void
-e_book_backend_webdav_init (EBookBackendWebdav *backend)
+e_book_backend_webdav_class_init (EBookBackendWebDAVClass *klass)
{
- backend->priv = E_BOOK_BACKEND_WEBDAV_GET_PRIVATE (backend);
-
- g_mutex_init (&backend->priv->cache_lock);
- g_mutex_init (&backend->priv->update_lock);
-
- g_signal_connect (
- backend, "notify::online",
- G_CALLBACK (e_book_backend_webdav_notify_online_cb), NULL);
+ GObjectClass *object_class;
+ EBookBackendClass *book_backend_class;
+ EBookMetaBackendClass *book_meta_backend_class;
+
+ g_type_class_add_private (klass, sizeof (EBookBackendWebDAVPrivate));
+
+ book_meta_backend_class = E_BOOK_META_BACKEND_CLASS (klass);
+ book_meta_backend_class->backend_module_filename = "libebookbackendwebdav.so";
+ book_meta_backend_class->backend_factory_type_name = "EBookBackendWebdavFactory";
+ book_meta_backend_class->connect_sync = ebb_webdav_connect_sync;
+ book_meta_backend_class->disconnect_sync = ebb_webdav_disconnect_sync;
+ book_meta_backend_class->get_changes_sync = ebb_webdav_get_changes_sync;
+ book_meta_backend_class->list_existing_sync = ebb_webdav_list_existing_sync;
+ book_meta_backend_class->load_contact_sync = ebb_webdav_load_contact_sync;
+ book_meta_backend_class->save_contact_sync = ebb_webdav_save_contact_sync;
+ book_meta_backend_class->remove_contact_sync = ebb_webdav_remove_contact_sync;
+
+ book_backend_class = E_BOOK_BACKEND_CLASS (klass);
+ book_backend_class->get_backend_property = ebb_webdav_get_backend_property;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = e_book_backend_webdav_constructed;
+ object_class->dispose = e_book_backend_webdav_dispose;
}
-
diff --git a/src/addressbook/backends/webdav/e-book-backend-webdav.h
b/src/addressbook/backends/webdav/e-book-backend-webdav.h
index e7b1ed0..93162fb 100644
--- a/src/addressbook/backends/webdav/e-book-backend-webdav.h
+++ b/src/addressbook/backends/webdav/e-book-backend-webdav.h
@@ -27,10 +27,10 @@
(e_book_backend_webdav_get_type ())
#define E_BOOK_BACKEND_WEBDAV(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
- ((obj), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebdav))
+ ((obj), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebDAV))
#define E_BOOK_BACKEND_WEBDAV_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
- ((cls), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebdavClass))
+ ((cls), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebDAVClass))
#define E_IS_BOOK_BACKEND_WEBDAV(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), E_TYPE_BOOK_BACKEND_WEBDAV))
@@ -39,21 +39,21 @@
((cls), E_TYPE_BOOK_BACKEND_WEBDAV))
#define E_BOOK_BACKEND_WEBDAV_GET_CLASS(cls) \
(G_TYPE_INSTANCE_GET_CLASS \
- ((obj), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebdavClass))
+ ((obj), E_TYPE_BOOK_BACKEND_WEBDAV, EBookBackendWebDAVClass))
G_BEGIN_DECLS
-typedef struct _EBookBackendWebdav EBookBackendWebdav;
-typedef struct _EBookBackendWebdavClass EBookBackendWebdavClass;
-typedef struct _EBookBackendWebdavPrivate EBookBackendWebdavPrivate;
+typedef struct _EBookBackendWebDAV EBookBackendWebDAV;
+typedef struct _EBookBackendWebDAVClass EBookBackendWebDAVClass;
+typedef struct _EBookBackendWebDAVPrivate EBookBackendWebDAVPrivate;
-struct _EBookBackendWebdav {
- EBookBackend parent;
- EBookBackendWebdavPrivate *priv;
+struct _EBookBackendWebDAV {
+ EBookMetaBackend parent;
+ EBookBackendWebDAVPrivate *priv;
};
-struct _EBookBackendWebdavClass {
- EBookBackendClass parent_class;
+struct _EBookBackendWebDAVClass {
+ EBookMetaBackendClass parent_class;
};
GType e_book_backend_webdav_get_type (void);
@@ -61,4 +61,3 @@ GType e_book_backend_webdav_get_type (void);
G_END_DECLS
#endif /* E_BOOK_BACKEND_WEBDAV_H */
-
diff --git a/src/addressbook/libedata-book/e-book-cache.c b/src/addressbook/libedata-book/e-book-cache.c
index 49f809c..8b1916a 100644
--- a/src/addressbook/libedata-book/e-book-cache.c
+++ b/src/addressbook/libedata-book/e-book-cache.c
@@ -131,6 +131,7 @@ enum {
enum {
E164_CHANGED,
+ DUP_CONTACT_REVISION,
LAST_SIGNAL
};
@@ -4592,6 +4593,34 @@ e_book_cache_ref_source (EBookCache *book_cache)
}
/**
+ * e_book_cache_dup_contact_revision:
+ * @book_cache: an #EBookCache
+ * @contact: an #EContact
+ *
+ * Returns the @contact revision, used to detect changes.
+ * The returned string should be freed with g_free(), when
+ * no longer needed.
+ *
+ * Returns: (transfer full): A newly allocated string containing
+ * revision of the @contact.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_book_cache_dup_contact_revision (EBookCache *book_cache,
+ EContact *contact)
+{
+ gchar *revision = NULL;
+
+ g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
+ g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+
+ g_signal_emit (book_cache, signals[DUP_CONTACT_REVISION], 0, contact, &revision);
+
+ return revision;
+}
+
+/**
* e_book_cache_set_locale:
* @book_cache: An #EBookCache
* @lc_collate: The new locale for the cache
@@ -4790,7 +4819,7 @@ e_book_cache_put_contacts (EBookCache *book_cache,
e_cache_column_values_take_value (other_columns, EBC_COLUMN_EXTRA, g_strdup (extra));
uid = e_contact_get (contact, E_CONTACT_UID);
- rev = e_contact_get (contact, E_CONTACT_REV);
+ rev = e_book_cache_dup_contact_revision (book_cache, contact);
ebc_fill_other_columns (book_cache, contact, other_columns);
@@ -5859,6 +5888,15 @@ e_book_cache_cursor_compare_contact (EBookCache *book_cache,
return comparison;
}
+static gchar *
+ebc_dup_contact_revision (EBookCache *book_cache,
+ EContact *contact)
+{
+ g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+
+ return e_contact_get (contact, E_CONTACT_REV);
+}
+
static gboolean
e_book_cache_put_locked (ECache *cache,
const gchar *uid,
@@ -6022,6 +6060,8 @@ e_book_cache_class_init (EBookCacheClass *klass)
cache_class->remove_all_locked = e_book_cache_remove_all_locked;
cache_class->clear_offline_changes_locked = e_book_cache_clear_offline_changes_locked;
+ klass->dup_contact_revision = ebc_dup_contact_revision;
+
g_object_class_install_property (
object_class,
PROP_LOCALE,
@@ -6044,6 +6084,22 @@ e_book_cache_class_init (EBookCacheClass *klass)
G_TYPE_NONE, 2,
E_TYPE_CONTACT,
G_TYPE_BOOLEAN);
+
+ /**
+ * @EBookCache:dup-contact-revision:
+ * A signal being called to get revision of an EContact.
+ * The default implementation returns E_CONTACT_REV field value.
+ **/
+ signals[DUP_CONTACT_REVISION] = g_signal_new (
+ "dup-contact-revision",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EBookCacheClass, dup_contact_revision),
+ g_signal_accumulator_first_wins,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ E_TYPE_CONTACT);
}
static void
diff --git a/src/addressbook/libedata-book/e-book-cache.h b/src/addressbook/libedata-book/e-book-cache.h
index e36b86c..5e13799 100644
--- a/src/addressbook/libedata-book/e-book-cache.h
+++ b/src/addressbook/libedata-book/e-book-cache.h
@@ -139,9 +139,13 @@ struct _EBookCacheClass {
ECacheClass parent_class;
/* Signals */
- void (* e164_changed) (EBookCache *book_cache,
- EContact *contact,
- gboolean is_replace);
+ void (* e164_changed) (EBookCache *book_cache,
+ EContact *contact,
+ gboolean is_replace);
+
+ gchar * (* dup_contact_revision)
+ (EBookCache *book_cache,
+ EContact *contact);
/* Padding for future expansion */
gpointer reserved[10];
@@ -202,6 +206,9 @@ EBookCache * e_book_cache_new_full (const gchar *filename,
GCancellable *cancellable,
GError **error);
ESource * e_book_cache_ref_source (EBookCache *book_cache);
+gchar * e_book_cache_dup_contact_revision
+ (EBookCache *book_cache,
+ EContact *contact);
gboolean e_book_cache_set_locale (EBookCache *book_cache,
const gchar *lc_collate,
GCancellable *cancellable,
diff --git a/src/addressbook/libedata-book/e-book-meta-backend.c
b/src/addressbook/libedata-book/e-book-meta-backend.c
index b0ebbcc..ee79232 100644
--- a/src/addressbook/libedata-book/e-book-meta-backend.c
+++ b/src/addressbook/libedata-book/e-book-meta-backend.c
@@ -108,6 +108,7 @@ static gboolean ebmb_load_contact_wrapper_sync (EBookMetaBackend *meta_backend,
const gchar *uid,
const gchar *preloaded_object,
const gchar *preloaded_extra,
+ gchar **out_new_uid,
GCancellable *cancellable,
GError **error);
static gboolean ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend,
@@ -902,7 +903,7 @@ ebmb_refresh_thread_func (EBookBackend *book_backend,
goto done;
}
- success = ebmb_upload_local_changes_sync (meta_backend, book_cache, E_CONFLICT_RESOLUTION_KEEP_LOCAL,
cancellable, error);
+ success = ebmb_upload_local_changes_sync (meta_backend, book_cache, E_CONFLICT_RESOLUTION_FAIL,
cancellable, error);
while (repeat && success &&
!g_cancellable_set_error_if_cancelled (cancellable, error)) {
@@ -947,12 +948,13 @@ ebmb_refresh_thread_func (EBookBackend *book_backend,
continue;
}
- if (g_hash_table_contains (covered_uids, nfo->uid))
+ if (!*nfo->uid ||
+ g_hash_table_contains (covered_uids, nfo->uid))
continue;
g_hash_table_insert (covered_uids, nfo->uid, NULL);
- success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid,
nfo->object, nfo->extra, cancellable, &local_error);
+ success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid,
nfo->object, nfo->extra, NULL, cancellable, &local_error);
/* Do not stop on invalid objects, just notify about them later, and load as
many as possible */
if (!success && g_error_matches (local_error, E_DATA_BOOK_ERROR,
E_DATA_BOOK_STATUS_INVALID_ARG)) {
@@ -981,7 +983,10 @@ ebmb_refresh_thread_func (EBookBackend *book_backend,
continue;
}
- success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid,
nfo->object, nfo->extra, cancellable, &local_error);
+ if (!*nfo->uid)
+ continue;
+
+ success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid,
nfo->object, nfo->extra, NULL, cancellable, &local_error);
/* Do not stop on invalid objects, just notify about them later, and load as
many as possible */
if (!success && g_error_matches (local_error, E_DATA_BOOK_ERROR,
E_DATA_BOOK_STATUS_INVALID_ARG)) {
@@ -1182,6 +1187,7 @@ ebmb_load_contact_wrapper_sync (EBookMetaBackend *meta_backend,
const gchar *uid,
const gchar *preloaded_object,
const gchar *preloaded_extra,
+ gchar **out_new_uid,
GCancellable *cancellable,
GError **error)
{
@@ -1208,6 +1214,9 @@ ebmb_load_contact_wrapper_sync (EBookMetaBackend *meta_backend,
success = ebmb_put_contact (meta_backend, book_cache, offline_flag,
contact, extra ? extra : preloaded_extra, cancellable, error);
+ if (success && out_new_uid)
+ *out_new_uid = e_contact_get (contact, E_CONTACT_UID);
+
g_object_unref (contact);
g_free (extra);
@@ -1245,22 +1254,26 @@ ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend,
success = success && e_book_meta_backend_save_contact_sync (meta_backend, overwrite_existing,
conflict_resolution,
contact, extra, &new_uid, &new_extra, cancellable, error);
- if (success && new_uid) {
+ if (success && new_uid && *new_uid) {
+ gchar *loaded_uid = NULL;
+
success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, new_uid, NULL,
- new_extra ? new_extra : extra, cancellable, error);
+ new_extra ? new_extra : extra, &loaded_uid, cancellable, error);
- if (success && g_strcmp0 (new_uid, orig_uid) != 0)
+ if (success && g_strcmp0 (loaded_uid, orig_uid) != 0)
success = ebmb_maybe_remove_from_cache (meta_backend, book_cache, E_CACHE_IS_ONLINE,
orig_uid, cancellable, error);
if (success && out_new_uid)
- *out_new_uid = new_uid;
+ *out_new_uid = loaded_uid;
else
- g_free (new_uid);
+ g_free (loaded_uid);
if (out_requires_put)
*out_requires_put = FALSE;
}
+ g_free (new_uid);
+
if (success && out_new_extra)
*out_new_extra = new_extra;
else
@@ -1292,6 +1305,22 @@ ebmb_get_backend_property (EBookBackend *book_backend,
return revision;
} else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
return g_strdup (e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)));
+ } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
+ return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
+ } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
+ GString *fields;
+ gint ii;
+
+ fields = g_string_sized_new (1024);
+
+ /* Claim to support everything by default */
+ for (ii = 1; ii < E_CONTACT_FIELD_LAST; ii++) {
+ if (fields->len > 0)
+ g_string_append_c (fields, ',');
+ g_string_append (fields, e_contact_field_name (ii));
+ }
+
+ return g_string_free (fields, FALSE);
}
/* Chain up to parent's method. */
@@ -1336,8 +1365,13 @@ ebmb_open_sync (EBookBackend *book_backend,
e_book_backend_set_writable (E_BOOK_BACKEND (meta_backend),
e_book_meta_backend_get_connected_writable (meta_backend));
} else {
- if (!ebmb_connect_wrapper_sync (meta_backend, cancellable, error))
+ if (!ebmb_connect_wrapper_sync (meta_backend, cancellable, error)) {
+ g_mutex_lock (&meta_backend->priv->property_lock);
+ meta_backend->priv->refresh_after_authenticate = TRUE;
+ g_mutex_unlock (&meta_backend->priv->property_lock);
+
return FALSE;
+ }
}
ebmb_schedule_refresh (E_BOOK_META_BACKEND (book_backend));
@@ -1491,7 +1525,7 @@ ebmb_create_contact_sync (EBookMetaBackend *meta_backend,
if (out_new_contact) {
if (new_uid) {
if (!e_book_cache_get_contact (book_cache, new_uid, FALSE, out_new_contact,
cancellable, NULL))
- *out_new_contact = NULL;
+ *out_new_contact = g_object_ref (contact);
} else {
*out_new_contact = g_object_ref (contact);
}
@@ -1514,7 +1548,7 @@ ebmb_create_contacts_sync (EBookBackend *book_backend,
EBookMetaBackend *meta_backend;
EBookCache *book_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
gint ii;
gboolean success = TRUE;
@@ -1650,7 +1684,7 @@ ebmb_modify_contacts_sync (EBookBackend *book_backend,
EBookMetaBackend *meta_backend;
EBookCache *book_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
gint ii;
gboolean success = TRUE;
@@ -1772,7 +1806,7 @@ ebmb_remove_contacts_sync (EBookBackend *book_backend,
EBookMetaBackend *meta_backend;
EBookCache *book_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
gint ii;
gboolean success = TRUE;
@@ -1822,7 +1856,7 @@ ebmb_get_contact_sync (EBookBackend *book_backend,
GError *local_error = NULL;
g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
- g_return_val_if_fail (uid != NULL, NULL);
+ g_return_val_if_fail (uid && *uid, NULL);
meta_backend = E_BOOK_META_BACKEND (book_backend);
book_cache = e_book_meta_backend_ref_cache (meta_backend);
@@ -1831,6 +1865,7 @@ ebmb_get_contact_sync (EBookBackend *book_backend,
if (!e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable, &local_error) &&
g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+ gchar *loaded_uid = NULL;
gboolean found = FALSE;
g_clear_error (&local_error);
@@ -1838,12 +1873,14 @@ ebmb_get_contact_sync (EBookBackend *book_backend,
/* Ignore errors here, just try whether it's on the remote side, but not in the local cache */
if (e_backend_get_online (E_BACKEND (meta_backend)) &&
ebmb_connect_wrapper_sync (meta_backend, cancellable, NULL) &&
- ebmb_load_contact_wrapper_sync (meta_backend, book_cache, uid, NULL, NULL, cancellable,
NULL)) {
- found = e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable,
NULL);
+ ebmb_load_contact_wrapper_sync (meta_backend, book_cache, uid, NULL, NULL, &loaded_uid,
cancellable, NULL)) {
+ found = e_book_cache_get_contact (book_cache, loaded_uid, FALSE, &contact,
cancellable, NULL);
}
if (!found)
g_propagate_error (error, e_data_book_create_error
(E_DATA_BOOK_STATUS_CONTACT_NOT_FOUND, NULL));
+
+ g_free (loaded_uid);
} else if (local_error) {
g_propagate_error (error, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR,
local_error->message));
g_clear_error (&local_error);
diff --git a/src/calendar/backends/caldav/e-cal-backend-caldav.c
b/src/calendar/backends/caldav/e-cal-backend-caldav.c
index 38b9594..f3800b7 100644
--- a/src/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/src/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -26,10 +26,6 @@
#include "e-cal-backend-caldav.h"
-#define E_CAL_BACKEND_CALDAV_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE \
- ((obj), E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate))
-
#define E_CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget
request */
#define E_CALDAV_X_ETAG "X-EVOLUTION-CALDAV-ETAG"
@@ -37,9 +33,8 @@
#define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
#define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
-/* Private part of the ECalBackendHttp structure */
struct _ECalBackendCalDAVPrivate {
- /* The main soup session */
+ /* The main WebDAV session */
EWebDAVSession *webdav;
/* support for 'getctag' extension */
@@ -155,6 +150,7 @@ ecb_caldav_connect_sync (ECalMetaBackend *meta_backend,
g_clear_object (&cal_cache);
soup_uri_free (soup_uri);
}
+
if (success) {
gchar *ctag = NULL;
@@ -447,9 +443,20 @@ ecb_caldav_multiget_from_sets_sync (ECalBackendCalDAV *cbdav,
if (!success)
break;
} else {
+ SoupURI *suri;
+ gchar *path = NULL;
+
+ suri = soup_uri_new (nfo->extra);
+ if (suri) {
+ path = soup_uri_to_string (suri, TRUE);
+ soup_uri_free (suri);
+ }
+
e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "href");
- e_xml_document_write_string (xml, nfo->extra);
+ e_xml_document_write_string (xml, path ? path : nfo->extra);
e_xml_document_end_element (xml); /* href */
+
+ g_free (path);
}
}
@@ -491,11 +498,13 @@ ecb_caldav_get_calendar_items_cb (EWebDAVSession *webdav,
g_return_val_if_fail (href != NULL, FALSE);
/* Skip collection resource, if returned by the server (like iCloud.com does) */
- if (request_uri && request_uri->path && g_str_has_suffix (href, request_uri->path))
+ if (g_str_has_suffix (href, "/") ||
+ (request_uri && request_uri->path && g_str_has_suffix (href, request_uri->path)))
return TRUE;
etag = e_webdav_session_util_maybe_dequote (e_xml_xpath_eval_as_string (xpath_ctx,
"%s/D:getetag", xpath_prop_prefix));
- g_return_val_if_fail (etag != NULL, FALSE);
+ /* Return 'TRUE' to not stop on faulty data from the server */
+ g_return_val_if_fail (etag != NULL, TRUE);
/* UID is unknown at this moment */
nfo = e_cal_meta_backend_info_new ("", etag, NULL, href);
@@ -598,7 +607,7 @@ ecb_caldav_get_changes_sync (ECalMetaBackend *meta_backend,
success = e_webdav_session_getctag_sync (cbdav->priv->webdav, NULL, &new_sync_tag,
cancellable, NULL);
if (!success) {
cbdav->priv->ctag_supported = g_cancellable_set_error_if_cancelled (cancellable,
error);
- if (cbdav->priv->ctag_supported)
+ if (cbdav->priv->ctag_supported || !cbdav->priv->webdav)
return FALSE;
} else if (!is_repeat && new_sync_tag && last_sync_tag && g_strcmp0 (last_sync_tag,
new_sync_tag) == 0) {
*out_new_sync_tag = new_sync_tag;
@@ -874,6 +883,9 @@ ecb_caldav_uid_to_uri (ECalBackendCalDAV *cbdav,
*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);
@@ -899,6 +911,7 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
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_CALDAV (meta_backend), FALSE);
g_return_val_if_fail (uid != NULL, FALSE);
@@ -909,7 +922,7 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
if (extra && *extra) {
uri = g_strdup (extra);
- success = e_webdav_session_get_data_sync (cbdav->priv->webdav, uri, &href, &etag, &bytes,
&length, cancellable, error);
+ success = e_webdav_session_get_data_sync (cbdav->priv->webdav, uri, &href, &etag, &bytes,
&length, cancellable, &local_error);
if (!success) {
g_free (uri);
@@ -918,11 +931,11 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
}
if (!success) {
- GError *local_error = NULL;
-
uri = ecb_caldav_uid_to_uri (cbdav, uid, ".ics");
g_return_val_if_fail (uri != NULL, FALSE);
+ g_clear_error (&local_error);
+
success = e_webdav_session_get_data_sync (cbdav->priv->webdav, uri, &href, &etag, &bytes,
&length, cancellable, &local_error);
if (!success && !g_cancellable_is_cancelled (cancellable) &&
g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
@@ -932,10 +945,8 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
if (uri) {
g_clear_error (&local_error);
- success = e_webdav_session_get_data_sync (cbdav->priv->webdav, uri, &href,
&etag, &bytes, &length, cancellable, error);
+ success = e_webdav_session_get_data_sync (cbdav->priv->webdav, uri, &href,
&etag, &bytes, &length, cancellable, &local_error);
}
- } else if (local_error) {
- g_propagate_error (error, local_error);
}
}
@@ -967,7 +978,7 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
if (!*out_component) {
success = FALSE;
- g_propagate_error (error, EDC_ERROR (InvalidObject));
+ g_propagate_error (&local_error, EDC_ERROR (InvalidObject));
}
}
@@ -976,6 +987,9 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
g_free (etag);
g_free (bytes);
+ if (local_error)
+ g_propagate_error (error, local_error);
+
return success;
}
@@ -1029,7 +1043,7 @@ ecb_caldav_save_component_sync (ECalMetaBackend *meta_backend,
if (uid && ical_string && (!overwrite_existing || (extra && *extra))) {
gboolean force_write = FALSE;
- if (!extra)
+ if (!extra || !*extra)
href = ecb_caldav_uid_to_uri (cbdav, uid, ".ics");
if (overwrite_existing) {
@@ -1078,6 +1092,7 @@ ecb_caldav_remove_component_sync (ECalMetaBackend *meta_backend,
icalcomponent *icalcomp;
gchar *etag = NULL;
gboolean success;
+ GError *local_error = NULL;
g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (meta_backend), FALSE);
g_return_val_if_fail (uid != NULL, FALSE);
@@ -1096,15 +1111,42 @@ ecb_caldav_remove_component_sync (ECalMetaBackend *meta_backend,
return FALSE;
}
- if (conflict_resolution != E_CONFLICT_RESOLUTION_FAIL)
+ if (conflict_resolution == E_CONFLICT_RESOLUTION_FAIL)
etag = e_cal_util_dup_x_property (icalcomp, E_CALDAV_X_ETAG);
success = e_webdav_session_delete_sync (cbdav->priv->webdav, extra,
- E_WEBDAV_DEPTH_THIS, etag, cancellable, error);
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
+
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ gchar *href;
+
+ href = ecb_caldav_uid_to_uri (cbdav, uid, ".ics");
+ if (href) {
+ g_clear_error (&local_error);
+ success = e_webdav_session_delete_sync (cbdav->priv->webdav, href,
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
+
+ g_free (href);
+ }
+
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND)) {
+ href = ecb_caldav_uid_to_uri (cbdav, uid, NULL);
+ if (href) {
+ g_clear_error (&local_error);
+ success = e_webdav_session_delete_sync (cbdav->priv->webdav, href,
+ E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
+
+ g_free (href);
+ }
+ }
+ }
icalcomponent_free (icalcomp);
g_free (etag);
+ if (local_error)
+ g_propagate_error (error, local_error);
+
return success;
}
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index 620f0bd..64b91b6 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -103,6 +103,7 @@ static gboolean ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
const gchar *uid,
const gchar *preloaded_object,
const gchar *preloaded_extra,
+ gchar **out_new_uid,
GCancellable *cancellable,
GError **error);
static gboolean ecmb_save_component_wrapper_sync (ECalMetaBackend *meta_backend,
@@ -806,7 +807,7 @@ ecmb_refresh_thread_func (ECalBackend *cal_backend,
goto done;
}
- success = ecmb_upload_local_changes_sync (meta_backend, cal_cache, E_CONFLICT_RESOLUTION_KEEP_LOCAL,
cancellable, error);
+ success = ecmb_upload_local_changes_sync (meta_backend, cal_cache, E_CONFLICT_RESOLUTION_FAIL,
cancellable, error);
while (repeat && success &&
!g_cancellable_set_error_if_cancelled (cancellable, error)) {
@@ -851,12 +852,13 @@ ecmb_refresh_thread_func (ECalBackend *cal_backend,
continue;
}
- if (g_hash_table_contains (covered_uids, nfo->uid))
+ if (!*nfo->uid ||
+ g_hash_table_contains (covered_uids, nfo->uid))
continue;
g_hash_table_insert (covered_uids, nfo->uid, NULL);
- success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache,
nfo->uid, nfo->object, nfo->extra, cancellable, &local_error);
+ success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache,
nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error);
/* Do not stop on invalid objects, just notify about them later, and load as
many as possible */
if (!success && g_error_matches (local_error, E_DATA_CAL_ERROR,
InvalidObject)) {
@@ -885,7 +887,10 @@ ecmb_refresh_thread_func (ECalBackend *cal_backend,
continue;
}
- success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache,
nfo->uid, nfo->object, nfo->extra, cancellable, &local_error);
+ if (!*nfo->uid)
+ continue;
+
+ success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache,
nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error);
/* Do not stop on invalid objects, just notify about them later, and load as
many as possible */
if (!success && g_error_matches (local_error, E_DATA_CAL_ERROR,
InvalidObject)) {
@@ -1189,6 +1194,7 @@ ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
const gchar *uid,
const gchar *preloaded_object,
const gchar *preloaded_extra,
+ gchar **out_new_uid,
GCancellable *cancellable,
GError **error)
{
@@ -1196,6 +1202,7 @@ ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
icalcomponent *icalcomp = NULL;
GSList *new_instances = NULL;
gchar *extra = NULL;
+ const gchar *loaded_uid = NULL;
gboolean success = TRUE;
if (preloaded_object && *preloaded_object) {
@@ -1226,23 +1233,34 @@ ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
subcomp = icalcomponent_get_next_component (icalcomp, kind)) {
ECalComponent *comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone
(subcomp));
- if (comp)
+ if (comp) {
new_instances = g_slist_prepend (new_instances, comp);
+
+ if (!loaded_uid)
+ loaded_uid = icalcomponent_get_uid (e_cal_component_get_icalcomponent
(comp));
+ }
}
} else {
ECalComponent *comp = e_cal_component_new_from_icalcomponent (icalcomp);
icalcomp = NULL;
- if (comp)
+ if (comp) {
new_instances = g_slist_prepend (new_instances, comp);
+
+ if (!loaded_uid)
+ loaded_uid = icalcomponent_get_uid (e_cal_component_get_icalcomponent (comp));
+ }
}
if (new_instances) {
new_instances = g_slist_reverse (new_instances);
- success = ecmb_put_instances (meta_backend, cal_cache, uid, offline_flag,
+ success = ecmb_put_instances (meta_backend, cal_cache, loaded_uid ? loaded_uid : uid,
offline_flag,
new_instances, extra ? extra : preloaded_extra, cancellable, error);
+
+ if (success && out_new_uid)
+ *out_new_uid = g_strdup (loaded_uid ? loaded_uid : uid);
} else {
g_propagate_error (error, e_data_cal_create_error_fmt (InvalidObject, _("Received object for
UID ā%sā doesn't contain any expected component"), uid));
success = FALSE;
@@ -1306,22 +1324,26 @@ ecmb_save_component_wrapper_sync (ECalMetaBackend *meta_backend,
success = success && e_cal_meta_backend_save_component_sync (meta_backend, overwrite_existing,
conflict_resolution,
instances ? instances : in_instances, extra, &new_uid, &new_extra, cancellable, error);
- if (success && new_uid) {
+ if (success && new_uid && *new_uid) {
+ gchar *loaded_uid = NULL;
+
success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache, new_uid, NULL,
- new_extra ? new_extra : extra, cancellable, error);
+ new_extra ? new_extra : extra, &loaded_uid, cancellable, error);
- if (success && g_strcmp0 (new_uid, orig_uid) != 0)
+ if (success && g_strcmp0 (loaded_uid, orig_uid) != 0)
success = ecmb_maybe_remove_from_cache (meta_backend, cal_cache, E_CACHE_IS_ONLINE,
orig_uid, cancellable, error);
if (success && out_new_uid)
- *out_new_uid = new_uid;
+ *out_new_uid = loaded_uid;
else
- g_free (new_uid);
+ g_free (loaded_uid);
if (out_requires_put)
*out_requires_put = FALSE;
}
+ g_free (new_uid);
+
if (success && out_new_extra)
*out_new_extra = new_extra;
else
@@ -1372,8 +1394,13 @@ ecmb_open_sync (ECalBackendSync *sync_backend,
e_cal_backend_set_writable (E_CAL_BACKEND (meta_backend),
e_cal_meta_backend_get_connected_writable (meta_backend));
} else {
- if (!ecmb_connect_wrapper_sync (meta_backend, cancellable, error))
+ if (!ecmb_connect_wrapper_sync (meta_backend, cancellable, error)) {
+ g_mutex_lock (&meta_backend->priv->property_lock);
+ meta_backend->priv->refresh_after_authenticate = TRUE;
+ g_mutex_unlock (&meta_backend->priv->property_lock);
+
return;
+ }
}
ecmb_schedule_refresh (E_CAL_META_BACKEND (sync_backend));
@@ -1412,7 +1439,7 @@ ecmb_get_object_sync (ECalBackendSync *sync_backend,
GError *local_error = NULL;
g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
- g_return_if_fail (uid != NULL);
+ g_return_if_fail (uid && *uid);
g_return_if_fail (calobj != NULL);
meta_backend = E_CAL_META_BACKEND (sync_backend);
@@ -1422,6 +1449,7 @@ ecmb_get_object_sync (ECalBackendSync *sync_backend,
if (!e_cal_cache_get_component_as_string (cal_cache, uid, rid, calobj, cancellable, &local_error) &&
g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+ gchar *loaded_uid = NULL;
gboolean found = FALSE;
g_clear_error (&local_error);
@@ -1429,12 +1457,14 @@ ecmb_get_object_sync (ECalBackendSync *sync_backend,
/* Ignore errors here, just try whether it's on the remote side, but not in the local cache */
if (e_backend_get_online (E_BACKEND (meta_backend)) &&
ecmb_connect_wrapper_sync (meta_backend, cancellable, NULL) &&
- ecmb_load_component_wrapper_sync (meta_backend, cal_cache, uid, NULL, NULL, cancellable,
NULL)) {
- found = e_cal_cache_get_component_as_string (cal_cache, uid, rid, calobj,
cancellable, NULL);
+ ecmb_load_component_wrapper_sync (meta_backend, cal_cache, uid, NULL, NULL, &loaded_uid,
cancellable, NULL)) {
+ found = e_cal_cache_get_component_as_string (cal_cache, loaded_uid, rid, calobj,
cancellable, NULL);
}
if (!found)
g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+
+ g_free (loaded_uid);
} else if (local_error) {
g_propagate_error (error, e_data_cal_create_error (OtherError, local_error->message));
g_clear_error (&local_error);
@@ -1705,7 +1735,7 @@ ecmb_create_object_sync (ECalMetaBackend *meta_backend,
if (out_new_comp) {
if (new_uid) {
if (!e_cal_cache_get_component (cal_cache, new_uid, NULL, out_new_comp,
cancellable, NULL))
- *out_new_comp = NULL;
+ *out_new_comp = g_object_ref (comp);
} else {
*out_new_comp = g_object_ref (comp);
}
@@ -1730,7 +1760,7 @@ ecmb_create_objects_sync (ECalBackendSync *sync_backend,
ECalMetaBackend *meta_backend;
ECalCache *cal_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
icalcomponent_kind backend_kind;
GSList *link;
gboolean success = TRUE;
@@ -2022,7 +2052,7 @@ ecmb_modify_objects_sync (ECalBackendSync *sync_backend,
ECalMetaBackend *meta_backend;
ECalCache *cal_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
icalcomponent_kind backend_kind;
GSList *link;
gboolean success = TRUE;
@@ -2329,7 +2359,7 @@ ecmb_remove_objects_sync (ECalBackendSync *sync_backend,
ECalMetaBackend *meta_backend;
ECalCache *cal_cache;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
GSList *link;
gboolean success = TRUE;
@@ -2465,7 +2495,7 @@ ecmb_receive_objects_sync (ECalBackendSync *sync_backend,
{
ECalMetaBackend *meta_backend;
ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
- EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+ EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_FAIL;
ECalCache *cal_cache;
ECalComponent *comp;
icalcomponent *icalcomp, *subcomp;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]