telepathy: add contact info interface
[siplcs.git] / src / telepathy / telepathy-search.c
blob83d626604ca92e1fbf00f529a8245158621788d7
1 /**
2 * @file telepathy-search.c
4 * pidgin-sipe
6 * Copyright (C) 2012 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <string.h>
25 #include <glib-object.h>
26 #include <telepathy-glib/svc-channel.h>
27 #include <telepathy-glib/telepathy-glib.h>
29 #include "sipe-backend.h"
30 #include "sipe-common.h"
31 #include "sipe-core.h"
33 #include "telepathy-private.h"
35 /* vCard/Telepathy search field names */
36 #define SIPE_TELEPATHY_SEARCH_KEY_FIRST "x-n-given"
37 #define SIPE_TELEPATHY_SEARCH_KEY_LAST "x-n-family"
38 #define SIPE_TELEPATHY_SEARCH_KEY_EMAIL "email"
39 #define SIPE_TELEPATHY_SEARCH_KEY_COMPANY "x-org-name"
40 #define SIPE_TELEPATHY_SEARCH_KEY_COUNTRY "x-adr-country"
41 #define SIPE_TELEPATHY_SEARCH_KEY_FULLNAME "fn"
42 #define SIPE_TELEPATHY_SEARCH_KEY_BLOB "" /* one big search box */
44 G_BEGIN_DECLS
46 * Search Manager class - data structures
48 typedef struct _SipeSearchManagerClass {
49 GObjectClass parent_class;
50 } SipeSearchManagerClass;
52 typedef struct _SipeSearchManager {
53 GObject parent;
55 GObject *connection;
57 GHashTable *channels;
58 } SipeSearchManager;
61 * Search Manager class - type macros
63 /* telepathy-private.h: #define SIPE_TYPE_SEARCH_MANAGER ... */
64 #define SIPE_SEARCH_MANAGER(obj) \
65 (G_TYPE_CHECK_INSTANCE_CAST((obj), SIPE_TYPE_SEARCH_MANAGER, \
66 SipeSearchManager))
69 * Search Channel class - data structures
71 typedef struct _SipeSearchChannelClass {
72 TpBaseChannelClass parent_class;
73 } SipeSearchChannelClass;
75 typedef struct _SipeSearchChannel {
76 TpBaseChannel parent;
78 GObject *connection;
79 GHashTable *results;
80 TpChannelContactSearchState state;
81 } SipeSearchChannel;
84 * Search Channel class - type macros
86 static GType sipe_search_channel_get_type(void) G_GNUC_CONST;
87 #define SIPE_TYPE_SEARCH_CHANNEL \
88 (sipe_search_channel_get_type())
89 #define SIPE_SEARCH_CHANNEL(obj) \
90 (G_TYPE_CHECK_INSTANCE_CAST((obj), SIPE_TYPE_SEARCH_CHANNEL, \
91 SipeSearchChannel))
92 G_END_DECLS
95 * Search Manager class - type definition
97 static void channel_manager_iface_init(gpointer, gpointer);
98 G_DEFINE_TYPE_WITH_CODE(SipeSearchManager,
99 sipe_search_manager,
100 G_TYPE_OBJECT,
101 G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_MANAGER,
102 channel_manager_iface_init);
106 * Search Manager class - type definition
108 static void contact_search_iface_init(gpointer, gpointer);
109 G_DEFINE_TYPE_WITH_CODE(SipeSearchChannel,
110 sipe_search_channel,
111 TP_TYPE_BASE_CHANNEL,
112 G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_SEARCH,
113 contact_search_iface_init);
117 * Search Manager class - instance methods
119 static void sipe_search_manager_constructed(GObject *object)
121 SipeSearchManager *self = SIPE_SEARCH_MANAGER(object);
122 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_manager_parent_class)->constructed;
124 if (chain_up)
125 chain_up(object);
127 self->channels = g_hash_table_new(g_direct_hash, g_direct_equal);
130 static void sipe_search_manager_dispose(GObject *object)
132 SipeSearchManager *self = SIPE_SEARCH_MANAGER(object);
133 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_manager_parent_class)->constructed;
135 tp_clear_pointer(&self->channels, g_hash_table_unref);
136 tp_clear_object(&self->connection);
138 if (chain_up)
139 chain_up(object);
143 * Search Manager class - type implementation
145 static void sipe_search_manager_class_init(SipeSearchManagerClass *klass)
147 GObjectClass *object_class = G_OBJECT_CLASS(klass);
149 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::class_init");
151 object_class->constructed = sipe_search_manager_constructed;
152 object_class->dispose = sipe_search_manager_dispose;
155 static void sipe_search_manager_init(SIPE_UNUSED_PARAMETER SipeSearchManager *self)
157 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::init");
161 * Search Manager class - interface implementation
163 * Channel Manager
165 static void foreach_channel(TpChannelManager *manager,
166 TpExportableChannelFunc func,
167 gpointer user_data)
169 SipeSearchManager *self = SIPE_SEARCH_MANAGER(manager);
170 GHashTableIter iter;
171 gpointer chan;
173 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::foreach_channel");
175 g_hash_table_iter_init(&iter, self->channels);
176 while (g_hash_table_iter_next(&iter, &chan, NULL))
177 func(chan, user_data);
180 static void type_foreach_channel_class(GType type,
181 TpChannelManagerTypeChannelClassFunc func,
182 gpointer user_data)
184 static const gchar *const no_props[] = {
185 NULL
187 GHashTable *table = g_hash_table_new_full(g_str_hash, g_str_equal,
188 NULL,
189 (GDestroyNotify) tp_g_value_slice_free);
191 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::type_foreach_channel_class");
193 g_hash_table_insert(table,
194 TP_IFACE_CHANNEL ".ChannelType",
195 tp_g_value_slice_new_string(TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH));
196 func(type, table, no_props, user_data);
197 g_hash_table_unref(table);
200 static void search_channel_closed_cb(SipeSearchChannel *channel,
201 SipeSearchManager *self)
203 SIPE_DEBUG_INFO("SipeSearchManager::search_channel_close_cb: %p", channel);
204 tp_channel_manager_emit_channel_closed_for_object(self,
205 (TpExportableChannel *) channel);
206 g_hash_table_remove(self->channels, channel);
209 static GObject *search_channel_new(GObject *connection);
210 static gboolean create_channel(TpChannelManager *manager,
211 gpointer request_token,
212 GHashTable *request_properties)
214 SipeSearchManager *self = SIPE_SEARCH_MANAGER(manager);
215 GObject *channel;
216 GSList *request_tokens;
218 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::create_channel");
220 if (tp_strdiff(tp_asv_get_string(request_properties,
221 TP_IFACE_CHANNEL ".ChannelType"),
222 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH))
223 return(FALSE);
225 /* create new search channel */
226 channel = search_channel_new(self->connection);
227 g_hash_table_insert(self->channels, channel, NULL);
228 g_signal_connect(channel,
229 "closed",
230 (GCallback) search_channel_closed_cb,
231 self);
233 /* publish new channel */
234 request_tokens = g_slist_prepend(NULL, request_token);
235 tp_channel_manager_emit_new_channel(self,
236 TP_EXPORTABLE_CHANNEL(channel),
237 request_tokens);
238 g_slist_free(request_tokens);
240 return(TRUE);
243 static void channel_manager_iface_init(gpointer g_iface,
244 SIPE_UNUSED_PARAMETER gpointer iface_data)
246 TpChannelManagerIface *iface = g_iface;
248 #define IMPLEMENT(x, y) iface->x = y
249 IMPLEMENT(foreach_channel, foreach_channel);
250 IMPLEMENT(type_foreach_channel_class, type_foreach_channel_class);
251 IMPLEMENT(create_channel, create_channel);
252 IMPLEMENT(request_channel, create_channel);
253 /* Ensuring these channels doesn't really make much sense. */
254 IMPLEMENT(ensure_channel, NULL);
255 #undef IMPLEMENT
258 /* create new search manager object */
259 GObject *sipe_telepathy_search_new(TpBaseConnection *connection)
261 SipeSearchManager *self = g_object_new(SIPE_TYPE_SEARCH_MANAGER, NULL);
262 self->connection = g_object_ref(connection);
263 return(G_OBJECT(self));
267 * Search Channel class - instance methods
269 enum {
270 CHANNEL_PROP_SEARCH_KEYS = 1,
271 CHANNEL_LAST_PROP
274 static void get_property(GObject *object,
275 guint property_id,
276 GValue *value,
277 GParamSpec *pspec)
279 switch (property_id)
281 case CHANNEL_PROP_SEARCH_KEYS: {
282 /* vCard/Telepathy search field names */
283 static const gchar const *search_keys[] = {
284 SIPE_TELEPATHY_SEARCH_KEY_FIRST,
285 SIPE_TELEPATHY_SEARCH_KEY_LAST,
286 SIPE_TELEPATHY_SEARCH_KEY_EMAIL,
287 SIPE_TELEPATHY_SEARCH_KEY_COMPANY,
288 SIPE_TELEPATHY_SEARCH_KEY_COUNTRY,
289 SIPE_TELEPATHY_SEARCH_KEY_FULLNAME,
290 SIPE_TELEPATHY_SEARCH_KEY_BLOB,
291 NULL
293 g_value_set_boxed(value, search_keys);
295 break;
296 default:
297 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
298 break;
302 static void fill_immutable_properties(TpBaseChannel *channel,
303 GHashTable *properties)
305 TP_BASE_CHANNEL_CLASS(sipe_search_channel_parent_class)->fill_immutable_properties(channel,
306 properties);
307 tp_dbus_properties_mixin_fill_properties_hash(G_OBJECT(channel),
308 properties,
309 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "AvailableSearchKeys",
310 NULL);
313 static gchar *get_object_path_suffix(TpBaseChannel *base)
315 return(g_strdup_printf ("SearchChannel_%p", base));
318 static GPtrArray *get_interfaces(TpBaseChannel *self)
320 GPtrArray *interfaces = TP_BASE_CHANNEL_CLASS(sipe_search_channel_parent_class)->get_interfaces(self);
321 return(interfaces);
324 static void sipe_search_channel_constructed(GObject *object)
326 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(object);
327 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_channel_parent_class)->constructed;
329 if (chain_up)
330 chain_up(object);
332 self->results = NULL;
335 static void sipe_search_channel_finalize(GObject *object)
337 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(object);
339 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::finalize");
341 if (self->results)
342 g_hash_table_unref(self->results);
344 G_OBJECT_CLASS(sipe_search_channel_parent_class)->finalize(object);
348 * Search Channel class - type implementation
350 static void sipe_search_channel_class_init(SipeSearchChannelClass *klass)
352 static TpDBusPropertiesMixinPropImpl props[] = {
354 .name = "AvailableSearchKeys",
355 .getter_data = "available-search-keys",
356 .setter_data = NULL
359 .name = NULL
362 GObjectClass *object_class = G_OBJECT_CLASS(klass);
363 TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS(klass);
364 GParamSpec *ps;
366 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::class_init");
368 object_class->constructed = sipe_search_channel_constructed;
369 object_class->finalize = sipe_search_channel_finalize;
370 object_class->get_property = get_property;
372 base_class->channel_type = TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH;
373 base_class->target_handle_type = TP_HANDLE_TYPE_NONE;
374 base_class->fill_immutable_properties = fill_immutable_properties;
375 base_class->get_object_path_suffix = get_object_path_suffix;
376 base_class->interfaces = NULL;
377 base_class->get_interfaces = get_interfaces;
378 base_class->close = tp_base_channel_destroyed;
380 ps = g_param_spec_boxed("available-search-keys",
381 "Available search keys",
382 "The set of search keys supported by this channel",
383 G_TYPE_STRV,
384 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
385 g_object_class_install_property(object_class,
386 CHANNEL_PROP_SEARCH_KEYS,
387 ps);
389 tp_dbus_properties_mixin_implement_interface(object_class,
390 TP_IFACE_QUARK_CHANNEL_TYPE_CONTACT_SEARCH,
391 tp_dbus_properties_mixin_getter_gobject_properties,
392 NULL,
393 props);
396 static void sipe_search_channel_init(SIPE_UNUSED_PARAMETER SipeSearchChannel *self)
398 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::init");
402 * Search Channel class - interface implementation
404 * Contact search
406 static void search_channel_state(SipeSearchChannel *self,
407 TpChannelContactSearchState new_state,
408 const gchar *msg)
410 GHashTable *details = tp_asv_new(NULL, NULL);
412 if (msg)
413 tp_asv_set_string(details, "debug-message", msg);
414 tp_svc_channel_type_contact_search_emit_search_state_changed(self,
415 new_state,
416 msg ? msg : "",
417 details);
418 g_hash_table_unref(details);
419 self->state = new_state;
422 static void search_channel_search(TpSvcChannelTypeContactSearch *channel,
423 GHashTable *terms,
424 DBusGMethodInvocation *context)
426 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(channel);
428 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::search");
430 if (self->state == TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED) {
431 const gchar *first = g_hash_table_lookup(terms,
432 SIPE_TELEPATHY_SEARCH_KEY_FIRST);
433 const gchar *last = g_hash_table_lookup(terms,
434 SIPE_TELEPATHY_SEARCH_KEY_LAST);
435 const gchar *email = g_hash_table_lookup(terms,
436 SIPE_TELEPATHY_SEARCH_KEY_EMAIL);
437 const gchar *company = g_hash_table_lookup(terms,
438 SIPE_TELEPATHY_SEARCH_KEY_COMPANY);
439 const gchar *country = g_hash_table_lookup(terms,
440 SIPE_TELEPATHY_SEARCH_KEY_COUNTRY);
441 struct sipe_backend_private *telepathy_private = sipe_telepathy_connection_private(self->connection);
442 gchar **split = NULL;
444 /* did the requester honor our "AvailableSearchKeys"? */
445 if (!(first || last || email || company || country)) {
446 const gchar *alternative = g_hash_table_lookup(terms,
447 SIPE_TELEPATHY_SEARCH_KEY_FULLNAME);
449 /* No. Did he give a full name instead? */
450 if (alternative) {
451 SIPE_DEBUG_INFO("SipeSearchChannel::search: full name given: '%s'",
452 alternative);
454 /* assume:
455 * - one word -> first name
456 * - two words -> first & last name
458 split = g_strsplit(alternative, " ", 3);
459 if (split[0]) {
460 first = split[0];
461 if (split[1])
462 last = split[1];
465 /* No. Did he give a "on big search box" instead? */
466 } else if ((alternative = g_hash_table_lookup(terms,
467 SIPE_TELEPATHY_SEARCH_KEY_BLOB))
468 != NULL) {
470 SIPE_DEBUG_INFO("SipeSearchChannel::search: one big search box given: '%s'",
471 alternative);
472 /* assume:
473 * - one word with '@' -> email
474 * - one word -> first name
475 * - two words -> first & last name
477 split = g_strsplit(alternative, " ", 3);
478 if (split[0]) {
479 if (strchr(split[0], '@')) {
480 email = split[0];
481 } else {
482 first = split[0];
483 if (split[1])
484 last = split[1];
488 } else
489 SIPE_DEBUG_ERROR_NOFORMAT("SipeSearchChannel::search: no valid terms found");
492 sipe_core_buddy_search(telepathy_private->public,
493 (struct sipe_backend_search_token *) self,
494 first, last, email, company, country);
495 g_strfreev(split);
497 /* only switch to "in progress" if the above didn't fail */
498 if (self->state == TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED)
499 search_channel_state(self,
500 TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS,
501 NULL);
503 tp_svc_channel_type_contact_search_return_from_search(context);
504 } else {
505 GError *error = g_error_new(TP_ERROR, TP_ERROR_NOT_AVAILABLE,
506 "invalid search state");
507 dbus_g_method_return_error(context, error);
508 g_error_free(error);
512 static void contact_search_iface_init(gpointer g_iface,
513 SIPE_UNUSED_PARAMETER gpointer iface_data)
515 TpSvcChannelTypeContactSearchClass *klass = g_iface;
517 #define IMPLEMENT(x) tp_svc_channel_type_contact_search_implement_##x( \
518 klass, search_channel_##x)
519 IMPLEMENT(search);
520 /* we don't support stopping a search */
521 #undef IMPLEMENT
524 /* create new search channel object */
525 static GObject *search_channel_new(GObject *connection)
527 /* property "connection" required by TpBaseChannel */
528 SipeSearchChannel *self = g_object_new(SIPE_TYPE_SEARCH_CHANNEL,
529 "connection", connection,
530 NULL);
532 self->connection = g_object_ref(connection);
533 self->state = TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED;
535 tp_base_channel_register(TP_BASE_CHANNEL(self));
537 return(G_OBJECT(self));
541 * Backend adaptor functions
543 void sipe_backend_search_failed(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
544 struct sipe_backend_search_token *token,
545 const gchar *msg)
547 SIPE_DEBUG_INFO("sipe_backend_search_failed: %s", msg);
548 search_channel_state(SIPE_SEARCH_CHANNEL(token),
549 TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED,
550 msg);
553 static void free_info(GPtrArray *info)
555 g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info);
558 struct sipe_backend_search_results *sipe_backend_search_results_start(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
559 struct sipe_backend_search_token *token)
561 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(token);
563 self->results = g_hash_table_new_full(g_str_hash, g_str_equal,
564 g_free,
565 (GDestroyNotify) free_info);
567 return((struct sipe_backend_search_results *) self);
570 /* adds: the Contact_Info_Field (field_name, [], values) */
571 static void add_search_result(GPtrArray *info,
572 const gchar *field_name,
573 const gchar *field_value)
575 if (field_value) {
576 static const gchar **empty = { NULL };
577 GValueArray *field = g_value_array_new(3);
578 const gchar *components[] = { field_value, NULL };
579 GValue *value;
581 SIPE_DEBUG_INFO("add_search_result: %s = '%s'",
582 field_name, field_value);
584 g_value_array_append(field, NULL);
585 value = g_value_array_get_nth(field, 0);
586 g_value_init(value, G_TYPE_STRING);
587 g_value_set_static_string(value, field_name);
589 g_value_array_append(field, NULL);
590 value = g_value_array_get_nth(field, 1);
591 g_value_init(value, G_TYPE_STRV);
592 g_value_set_static_boxed(value, empty);
594 g_value_array_append(field, NULL);
595 value = g_value_array_get_nth(field, 2);
596 g_value_init(value, G_TYPE_STRV);
597 g_value_set_boxed(value, components);
599 g_ptr_array_add(info, field);
603 void sipe_backend_search_results_add(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
604 struct sipe_backend_search_results *results,
605 const gchar *uri,
606 const gchar *name,
607 const gchar *company,
608 const gchar *country,
609 const gchar *email)
611 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(results);
612 GPtrArray *info = g_ptr_array_new();
614 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_FULLNAME, name);
615 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_COMPANY, company);
616 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_COUNTRY, country);
617 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_EMAIL, email);
619 g_hash_table_insert(self->results, g_strdup(uri), info);
622 void sipe_backend_search_results_finalize(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
623 struct sipe_backend_search_results *results,
624 SIPE_UNUSED_PARAMETER const gchar *description,
625 SIPE_UNUSED_PARAMETER gboolean more)
627 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(results);
629 tp_svc_channel_type_contact_search_emit_search_result_received(self,
630 self->results);
631 search_channel_state(self,
632 TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED,
633 NULL);
638 Local Variables:
639 mode: c
640 c-file-style: "bsd"
641 indent-tabs-mode: t
642 tab-width: 8
643 End: