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
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 */
50 * Search Manager class - data structures
52 typedef struct _SipeSearchManagerClass
{
53 GObjectClass parent_class
;
54 } SipeSearchManagerClass
;
56 typedef struct _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, \
73 * Search Channel class - data structures
75 typedef struct _SipeSearchChannelClass
{
76 TpBaseChannelClass parent_class
;
77 } SipeSearchChannelClass
;
79 typedef struct _SipeSearchChannel
{
84 TpChannelContactSearchState state
;
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, \
99 * Search Manager class - type definition
101 static void channel_manager_iface_init(gpointer
, gpointer
);
102 G_DEFINE_TYPE_WITH_CODE(SipeSearchManager
,
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
,
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
;
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
);
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
169 static void foreach_channel(TpChannelManager
*manager
,
170 TpExportableChannelFunc func
,
173 SipeSearchManager
*self
= SIPE_SEARCH_MANAGER(manager
);
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
,
188 static const gchar
*const no_props
[] = {
191 GHashTable
*table
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
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
);
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
))
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
,
234 (GCallback
) search_channel_closed_cb
,
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
),
242 g_slist_free(request_tokens
);
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
);
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
274 CHANNEL_PROP_SEARCH_KEYS
= 1,
278 static void get_property(GObject
*object
,
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
,
297 g_value_set_boxed(value
, search_keys
);
301 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, property_id
, pspec
);
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
,
311 tp_dbus_properties_mixin_fill_properties_hash(G_OBJECT(channel
),
313 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH
, "AvailableSearchKeys",
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
);
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
;
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");
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",
366 GObjectClass
*object_class
= G_OBJECT_CLASS(klass
);
367 TpBaseChannelClass
*base_class
= TP_BASE_CHANNEL_CLASS(klass
);
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",
388 G_PARAM_READABLE
| G_PARAM_STATIC_STRINGS
);
389 g_object_class_install_property(object_class
,
390 CHANNEL_PROP_SEARCH_KEYS
,
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
,
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
410 static void search_channel_state(SipeSearchChannel
*self
,
411 TpChannelContactSearchState new_state
,
414 GHashTable
*details
= tp_asv_new(NULL
, NULL
);
417 tp_asv_set_string(details
, "debug-message", msg
);
418 tp_svc_channel_type_contact_search_emit_search_state_changed(self
,
422 g_hash_table_unref(details
);
423 self
->state
= new_state
;
426 static void search_channel_search(TpSvcChannelTypeContactSearch
*channel
,
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? */
455 SIPE_DEBUG_INFO("SipeSearchChannel::search: full name given: '%s'",
459 * - one word -> first name
460 * - two words -> first & last name
462 split
= g_strsplit(alternative
, " ", 3);
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
))
474 SIPE_DEBUG_INFO("SipeSearchChannel::search: one big search box given: '%s'",
477 * - one word with '@' -> email
478 * - one word -> first name
479 * - two words -> first & last name
481 split
= g_strsplit(alternative
, " ", 3);
483 if (strchr(split
[0], '@')) {
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
, company
, country
);
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
,
507 tp_svc_channel_type_contact_search_return_from_search(context
);
509 GError
*error
= g_error_new(TP_ERROR
, TP_ERROR_NOT_AVAILABLE
,
510 "invalid search state");
511 dbus_g_method_return_error(context
, 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)
524 /* we don't support stopping a search */
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
,
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
,
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
,
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
,
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
)
580 static const gchar
**empty
= { NULL
};
581 GValueArray
*field
= g_value_array_new(3);
582 const gchar
*components
[] = { field_value
, NULL
};
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
,
611 const gchar
*company
,
612 const gchar
*country
,
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
,
635 search_channel_state(self
,
636 TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED
,