linux-bluetooth.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/4] obexd: phonebook-ebook: modernize
@ 2021-07-17 12:55 Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 2/4] configure.ac: specify phonebook plugin at build time Dylan Van Assche
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Dylan Van Assche @ 2021-07-17 12:55 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Dylan Van Assche

Modernize PBAP phonebook-ebook plugin for newer libebook version
of the Evolution Data Server.

The ebook plugin was introduced during GSoC 2011 [1] and allows
BlueZ to share contacts stored in the Evolution Data Server to
connected clients such as car multimedia systems.
With the rise of Mobile Linux thanks to the PinePhone and Librem 5,
this plugin was modernized to compile with newer libebook versions
because the API was changed [2].

[1] http://www.bluez.org/gsoc-eds-backend-of-phonebook-access-profilepbap/
[2] https://wiki.gnome.org/Apps/Evolution/ESourceMigrationGuide

Signed-off-by: Dylan Van Assche <me@dylanvanassche.be>
---
 obexd/plugins/phonebook-ebook.c | 676 +++++++++++++++-----------------
 1 file changed, 327 insertions(+), 349 deletions(-)

diff --git a/obexd/plugins/phonebook-ebook.c b/obexd/plugins/phonebook-ebook.c
index 30be9d921..491aef7ff 100644
--- a/obexd/plugins/phonebook-ebook.c
+++ b/obexd/plugins/phonebook-ebook.c
@@ -3,8 +3,9 @@
  *
  *  OBEX Server
  *
- *  Copyright (C) 2009-2010  Intel Corporation
- *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2009-2021  Intel Corporation
+ *  Copyright (C) 2007-2021  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2021       Dylan Van Assche <me@dylanvanassche.be>
  *
  *
  */
@@ -13,35 +14,44 @@
 #include <config.h>
 #endif
 
-#include <string.h>
+#define _GNU_SOURCE
+#include <dirent.h>
 #include <errno.h>
-
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
 #include <glib.h>
-#include <libebook/e-book.h>
-
-#include "lib/bluetooth.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libebook/libebook.h>
+#include <libedataserver/libedataserver.h>
 
 #include "obexd/src/log.h"
-#include "obexd/src/obex.h"
-#include "obexd/src/service.h"
 #include "phonebook.h"
 
-#define QUERY_FN "(contains \"family_name\" \"%s\")"
-#define QUERY_NAME "(contains \"given_name\" \"%s\")"
-#define QUERY_PHONE "(contains \"phone\" \"%s\")"
+#define CONNECTION_TIMEOUT 30  // seconds
+#define PB_FORMAT_VCARD21	0
+#define PB_FORMAT_VCARD30	1
+#define PB_FORMAT_NONE		2
+
+ESourceRegistry *registry;
+ESource *address_book;
+EBookClient *book_client;
 
 struct query_context {
 	const struct apparam_field *params;
 	phonebook_cb contacts_cb;
 	phonebook_entry_cb entry_cb;
 	phonebook_cache_ready_cb ready_cb;
-	EBookQuery *query;
+	gchar *query;
 	unsigned int count;
 	GString *buf;
-	char *id;
+	char *uid;
 	unsigned queued_calls;
 	void *user_data;
-	GSList *ebooks;
 	gboolean canceled;
 };
 
@@ -76,39 +86,30 @@ static char *attribute_mask[] = {
 		"SORT-STRING",
 /* 28 */	"X-IRMC-CALL-DATETIME",
 		NULL
-
 };
 
-static void close_ebooks(GSList *ebooks)
-{
-	g_slist_free_full(ebooks, g_object_unref);
-}
-
 static void free_query_context(struct query_context *data)
 {
-	g_free(data->id);
+	g_free(data->uid);
 
 	if (data->buf != NULL)
 		g_string_free(data->buf, TRUE);
 
 	if (data->query != NULL)
-		e_book_query_unref(data->query);
-
-	close_ebooks(data->ebooks);
+		g_free(data->query);
 
 	g_free(data);
 }
 
 static char *evcard_to_string(EVCard *evcard, unsigned int format,
-							uint64_t filter)
+			      uint64_t filter)
 {
 	EVCard *evcard2;
 	GList *l;
 	char *vcard;
 
 	if (!filter)
-		return e_vcard_to_string(evcard, EVC_FORMAT_VCARD_30);
-		/* XXX There is no support for VCARD 2.1 at this time */
+		return e_vcard_to_string(evcard, format);
 
 	/*
 	 * Mandatory attributes for vCard 2.1 are VERSION ,N and TEL.
@@ -134,8 +135,7 @@ static char *evcard_to_string(EVCard *evcard, unsigned int format,
 			if (g_strcmp0(name, attribute_mask[i]) != 0)
 				continue;
 
-			e_vcard_add_attribute(evcard2,
-					e_vcard_attribute_copy(attrib));
+			e_vcard_add_attribute(evcard2, e_vcard_attribute_copy(attrib));
 		}
 	}
 
@@ -145,25 +145,151 @@ static char *evcard_to_string(EVCard *evcard, unsigned int format,
 	return vcard;
 }
 
-static void ebookpull_cb(EBook *book, const GError *gerr, GList *contacts,
-							void *user_data)
+char *phonebook_set_folder(const char *current_folder,
+			   const char *new_folder, uint8_t flags, int *err)
+{
+	gboolean root, child;
+	char *fullname = NULL, *tmp1, *tmp2, *base;
+	int ret = 0, len;
+
+	root = (g_strcmp0("/", current_folder) == 0);
+	child = (new_folder && strlen(new_folder) != 0);
+
+	/* Evolution back-end will support /telecom/pb folder only */
+	switch (flags) {
+	case 0x02:
+		/* Go back to root */
+		if (!child) {
+			fullname = g_strdup("/");
+			goto done;
+		}
+
+		/* Go down 1 level */
+		fullname = g_build_filename(current_folder, new_folder, NULL);
+		if (strcmp(PB_TELECOM_FOLDER, fullname) != 0 &&
+				strcmp(PB_CONTACTS_FOLDER, fullname) != 0) {
+			g_free(fullname);
+			fullname = NULL;
+			ret = -ENOENT;
+		}
+
+		break;
+	case 0x03:
+		/* Go up 1 level */
+		if (root) {
+			/* Already root */
+			ret = -EBADR;
+			goto done;
+		}
+
+		/*
+		 * Removing one level of the current folder. Current folder
+		 * contains AT LEAST one level since it is not at root folder.
+		 * Use glib utility functions to handle invalid chars in the
+		 * folder path properly.
+		 */
+		tmp1 = g_path_get_basename(current_folder);
+		tmp2 = g_strrstr(current_folder, tmp1);
+		len = tmp2 - (current_folder + 1);
+
+		g_free(tmp1);
+
+		if (len == 0)
+			base = g_strdup("/");
+		else
+			base = g_strndup(current_folder, len);
+
+		/* Return one level only */
+		if (!child) {
+			fullname = base;
+			goto done;
+		}
+
+		fullname = g_build_filename(base, new_folder, NULL);
+		if (strcmp(fullname, PB_TELECOM_FOLDER) != 0 &&
+				strcmp(fullname, PB_CONTACTS_FOLDER) != 0) {
+			g_free(fullname);
+			fullname = NULL;
+			ret = -ENOENT;
+		}
+
+		g_free(base);
+
+		break;
+	default:
+		ret = -EBADR;
+		break;
+	}
+
+done:
+	if (err)
+		*err = ret;
+
+	return fullname;
+}
+
+void phonebook_req_finalize(void *request)
+{
+	/* Free resources after pull request */
+	struct query_context *data = request;
+
+	if (data->queued_calls == 0)
+		free_query_context(data);
+	else
+		data->canceled = TRUE;
+}
+
+void *phonebook_pull(const char *name, const struct apparam_field *params,
+		     phonebook_cb cb, void *user_data, int *err)
+{
+	/* Prepare pull request, return the prepared request which is passed to phonebook_pull_read */
+	struct query_context *data;
+	EBookQuery *query;
+
+	/* Request should be for '/telecom/pb.vcf', reject others */
+	if (g_strcmp0(PB_CONTACTS, name) != 0) {
+		if (err)
+			*err = -ENOENT;
+		return NULL;
+	}
+
+	data = g_new0(struct query_context, 1);
+	data->contacts_cb = cb;
+	data->params = params;
+	data->user_data = user_data;
+	data->buf = g_string_new("");
+	query = e_book_query_any_field_contains("");  // all contacts
+	data->query = e_book_query_to_string(query);
+	e_book_query_unref(query);
+
+	return data;
+}
+
+static void phonebook_pull_read_ready (GObject *source_object,
+				       GAsyncResult *result,
+				       gpointer user_data)
 {
 	struct query_context *data = user_data;
-	GList *l;
+	GSList *l = NULL;
+	GSList *contacts = NULL;
+	GError *gerr = NULL;
 	unsigned int count, maxcount;
 
+	/* Finish async call to retrieve contacts */
 	data->queued_calls--;
 
 	if (data->canceled)
 		goto canceled;
 
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),
+					   result, &contacts, &gerr);
+
 	if (gerr != NULL) {
-		error("E-Book query failed: %s", gerr->message);
+		error("Failed to retrieve contacts, invalid query");
+		g_error_free(gerr);
 		goto done;
 	}
 
-	DBG("");
-
 	/*
 	 * When MaxListCount is zero, PCE wants to know the number of used
 	 * indexes in the phonebook of interest. All other parameters that
@@ -171,42 +297,46 @@ static void ebookpull_cb(EBook *book, const GError *gerr, GList *contacts,
 	 */
 	maxcount = data->params->maxlistcount;
 	if (maxcount == 0) {
-		data->count += g_list_length(contacts);
+		data->count += g_slist_length(contacts);
 		goto done;
 	}
 
-	l = g_list_nth(contacts, data->params->liststartoffset);
-
-	for (count = 0; l && count + data->count < maxcount; l = g_list_next(l),
-								count++) {
+	/*
+	* Convert each contact to a vCard and append the card to the buffer string.
+	*/
+	l = g_slist_nth(contacts, data->params->liststartoffset);
+	for (count = 0; l && count + data->count < maxcount; l = g_slist_next(l), count++) {
 		EContact *contact = E_CONTACT(l->data);
 		EVCard *evcard = E_VCARD(contact);
 		char *vcard;
 
-		vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
-						data->params->filter);
+		if (data->params->format == PB_FORMAT_VCARD30)
+			vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
+						 data->params->filter);
+		else if (data->params->format == PB_FORMAT_VCARD21)
+			vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_21,
+						 data->params->filter);
+		else
+			error("vCard format unknown: %d", data->params->format);
 
 		data->buf = g_string_append(data->buf, vcard);
 		data->buf = g_string_append(data->buf, "\r\n");
 		g_free(vcard);
 	}
 
-	DBG("collected %d vcards", count);
+	DBG("collected %d contacts", count);
 
 	data->count += count;
-
-	g_list_free_full(contacts, g_object_unref);
+	g_slist_free_full (contacts, (GDestroyNotify) g_object_unref);
 
 done:
 	if (data->queued_calls == 0) {
 		GString *buf = data->buf;
 		data->buf = NULL;
 
-		data->contacts_cb(buf->str, buf->len, data->count,
-						0, TRUE, data->user_data);
-
+		data->contacts_cb(buf->str, buf->len, data->count, 0,
+				  TRUE, data->user_data);
 		g_string_free(buf, TRUE);
-
 	}
 
 	return;
@@ -216,9 +346,34 @@ canceled:
 		free_query_context(data);
 }
 
-static void ebook_entry_cb(EBook *book, const GError *gerr,
-				EContact *contact, void *user_data)
+int phonebook_pull_read(void *request)
 {
+	/* Retrieve results from the backend, based on the request. Return 0 on success. */
+	struct query_context *data = request;
+	GError *gerr = NULL;
+
+	if (!data) {
+		error("Request data is empty");
+		return -ENOENT;
+	}
+
+	DBG("retrieving all contacts");
+
+	/* Fetch async contacts from default address book */
+	e_book_client_get_contacts(book_client, data->query, NULL,
+				   (GAsyncReadyCallback) phonebook_pull_read_ready,
+				   data);
+	data->queued_calls++;
+
+	return 0;
+}
+
+static void phonebook_get_entry_ready(GObject *source_object,
+				      GAsyncResult *result,
+				      gpointer user_data)
+{
+	GError *gerr = NULL;
+	EContact *contact = NULL;
 	struct query_context *data = user_data;
 	EVCard *evcard;
 	char *vcard;
@@ -226,20 +381,27 @@ static void ebook_entry_cb(EBook *book, const GError *gerr,
 
 	data->queued_calls--;
 
+	e_book_client_get_contact_finish(E_BOOK_CLIENT (source_object),
+					 result, &contact, &gerr);
 	if (data->canceled)
 		goto done;
 
 	if (gerr != NULL) {
-		error("E-Book query failed: %s", gerr->message);
+		error("Getting contact failed: %s", gerr->message);
+		g_error_free(gerr);
 		goto done;
 	}
 
-	DBG("");
-
 	evcard = E_VCARD(contact);
 
-	vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
-					data->params->filter);
+	if (data->params->format == PB_FORMAT_VCARD30)
+		vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
+					 data->params->filter);
+	else if (data->params->format == PB_FORMAT_VCARD21)
+		vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_21,
+					 data->params->filter);
+	else
+		error("Unknown vCard format: %d", data->params->format);
 
 	len = vcard ? strlen(vcard) : 0;
 
@@ -247,18 +409,47 @@ static void ebook_entry_cb(EBook *book, const GError *gerr,
 	data->contacts_cb(vcard, len, 1, 0, TRUE, data->user_data);
 
 	g_free(vcard);
-	g_object_unref(contact);
 
-	return;
+	DBG("retrieving entry successful");
 
 done:
 	if (data->queued_calls == 0) {
 		if (data->count == 0)
 			data->contacts_cb(NULL, 0, 1, 0, TRUE,
-						data->user_data);
+					  data->user_data);
 		else if (data->canceled)
 			free_query_context(data);
 	}
+
+	g_object_unref(contact);
+}
+
+void *phonebook_get_entry(const char *folder, const char *id,
+			  const struct apparam_field *params, phonebook_cb cb,
+			  void *user_data, int *err)
+{
+	/* Get a contact from the backend, contact must be in cache to succeed */
+	struct query_context *data;
+	GSList *l;
+
+	DBG("retrieving entry: %s", id);
+
+	data = g_new0(struct query_context, 1);
+	data->contacts_cb = cb;
+	data->params = params;
+	data->user_data = user_data;
+	data->uid = g_strdup(id);
+
+	/* Fetch async contacts from default address book */
+	e_book_client_get_contact(book_client, data->uid, NULL,
+				  (GAsyncReadyCallback) phonebook_get_entry_ready,
+				  data);
+	data->queued_calls++;
+
+	if (err)
+		*err = (data->queued_calls == 0 ? -ENOENT : 0);
+
+	return data;
 }
 
 static char *evcard_name_attribute_to_string(EVCard *evcard)
@@ -291,25 +482,29 @@ static char *evcard_name_attribute_to_string(EVCard *evcard)
 	return g_string_free(name, FALSE);
 }
 
-static void cache_cb(EBook *book, const GError *gerr, GList *contacts,
-							void *user_data)
+static void phonebook_create_cache_ready(GObject *source_object,
+					 GAsyncResult *result,
+					 gpointer user_data)
 {
 	struct query_context *data = user_data;
-	GList *l;
+	GSList *l = NULL;
+	GSList *contacts = NULL;
+	GError *gerr = NULL;
 
 	data->queued_calls--;
 
 	if (data->canceled)
 		goto canceled;
 
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),
+					   result, &contacts, &gerr);
+
 	if (gerr != NULL) {
-		error("E-Book operation failed: %s", gerr->message);
+		error("Getting contacts failed: %s", gerr->message);
 		goto done;
 	}
 
-	DBG("");
-
-	for (l = contacts; l; l = g_list_next(l)) {
+	for (l = contacts; l; l = g_slist_next(l)) {
 		EContact *contact = E_CONTACT(l->data);
 		EVCard *evcard = E_VCARD(contact);
 		EVCardAttribute *attrib;
@@ -341,7 +536,9 @@ static void cache_cb(EBook *book, const GError *gerr, GList *contacts,
 		g_free(tel);
 	}
 
-	g_list_free_full(contacts, g_object_unref);
+	DBG("caching successful");
+
+	g_slist_free_full(contacts, (GDestroyNotify) g_object_unref);
 
 done:
 	if (data->queued_calls == 0)
@@ -354,275 +551,16 @@ canceled:
 		free_query_context(data);
 }
 
-static GSList *traverse_sources(GSList *ebooks, GSList *sources,
-							char **default_src) {
-	GError *gerr = NULL;
-
-	for (; sources != NULL; sources = g_slist_next(sources)) {
-		char *uri;
-		ESource *source = E_SOURCE(sources->data);
-		EBook *ebook = e_book_new(source, &gerr);
-
-		if (ebook == NULL) {
-			error("Can't create user's address book: %s",
-								gerr->message);
-			g_clear_error(&gerr);
-			continue;
-		}
-
-		uri = e_source_get_uri(source);
-		if (g_strcmp0(*default_src, uri) == 0) {
-			g_free(uri);
-			continue;
-		}
-		g_free(uri);
-
-		if (e_book_open(ebook, FALSE, &gerr) == FALSE) {
-			error("Can't open e-book address book: %s",
-							gerr->message);
-			g_object_unref(ebook);
-			g_clear_error(&gerr);
-			continue;
-		}
-
-		if (*default_src == NULL)
-			*default_src = e_source_get_uri(source);
-
-		DBG("%s address book opened", e_source_peek_name(source));
-
-		ebooks = g_slist_append(ebooks, ebook);
-	}
-
-	return ebooks;
-}
-
-int phonebook_init(void)
-{
-	g_type_init();
-
-	return 0;
-}
-
-static GSList *open_ebooks(void)
-{
-	GError *gerr = NULL;
-	ESourceList *src_list;
-	GSList *list;
-	char *default_src = NULL;
-	GSList *ebooks = NULL;
-
-	if (e_book_get_addressbooks(&src_list, &gerr) == FALSE) {
-		error("Can't list user's address books: %s", gerr->message);
-		g_error_free(gerr);
-		return NULL;
-	}
-
-	list = e_source_list_peek_groups(src_list);
-	while (list != NULL) {
-		ESourceGroup *group = E_SOURCE_GROUP(list->data);
-		GSList *sources = e_source_group_peek_sources(group);
-
-		ebooks = traverse_sources(ebooks, sources, &default_src);
-
-		list = list->next;
-	}
-
-	g_free(default_src);
-	g_object_unref(src_list);
-
-	return ebooks;
-}
-
-void phonebook_exit(void)
-{
-}
-
-char *phonebook_set_folder(const char *current_folder,
-		const char *new_folder, uint8_t flags, int *err)
-{
-	gboolean root, child;
-	char *fullname = NULL, *tmp1, *tmp2, *base;
-	int ret = 0, len;
-
-	root = (g_strcmp0("/", current_folder) == 0);
-	child = (new_folder && strlen(new_folder) != 0);
-
-	/* Evolution back-end will support /telecom/pb folder only */
-
-	switch (flags) {
-	case 0x02:
-		/* Go back to root */
-		if (!child) {
-			fullname = g_strdup("/");
-			goto done;
-		}
-
-		/* Go down 1 level */
-		fullname = g_build_filename(current_folder, new_folder, NULL);
-		if (strcmp(PB_TELECOM_FOLDER, fullname) != 0 &&
-				strcmp(PB_CONTACTS_FOLDER, fullname) != 0) {
-			g_free(fullname);
-			fullname = NULL;
-			ret = -ENOENT;
-		}
-
-		break;
-	case 0x03:
-		/* Go up 1 level */
-		if (root) {
-			/* Already root */
-			ret = -EBADR;
-			goto done;
-		}
-
-		/*
-		 * Removing one level of the current folder. Current folder
-		 * contains AT LEAST one level since it is not at root folder.
-		 * Use glib utility functions to handle invalid chars in the
-		 * folder path properly.
-		 */
-		tmp1 = g_path_get_basename(current_folder);
-		tmp2 = g_strrstr(current_folder, tmp1);
-		len = tmp2 - (current_folder + 1);
-
-		g_free(tmp1);
-
-		if (len == 0)
-			base = g_strdup("/");
-		else
-			base = g_strndup(current_folder, len);
-
-		/* Return one level only */
-		if (!child) {
-			fullname = base;
-			goto done;
-		}
-
-		fullname = g_build_filename(base, new_folder, NULL);
-		if (strcmp(fullname, PB_TELECOM_FOLDER) != 0 &&
-				strcmp(fullname, PB_CONTACTS_FOLDER) != 0) {
-			g_free(fullname);
-			fullname = NULL;
-			ret = -ENOENT;
-		}
-
-		g_free(base);
-
-		break;
-	default:
-		ret = -EBADR;
-		break;
-	}
-
-done:
-	if (err)
-		*err = ret;
-
-	return fullname;
-}
-
-void phonebook_req_finalize(void *request)
-{
-	struct query_context *data = request;
-
-	if (data->queued_calls == 0)
-		free_query_context(data);
-	else
-		data->canceled = TRUE;
-}
-
-void *phonebook_pull(const char *name, const struct apparam_field *params,
-				phonebook_cb cb, void *user_data, int *err)
-{
-	struct query_context *data;
-
-	if (g_strcmp0(PB_CONTACTS, name) != 0) {
-		if (err)
-			*err = -ENOENT;
-
-		return NULL;
-	}
-
-	data = g_new0(struct query_context, 1);
-	data->contacts_cb = cb;
-	data->params = params;
-	data->user_data = user_data;
-	data->buf = g_string_new("");
-	data->query = e_book_query_any_field_contains("");
-	data->ebooks = open_ebooks();
-
-	if (err)
-		*err = data->ebooks == NULL ? -EIO : 0;
-
-	return data;
-}
-
-int phonebook_pull_read(void *request)
-{
-	struct query_context *data = request;
-	GSList *l;
-
-	if (!data)
-		return -ENOENT;
-
-	for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
-		EBook *ebook = l->data;
-
-		if (e_book_is_opened(ebook) == FALSE)
-			continue;
-
-		if (e_book_get_contacts_async(ebook, data->query,
-						ebookpull_cb, data) == TRUE)
-			data->queued_calls++;
-	}
-
-	if (data->queued_calls == 0)
-		return -ENOENT;
-
-	return 0;
-}
-
-void *phonebook_get_entry(const char *folder, const char *id,
-				const struct apparam_field *params,
-				phonebook_cb cb, void *user_data, int *err)
-{
-	struct query_context *data;
-	GSList *l;
-
-	data = g_new0(struct query_context, 1);
-	data->contacts_cb = cb;
-	data->params = params;
-	data->user_data = user_data;
-	data->id = g_strdup(id);
-	data->ebooks = open_ebooks();
-
-	for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
-		EBook *ebook = l->data;
-
-		if (e_book_is_opened(ebook) == FALSE)
-			continue;
-
-		if (e_book_get_contact_async(ebook, data->id,
-						ebook_entry_cb, data) == TRUE)
-			data->queued_calls++;
-	}
-
-	if (err)
-		*err = (data->queued_calls == 0 ? -ENOENT : 0);
-
-	return data;
-}
-
 void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
-		phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
+			     phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
 {
+	/* Build a cache of contacts */
 	struct query_context *data;
 	EBookQuery *query;
-	GSList *l;
 	EContact *me;
+	EBookClient *me_client;
 	EVCard *evcard;
 	GError *gerr = NULL;
-	EBook *eb;
 	EVCardAttribute *attrib;
 	char *uid, *tel, *cname;
 
@@ -633,23 +571,25 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
 		return NULL;
 	}
 
-	DBG("");
-
-	query = e_book_query_any_field_contains("");
+	DBG("creating cache");
 
 	data = g_new0(struct query_context, 1);
 	data->entry_cb = entry_cb;
 	data->ready_cb = ready_cb;
 	data->user_data = user_data;
-	data->query = query;
-	data->ebooks = open_ebooks();
+	query = e_book_query_any_field_contains("");  // all contacts
+	data->query = e_book_query_to_string(query);
+	e_book_query_unref(query);
 
-	/* Add 0.vcf */
-	if (e_book_get_self(&me, &eb, &gerr) == FALSE) {
+	/* Myself as contact should always be 0.vcf if found in address book */
+	if (!e_book_client_get_self(registry, &me, &me_client, &gerr)) {
+		DBG("owner is not in address book: %s", gerr->message);
 		g_error_free(gerr);
 		goto next;
 	}
 
+	DBG("caching address book owner");
+
 	evcard = E_VCARD(me);
 
 	cname = evcard_name_attribute_to_string(evcard);
@@ -668,28 +608,66 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
 		tel = g_strdup("");
 
 	data->entry_cb(uid, 0, cname, NULL, tel, data->user_data);
-
 	data->count++;
 
 	g_free(cname);
 	g_free(uid);
 	g_free(tel);
-	g_object_unref(eb);
 
 next:
-	for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
-		EBook *ebook = l->data;
-
-		if (e_book_is_opened(ebook) == FALSE)
-			continue;
-
-		if (e_book_get_contacts_async(ebook, query,
-						cache_cb, data) == TRUE)
-			data->queued_calls++;
-	}
+	/* Fetch async contacts from default address book */
+	DBG("caching contacts");
+	e_book_client_get_contacts(book_client, data->query, NULL,
+				   (GAsyncReadyCallback) phonebook_create_cache_ready,
+				   data);
+	data->queued_calls++;
 
 	if (err)
 		*err = (data->queued_calls == 0 ? -ENOENT : 0);
 
 	return data;
 }
+
+int phonebook_init(void)
+{
+	EClient *client;
+	GError *gerr = NULL;
+
+	/* Acquire ESource Registry */
+	registry = e_source_registry_new_sync(NULL, &gerr);
+	if (gerr != NULL) {
+		error("Unable to acquire registery: %s\n", gerr->message);
+		g_error_free(gerr);
+		return -1;
+	}
+
+	/* Get ref to default address book */
+	address_book = e_source_registry_ref_default_address_book(registry);
+	if (address_book == NULL) {
+		error("Unable to get reference to default address book");
+		return -2;
+	}
+
+	/* Allocate e-book client for address book */
+	gerr = NULL;
+	client = e_book_client_connect_sync(address_book, CONNECTION_TIMEOUT,
+					    NULL, &gerr);
+	if (gerr != NULL || client == NULL) {
+		error("Cannot connect ebook client to EDS: %s",
+		      gerr != NULL? gerr->message: "NULL");
+		g_error_free(gerr);
+		return -3;
+	}
+	book_client = E_BOOK_CLIENT(client);
+
+	DBG("created address book client");
+
+	return 0;
+}
+
+void phonebook_exit(void)
+{
+	g_object_unref(book_client);
+	g_object_unref(address_book);
+	g_object_unref(registry);
+}
-- 
2.32.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/4] configure.ac: specify phonebook plugin at build time
  2021-07-17 12:55 [PATCH 1/4] obexd: phonebook-ebook: modernize Dylan Van Assche
@ 2021-07-17 12:55 ` Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 3/4] configure.ac: add libebook dependency Dylan Van Assche
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Dylan Van Assche @ 2021-07-17 12:55 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Juho Hamalainen, Dylan Van Assche

From: Juho Hamalainen <juho.hamalainen@jolla.com>

Various phonebook plugins are available, by default
phonebook-dummy is always built. Allow to specify
which plugin to use at build time.

Cherry-picked from https://github.com/sailfishos/bluez5/commit/d39ed9e2db8d4f9ab239a859c777e5504459b9dd

Co-authored-by: Dylan Van Assche <me@dylanvanassche.be>
Signed-off-by: Dylan Van Assche <me@dylanvanassche.be>
---
 Makefile.obexd | 2 +-
 configure.ac   | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/Makefile.obexd b/Makefile.obexd
index 6f5dc04c0..285a56197 100644
--- a/Makefile.obexd
+++ b/Makefile.obexd
@@ -41,7 +41,7 @@ obexd_builtin_modules += pbap
 obexd_builtin_sources += obexd/plugins/pbap.c \
 				obexd/plugins/vcard.h obexd/plugins/vcard.c \
 				obexd/plugins/phonebook.h \
-				obexd/plugins/phonebook-dummy.c
+				obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c
 
 obexd_builtin_modules += mas
 obexd_builtin_sources += obexd/plugins/mas.c obexd/src/map_ap.h \
diff --git a/configure.ac b/configure.ac
index be32782a6..113ab0e0f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -416,4 +416,12 @@ fi
 AC_DEFINE_UNQUOTED(ANDROID_STORAGEDIR, "${storagedir}/android",
 			[Directory for the Android daemon storage files])
 
+AC_ARG_WITH([phonebook], AC_HELP_STRING([--with-phonebook=PLUGIN],
+				[obexd phonebook plugin (default=dummy)]),
+					[plugin_phonebook=${withval}])
+if (test -z "${plugin_phonebook}"); then
+	plugin_phonebook=dummy
+fi
+AC_SUBST(PLUGIN_PHONEBOOK, [${plugin_phonebook}])
+
 AC_OUTPUT(Makefile src/bluetoothd.rst lib/bluez.pc mesh/bluetooth-meshd.rst)
-- 
2.32.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/4] configure.ac: add libebook dependency
  2021-07-17 12:55 [PATCH 1/4] obexd: phonebook-ebook: modernize Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 2/4] configure.ac: specify phonebook plugin at build time Dylan Van Assche
@ 2021-07-17 12:55 ` Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 4/4] obexd: phonebook: Workaround for PTS client not sending mandatory apparams Dylan Van Assche
  2021-07-17 13:15 ` [1/4] obexd: phonebook-ebook: modernize bluez.test.bot
  3 siblings, 0 replies; 5+ messages in thread
From: Dylan Van Assche @ 2021-07-17 12:55 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Dylan Van Assche

Require libebook when phonebook-ebook plugin is built.

Signed-off-by: Dylan Van Assche <me@dylanvanassche.be>
---
 Makefile.am    | 2 +-
 Makefile.obexd | 2 +-
 configure.ac   | 9 +++++++++
 3 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 497f05f06..0fd405e8c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,7 +24,7 @@ pkgincludedir = $(includedir)/bluetooth
 
 pkginclude_HEADERS =
 
-AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) $(UDEV_CFLAGS) $(ell_cflags)
+AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) $(UDEV_CFLAGS) $(LIBEBOOK_CFLAGS) $(LIBEDATASERVER_CFLAGS) $(ell_cflags)
 AM_LDFLAGS = $(MISC_LDFLAGS)
 
 if DATAFILES
diff --git a/Makefile.obexd b/Makefile.obexd
index 285a56197..2c0d19518 100644
--- a/Makefile.obexd
+++ b/Makefile.obexd
@@ -80,7 +80,7 @@ obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \
 			obexd/src/map_ap.h
 obexd_src_obexd_LDADD = lib/libbluetooth-internal.la \
 			gdbus/libgdbus-internal.la \
-			$(ICAL_LIBS) $(DBUS_LIBS) $(GLIB_LIBS) -ldl
+			$(ICAL_LIBS) $(DBUS_LIBS) $(LIBEBOOK_LIBS) $(LIBEDATASERVER_LIBS) $(GLIB_LIBS) -ldl
 
 obexd_src_obexd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic
 
diff --git a/configure.ac b/configure.ac
index 113ab0e0f..a5afaea6c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -422,6 +422,15 @@ AC_ARG_WITH([phonebook], AC_HELP_STRING([--with-phonebook=PLUGIN],
 if (test -z "${plugin_phonebook}"); then
 	plugin_phonebook=dummy
 fi
+
+if (test "${plugin_phonebook}" = "ebook"); then
+	PKG_CHECK_MODULES(LIBEBOOK, [libebook-1.2 >= 3.3], dummy=yes, AC_MSG_ERROR(libebook >= 3.3 is required))
+	AC_SUBST(LIBEBOOK_CFLAGS)
+	AC_SUBST(LIBEBOOK_LIBS)
+	PKG_CHECK_MODULES(LIBEDATESERVER, [libedataserver-1.2 >= 3.3], dummy=yes, AC_MSG_ERROR(libedataserver >= 3.3 is required))
+	AC_SUBST(LIBEDATESERVER_CFLAGS)
+	AC_SUBST(LIBEDATESERVER_LIBS)
+fi
 AC_SUBST(PLUGIN_PHONEBOOK, [${plugin_phonebook}])
 
 AC_OUTPUT(Makefile src/bluetoothd.rst lib/bluez.pc mesh/bluetooth-meshd.rst)
-- 
2.32.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 4/4] obexd: phonebook: Workaround for PTS client not sending mandatory apparams
  2021-07-17 12:55 [PATCH 1/4] obexd: phonebook-ebook: modernize Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 2/4] configure.ac: specify phonebook plugin at build time Dylan Van Assche
  2021-07-17 12:55 ` [PATCH 3/4] configure.ac: add libebook dependency Dylan Van Assche
@ 2021-07-17 12:55 ` Dylan Van Assche
  2021-07-17 13:15 ` [1/4] obexd: phonebook-ebook: modernize bluez.test.bot
  3 siblings, 0 replies; 5+ messages in thread
From: Dylan Van Assche @ 2021-07-17 12:55 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Hannu Mallat, Dylan Van Assche

From: Hannu Mallat <hmallat@gmail.com>

Some PTS clients do not send all the mandatory apparams
when retrieving the phonebook. Clients such as car multimedia systems
cannot be fixed, therefore working around this issue by inserting
default apparams which makes these clients work as well.

Cherry-picked from https://github.com/sailfishos/bluez5/commit/e8001df26e1964ddb98d02ad27ab8f909ffe2f17

Co-authored-by: Dylan Van Assche <me@dylanvanassche.be>
Signed-off-by: Dylan Van Assche <me@dylanvanassche.be>
---
 obexd/plugins/pbap.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index efae92cd8..ab5236316 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -511,6 +511,21 @@ static int pbap_get(struct obex_session *os, void *user_data)
 		rsize = 0;
 	}
 
+	/* Workaround for PTS client not sending mandatory apparams */
+	if (!rsize && g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) {
+		static const uint8_t default_apparams[] = {
+			0x04, 0x02, 0xff, 0xff
+		};
+		buffer = default_apparams;
+		rsize = sizeof(default_apparams);
+	} else if (!rsize && g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) {
+		static const uint8_t default_apparams[] = {
+			0x07, 0x01, 0x00
+		};
+		buffer = default_apparams;
+		rsize = sizeof(default_apparams);
+	}
+
 	params = parse_aparam(buffer, rsize);
 	if (params == NULL)
 		return -EBADR;
-- 
2.32.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* RE: [1/4] obexd: phonebook-ebook: modernize
  2021-07-17 12:55 [PATCH 1/4] obexd: phonebook-ebook: modernize Dylan Van Assche
                   ` (2 preceding siblings ...)
  2021-07-17 12:55 ` [PATCH 4/4] obexd: phonebook: Workaround for PTS client not sending mandatory apparams Dylan Van Assche
@ 2021-07-17 13:15 ` bluez.test.bot
  3 siblings, 0 replies; 5+ messages in thread
From: bluez.test.bot @ 2021-07-17 13:15 UTC (permalink / raw)
  To: linux-bluetooth, me

[-- Attachment #1: Type: text/plain, Size: 8884 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=517051

---Test result---

Test Summary:
CheckPatch                    FAIL      1.55 seconds
GitLint                       FAIL      0.49 seconds
Prep - Setup ELL              PASS      47.28 seconds
Build - Prep                  PASS      0.11 seconds
Build - Configure             PASS      8.39 seconds
Build - Make                  PASS      204.40 seconds
Make Check                    PASS      9.50 seconds
Make Distcheck                PASS      248.76 seconds
Build w/ext ELL - Configure   PASS      9.13 seconds
Build w/ext ELL - Make        PASS      192.09 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script with rule in .checkpatch.conf
Output:
obexd: phonebook-ebook: modernize
WARNING:LONG_LINE: line length of 87 exceeds 80 columns
#145: FILE: obexd/plugins/phonebook-ebook.c:138:
+			e_vcard_add_attribute(evcard2, e_vcard_attribute_copy(attrib));

WARNING:LONG_LINE_COMMENT: line length of 102 exceeds 80 columns
#252: FILE: obexd/plugins/phonebook-ebook.c:245:
+	/* Prepare pull request, return the prepared request which is passed to phonebook_pull_read */

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#275: FILE: obexd/plugins/phonebook-ebook.c:268:
+static void phonebook_pull_read_ready (GObject *source_object,

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#292: FILE: obexd/plugins/phonebook-ebook.c:284:
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#292: FILE: obexd/plugins/phonebook-ebook.c:284:
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),

WARNING:LONG_LINE_COMMENT: line length of 83 exceeds 80 columns
#321: FILE: obexd/plugins/phonebook-ebook.c:305:
+	* Convert each contact to a vCard and append the card to the buffer string.

WARNING:BLOCK_COMMENT_STYLE: Block comments should align the * on each line
#321: FILE: obexd/plugins/phonebook-ebook.c:305:
+	/*
+	* Convert each contact to a vCard and append the card to the buffer string.

WARNING:LONG_LINE: line length of 92 exceeds 80 columns
#324: FILE: obexd/plugins/phonebook-ebook.c:308:
+	for (count = 0; l && count + data->count < maxcount; l = g_slist_next(l), count++) {

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#351: FILE: obexd/plugins/phonebook-ebook.c:330:
+	g_slist_free_full (contacts, (GDestroyNotify) g_object_unref);

WARNING:LONG_LINE_COMMENT: line length of 91 exceeds 80 columns
#376: FILE: obexd/plugins/phonebook-ebook.c:351:
+	/* Retrieve results from the backend, based on the request. Return 0 on success. */

WARNING:LONG_LINE: line length of 83 exceeds 80 columns
#389: FILE: obexd/plugins/phonebook-ebook.c:364:
+				   (GAsyncReadyCallback) phonebook_pull_read_ready,

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#409: FILE: obexd/plugins/phonebook-ebook.c:384:
+	e_book_client_get_contact_finish(E_BOOK_CLIENT (source_object),

WARNING:LONG_LINE_COMMENT: line length of 81 exceeds 80 columns
#464: FILE: obexd/plugins/phonebook-ebook.c:431:
+	/* Get a contact from the backend, contact must be in cache to succeed */

WARNING:LONG_LINE: line length of 82 exceeds 80 columns
#478: FILE: obexd/plugins/phonebook-ebook.c:445:
+				  (GAsyncReadyCallback) phonebook_get_entry_ready,

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#510: FILE: obexd/plugins/phonebook-ebook.c:499:
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#510: FILE: obexd/plugins/phonebook-ebook.c:499:
+	e_book_client_get_contacts_finish (E_BOOK_CLIENT (source_object),

WARNING:LONG_LINE: line length of 90 exceeds 80 columns
#802: FILE: obexd/plugins/phonebook-ebook.c:555:
+			     phonebook_cache_ready_cb ready_cb, void *user_data, int *err)

WARNING:LONG_LINE: line length of 86 exceeds 80 columns
#875: FILE: obexd/plugins/phonebook-ebook.c:621:
+				   (GAsyncReadyCallback) phonebook_create_cache_ready,

ERROR:SPACING: spaces required around that '?' (ctx:VxW)
#911: FILE: obexd/plugins/phonebook-ebook.c:657:
+		      gerr != NULL? gerr->message: "NULL");
 		                  ^

ERROR:SPACING: spaces required around that ':' (ctx:VxW)
#911: FILE: obexd/plugins/phonebook-ebook.c:657:
+		      gerr != NULL? gerr->message: "NULL");
 		                                 ^

- total: 2 errors, 18 warnings, 889 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] obexd: phonebook-ebook: modernize" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

configure.ac: specify phonebook plugin at build time
WARNING:COMMIT_LOG_LONG_LINE: Possible unwrapped commit description (prefer a maximum 75 chars per line)
#10: 
Cherry-picked from https://github.com/sailfishos/bluez5/commit/d39ed9e2db8d4f9ab239a859c777e5504459b9dd

WARNING:BAD_SIGN_OFF: Non-standard signature: Co-authored-by:
#12: 
Co-authored-by: Dylan Van Assche <me@dylanvanassche.be>

- total: 0 errors, 2 warnings, 20 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] configure.ac: specify phonebook plugin at build time" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

obexd: phonebook: Workaround for PTS client not sending mandatory apparams
WARNING:COMMIT_LOG_LONG_LINE: Possible unwrapped commit description (prefer a maximum 75 chars per line)
#12: 
Cherry-picked from https://github.com/sailfishos/bluez5/commit/e8001df26e1964ddb98d02ad27ab8f909ffe2f17

WARNING:BAD_SIGN_OFF: Non-standard signature: Co-authored-by:
#14: 
Co-authored-by: Dylan Van Assche <me@dylanvanassche.be>

- total: 0 errors, 2 warnings, 21 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] obexd: phonebook: Workaround for PTS client not sending" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: GitLint - FAIL
Desc: Run gitlint with rule in .gitlint
Output:
configure.ac: specify phonebook plugin at build time
7: B1 Line exceeds max length (103>80): "Cherry-picked from https://github.com/sailfishos/bluez5/commit/d39ed9e2db8d4f9ab239a859c777e5504459b9dd"

obexd: phonebook: Workaround for PTS client not sending mandatory apparams
1: T1 Title exceeds max length (74>72): "obexd: phonebook: Workaround for PTS client not sending mandatory apparams"
8: B1 Line exceeds max length (103>80): "Cherry-picked from https://github.com/sailfishos/bluez5/commit/e8001df26e1964ddb98d02ad27ab8f909ffe2f17"


##############################
Test: Prep - Setup ELL - PASS
Desc: Clone, build, and install ELL

##############################
Test: Build - Prep - PASS
Desc: Prepare environment for build

##############################
Test: Build - Configure - PASS
Desc: Configure the BlueZ source tree

##############################
Test: Build - Make - PASS
Desc: Build the BlueZ source tree

##############################
Test: Make Check - PASS
Desc: Run 'make check'

##############################
Test: Make Distcheck - PASS
Desc: Run distcheck to check the distribution

##############################
Test: Build w/ext ELL - Configure - PASS
Desc: Configure BlueZ source with '--enable-external-ell' configuration

##############################
Test: Build w/ext ELL - Make - PASS
Desc: Build BlueZ source with '--enable-external-ell' configuration



---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-07-17 13:15 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-17 12:55 [PATCH 1/4] obexd: phonebook-ebook: modernize Dylan Van Assche
2021-07-17 12:55 ` [PATCH 2/4] configure.ac: specify phonebook plugin at build time Dylan Van Assche
2021-07-17 12:55 ` [PATCH 3/4] configure.ac: add libebook dependency Dylan Van Assche
2021-07-17 12:55 ` [PATCH 4/4] obexd: phonebook: Workaround for PTS client not sending mandatory apparams Dylan Van Assche
2021-07-17 13:15 ` [1/4] obexd: phonebook-ebook: modernize bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).