static void
e_book_backend_webdav_remove_contacts (EBookBackend *backend,
                                       EDataBook *book,
                                       guint32 opid,
                                       GCancellable *cancellable,
                                       const GSList *id_list)
{
	EBookBackendWebdav        *webdav      = E_BOOK_BACKEND_WEBDAV (backend);
	EBookBackendWebdavPrivate *priv        = webdav->priv;
	gchar                     *uid         = id_list->data;
	GSList                     deleted_ids = {NULL,};
	guint                      status;

	if (!e_backend_get_online (E_BACKEND (backend))) {
		e_data_book_respond_remove_contacts (
			book, opid,
			EDB_ERROR (REPOSITORY_OFFLINE), NULL);
		return;
	}

	/* 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 (id_list->next != NULL) {
		e_data_book_respond_remove_contacts (
			book, opid,
			EDB_ERROR_EX (NOT_SUPPORTED,
			_("The backend does not support bulk removals")),
			NULL);
		return;
	}

	status = delete_contact (webdav, uid, cancellable);
	if (status != 204) {
		if (status == 401 || status == 407) {
			e_data_book_respond_remove_contacts (
				book, opid,
				webdav_handle_auth_request (webdav), NULL);
		} else {
			g_warning ("DELETE failed with HTTP status %d", status);
			e_data_book_respond_remove_contacts (
				book, opid,
				e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR,
				_("DELETE failed with HTTP status %d"), status),
				NULL);
		}
		return;
	}

	g_mutex_lock (&priv->cache_lock);
	e_book_backend_cache_remove_contact (priv->cache, uid);
	g_mutex_unlock (&priv->cache_lock);

	deleted_ids.data = uid;
	e_data_book_respond_remove_contacts (book, opid, EDB_ERROR (SUCCESS), &deleted_ids);
}
static GError *
download_contacts (EBookBackendWebdav *webdav,
                   EFlag *running,
                   EDataBookView *book_view,
                   GCancellable *cancellable)
{
	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;

	g_mutex_lock (&priv->update_lock);

	if (!check_addressbook_changed (webdav, &new_ctag, cancellable)) {
		g_free (new_ctag);
		g_mutex_unlock (&priv->update_lock);
		return EDB_ERROR (SUCCESS);
	}

	book_backend = E_BOOK_BACKEND (webdav);

	if (book_view != NULL) {
		e_data_book_view_notify_progress (book_view, -1,
				_("Loading Addressbook summary..."));
	}

	message = send_propfind (webdav, cancellable);
	status  = message->status_code;

	if (status == 401 || status == 407) {
		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);
	}
	if (status != 207) {
		GError *error;

		error = e_data_book_create_error_fmt (
			E_DATA_BOOK_STATUS_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")));

		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 error;
	}
	if (message->response_body == NULL) {
		g_warning ("No response body in webdav PROPFIND result");
		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 e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, _("No response body in webdav PROPFIND result"));
	}

	/* parse response */
	reader = xmlReaderForMemory (
		message->response_body->data,
		message->response_body->length, NULL, NULL,
		XML_PARSE_NOWARNING);

	elements = parse_propfind_response (reader);

	/* count contacts */
	count = 0;
	for (element = elements; element != NULL; element = element->next) {
		++count;
	}

	/* download contacts */
	i = 0;
	for (element = elements; element != NULL; element = element->next, ++i) {
		const gchar  *uri;
		const gchar *etag;
		EContact    *contact;
		gchar *complete_uri;

		/* 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);
		}

		/* skip collections */
		uri = (const gchar *) element->href;
		if (uri[strlen (uri) - 1] == '/')
			continue;

		/* uri might be relative, construct complete one */
		if (uri[0] == '/') {
			SoupURI *soup_uri = soup_uri_new (priv->uri);
			soup_uri->path    = g_strdup (uri);

			complete_uri = soup_uri_to_string (soup_uri, 0);
			soup_uri_free (soup_uri);
		} else {
			complete_uri = g_strdup (uri);
		}

		etag = (const gchar *) element->etag;

		g_mutex_lock (&priv->cache_lock);
		contact = e_book_backend_cache_get_contact (priv->cache, complete_uri);
		g_mutex_unlock (&priv->cache_lock);

		/* download contact if it is not cached or its ETag changed */
		if (contact == NULL || etag == NULL ||
		    strcmp (e_contact_get_const (contact, E_CONTACT_REV), etag) != 0) {
			contact = download_contact (webdav, complete_uri, cancellable);
			if (contact != NULL) {
				g_mutex_lock (&priv->cache_lock);
				if (e_book_backend_cache_remove_contact (priv->cache, complete_uri))
					e_book_backend_notify_remove (book_backend, complete_uri);
				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 (complete_uri);
	}

	/* free element list */
	for (element = elements; element != NULL; element = next) {
		next = element->next;

		xmlFree (element->href);
		xmlFree (element->etag);
		g_free (element);
	}

	xmlFreeTextReader (reader);
	g_object_unref (message);

	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);

	if (book_view)
		e_data_book_view_notify_progress (book_view, -1, NULL);

	g_mutex_unlock (&priv->update_lock);

	return EDB_ERROR (SUCCESS);
}
static void
e_book_backend_webdav_modify_contacts (EBookBackend *backend,
                                       EDataBook *book,
                                       guint32 opid,
                                       GCancellable *cancellable,
                                       const GSList *vcards)
{
	EBookBackendWebdav        *webdav  = E_BOOK_BACKEND_WEBDAV (backend);
	EBookBackendWebdavPrivate *priv    = webdav->priv;
	EContact                  *contact;
	GSList                     modified_contacts = {NULL,};
	const gchar                *uid;
	const gchar                *etag;
	guint status;
	gchar *status_reason = NULL;
	const gchar *vcard = vcards->data;

	if (!e_backend_get_online (E_BACKEND (backend))) {
		e_data_book_respond_create_contacts (
			book, opid,
			EDB_ERROR (REPOSITORY_OFFLINE), NULL);
		return;
	}

	/* 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 (vcards->next != NULL) {
		e_data_book_respond_modify_contacts (
			book, opid,
			EDB_ERROR_EX (
				NOT_SUPPORTED,
				_("The backend does not support bulk modifications")),
			NULL);
		return;
	}

	/* modify contact */
	contact = e_contact_new_from_vcard (vcard);
	status = upload_contact (webdav, contact, &status_reason, cancellable);
	if (status != 201 && status != 204) {
		g_object_unref (contact);
		if (status == 401 || status == 407) {
			e_data_book_respond_modify_contacts (book, opid, webdav_handle_auth_request (webdav), NULL);
			g_free (status_reason);
			return;
		}
		/* data changed on server while we were editing */
		if (status == 412) {
			/* too bad no special error code in evolution for this... */
			e_data_book_respond_modify_contacts (book, opid,
				e_data_book_create_error_fmt (
				E_DATA_BOOK_STATUS_OTHER_ERROR,
				_("Contact on server changed -> not modifying")),
				NULL);
			g_free (status_reason);
			return;
		}

		e_data_book_respond_modify_contacts (book, opid,
			e_data_book_create_error_fmt (
			E_DATA_BOOK_STATUS_OTHER_ERROR,
			_("Modify contact failed with HTTP status: %d (%s)"),
			status, status_reason),
			NULL);
		g_free (status_reason);
		return;
	}

	g_free (status_reason);

	uid = e_contact_get_const (contact, E_CONTACT_UID);
	g_mutex_lock (&priv->cache_lock);
	e_book_backend_cache_remove_contact (priv->cache, uid);

	etag = e_contact_get_const (contact, E_CONTACT_REV);

	/* PUT request didn't return an etag? try downloading to get one */
	if (etag == NULL || (etag[0] == 'W' && etag[1] == '/')) {
		EContact *new_contact;

		g_warning ("Server didn't return etag for modified address resource");
		new_contact = download_contact (webdav, uid, cancellable);
		if (new_contact != NULL) {
			contact = new_contact;
		}
	}
	e_book_backend_cache_add_contact (priv->cache, contact);
	g_mutex_unlock (&priv->cache_lock);

	modified_contacts.data = contact;
	e_data_book_respond_modify_contacts (book, opid, EDB_ERROR (SUCCESS), &modified_contacts);

	g_object_unref (contact);
}
static void
e_book_backend_webdav_create_contacts (EBookBackend *backend,
                                       EDataBook *book,
                                       guint32 opid,
                                       GCancellable *cancellable,
                                       const GSList *vcards)
{
	EBookBackendWebdav        *webdav = E_BOOK_BACKEND_WEBDAV (backend);
	EBookBackendWebdavPrivate *priv   = webdav->priv;
	EContact                  *contact;
	gchar                     *uid;
	guint                      status;
	gchar                     *status_reason = NULL;
	const gchar               *vcard = (const gchar *) vcards->data;
	GSList                     added_contacts = {NULL,};

	/* 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 clean way to roll back changes in case of an error. */
	if (vcards->next != NULL) {
		e_data_book_respond_create_contacts (
			book, opid,
			EDB_ERROR_EX (NOT_SUPPORTED,
			_("The backend does not support bulk additions")),
			NULL);
		return;
	}

	if (!e_backend_get_online (E_BACKEND (backend))) {
		e_data_book_respond_create_contacts (book, opid, EDB_ERROR (REPOSITORY_OFFLINE), NULL);
		return;
	}

	/* do 3 rand() calls to construct a unique ID... poor way but should be
	 * good enough for us */
	uid = g_strdup_printf (
		"%s%08X-%08X-%08X.vcf",
		priv->uri, rand (), rand (), rand ());

	contact = e_contact_new_from_vcard_with_uid (vcard, uid);

	/* kill revision field (might have been set by some other backend) */
	e_contact_set (contact, E_CONTACT_REV, NULL);

	status = upload_contact (webdav, contact, &status_reason, cancellable);
	if (status != 201 && status != 204) {
		g_object_unref (contact);
		if (status == 401 || status == 407) {
			e_data_book_respond_create_contacts (book, opid, webdav_handle_auth_request (webdav), NULL);
		} else {
			e_data_book_respond_create_contacts (
				book, opid,
				e_data_book_create_error_fmt (
				E_DATA_BOOK_STATUS_OTHER_ERROR,
				_("Create resource '%s' failed with HTTP status: %d (%s)"),
				uid, status, status_reason),
					NULL);
		}
		g_free (uid);
		g_free (status_reason);
		return;
	}

	g_free (status_reason);

	/* PUT request didn't return an etag? try downloading to get one */
	if (e_contact_get_const (contact, E_CONTACT_REV) == NULL) {
		const gchar *new_uid;
		EContact *new_contact;

		g_warning ("Server didn't return etag for new address resource");
		new_uid = e_contact_get_const (contact, E_CONTACT_UID);
		new_contact = download_contact (webdav, new_uid, cancellable);
		g_object_unref (contact);

		if (new_contact == NULL) {
			e_data_book_respond_create_contacts (
				book, opid,
				EDB_ERROR (OTHER_ERROR), NULL);
			g_free (uid);
			return;
		}
		contact = new_contact;
	}

	g_mutex_lock (&priv->cache_lock);
	e_book_backend_cache_add_contact (priv->cache, contact);
	g_mutex_unlock (&priv->cache_lock);

	added_contacts.data = contact;
	e_data_book_respond_create_contacts (book, opid, EDB_ERROR (SUCCESS), &added_contacts);

	g_object_unref (contact);
	g_free (uid);
}
static void
e_book_backend_vcf_load_source (EBookBackend             *backend,
				ESource                  *source,
				gboolean                  only_if_exists,
				GError                  **perror)
{
	EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
	gchar           *dirname;
	gboolean        writable = FALSE;
	gchar          *uri;
	gint fd;

	uri = e_source_get_uri (source);

	dirname = e_book_backend_vcf_extract_path_from_uri (uri);
	bvcf->priv->filename = g_build_filename (dirname, "addressbook.vcf", NULL);

	fd = g_open (bvcf->priv->filename, O_RDWR | O_BINARY, 0);

	bvcf->priv->contacts = g_hash_table_new_full (
		g_str_hash, g_str_equal,
		(GDestroyNotify) g_free,
		(GDestroyNotify) NULL);

	if (fd != -1) {
		writable = TRUE;
	} else {
		fd = g_open (bvcf->priv->filename, O_RDONLY | O_BINARY, 0);

		if (fd == -1 && !only_if_exists) {
			gint rv;

			/* the database didn't exist, so we create the
			   directory then the .vcf file */
			rv = g_mkdir_with_parents (dirname, 0700);
			if (rv == -1 && errno != EEXIST) {
				g_warning ("failed to make directory %s: %s", dirname, g_strerror (errno));
				if (errno == EACCES || errno == EPERM) {
					g_propagate_error (perror, EDB_ERROR (PERMISSION_DENIED));
				} else {
					g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "Failed to make directory %s: %s", dirname, g_strerror (errno)));
				}
				return;
			}

			fd = g_open (bvcf->priv->filename, O_CREAT | O_BINARY, 0666);

			if (fd != -1) {
#ifdef CREATE_DEFAULT_VCARD
				EContact *contact;

				contact = do_create (bvcf, XIMIAN_VCARD, FALSE);
				save_file (bvcf);

				/* XXX check errors here */
				g_object_unref (contact);
#endif

				writable = TRUE;
			}
		}
	}

	if (fd == -1) {
		g_warning ("Failed to open addressbook at uri `%s'", uri);
		g_warning ("error == %s", g_strerror(errno));
		g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "Failed to open addressbook at uri '%s': %s", uri, g_strerror (errno)));
		g_free (uri);
		return;
	}

	load_file (bvcf, fd);

	e_book_backend_set_is_loaded (backend, TRUE);
	e_book_backend_set_is_writable (backend, writable);

	g_free (uri);
}