core: add "sipid" parameter to search
[siplcs.git] / src / telepathy / telepathy-search.c
blob85ae033a14b74479563667610b593b0253d02edd
1 /**
2 * @file telepathy-search.c
4 * pidgin-sipe
6 * Copyright (C) 2012-2014 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 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <string.h>
29 #include <glib-object.h>
30 #include <telepathy-glib/svc-channel.h>
31 #include <telepathy-glib/telepathy-glib.h>
33 #include "sipe-backend.h"
34 #include "sipe-common.h"
35 #include "sipe-core.h"
37 #include "telepathy-private.h"
39 /* vCard/Telepathy search field names */
40 #define SIPE_TELEPATHY_SEARCH_KEY_FIRST "x-n-given"
41 #define SIPE_TELEPATHY_SEARCH_KEY_LAST "x-n-family"
42 #define SIPE_TELEPATHY_SEARCH_KEY_EMAIL "email"
43 #define SIPE_TELEPATHY_SEARCH_KEY_COMPANY "x-org-name"
44 #define SIPE_TELEPATHY_SEARCH_KEY_COUNTRY "x-adr-country"
45 #define SIPE_TELEPATHY_SEARCH_KEY_FULLNAME "fn"
46 #define SIPE_TELEPATHY_SEARCH_KEY_BLOB "" /* one big search box */
48 G_BEGIN_DECLS
50 * Search Manager class - data structures
52 typedef struct _SipeSearchManagerClass {
53 GObjectClass parent_class;
54 } SipeSearchManagerClass;
56 typedef struct _SipeSearchManager {
57 GObject parent;
59 GObject *connection;
61 GHashTable *channels;
62 } SipeSearchManager;
65 * Search Manager class - type macros
67 /* telepathy-private.h: #define SIPE_TYPE_SEARCH_MANAGER ... */
68 #define SIPE_SEARCH_MANAGER(obj) \
69 (G_TYPE_CHECK_INSTANCE_CAST((obj), SIPE_TYPE_SEARCH_MANAGER, \
70 SipeSearchManager))
73 * Search Channel class - data structures
75 typedef struct _SipeSearchChannelClass {
76 TpBaseChannelClass parent_class;
77 } SipeSearchChannelClass;
79 typedef struct _SipeSearchChannel {
80 TpBaseChannel parent;
82 GObject *connection;
83 GHashTable *results;
84 TpChannelContactSearchState state;
85 } SipeSearchChannel;
88 * Search Channel class - type macros
90 static GType sipe_search_channel_get_type(void) G_GNUC_CONST;
91 #define SIPE_TYPE_SEARCH_CHANNEL \
92 (sipe_search_channel_get_type())
93 #define SIPE_SEARCH_CHANNEL(obj) \
94 (G_TYPE_CHECK_INSTANCE_CAST((obj), SIPE_TYPE_SEARCH_CHANNEL, \
95 SipeSearchChannel))
96 G_END_DECLS
99 * Search Manager class - type definition
101 static void channel_manager_iface_init(gpointer, gpointer);
102 G_DEFINE_TYPE_WITH_CODE(SipeSearchManager,
103 sipe_search_manager,
104 G_TYPE_OBJECT,
105 G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_MANAGER,
106 channel_manager_iface_init);
110 * Search Manager class - type definition
112 static void contact_search_iface_init(gpointer, gpointer);
113 G_DEFINE_TYPE_WITH_CODE(SipeSearchChannel,
114 sipe_search_channel,
115 TP_TYPE_BASE_CHANNEL,
116 G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_SEARCH,
117 contact_search_iface_init);
121 * Search Manager class - instance methods
123 static void sipe_search_manager_constructed(GObject *object)
125 SipeSearchManager *self = SIPE_SEARCH_MANAGER(object);
126 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_manager_parent_class)->constructed;
128 if (chain_up)
129 chain_up(object);
131 self->channels = g_hash_table_new(g_direct_hash, g_direct_equal);
134 static void sipe_search_manager_dispose(GObject *object)
136 SipeSearchManager *self = SIPE_SEARCH_MANAGER(object);
137 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_manager_parent_class)->constructed;
139 tp_clear_pointer(&self->channels, g_hash_table_unref);
140 tp_clear_object(&self->connection);
142 if (chain_up)
143 chain_up(object);
147 * Search Manager class - type implementation
149 static void sipe_search_manager_class_init(SipeSearchManagerClass *klass)
151 GObjectClass *object_class = G_OBJECT_CLASS(klass);
153 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::class_init");
155 object_class->constructed = sipe_search_manager_constructed;
156 object_class->dispose = sipe_search_manager_dispose;
159 static void sipe_search_manager_init(SIPE_UNUSED_PARAMETER SipeSearchManager *self)
161 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::init");
165 * Search Manager class - interface implementation
167 * Channel Manager
169 static void foreach_channel(TpChannelManager *manager,
170 TpExportableChannelFunc func,
171 gpointer user_data)
173 SipeSearchManager *self = SIPE_SEARCH_MANAGER(manager);
174 GHashTableIter iter;
175 gpointer chan;
177 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::foreach_channel");
179 g_hash_table_iter_init(&iter, self->channels);
180 while (g_hash_table_iter_next(&iter, &chan, NULL))
181 func(chan, user_data);
184 static void type_foreach_channel_class(GType type,
185 TpChannelManagerTypeChannelClassFunc func,
186 gpointer user_data)
188 static const gchar *const no_props[] = {
189 NULL
191 GHashTable *table = g_hash_table_new_full(g_str_hash, g_str_equal,
192 NULL,
193 (GDestroyNotify) tp_g_value_slice_free);
195 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::type_foreach_channel_class");
197 g_hash_table_insert(table,
198 TP_IFACE_CHANNEL ".ChannelType",
199 tp_g_value_slice_new_string(TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH));
200 func(type, table, no_props, user_data);
201 g_hash_table_unref(table);
204 static void search_channel_closed_cb(SipeSearchChannel *channel,
205 SipeSearchManager *self)
207 SIPE_DEBUG_INFO("SipeSearchManager::search_channel_close_cb: %p", channel);
208 tp_channel_manager_emit_channel_closed_for_object(self,
209 (TpExportableChannel *) channel);
210 g_hash_table_remove(self->channels, channel);
213 static GObject *search_channel_new(GObject *connection);
214 static gboolean create_channel(TpChannelManager *manager,
215 gpointer request_token,
216 GHashTable *request_properties)
218 SipeSearchManager *self = SIPE_SEARCH_MANAGER(manager);
219 GObject *channel;
220 GSList *request_tokens;
222 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchManager::create_channel");
224 if (tp_strdiff(tp_asv_get_string(request_properties,
225 TP_IFACE_CHANNEL ".ChannelType"),
226 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH))
227 return(FALSE);
229 /* create new search channel */
230 channel = search_channel_new(self->connection);
231 g_hash_table_insert(self->channels, channel, NULL);
232 g_signal_connect(channel,
233 "closed",
234 (GCallback) search_channel_closed_cb,
235 self);
237 /* publish new channel */
238 request_tokens = g_slist_prepend(NULL, request_token);
239 tp_channel_manager_emit_new_channel(self,
240 TP_EXPORTABLE_CHANNEL(channel),
241 request_tokens);
242 g_slist_free(request_tokens);
244 return(TRUE);
247 static void channel_manager_iface_init(gpointer g_iface,
248 SIPE_UNUSED_PARAMETER gpointer iface_data)
250 TpChannelManagerIface *iface = g_iface;
252 #define IMPLEMENT(x, y) iface->x = y
253 IMPLEMENT(foreach_channel, foreach_channel);
254 IMPLEMENT(type_foreach_channel_class, type_foreach_channel_class);
255 IMPLEMENT(create_channel, create_channel);
256 IMPLEMENT(request_channel, create_channel);
257 /* Ensuring these channels doesn't really make much sense. */
258 IMPLEMENT(ensure_channel, NULL);
259 #undef IMPLEMENT
262 /* create new search manager object */
263 GObject *sipe_telepathy_search_new(TpBaseConnection *connection)
265 SipeSearchManager *self = g_object_new(SIPE_TYPE_SEARCH_MANAGER, NULL);
266 self->connection = g_object_ref(connection);
267 return(G_OBJECT(self));
271 * Search Channel class - instance methods
273 enum {
274 CHANNEL_PROP_SEARCH_KEYS = 1,
275 CHANNEL_LAST_PROP
278 static void get_property(GObject *object,
279 guint property_id,
280 GValue *value,
281 GParamSpec *pspec)
283 switch (property_id)
285 case CHANNEL_PROP_SEARCH_KEYS: {
286 /* vCard/Telepathy search field names */
287 static const gchar const *search_keys[] = {
288 SIPE_TELEPATHY_SEARCH_KEY_FIRST,
289 SIPE_TELEPATHY_SEARCH_KEY_LAST,
290 SIPE_TELEPATHY_SEARCH_KEY_EMAIL,
291 SIPE_TELEPATHY_SEARCH_KEY_COMPANY,
292 SIPE_TELEPATHY_SEARCH_KEY_COUNTRY,
293 SIPE_TELEPATHY_SEARCH_KEY_FULLNAME,
294 SIPE_TELEPATHY_SEARCH_KEY_BLOB,
295 NULL
297 g_value_set_boxed(value, search_keys);
299 break;
300 default:
301 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
302 break;
306 static void fill_immutable_properties(TpBaseChannel *channel,
307 GHashTable *properties)
309 TP_BASE_CHANNEL_CLASS(sipe_search_channel_parent_class)->fill_immutable_properties(channel,
310 properties);
311 tp_dbus_properties_mixin_fill_properties_hash(G_OBJECT(channel),
312 properties,
313 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "AvailableSearchKeys",
314 NULL);
317 static gchar *get_object_path_suffix(TpBaseChannel *base)
319 return(g_strdup_printf ("SearchChannel_%p", base));
322 static GPtrArray *get_interfaces(TpBaseChannel *self)
324 GPtrArray *interfaces = TP_BASE_CHANNEL_CLASS(sipe_search_channel_parent_class)->get_interfaces(self);
325 return(interfaces);
328 static void sipe_search_channel_constructed(GObject *object)
330 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(object);
331 void (*chain_up)(GObject *) = G_OBJECT_CLASS(sipe_search_channel_parent_class)->constructed;
333 if (chain_up)
334 chain_up(object);
336 self->results = NULL;
339 static void sipe_search_channel_finalize(GObject *object)
341 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(object);
343 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::finalize");
345 if (self->results)
346 g_hash_table_unref(self->results);
348 G_OBJECT_CLASS(sipe_search_channel_parent_class)->finalize(object);
352 * Search Channel class - type implementation
354 static void sipe_search_channel_class_init(SipeSearchChannelClass *klass)
356 static TpDBusPropertiesMixinPropImpl props[] = {
358 .name = "AvailableSearchKeys",
359 .getter_data = "available-search-keys",
360 .setter_data = NULL
363 .name = NULL
366 GObjectClass *object_class = G_OBJECT_CLASS(klass);
367 TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS(klass);
368 GParamSpec *ps;
370 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::class_init");
372 object_class->constructed = sipe_search_channel_constructed;
373 object_class->finalize = sipe_search_channel_finalize;
374 object_class->get_property = get_property;
376 base_class->channel_type = TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH;
377 base_class->target_handle_type = TP_HANDLE_TYPE_NONE;
378 base_class->fill_immutable_properties = fill_immutable_properties;
379 base_class->get_object_path_suffix = get_object_path_suffix;
380 base_class->interfaces = NULL;
381 base_class->get_interfaces = get_interfaces;
382 base_class->close = tp_base_channel_destroyed;
384 ps = g_param_spec_boxed("available-search-keys",
385 "Available search keys",
386 "The set of search keys supported by this channel",
387 G_TYPE_STRV,
388 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
389 g_object_class_install_property(object_class,
390 CHANNEL_PROP_SEARCH_KEYS,
391 ps);
393 tp_dbus_properties_mixin_implement_interface(object_class,
394 TP_IFACE_QUARK_CHANNEL_TYPE_CONTACT_SEARCH,
395 tp_dbus_properties_mixin_getter_gobject_properties,
396 NULL,
397 props);
400 static void sipe_search_channel_init(SIPE_UNUSED_PARAMETER SipeSearchChannel *self)
402 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::init");
406 * Search Channel class - interface implementation
408 * Contact search
410 static void search_channel_state(SipeSearchChannel *self,
411 TpChannelContactSearchState new_state,
412 const gchar *msg)
414 GHashTable *details = tp_asv_new(NULL, NULL);
416 if (msg)
417 tp_asv_set_string(details, "debug-message", msg);
418 tp_svc_channel_type_contact_search_emit_search_state_changed(self,
419 new_state,
420 msg ? msg : "",
421 details);
422 g_hash_table_unref(details);
423 self->state = new_state;
426 static void search_channel_search(TpSvcChannelTypeContactSearch *channel,
427 GHashTable *terms,
428 DBusGMethodInvocation *context)
430 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(channel);
432 SIPE_DEBUG_INFO_NOFORMAT("SipeSearchChannel::search");
434 if (self->state == TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED) {
435 const gchar *first = g_hash_table_lookup(terms,
436 SIPE_TELEPATHY_SEARCH_KEY_FIRST);
437 const gchar *last = g_hash_table_lookup(terms,
438 SIPE_TELEPATHY_SEARCH_KEY_LAST);
439 const gchar *email = g_hash_table_lookup(terms,
440 SIPE_TELEPATHY_SEARCH_KEY_EMAIL);
441 const gchar *company = g_hash_table_lookup(terms,
442 SIPE_TELEPATHY_SEARCH_KEY_COMPANY);
443 const gchar *country = g_hash_table_lookup(terms,
444 SIPE_TELEPATHY_SEARCH_KEY_COUNTRY);
445 struct sipe_backend_private *telepathy_private = sipe_telepathy_connection_private(self->connection);
446 gchar **split = NULL;
448 /* did the requester honor our "AvailableSearchKeys"? */
449 if (!(first || last || email || company || country)) {
450 const gchar *alternative = g_hash_table_lookup(terms,
451 SIPE_TELEPATHY_SEARCH_KEY_FULLNAME);
453 /* No. Did he give a full name instead? */
454 if (alternative) {
455 SIPE_DEBUG_INFO("SipeSearchChannel::search: full name given: '%s'",
456 alternative);
458 /* assume:
459 * - one word -> first name
460 * - two words -> first & last name
462 split = g_strsplit(alternative, " ", 3);
463 if (split[0]) {
464 first = split[0];
465 if (split[1])
466 last = split[1];
469 /* No. Did he give a "on big search box" instead? */
470 } else if ((alternative = g_hash_table_lookup(terms,
471 SIPE_TELEPATHY_SEARCH_KEY_BLOB))
472 != NULL) {
474 SIPE_DEBUG_INFO("SipeSearchChannel::search: one big search box given: '%s'",
475 alternative);
476 /* assume:
477 * - one word with '@' -> email
478 * - one word -> first name
479 * - two words -> first & last name
481 split = g_strsplit(alternative, " ", 3);
482 if (split[0]) {
483 if (strchr(split[0], '@')) {
484 email = split[0];
485 } else {
486 first = split[0];
487 if (split[1])
488 last = split[1];
492 } else
493 SIPE_DEBUG_ERROR_NOFORMAT("SipeSearchChannel::search: no valid terms found");
496 sipe_core_buddy_search(telepathy_private->public,
497 (struct sipe_backend_search_token *) self,
498 first, last, email, NULL, company, country);
499 g_strfreev(split);
501 /* only switch to "in progress" if the above didn't fail */
502 if (self->state == TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED)
503 search_channel_state(self,
504 TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS,
505 NULL);
507 tp_svc_channel_type_contact_search_return_from_search(context);
508 } else {
509 GError *error = g_error_new(TP_ERROR, TP_ERROR_NOT_AVAILABLE,
510 "invalid search state");
511 dbus_g_method_return_error(context, error);
512 g_error_free(error);
516 static void contact_search_iface_init(gpointer g_iface,
517 SIPE_UNUSED_PARAMETER gpointer iface_data)
519 TpSvcChannelTypeContactSearchClass *klass = g_iface;
521 #define IMPLEMENT(x) tp_svc_channel_type_contact_search_implement_##x( \
522 klass, search_channel_##x)
523 IMPLEMENT(search);
524 /* we don't support stopping a search */
525 #undef IMPLEMENT
528 /* create new search channel object */
529 static GObject *search_channel_new(GObject *connection)
531 /* property "connection" required by TpBaseChannel */
532 SipeSearchChannel *self = g_object_new(SIPE_TYPE_SEARCH_CHANNEL,
533 "connection", connection,
534 NULL);
536 self->connection = g_object_ref(connection);
537 self->state = TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED;
539 tp_base_channel_register(TP_BASE_CHANNEL(self));
541 return(G_OBJECT(self));
545 * Backend adaptor functions
547 void sipe_backend_search_failed(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
548 struct sipe_backend_search_token *token,
549 const gchar *msg)
551 SIPE_DEBUG_INFO("sipe_backend_search_failed: %s", msg);
552 search_channel_state(SIPE_SEARCH_CHANNEL(token),
553 TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED,
554 msg);
557 static void free_info(GPtrArray *info)
559 g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info);
562 struct sipe_backend_search_results *sipe_backend_search_results_start(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
563 struct sipe_backend_search_token *token)
565 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(token);
567 self->results = g_hash_table_new_full(g_str_hash, g_str_equal,
568 g_free,
569 (GDestroyNotify) free_info);
571 return((struct sipe_backend_search_results *) self);
574 /* adds: the Contact_Info_Field (field_name, [], values) */
575 static void add_search_result(GPtrArray *info,
576 const gchar *field_name,
577 const gchar *field_value)
579 if (field_value) {
580 static const gchar **empty = { NULL };
581 GValueArray *field = g_value_array_new(3);
582 const gchar *components[] = { field_value, NULL };
583 GValue *value;
585 SIPE_DEBUG_INFO("add_search_result: %s = '%s'",
586 field_name, field_value);
588 g_value_array_append(field, NULL);
589 value = g_value_array_get_nth(field, 0);
590 g_value_init(value, G_TYPE_STRING);
591 g_value_set_static_string(value, field_name);
593 g_value_array_append(field, NULL);
594 value = g_value_array_get_nth(field, 1);
595 g_value_init(value, G_TYPE_STRV);
596 g_value_set_static_boxed(value, empty);
598 g_value_array_append(field, NULL);
599 value = g_value_array_get_nth(field, 2);
600 g_value_init(value, G_TYPE_STRV);
601 g_value_set_boxed(value, components);
603 g_ptr_array_add(info, field);
607 void sipe_backend_search_results_add(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
608 struct sipe_backend_search_results *results,
609 const gchar *uri,
610 const gchar *name,
611 const gchar *company,
612 const gchar *country,
613 const gchar *email)
615 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(results);
616 GPtrArray *info = g_ptr_array_new();
618 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_FULLNAME, name);
619 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_COMPANY, company);
620 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_COUNTRY, country);
621 add_search_result(info, SIPE_TELEPATHY_SEARCH_KEY_EMAIL, email);
623 g_hash_table_insert(self->results, g_strdup(uri), info);
626 void sipe_backend_search_results_finalize(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
627 struct sipe_backend_search_results *results,
628 SIPE_UNUSED_PARAMETER const gchar *description,
629 SIPE_UNUSED_PARAMETER gboolean more)
631 SipeSearchChannel *self = SIPE_SEARCH_CHANNEL(results);
633 tp_svc_channel_type_contact_search_emit_search_result_received(self,
634 self->results);
635 search_channel_state(self,
636 TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED,
637 NULL);
642 Local Variables:
643 mode: c
644 c-file-style: "bsd"
645 indent-tabs-mode: t
646 tab-width: 8
647 End: