1 /* |
|
2 * gabble-presence.c - Gabble's per-contact presence structure |
|
3 * Copyright (C) 2005 Collabora Ltd. |
|
4 * and/or its subsidiaries. All rights reserved. |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Lesser General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2.1 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Lesser General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Lesser General Public |
|
17 * License along with this library; if not, write to the Free Software |
|
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 */ |
|
20 |
|
21 #include <string.h> |
|
22 #include <glib.h> |
|
23 |
|
24 #include "gabble-presence-cache.h" |
|
25 #include "gabble-presence.h" |
|
26 #include "util.h" |
|
27 |
|
28 #include "config.h" |
|
29 #define DEBUG_FLAG GABBLE_DEBUG_PRESENCE |
|
30 #include "debug.h" |
|
31 |
|
32 #ifndef EMULATOR |
|
33 G_DEFINE_TYPE (GabblePresence, gabble_presence, G_TYPE_OBJECT); |
|
34 #endif |
|
35 |
|
36 #ifdef EMULATOR |
|
37 #include "libgabble_wsd_solution.h" |
|
38 |
|
39 GET_STATIC_VAR_FROM_TLS(gabble_presence_parent_class,gabble_presence,gpointer) |
|
40 #define gabble_presence_parent_class (*GET_WSD_VAR_NAME(gabble_presence_parent_class,gabble_presence,s)()) |
|
41 |
|
42 GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_presence,GType) |
|
43 #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_presence,s)()) |
|
44 |
|
45 static void gabble_presence_init (GabblePresence *self); |
|
46 static void gabble_presence_class_init (GabblePresenceClass *klass); |
|
47 static void gabble_presence_class_intern_init (gpointer klass) |
|
48 { |
|
49 gabble_presence_parent_class = g_type_class_peek_parent (klass); |
|
50 gabble_presence_class_init ((GabblePresenceClass*) klass); |
|
51 } |
|
52 EXPORT_C GType gabble_presence_get_type (void) |
|
53 { |
|
54 if ((g_define_type_id == 0)) |
|
55 { static const GTypeInfo g_define_type_info = |
|
56 { sizeof (GabblePresenceClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_presence_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabblePresence), 0, (GInstanceInitFunc) gabble_presence_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabblePresence"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; |
|
57 } ; |
|
58 |
|
59 #endif |
|
60 |
|
61 #define GABBLE_PRESENCE_PRIV(account) ((GabblePresencePrivate *)account->priv) |
|
62 |
|
63 typedef struct _Resource Resource; |
|
64 |
|
65 struct _Resource { |
|
66 gchar *name; |
|
67 GabblePresenceCapabilities caps; |
|
68 guint caps_serial; |
|
69 GabblePresenceId status; |
|
70 gchar *status_message; |
|
71 gint8 priority; |
|
72 }; |
|
73 |
|
74 typedef struct _GabblePresencePrivate GabblePresencePrivate; |
|
75 |
|
76 struct _GabblePresencePrivate { |
|
77 gchar *no_resource_status_message; |
|
78 GSList *resources; |
|
79 }; |
|
80 |
|
81 static Resource * |
|
82 _resource_new (gchar *name) |
|
83 { |
|
84 Resource *new = g_new (Resource, 1); |
|
85 new->name = name; |
|
86 new->caps = PRESENCE_CAP_NONE; |
|
87 new->status = GABBLE_PRESENCE_OFFLINE; |
|
88 new->status_message = NULL; |
|
89 new->priority = 0; |
|
90 new->caps_serial = 0; |
|
91 return new; |
|
92 } |
|
93 |
|
94 static void |
|
95 _resource_free (Resource *resource) |
|
96 { |
|
97 g_free (resource->status_message); |
|
98 g_free (resource); |
|
99 } |
|
100 |
|
101 static void |
|
102 gabble_presence_finalize (GObject *object) |
|
103 { |
|
104 GSList *i; |
|
105 GabblePresence *presence = GABBLE_PRESENCE (object); |
|
106 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
107 |
|
108 for (i = priv->resources; NULL != i; i = i->next) |
|
109 _resource_free (i->data); |
|
110 |
|
111 g_slist_free (priv->resources); |
|
112 g_free (presence->nickname); |
|
113 g_free (priv->no_resource_status_message); |
|
114 } |
|
115 |
|
116 static void |
|
117 gabble_presence_class_init (GabblePresenceClass *klass) |
|
118 { |
|
119 GObjectClass *object_class = G_OBJECT_CLASS (klass); |
|
120 g_type_class_add_private (object_class, sizeof (GabblePresencePrivate)); |
|
121 object_class->finalize = gabble_presence_finalize; |
|
122 } |
|
123 |
|
124 static void |
|
125 gabble_presence_init (GabblePresence *self) |
|
126 { |
|
127 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, |
|
128 GABBLE_TYPE_PRESENCE, GabblePresencePrivate); |
|
129 ((GabblePresencePrivate *)self->priv)->resources = NULL; |
|
130 } |
|
131 |
|
132 |
|
133 GabblePresence* |
|
134 gabble_presence_new (void) |
|
135 { |
|
136 return g_object_new (GABBLE_TYPE_PRESENCE, NULL); |
|
137 } |
|
138 |
|
139 |
|
140 const gchar * |
|
141 gabble_presence_pick_resource_by_caps ( |
|
142 GabblePresence *presence, |
|
143 GabblePresenceCapabilities caps) |
|
144 { |
|
145 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
146 GSList *i; |
|
147 Resource *chosen = NULL; |
|
148 |
|
149 for (i = priv->resources; NULL != i; i = i->next) |
|
150 { |
|
151 Resource *res = (Resource *) i->data; |
|
152 |
|
153 if ((res->priority >= 0) && |
|
154 ((res->caps & caps) == caps) && |
|
155 (NULL == chosen || res->priority > chosen->priority)) |
|
156 chosen = res; |
|
157 } |
|
158 |
|
159 if (chosen) |
|
160 return chosen->name; |
|
161 else |
|
162 return NULL; |
|
163 } |
|
164 |
|
165 gboolean |
|
166 gabble_presence_resource_has_caps (GabblePresence *presence, |
|
167 const gchar *resource, |
|
168 GabblePresenceCapabilities caps) |
|
169 { |
|
170 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
171 GSList *i; |
|
172 |
|
173 for (i = priv->resources; NULL != i; i = i->next) |
|
174 { |
|
175 Resource *res = (Resource *) i->data; |
|
176 |
|
177 if (!g_strdiff (res->name, resource) && (res->caps & caps)) |
|
178 return TRUE; |
|
179 } |
|
180 |
|
181 return FALSE; |
|
182 } |
|
183 |
|
184 |
|
185 void |
|
186 gabble_presence_set_capabilities (GabblePresence *presence, |
|
187 const gchar *resource, |
|
188 GabblePresenceCapabilities caps, |
|
189 guint serial) |
|
190 { |
|
191 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
192 GSList *i; |
|
193 g_message("[gabble_presence_set_capabilities]\n"); |
|
194 presence->caps = 0; |
|
195 |
|
196 gabble_debug (DEBUG_FLAG, "about to add caps %u to resource %s with serial %u", caps, resource, |
|
197 serial); |
|
198 |
|
199 for (i = priv->resources; NULL != i; i = i->next) |
|
200 { |
|
201 Resource *tmp = (Resource *) i->data; |
|
202 |
|
203 if (0 == strcmp (tmp->name, resource)) |
|
204 { |
|
205 gabble_debug (DEBUG_FLAG, "found resource %s", resource); |
|
206 |
|
207 if (serial > tmp->caps_serial) |
|
208 { |
|
209 gabble_debug (DEBUG_FLAG, "new serial %u, old %u, clearing caps", serial, |
|
210 tmp->caps_serial); |
|
211 tmp->caps = 0; |
|
212 tmp->caps_serial = serial; |
|
213 } |
|
214 |
|
215 if (serial >= tmp->caps_serial) |
|
216 { |
|
217 gabble_debug (DEBUG_FLAG, "adding caps %u to resource %s", caps, resource); |
|
218 tmp->caps |= caps; |
|
219 gabble_debug (DEBUG_FLAG, "resource %s caps now %u", resource, tmp->caps); |
|
220 } |
|
221 } |
|
222 |
|
223 presence->caps |= tmp->caps; |
|
224 } |
|
225 g_message("[leaving connect_callbacks\n]"); |
|
226 gabble_debug (DEBUG_FLAG, "total caps now %u", presence->caps); |
|
227 } |
|
228 |
|
229 static Resource * |
|
230 _find_resource (GabblePresence *presence, const gchar *resource) |
|
231 { |
|
232 GSList *i; |
|
233 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
234 |
|
235 for (i = priv->resources; NULL != i; i = i->next) |
|
236 { |
|
237 Resource *res = (Resource *) i->data; |
|
238 |
|
239 if (0 == strcmp (res->name, resource)) |
|
240 return res; |
|
241 } |
|
242 |
|
243 return NULL; |
|
244 } |
|
245 |
|
246 |
|
247 gboolean |
|
248 gabble_presence_update (GabblePresence *presence, |
|
249 const gchar *resource, |
|
250 GabblePresenceId status, |
|
251 const gchar *status_message, |
|
252 gint8 priority) |
|
253 { |
|
254 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
255 Resource *res; |
|
256 GabblePresenceId old_status; |
|
257 gchar *old_status_message; |
|
258 GSList *i; |
|
259 gint8 prio; |
|
260 gboolean ret = FALSE; |
|
261 |
|
262 /* save our current state */ |
|
263 old_status = presence->status; |
|
264 old_status_message = g_strdup (presence->status_message); |
|
265 |
|
266 if (NULL == resource) |
|
267 { |
|
268 /* presence from a JID with no resource: free all resources and set |
|
269 * presence directly */ |
|
270 |
|
271 for (i = priv->resources; i; i = i->next) |
|
272 _resource_free (i->data); |
|
273 |
|
274 g_slist_free (priv->resources); |
|
275 priv->resources = NULL; |
|
276 |
|
277 if (g_strdiff (priv->no_resource_status_message, status_message)) |
|
278 { |
|
279 g_free (priv->no_resource_status_message); |
|
280 priv->no_resource_status_message = g_strdup (status_message); |
|
281 } |
|
282 |
|
283 presence->status = status; |
|
284 presence->status_message = priv->no_resource_status_message; |
|
285 goto OUT; |
|
286 } |
|
287 |
|
288 res = _find_resource (presence, resource); |
|
289 |
|
290 /* remove, create or update a Resource as appropriate */ |
|
291 if (GABBLE_PRESENCE_OFFLINE == status && |
|
292 NULL == status_message) |
|
293 { |
|
294 if (NULL != res) |
|
295 { |
|
296 priv->resources = g_slist_remove (priv->resources, res); |
|
297 _resource_free (res); |
|
298 res = NULL; |
|
299 } |
|
300 } |
|
301 else |
|
302 { |
|
303 if (NULL == res) |
|
304 { |
|
305 res = _resource_new (g_strdup (resource)); |
|
306 priv->resources = g_slist_append (priv->resources, res); |
|
307 } |
|
308 |
|
309 res->status = status; |
|
310 |
|
311 if (g_strdiff (res->status_message, status_message)) |
|
312 { |
|
313 g_free (res->status_message); |
|
314 res->status_message = g_strdup (status_message); |
|
315 } |
|
316 |
|
317 res->priority = priority; |
|
318 } |
|
319 |
|
320 /* select the most preferable Resource and update presence->* based on our |
|
321 * choice */ |
|
322 presence->caps = 0; |
|
323 presence->status = GABBLE_PRESENCE_OFFLINE; |
|
324 |
|
325 /* use the status message from any offline Resource we're |
|
326 * keeping around just because it has a message on it */ |
|
327 presence->status_message = res ? res->status_message : NULL; |
|
328 |
|
329 prio = -128; |
|
330 |
|
331 for (i = priv->resources; NULL != i; i = i->next) |
|
332 { |
|
333 Resource *res = (Resource *) i->data; |
|
334 |
|
335 presence->caps |= res->caps; |
|
336 |
|
337 /* trump existing status & message if it's more present |
|
338 * or has the same presence and a higher priority */ |
|
339 if (res->status > presence->status || |
|
340 (res->status == presence->status && res->priority > prio)) |
|
341 { |
|
342 presence->status = res->status; |
|
343 presence->status_message = res->status_message; |
|
344 prio = res->priority; |
|
345 } |
|
346 } |
|
347 |
|
348 OUT: |
|
349 /* detect changes */ |
|
350 if (presence->status != old_status || |
|
351 g_strdiff (presence->status_message, old_status_message)) |
|
352 ret = TRUE; |
|
353 |
|
354 g_free (old_status_message); |
|
355 return ret; |
|
356 } |
|
357 |
|
358 LmMessage * |
|
359 gabble_presence_as_message (GabblePresence *presence, const gchar *resource) |
|
360 { |
|
361 LmMessage *message; |
|
362 LmMessageNode *node; |
|
363 LmMessageSubType subtype; |
|
364 Resource *res = _find_resource (presence, resource); |
|
365 |
|
366 g_assert (NULL != res); |
|
367 |
|
368 if (presence->status == GABBLE_PRESENCE_OFFLINE) |
|
369 subtype = LM_MESSAGE_SUB_TYPE_UNAVAILABLE; |
|
370 else |
|
371 subtype = LM_MESSAGE_SUB_TYPE_AVAILABLE; |
|
372 |
|
373 message = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_PRESENCE, |
|
374 subtype); |
|
375 node = lm_message_get_node (message); |
|
376 |
|
377 switch (presence->status) |
|
378 { |
|
379 case GABBLE_PRESENCE_AVAILABLE: |
|
380 case GABBLE_PRESENCE_OFFLINE: |
|
381 case GABBLE_PRESENCE_HIDDEN: |
|
382 break; |
|
383 case GABBLE_PRESENCE_AWAY: |
|
384 lm_message_node_add_child (node, "show", JABBER_PRESENCE_SHOW_AWAY); |
|
385 break; |
|
386 case GABBLE_PRESENCE_CHAT: |
|
387 lm_message_node_add_child (node, "show", JABBER_PRESENCE_SHOW_CHAT); |
|
388 break; |
|
389 case GABBLE_PRESENCE_DND: |
|
390 lm_message_node_add_child (node, "show", JABBER_PRESENCE_SHOW_DND); |
|
391 break; |
|
392 case GABBLE_PRESENCE_XA: |
|
393 lm_message_node_add_child (node, "show", JABBER_PRESENCE_SHOW_XA); |
|
394 break; |
|
395 default: |
|
396 g_critical ("%s: Unexpected Telepathy presence type", G_STRFUNC); |
|
397 break; |
|
398 } |
|
399 |
|
400 if (presence->status_message) |
|
401 lm_message_node_add_child (node, "status", presence->status_message); |
|
402 |
|
403 if (res->priority) |
|
404 { |
|
405 gchar *priority = g_strdup_printf ("%d", res->priority); |
|
406 lm_message_node_add_child (node, "priority", priority); |
|
407 g_free (priority); |
|
408 } |
|
409 |
|
410 return message; |
|
411 } |
|
412 |
|
413 |
|
414 gchar * |
|
415 gabble_presence_dump (GabblePresence *presence) |
|
416 { |
|
417 GSList *i; |
|
418 GString *ret = g_string_new (""); |
|
419 GabblePresencePrivate *priv = GABBLE_PRESENCE_PRIV (presence); |
|
420 |
|
421 g_string_append_printf (ret, |
|
422 "nickname: %s\n" |
|
423 "accumulated status: %d\n" |
|
424 "accumulated status msg: %s\n" |
|
425 "accumulated capabilities: %d\n" |
|
426 "kept while unavailable: %d\n" |
|
427 "resources:\n", presence->nickname, presence->status, |
|
428 presence->status_message, presence->caps, |
|
429 presence->keep_unavailable); |
|
430 |
|
431 for (i = priv->resources; i; i = i->next) |
|
432 { |
|
433 Resource *res = (Resource *) i->data; |
|
434 |
|
435 g_string_append_printf(ret, |
|
436 " %s\n" |
|
437 " capabilities: %d\n" |
|
438 " status: %d\n" |
|
439 " status msg: %s\n" |
|
440 " priority: %d\n", res->name, res->caps, res->status, |
|
441 res->status_message, res->priority); |
|
442 } |
|
443 |
|
444 if (priv->resources == NULL) |
|
445 g_string_append_printf(ret, " (none)\n"); |
|
446 |
|
447 return g_string_free (ret, FALSE); |
|
448 } |
|