2 * @file telepathy-search.c
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
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 */
46 * Search Manager class - data structures
48 typedef struct _SipeSearchManagerClass
{
49 GObjectClass parent_class
;
50 } SipeSearchManagerClass
;
52 typedef struct _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, \
69 * Search Channel class - data structures
71 typedef struct _SipeSearchChannelClass
{
72 TpBaseChannelClass parent_class
;
73 } SipeSearchChannelClass
;
75 typedef struct _SipeSearchChannel
{
80 TpChannelContactSearchState state
;
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, \
95 * Search Manager class - type definition
97 static void channel_manager_iface_init(gpointer
, gpointer
);
98 G_DEFINE_TYPE_WITH_CODE(SipeSearchManager
,
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
,
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
;
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
);
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
165 static void foreach_channel(TpChannelManager
*manager
,
166 TpExportableChannelFunc func
,
169 SipeSearchManager
*self
= SIPE_SEARCH_MANAGER(manager
);
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
,
184 static const gchar
*const no_props
[] = {
187 GHashTable
*table
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
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
);
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
))
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
,
230 (GCallback
) search_channel_closed_cb
,
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
),
238 g_slist_free(request_tokens
);
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
);
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
270 CHANNEL_PROP_SEARCH_KEYS
= 1,
274 static void get_property(GObject
*object
,
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
,
293 g_value_set_boxed(value
, search_keys
);
297 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, property_id
, pspec
);
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
,
307 tp_dbus_properties_mixin_fill_properties_hash(G_OBJECT(channel
),
309 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH
, "AvailableSearchKeys",
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
);
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
;
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");
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",
362 GObjectClass
*object_class
= G_OBJECT_CLASS(klass
);
363 TpBaseChannelClass
*base_class
= TP_BASE_CHANNEL_CLASS(klass
);
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",
384 G_PARAM_READABLE
| G_PARAM_STATIC_STRINGS
);
385 g_object_class_install_property(object_class
,
386 CHANNEL_PROP_SEARCH_KEYS
,
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
,
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
406 static void search_channel_state(SipeSearchChannel
*self
,
407 TpChannelContactSearchState new_state
,
410 GHashTable
*details
= tp_asv_new(NULL
, NULL
);
413 tp_asv_set_string(details
, "debug-message", msg
);
414 tp_svc_channel_type_contact_search_emit_search_state_changed(self
,
418 g_hash_table_unref(details
);
419 self
->state
= new_state
;
422 static void search_channel_search(TpSvcChannelTypeContactSearch
*channel
,
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? */
451 SIPE_DEBUG_INFO("SipeSearchChannel::search: full name given: '%s'",
455 * - one word -> first name
456 * - two words -> first & last name
458 split
= g_strsplit(alternative
, " ", 3);
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
))
470 SIPE_DEBUG_INFO("SipeSearchChannel::search: one big search box given: '%s'",
473 * - one word with '@' -> email
474 * - one word -> first name
475 * - two words -> first & last name
477 split
= g_strsplit(alternative
, " ", 3);
479 if (strchr(split
[0], '@')) {
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
);
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
,
503 tp_svc_channel_type_contact_search_return_from_search(context
);
505 GError
*error
= g_error_new(TP_ERROR
, TP_ERROR_NOT_AVAILABLE
,
506 "invalid search state");
507 dbus_g_method_return_error(context
, 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)
520 /* we don't support stopping a search */
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
,
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
,
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
,
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
,
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
)
576 static const gchar
**empty
= { NULL
};
577 GValueArray
*field
= g_value_array_new(3);
578 const gchar
*components
[] = { field_value
, NULL
};
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
,
607 const gchar
*company
,
608 const gchar
*country
,
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
,
631 search_channel_state(self
,
632 TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED
,