|
1 /* |
|
2 * muc-factory.c - Source for GabbleMucFactory |
|
3 * Copyright (C) 2006 Collabora Ltd. |
|
4 * |
|
5 * This library is free software; you can redistribute it and/or |
|
6 * modify it under the terms of the GNU Lesser General Public |
|
7 * License as published by the Free Software Foundation; either |
|
8 * version 2.1 of the License, or (at your option) any later version. |
|
9 * |
|
10 * This library is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 * Lesser General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU Lesser General Public |
|
16 * License along with this library; if not, write to the Free Software |
|
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
18 */ |
|
19 |
|
20 |
|
21 #include <stdlib.h> |
|
22 #include <string.h> |
|
23 #include <time.h> |
|
24 |
|
25 #include <glib.h> |
|
26 |
|
27 #include <dbus/dbus-glib.h> |
|
28 #include <dbus/dbus-glib-lowlevel.h> |
|
29 |
|
30 #include "loudmouth/loudmouth.h" |
|
31 |
|
32 #include "debug.h" |
|
33 #include "disco.h" |
|
34 #include "gabble-connection.h" |
|
35 #include "gabble-presence-cache.h" |
|
36 #include "gabble-muc-channel.h" |
|
37 #include "gabble-roomlist-channel.h" |
|
38 #include "handles.h" |
|
39 #include "muc-factory.h" |
|
40 #include "namespaces.h" |
|
41 #include "telepathy-interfaces.h" |
|
42 #include "text-mixin.h" |
|
43 #include "tp-channel-factory-iface.h" |
|
44 #include "util.h" |
|
45 |
|
46 static void gabble_muc_factory_iface_init (gpointer g_iface, gpointer iface_data); |
|
47 |
|
48 #ifndef EMULATOR |
|
49 G_DEFINE_TYPE_WITH_CODE (GabbleMucFactory, gabble_muc_factory, G_TYPE_OBJECT, |
|
50 G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE, gabble_muc_factory_iface_init)); |
|
51 #endif |
|
52 |
|
53 #ifdef EMULATOR |
|
54 #include "libgabble_wsd_solution.h" |
|
55 |
|
56 GET_STATIC_VAR_FROM_TLS(gabble_muc_factory_parent_class,muc_factory,gpointer) |
|
57 #define gabble_muc_factory_parent_class (*GET_WSD_VAR_NAME(gabble_muc_factory_parent_class,muc_factory,s)()) |
|
58 |
|
59 GET_STATIC_VAR_FROM_TLS(g_define_type_id,muc_factory,GType) |
|
60 #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,muc_factory,s)()) |
|
61 |
|
62 static void gabble_muc_factory_init (GabbleMucFactory *self); |
|
63 static void gabble_muc_factory_class_init (GabbleMucFactoryClass *klass); |
|
64 static void gabble_muc_factory_class_intern_init (gpointer klass) |
|
65 { |
|
66 gabble_muc_factory_parent_class = g_type_class_peek_parent (klass); |
|
67 gabble_muc_factory_class_init ((GabbleMucFactoryClass*) klass); |
|
68 } |
|
69 EXPORT_C GType gabble_muc_factory_get_type (void) |
|
70 { |
|
71 if ((g_define_type_id == 0)) { static const GTypeInfo g_define_type_info = { sizeof (GabbleMucFactoryClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_muc_factory_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleMucFactory), 0, (GInstanceInitFunc) gabble_muc_factory_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleMucFactory"), &g_define_type_info, (GTypeFlags) 0); { { static const GInterfaceInfo g_implement_interface_info = { (GInterfaceInitFunc) gabble_muc_factory_iface_init }; g_type_add_interface_static (g_define_type_id, tp_channel_factory_iface_get_type(), &g_implement_interface_info); } ; } } return g_define_type_id; } ; |
|
72 |
|
73 #endif |
|
74 #define DBUS_API_SUBJECT_TO_CHANGE |
|
75 #define DEBUG_FLAG GABBLE_DEBUG_MUC |
|
76 |
|
77 #ifdef DEBUG_FLAG |
|
78 //#define DEBUG(format, ...) |
|
79 #define DEBUGGING 0 |
|
80 #define NODE_DEBUG(n, s) |
|
81 #endif /* DEBUG_FLAG */ |
|
82 |
|
83 /* properties */ |
|
84 enum |
|
85 { |
|
86 PROP_CONNECTION = 1, |
|
87 LAST_PROPERTY |
|
88 }; |
|
89 |
|
90 typedef struct _GabbleMucFactoryPrivate GabbleMucFactoryPrivate; |
|
91 struct _GabbleMucFactoryPrivate |
|
92 { |
|
93 GabbleConnection *conn; |
|
94 |
|
95 LmMessageHandler *message_cb; |
|
96 LmMessageHandler *presence_cb; |
|
97 |
|
98 GHashTable *channels; |
|
99 GabbleRoomlistChannel *roomlist_channel; |
|
100 |
|
101 GHashTable *disco_requests; |
|
102 |
|
103 gboolean dispose_has_run; |
|
104 }; |
|
105 |
|
106 #define GABBLE_MUC_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GABBLE_TYPE_MUC_FACTORY, GabbleMucFactoryPrivate)) |
|
107 |
|
108 static GObject *gabble_muc_factory_constructor (GType type, guint n_props, GObjectConstructParam *props); |
|
109 |
|
110 static void |
|
111 gabble_muc_factory_init (GabbleMucFactory *fac) |
|
112 { |
|
113 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
114 |
|
115 priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
|
116 NULL, g_object_unref); |
|
117 |
|
118 priv->disco_requests = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
|
119 NULL, NULL); |
|
120 |
|
121 priv->message_cb = NULL; |
|
122 priv->presence_cb = NULL; |
|
123 |
|
124 priv->conn = NULL; |
|
125 priv->dispose_has_run = FALSE; |
|
126 } |
|
127 |
|
128 static GObject * |
|
129 gabble_muc_factory_constructor (GType type, guint n_props, |
|
130 GObjectConstructParam *props) |
|
131 { |
|
132 GObject *obj; |
|
133 /* GabbleMucFactoryPrivate *priv; */ |
|
134 |
|
135 obj = G_OBJECT_CLASS (gabble_muc_factory_parent_class)-> |
|
136 constructor (type, n_props, props); |
|
137 /* priv = GABBLE_MUC_FACTORY_GET_PRIVATE (obj); */ |
|
138 |
|
139 return obj; |
|
140 } |
|
141 |
|
142 static void |
|
143 cancel_disco_request (gpointer key, gpointer value, gpointer user_data) |
|
144 { |
|
145 GabbleDisco *disco = GABBLE_DISCO (user_data); |
|
146 GabbleDiscoRequest *request = (GabbleDiscoRequest *) key; |
|
147 |
|
148 gabble_disco_cancel_request (disco, request); |
|
149 } |
|
150 |
|
151 static void |
|
152 gabble_muc_factory_dispose (GObject *object) |
|
153 { |
|
154 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); |
|
155 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
156 |
|
157 if (priv->dispose_has_run) |
|
158 return; |
|
159 |
|
160 gabble_debug (DEBUG_FLAG, "dispose called"); |
|
161 priv->dispose_has_run = TRUE; |
|
162 |
|
163 tp_channel_factory_iface_close_all (TP_CHANNEL_FACTORY_IFACE (object)); |
|
164 |
|
165 g_hash_table_foreach (priv->disco_requests, cancel_disco_request, priv->conn->disco); |
|
166 g_hash_table_destroy (priv->disco_requests); |
|
167 |
|
168 if (G_OBJECT_CLASS (gabble_muc_factory_parent_class)->dispose) |
|
169 G_OBJECT_CLASS (gabble_muc_factory_parent_class)->dispose (object); |
|
170 } |
|
171 |
|
172 static void |
|
173 gabble_muc_factory_get_property (GObject *object, |
|
174 guint property_id, |
|
175 GValue *value, |
|
176 GParamSpec *pspec) |
|
177 { |
|
178 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); |
|
179 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
180 |
|
181 switch (property_id) { |
|
182 case PROP_CONNECTION: |
|
183 g_value_set_object (value, priv->conn); |
|
184 break; |
|
185 default: |
|
186 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
|
187 break; |
|
188 } |
|
189 } |
|
190 |
|
191 static void |
|
192 gabble_muc_factory_set_property (GObject *object, |
|
193 guint property_id, |
|
194 const GValue *value, |
|
195 GParamSpec *pspec) |
|
196 { |
|
197 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); |
|
198 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
199 |
|
200 switch (property_id) { |
|
201 case PROP_CONNECTION: |
|
202 priv->conn = g_value_get_object (value); |
|
203 break; |
|
204 default: |
|
205 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
|
206 break; |
|
207 } |
|
208 } |
|
209 |
|
210 static void |
|
211 gabble_muc_factory_class_init (GabbleMucFactoryClass *gabble_muc_factory_class) |
|
212 { |
|
213 GObjectClass *object_class = G_OBJECT_CLASS (gabble_muc_factory_class); |
|
214 GParamSpec *param_spec; |
|
215 |
|
216 g_type_class_add_private (gabble_muc_factory_class, sizeof (GabbleMucFactoryPrivate)); |
|
217 |
|
218 object_class->constructor = gabble_muc_factory_constructor; |
|
219 object_class->dispose = gabble_muc_factory_dispose; |
|
220 |
|
221 object_class->get_property = gabble_muc_factory_get_property; |
|
222 object_class->set_property = gabble_muc_factory_set_property; |
|
223 |
|
224 param_spec = g_param_spec_object ("connection", "GabbleConnection object", |
|
225 "Gabble connection object that owns this " |
|
226 "MUC factory object.", |
|
227 GABBLE_TYPE_CONNECTION, |
|
228 G_PARAM_CONSTRUCT_ONLY | |
|
229 G_PARAM_READWRITE | |
|
230 G_PARAM_STATIC_NICK | |
|
231 G_PARAM_STATIC_BLURB); |
|
232 g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); |
|
233 } |
|
234 |
|
235 |
|
236 static GabbleMucChannel * |
|
237 get_muc_from_jid (GabbleMucFactory *fac, const gchar *jid) |
|
238 { |
|
239 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
240 GabbleHandle handle; |
|
241 GabbleMucChannel *chan = NULL; |
|
242 |
|
243 if (gabble_handle_for_room_exists (priv->conn->handles, jid, TRUE)) |
|
244 { |
|
245 handle = gabble_handle_for_room (priv->conn->handles, jid); |
|
246 |
|
247 chan = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle)); |
|
248 } |
|
249 |
|
250 return chan; |
|
251 } |
|
252 |
|
253 |
|
254 /** |
|
255 * muc_channel_closed_cb: |
|
256 * |
|
257 * Signal callback for when a MUC channel is closed. Removes the references |
|
258 * that MucFactory holds to them. |
|
259 */ |
|
260 static void |
|
261 muc_channel_closed_cb (GabbleMucChannel *chan, gpointer user_data) |
|
262 { |
|
263 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data); |
|
264 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
265 GabbleHandle room_handle; |
|
266 |
|
267 if (priv->channels != NULL) |
|
268 { |
|
269 g_object_get (chan, "handle", &room_handle, NULL); |
|
270 |
|
271 gabble_debug (DEBUG_FLAG, "removing MUC channel with handle %d", room_handle); |
|
272 |
|
273 g_hash_table_remove (priv->channels, GINT_TO_POINTER (room_handle)); |
|
274 } |
|
275 } |
|
276 |
|
277 static void |
|
278 muc_ready_cb (GabbleMucChannel *chan, |
|
279 gpointer data) |
|
280 { |
|
281 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data); |
|
282 |
|
283 gabble_debug (DEBUG_FLAG, "chan=%p", chan); |
|
284 |
|
285 g_signal_emit_by_name (fac, "new-channel", chan); |
|
286 } |
|
287 |
|
288 static void |
|
289 muc_join_error_cb (GabbleMucChannel *chan, |
|
290 GError *error, |
|
291 gpointer data) |
|
292 { |
|
293 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data); |
|
294 |
|
295 gabble_debug (DEBUG_FLAG, "error->code=%u, error->message=\"%s\"", error->code, error->message); |
|
296 |
|
297 g_signal_emit_by_name (fac, "channel-error", chan, error); |
|
298 } |
|
299 |
|
300 /** |
|
301 * new_muc_channel |
|
302 */ |
|
303 static GabbleMucChannel * |
|
304 new_muc_channel (GabbleMucFactory *fac, GabbleHandle handle, gboolean invite_self) |
|
305 { |
|
306 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
307 GabbleMucChannel *chan; |
|
308 char *object_path; |
|
309 |
|
310 g_assert (g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle)) == NULL); |
|
311 |
|
312 object_path = g_strdup_printf ("%s/MucChannel%u", priv->conn->object_path, handle); |
|
313 |
|
314 gabble_debug (DEBUG_FLAG, "creating new chan, object path %s", object_path); |
|
315 |
|
316 chan = g_object_new (GABBLE_TYPE_MUC_CHANNEL, |
|
317 "connection", priv->conn, |
|
318 "object-path", object_path, |
|
319 "handle", handle, |
|
320 "invite-self", invite_self, |
|
321 NULL); |
|
322 |
|
323 g_signal_connect (chan, "closed", (GCallback) muc_channel_closed_cb, fac); |
|
324 |
|
325 g_hash_table_insert (priv->channels, GINT_TO_POINTER (handle), chan); |
|
326 |
|
327 g_free (object_path); |
|
328 |
|
329 g_signal_connect (chan, "ready", G_CALLBACK (muc_ready_cb), fac); |
|
330 g_signal_connect (chan, "join-error", G_CALLBACK (muc_join_error_cb), |
|
331 fac); |
|
332 |
|
333 return chan; |
|
334 } |
|
335 |
|
336 |
|
337 struct DiscoInviteData { |
|
338 GabbleMucFactory *factory; |
|
339 gchar *reason; |
|
340 GabbleHandle inviter; |
|
341 }; |
|
342 |
|
343 /** |
|
344 * obsolete_invite_disco_cb: |
|
345 * |
|
346 * Callback for disco request we fired upon encountering obsolete disco. |
|
347 * If the object is in fact MUC room, create a channel for it. |
|
348 */ |
|
349 static void |
|
350 obsolete_invite_disco_cb (GabbleDisco *self, |
|
351 GabbleDiscoRequest *request, |
|
352 const gchar *jid, |
|
353 const gchar *node, |
|
354 LmMessageNode *query_result, |
|
355 GError* error, |
|
356 gpointer user_data) |
|
357 { |
|
358 struct DiscoInviteData *data = (struct DiscoInviteData *) user_data; |
|
359 |
|
360 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data->factory); |
|
361 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
362 LmMessageNode *identity; |
|
363 const char *category, *type; |
|
364 GabbleHandle handle; |
|
365 |
|
366 g_hash_table_remove (priv->disco_requests, request); |
|
367 |
|
368 identity = lm_message_node_get_child (query_result, "identity"); |
|
369 if (NULL == identity) |
|
370 return; |
|
371 |
|
372 category = lm_message_node_get_attribute (identity, "category"); |
|
373 if (NULL == category) |
|
374 return; |
|
375 |
|
376 type = lm_message_node_get_attribute (identity, "type"); |
|
377 if (NULL == type) |
|
378 return; |
|
379 |
|
380 if (0 != strcmp (category, "conference") || |
|
381 0 != strcmp (type, "text")) |
|
382 { |
|
383 gabble_debug (DEBUG_FLAG, "obsolete invite request specified invalid jid '%s', ignoring", jid); |
|
384 } |
|
385 |
|
386 /* OK, it's MUC after all, create a new channel */ |
|
387 handle = gabble_handle_for_room (priv->conn->handles, jid); |
|
388 |
|
389 if (g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle)) == NULL) |
|
390 { |
|
391 GabbleMucChannel *chan; |
|
392 chan = new_muc_channel (fac, handle, FALSE); |
|
393 _gabble_muc_channel_handle_invited (chan, data->inviter, data->reason); |
|
394 } |
|
395 else |
|
396 { |
|
397 gabble_debug (DEBUG_FLAG, "ignoring invite to a room '%s' we're already in", jid); |
|
398 } |
|
399 |
|
400 g_free (data->reason); |
|
401 g_free (data); |
|
402 } |
|
403 |
|
404 |
|
405 /** |
|
406 * muc_factory_message_cb: |
|
407 * |
|
408 * Called by loudmouth when we get an incoming <message>. |
|
409 * We filter only groupchat and MUC messages, ignoring the rest. |
|
410 */ |
|
411 static LmHandlerResult |
|
412 muc_factory_message_cb (LmMessageHandler *handler, |
|
413 LmConnection *connection, |
|
414 LmMessage *message, |
|
415 gpointer user_data) |
|
416 { |
|
417 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data); |
|
418 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
419 |
|
420 const gchar *from, *body, *body_offset; |
|
421 time_t stamp; |
|
422 TpChannelTextMessageType msgtype; |
|
423 LmMessageNode *node; |
|
424 TpHandleType handle_type; |
|
425 GabbleHandle room_handle, handle; |
|
426 GabbleMucChannel *chan; |
|
427 GabbleTextMixinSendError send_error; |
|
428 |
|
429 if (!gabble_text_mixin_parse_incoming_message (message, &from, &stamp, |
|
430 &msgtype, &body, &body_offset, &send_error)) |
|
431 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
432 |
|
433 /* does it have a muc subnode? */ |
|
434 node = lm_message_node_get_child_with_namespace (message->node, "x", |
|
435 NS_MUC_USER); |
|
436 if (node != NULL) |
|
437 { |
|
438 /* and an invitation? */ |
|
439 node = lm_message_node_get_child (node, "invite"); |
|
440 if (node != NULL) |
|
441 { |
|
442 LmMessageNode *reason_node; |
|
443 const gchar *invite_from, *reason; |
|
444 GabbleHandle inviter_handle; |
|
445 |
|
446 if (send_error != CHANNEL_TEXT_SEND_NO_ERROR) |
|
447 { |
|
448 NODE_DEBUG (message->node, "got a MUC invitation message " |
|
449 "with a send error; ignoring"); |
|
450 |
|
451 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
452 } |
|
453 |
|
454 invite_from = lm_message_node_get_attribute (node, "from"); |
|
455 if (invite_from == NULL) |
|
456 { |
|
457 NODE_DEBUG (message->node, "got a MUC invitation message " |
|
458 "without a from field on the invite node, " |
|
459 "ignoring"); |
|
460 |
|
461 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
462 } |
|
463 |
|
464 inviter_handle = gabble_handle_for_contact (priv->conn->handles, |
|
465 invite_from, FALSE); |
|
466 |
|
467 reason_node = lm_message_node_get_child (node, "reason"); |
|
468 if (reason_node != NULL) |
|
469 { |
|
470 reason = lm_message_node_get_value (reason_node); |
|
471 } |
|
472 else |
|
473 { |
|
474 reason = ""; |
|
475 NODE_DEBUG (message->node, "no MUC invite reason specified"); |
|
476 } |
|
477 |
|
478 /* create the channel */ |
|
479 handle = gabble_handle_for_room (priv->conn->handles, from); |
|
480 |
|
481 if (g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle)) == NULL) |
|
482 { |
|
483 chan = new_muc_channel (fac, handle, FALSE); |
|
484 _gabble_muc_channel_handle_invited (chan, inviter_handle, reason); |
|
485 } |
|
486 else |
|
487 { |
|
488 NODE_DEBUG (message->node, "ignoring invite to a room we're already in"); |
|
489 } |
|
490 |
|
491 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
492 } |
|
493 } |
|
494 else |
|
495 { |
|
496 GabbleHandle inviter_handle; |
|
497 GabbleDiscoRequest *request; |
|
498 const gchar *reason; |
|
499 struct DiscoInviteData *disco_udata; |
|
500 |
|
501 /* check for obsolete invite method */ |
|
502 for (node = message->node->children; node != NULL; node = node->next) |
|
503 if (strcmp (node->name, "x") == 0) |
|
504 if (lm_message_node_has_namespace (node, NS_X_CONFERENCE, NULL)) |
|
505 break; |
|
506 |
|
507 if (node == NULL) |
|
508 goto HANDLE_MESSAGE; |
|
509 |
|
510 /* the room JID is in x */ |
|
511 from = lm_message_node_get_attribute (node, "jid"); |
|
512 if (from == NULL) |
|
513 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
514 |
|
515 /* the inviter JID is in "from" */ |
|
516 inviter_handle = gabble_handle_for_contact (priv->conn->handles, |
|
517 from, FALSE); |
|
518 |
|
519 /* reason is the body */ |
|
520 reason = body; |
|
521 |
|
522 disco_udata = g_new0 (struct DiscoInviteData, 1); |
|
523 disco_udata->factory = fac; |
|
524 disco_udata->reason = g_strdup (reason); |
|
525 disco_udata->inviter = inviter_handle; |
|
526 |
|
527 NODE_DEBUG (message->node, "received obsolete invite method"); |
|
528 |
|
529 request = gabble_disco_request (priv->conn->disco, GABBLE_DISCO_TYPE_INFO, |
|
530 from, NULL, obsolete_invite_disco_cb, disco_udata, G_OBJECT (fac), NULL); |
|
531 |
|
532 if (request != NULL) |
|
533 g_hash_table_insert (priv->disco_requests, request, NULL); |
|
534 |
|
535 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
536 } |
|
537 |
|
538 HANDLE_MESSAGE: |
|
539 |
|
540 /* check if a room with the jid exists */ |
|
541 if (!gabble_handle_for_room_exists (priv->conn->handles, from, TRUE)) |
|
542 { |
|
543 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
544 } |
|
545 |
|
546 room_handle = gabble_handle_for_room (priv->conn->handles, from); |
|
547 |
|
548 /* find the MUC channel */ |
|
549 chan = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (room_handle)); |
|
550 |
|
551 if (chan == NULL) |
|
552 { |
|
553 g_warning ("%s: ignoring groupchat message from known handle with " |
|
554 "no MUC channel", G_STRFUNC); |
|
555 |
|
556 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
557 } |
|
558 |
|
559 /* get the handle of the sender, which is either the room |
|
560 * itself or one of its members */ |
|
561 if (gabble_handle_for_room_exists (priv->conn->handles, from, FALSE)) |
|
562 { |
|
563 handle_type = TP_HANDLE_TYPE_ROOM; |
|
564 handle = room_handle; |
|
565 } |
|
566 else |
|
567 { |
|
568 handle_type = TP_HANDLE_TYPE_CONTACT; |
|
569 handle = gabble_handle_for_contact (priv->conn->handles, from, TRUE); |
|
570 } |
|
571 |
|
572 if (send_error != CHANNEL_TEXT_SEND_NO_ERROR) |
|
573 { |
|
574 _gabble_text_mixin_send_error_signal (G_OBJECT (chan), send_error, stamp, |
|
575 msgtype, body_offset); |
|
576 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
577 } |
|
578 |
|
579 if (_gabble_muc_channel_receive (chan, msgtype, handle_type, handle, stamp, |
|
580 body_offset, message)) |
|
581 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
582 |
|
583 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
584 } |
|
585 |
|
586 |
|
587 /** |
|
588 * connection_presence_muc_cb: |
|
589 * @handler: #LmMessageHandler for this message |
|
590 * @connection: #LmConnection that originated the message |
|
591 * @message: the presence message |
|
592 * @user_data: callback data |
|
593 * |
|
594 * Called by loudmouth when we get an incoming <presence>. |
|
595 */ |
|
596 static LmHandlerResult |
|
597 muc_factory_presence_cb (LmMessageHandler *handler, |
|
598 LmConnection *lmconn, |
|
599 LmMessage *msg, |
|
600 gpointer user_data) |
|
601 { |
|
602 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data); |
|
603 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
604 const char *from; |
|
605 LmMessageSubType sub_type; |
|
606 GabbleMucChannel *muc_chan; |
|
607 LmMessageNode *x_node; |
|
608 |
|
609 g_assert (lmconn == priv->conn->lmconn); |
|
610 |
|
611 from = lm_message_node_get_attribute (msg->node, "from"); |
|
612 |
|
613 if (from == NULL) |
|
614 { |
|
615 NODE_DEBUG (msg->node, "presence stanza without from attribute, ignoring"); |
|
616 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
617 } |
|
618 |
|
619 sub_type = lm_message_get_sub_type (msg); |
|
620 |
|
621 muc_chan = get_muc_from_jid (fac, from); |
|
622 |
|
623 /* is it an error and for a MUC? */ |
|
624 if (sub_type == LM_MESSAGE_SUB_TYPE_ERROR |
|
625 && muc_chan != NULL) |
|
626 { |
|
627 _gabble_muc_channel_presence_error (muc_chan, from, msg->node); |
|
628 |
|
629 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
630 } |
|
631 |
|
632 x_node = lm_message_node_get_child_with_namespace (msg->node, "x", NS_MUC_USER); |
|
633 |
|
634 /* is it a MUC member presence? */ |
|
635 if (x_node != NULL) |
|
636 { |
|
637 if (muc_chan != NULL) |
|
638 { |
|
639 GabbleHandle handle; |
|
640 |
|
641 handle = gabble_handle_for_contact (priv->conn->handles, from, TRUE); |
|
642 if (handle == 0) |
|
643 { |
|
644 NODE_DEBUG (msg->node, "discarding MUC presence from malformed jid"); |
|
645 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
646 } |
|
647 |
|
648 _gabble_muc_channel_member_presence_updated (muc_chan, handle, |
|
649 msg, x_node); |
|
650 } |
|
651 else |
|
652 { |
|
653 NODE_DEBUG (msg->node, "discarding unexpected MUC member presence"); |
|
654 |
|
655 return LM_HANDLER_RESULT_REMOVE_MESSAGE; |
|
656 } |
|
657 } |
|
658 |
|
659 return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; |
|
660 |
|
661 } |
|
662 |
|
663 static void |
|
664 roomlist_channel_closed_cb (GabbleRoomlistChannel *chan, gpointer data) |
|
665 { |
|
666 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data); |
|
667 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
668 |
|
669 if (priv->roomlist_channel != NULL) |
|
670 { |
|
671 g_object_unref (priv->roomlist_channel); |
|
672 priv->roomlist_channel = NULL; |
|
673 } |
|
674 } |
|
675 |
|
676 static gboolean |
|
677 make_roomlist_channel (GabbleMucFactory *fac) |
|
678 { |
|
679 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
680 |
|
681 if (priv->roomlist_channel == NULL) |
|
682 { |
|
683 const gchar *server; |
|
684 gchar *object_path; |
|
685 |
|
686 server = _gabble_connection_find_conference_server (priv->conn); |
|
687 |
|
688 if (server == NULL) |
|
689 return FALSE; |
|
690 |
|
691 object_path = g_strdup_printf ("%s/RoomlistChannel", priv->conn->object_path); |
|
692 |
|
693 priv->roomlist_channel = _gabble_roomlist_channel_new (priv->conn, |
|
694 object_path, server); |
|
695 |
|
696 g_signal_connect (priv->roomlist_channel, "closed", |
|
697 (GCallback) roomlist_channel_closed_cb, fac); |
|
698 |
|
699 g_signal_emit_by_name (fac, "new-channel", priv->roomlist_channel); |
|
700 |
|
701 g_free (object_path); |
|
702 } |
|
703 |
|
704 return TRUE; |
|
705 } |
|
706 |
|
707 |
|
708 |
|
709 static void |
|
710 gabble_muc_factory_iface_close_all (TpChannelFactoryIface *iface) |
|
711 { |
|
712 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (iface); |
|
713 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
714 |
|
715 gabble_debug (DEBUG_FLAG, "closing channels"); |
|
716 |
|
717 if (priv->channels != NULL) |
|
718 { |
|
719 GHashTable *tmp = priv->channels; |
|
720 priv->channels = NULL; |
|
721 g_hash_table_destroy (tmp); |
|
722 } |
|
723 |
|
724 if (priv->roomlist_channel != NULL) |
|
725 { |
|
726 GObject *tmp = G_OBJECT (priv->roomlist_channel); |
|
727 priv->roomlist_channel = NULL; |
|
728 g_object_unref (tmp); |
|
729 } |
|
730 } |
|
731 |
|
732 static void |
|
733 gabble_muc_factory_iface_connecting (TpChannelFactoryIface *iface) |
|
734 { |
|
735 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (iface); |
|
736 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
737 |
|
738 gabble_debug (DEBUG_FLAG, "adding callbacks"); |
|
739 |
|
740 g_assert (priv->message_cb == NULL); |
|
741 g_assert (priv->presence_cb == NULL); |
|
742 |
|
743 priv->message_cb = lm_message_handler_new (muc_factory_message_cb, fac, NULL); |
|
744 lm_connection_register_message_handler (priv->conn->lmconn, priv->message_cb, |
|
745 LM_MESSAGE_TYPE_MESSAGE, |
|
746 LM_HANDLER_PRIORITY_NORMAL); |
|
747 |
|
748 priv->presence_cb = lm_message_handler_new (muc_factory_presence_cb, fac, NULL); |
|
749 lm_connection_register_message_handler (priv->conn->lmconn, priv->presence_cb, |
|
750 LM_MESSAGE_TYPE_PRESENCE, |
|
751 LM_HANDLER_PRIORITY_NORMAL); |
|
752 } |
|
753 |
|
754 |
|
755 static void |
|
756 gabble_muc_factory_iface_connected (TpChannelFactoryIface *iface) |
|
757 { |
|
758 /* nothing to do */ |
|
759 } |
|
760 |
|
761 static void |
|
762 gabble_muc_factory_iface_disconnected (TpChannelFactoryIface *iface) |
|
763 { |
|
764 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (iface); |
|
765 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
766 |
|
767 gabble_debug (DEBUG_FLAG, "removing callbacks"); |
|
768 |
|
769 g_assert (priv->message_cb != NULL); |
|
770 g_assert (priv->presence_cb != NULL); |
|
771 |
|
772 lm_connection_unregister_message_handler (priv->conn->lmconn, priv->message_cb, |
|
773 LM_MESSAGE_TYPE_MESSAGE); |
|
774 lm_message_handler_unref (priv->message_cb); |
|
775 priv->message_cb = NULL; |
|
776 |
|
777 lm_connection_unregister_message_handler (priv->conn->lmconn, priv->presence_cb, |
|
778 LM_MESSAGE_TYPE_PRESENCE); |
|
779 lm_message_handler_unref (priv->presence_cb); |
|
780 priv->presence_cb = NULL; |
|
781 } |
|
782 |
|
783 struct _ForeachData |
|
784 { |
|
785 TpChannelFunc foreach; |
|
786 gpointer user_data; |
|
787 }; |
|
788 |
|
789 static void |
|
790 _foreach_slave (gpointer key, gpointer value, gpointer user_data) |
|
791 { |
|
792 struct _ForeachData *data = (struct _ForeachData *) user_data; |
|
793 TpChannelIface *chan = TP_CHANNEL_IFACE (value); |
|
794 |
|
795 data->foreach (chan, data->user_data); |
|
796 } |
|
797 |
|
798 static void |
|
799 gabble_muc_factory_iface_foreach (TpChannelFactoryIface *iface, TpChannelFunc foreach, gpointer user_data) |
|
800 { |
|
801 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (iface); |
|
802 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
803 struct _ForeachData data; |
|
804 |
|
805 data.user_data = user_data; |
|
806 data.foreach = foreach; |
|
807 |
|
808 g_hash_table_foreach (priv->channels, _foreach_slave, &data); |
|
809 |
|
810 if (priv->roomlist_channel != NULL) |
|
811 foreach (TP_CHANNEL_IFACE (priv->roomlist_channel), user_data); |
|
812 } |
|
813 |
|
814 static TpChannelFactoryRequestStatus |
|
815 gabble_muc_factory_iface_request (TpChannelFactoryIface *iface, |
|
816 const gchar *chan_type, |
|
817 TpHandleType handle_type, |
|
818 guint handle, |
|
819 TpChannelIface **ret, |
|
820 GError **error) |
|
821 { |
|
822 GabbleMucFactory *fac = GABBLE_MUC_FACTORY (iface); |
|
823 GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); |
|
824 GabbleMucChannel *chan; |
|
825 |
|
826 if (!g_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_ROOM_LIST)) |
|
827 { |
|
828 /* FIXME - delay if services aren't discovered yet? */ |
|
829 if (!make_roomlist_channel (fac)) |
|
830 { |
|
831 gabble_debug (DEBUG_FLAG, "no conference server available for roomlist request"); |
|
832 return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE; |
|
833 } |
|
834 *ret = TP_CHANNEL_IFACE (priv->roomlist_channel); |
|
835 return TP_CHANNEL_FACTORY_REQUEST_STATUS_DONE; |
|
836 } |
|
837 |
|
838 if (strcmp (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT)) |
|
839 return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED; |
|
840 |
|
841 if (handle_type != TP_HANDLE_TYPE_ROOM) |
|
842 return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE; |
|
843 |
|
844 if (!gabble_handle_is_valid (priv->conn->handles, TP_HANDLE_TYPE_ROOM, handle, NULL)) |
|
845 return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE; |
|
846 |
|
847 chan = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle)); |
|
848 if (!chan) |
|
849 { |
|
850 chan = new_muc_channel (fac, handle, TRUE); |
|
851 return TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED; |
|
852 } |
|
853 |
|
854 if (_gabble_muc_channel_is_ready (chan)) |
|
855 { |
|
856 *ret = TP_CHANNEL_IFACE (chan); |
|
857 return TP_CHANNEL_FACTORY_REQUEST_STATUS_DONE; |
|
858 } |
|
859 else |
|
860 { |
|
861 return TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED; |
|
862 } |
|
863 } |
|
864 |
|
865 static void |
|
866 gabble_muc_factory_iface_init (gpointer g_iface, |
|
867 gpointer iface_data) |
|
868 { |
|
869 TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *) g_iface; |
|
870 |
|
871 klass->close_all = gabble_muc_factory_iface_close_all; |
|
872 klass->connecting = gabble_muc_factory_iface_connecting; |
|
873 klass->connected = gabble_muc_factory_iface_connected; |
|
874 klass->disconnected = gabble_muc_factory_iface_disconnected; |
|
875 klass->foreach = gabble_muc_factory_iface_foreach; |
|
876 klass->request = gabble_muc_factory_iface_request; |
|
877 } |
|
878 |
|
879 |