6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #include "conversation.h"
39 #include "win32/win32dep.h"
42 #include "sipe-common.h"
43 #include "sipe-backend.h"
44 #include "sipe-core.h"
47 #define _PurpleMessageFlags PurpleMessageFlags
48 #include "purple-private.h"
51 * Mapping between chat sessions in SIPE core and libpurple backend
54 * This data structure is created when the user creates the account or at
55 * startup. It lives as long as the account exists, i.e. until the user
56 * deletes it or shutdown.
58 * Value does not change when connection is dropped & re-created.
59 * HAS: gc (PurpleConnection *)
61 * PurpleConversation / PurpleConvChat (sub-type)
62 * This data structure is created by serv_got_join_chat(). It lives as long
63 * as the user doesn't leave the chat or until shutdown.
65 * Value does not change when connection is dropped & re-created.
66 * HAS: account (PurpleAccount *)
67 * HAS: chat ID (int), must be unique
68 * HAS: name (char *), must be unique
69 * HAS: data (GHashTable *)
72 * This data structure is created when the connection to the service is
73 * set up. It lives as long as the connection stays open, the user disables
74 * the account or until shutdown.
76 * Value *DOES NOT* survive a connection drop & re-creation.
77 * ASSOCIATED TO: account
79 * SIPE -> libpurple API
80 * add user: purple_conv_chat_add_user(conv, ...)
81 * create: serv_got_joined_chat(gc, chat ID, name)
82 * find user: purple_conv_chat_find_user(conv, ...)
83 * message: serv_got_chat_in(gc, chat ID, ...)
84 * remove user: purple_conv_chat_remove_user(conv, ...)
85 * topic: purple_conv_chat_set_topic(conv, ...)
87 * libpurple -> SIPE API
88 * join_chat(gc, params (GHashTable *))
89 * request to join a channel (again) [only Group Chat]
90 * SIPE must call serv_got_joined_chat() on join response
92 * reject_chat(gc, params (GHashTable *)) NOT IMPLEMENTED
93 * get_chat_name(params (GHashTable *)) NOT IMPLEMENTED
95 * chat_invite(gc, chat ID,...)
96 * invite a user to a join a chat
98 * chat_leave(gc, chat ID)
99 * request to leave a channel, also called on conversation destroy
100 * SIPE must call serv_got_chat_left() immediately!
102 * chat_whisper(gc, chat ID, ...) NOT IMPLEMENTED
104 * chat_send(gc, chat ID, ...)
105 * send a message to the channel
107 * set_chat_topic(gc, chat ID, ...) NOT IMPLEMENTED
108 * set channel topic [@TODO: for Group Chat]
111 * struct sipe_chat_session
112 * Same life span as PurpleConversation
113 * Pointer stored under key "sipe" in PurpleConversation->data
114 * Contains information private to core to identify chat session on server
116 * If connection is closed and THEN the conversation, then libpurple will
117 * not call chat_leave() and this will be a dangling data structure! Core
118 * must take care to release them at unload.
120 * HAS: backend_session (gpointer) -> PurpleConversation
122 * struct sipe_backend_private
124 * HAS: rejoin_chats (GList *)
125 * created on login() for existing chats
126 * initiate re-join calls to core (sipe_backend_chat_rejoin_all)
129 #define SIPE_PURPLE_KEY_CHAT_SESSION "sipe"
131 #define BACKEND_SESSION_TO_PURPLE_CONV_CHAT(s) \
132 (PURPLE_CONV_CHAT(((PurpleConversation *)s)))
134 #define PURPLE_CONV_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) conv->account->gc->proto_data)
136 static struct sipe_chat_session
*sipe_purple_chat_get_session(PurpleConversation
*conv
)
138 return purple_conversation_get_data(conv
,
139 SIPE_PURPLE_KEY_CHAT_SESSION
);
142 static struct sipe_chat_session
*sipe_purple_chat_find(PurpleConnection
*gc
,
145 PurpleConversation
*conv
= purple_find_chat(gc
, id
);
148 SIPE_DEBUG_ERROR("sipe_purple_chat_find: can't find chat with ID %d?!?",
153 return sipe_purple_chat_get_session(conv
);
156 void sipe_purple_chat_setup_rejoin(struct sipe_backend_private
*purple_private
)
158 GList
*entry
= purple_get_chats();
161 PurpleConversation
*conv
= entry
->data
;
162 if (purple_conversation_get_gc(conv
) == purple_private
->gc
)
163 purple_private
->rejoin_chats
= g_list_prepend(purple_private
->rejoin_chats
,
164 sipe_purple_chat_get_session(conv
));
169 void sipe_purple_chat_destroy_rejoin(struct sipe_backend_private
*purple_private
)
171 g_list_free(purple_private
->rejoin_chats
);
172 purple_private
->rejoin_chats
= NULL
;
175 void sipe_purple_chat_invite(PurpleConnection
*gc
, int id
,
176 SIPE_UNUSED_PARAMETER
const char *message
,
179 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
180 if (!session
) return;
182 sipe_core_chat_invite(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
, name
);
185 void sipe_purple_chat_leave(PurpleConnection
*gc
, int id
)
187 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
188 if (!session
) return;
190 sipe_core_chat_leave(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
);
193 int sipe_purple_chat_send(PurpleConnection
*gc
,
196 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
198 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
199 if (!session
) return -ENOTCONN
;
200 sipe_core_chat_send(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
, what
);
204 static void sipe_purple_chat_menu_unlock_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
205 PurpleConversation
*conv
)
207 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
208 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
209 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv
, chat_session
);
210 sipe_core_chat_modify_lock(sipe_public
, chat_session
, FALSE
);
213 static void sipe_purple_chat_menu_lock_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
214 PurpleConversation
*conv
)
216 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
217 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
218 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv
, chat_session
);
219 sipe_core_chat_modify_lock(sipe_public
, chat_session
, TRUE
);
224 static void sipe_purple_chat_menu_join_call_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
225 PurpleConversation
*conv
)
227 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
228 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
229 SIPE_DEBUG_INFO("sipe_purple_chat_join_call_cb: %p %p", conv
, chat_session
);
230 sipe_core_media_connect_conference(sipe_public
, chat_session
);
236 sipe_purple_chat_menu(PurpleChat
*chat
)
238 PurpleConversation
*conv
= g_hash_table_lookup(chat
->components
,
239 SIPE_PURPLE_COMPONENT_KEY_CONVERSATION
);
243 PurpleMenuAction
*act
= NULL
;
245 SIPE_DEBUG_INFO("sipe_purple_chat_menu: %p", conv
);
247 switch (sipe_core_chat_lock_status(PURPLE_CONV_TO_SIPE_CORE_PUBLIC
,
248 sipe_purple_chat_get_session(conv
))) {
249 case SIPE_CHAT_LOCK_STATUS_UNLOCKED
:
250 act
= purple_menu_action_new(_("Lock"),
251 PURPLE_CALLBACK(sipe_purple_chat_menu_lock_cb
),
254 case SIPE_CHAT_LOCK_STATUS_LOCKED
:
255 act
= purple_menu_action_new(_("Unlock"),
256 PURPLE_CALLBACK(sipe_purple_chat_menu_unlock_cb
),
265 menu
= g_list_prepend(menu
, act
);
267 if (!sipe_core_media_in_call(PURPLE_CONV_TO_SIPE_CORE_PUBLIC
)) {
269 act
= purple_menu_action_new(_("Join conference call"),
270 PURPLE_CALLBACK(sipe_purple_chat_menu_join_call_cb
),
273 menu
= g_list_prepend(menu
, act
);
281 void sipe_backend_chat_session_destroy(SIPE_UNUSED_PARAMETER
struct sipe_backend_chat_session
*session
)
283 /* Nothing to do here */
286 void sipe_backend_chat_add(struct sipe_backend_chat_session
*backend_session
,
290 purple_conv_chat_add_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
291 uri
, NULL
, PURPLE_CBFLAGS_NONE
, is_new
);
294 void sipe_backend_chat_close(struct sipe_backend_chat_session
*backend_session
)
296 purple_conv_chat_clear_users(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
));
299 static int sipe_purple_chat_id(PurpleConnection
*gc
)
302 * A non-volatile ID counter.
303 * Should survive connection drop & reconnect.
305 static int chat_id
= 0;
307 /* Find next free ID */
309 if (++chat_id
< 0) chat_id
= 0;
310 } while (purple_find_chat(gc
, chat_id
) != NULL
)
315 struct sipe_backend_chat_session
*sipe_backend_chat_create(struct sipe_core_public
*sipe_public
,
316 struct sipe_chat_session
*session
,
320 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
321 PurpleConversation
*conv
= serv_got_joined_chat(purple_private
->gc
,
322 sipe_purple_chat_id(purple_private
->gc
),
324 purple_conversation_set_data(conv
,
325 SIPE_PURPLE_KEY_CHAT_SESSION
,
327 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(conv
), nick
);
328 return((struct sipe_backend_chat_session
*) conv
);
331 gboolean
sipe_backend_chat_find(struct sipe_backend_chat_session
*backend_session
,
334 return purple_conv_chat_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
338 gboolean
sipe_backend_chat_is_operator(struct sipe_backend_chat_session
*backend_session
,
341 return (purple_conv_chat_user_get_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
342 uri
) & PURPLE_CBFLAGS_OP
)
343 == PURPLE_CBFLAGS_OP
;
346 void sipe_backend_chat_message(struct sipe_core_public
*sipe_public
,
347 struct sipe_backend_chat_session
*backend_session
,
351 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
352 serv_got_chat_in(purple_private
->gc
,
353 purple_conv_chat_get_id(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
)),
360 void sipe_backend_chat_operator(struct sipe_backend_chat_session
*backend_session
,
363 purple_conv_chat_user_set_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
365 PURPLE_CBFLAGS_NONE
| PURPLE_CBFLAGS_OP
);
368 void sipe_backend_chat_rejoin(struct sipe_core_public
*sipe_public
,
369 struct sipe_backend_chat_session
*backend_session
,
373 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
374 PurpleConvChat
*chat
= BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
);
375 PurpleConversation
*new;
378 * As the chat is marked as "left", serv_got_joined_chat() will
379 * do a "rejoin cleanup" and return the same conversation.
381 new = serv_got_joined_chat(purple_private
->gc
,
382 purple_conv_chat_get_id(chat
),
384 SIPE_DEBUG_INFO("sipe_backend_chat_rejoin: old %p (%p) == new %p (%p)",
385 backend_session
, chat
,
386 new, PURPLE_CONV_CHAT(new));
387 purple_conv_chat_set_nick(chat
, nick
);
391 * Connection re-established: tell core what chats need to be rejoined
393 void sipe_backend_chat_rejoin_all(struct sipe_core_public
*sipe_public
)
395 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
396 GList
*entry
= purple_private
->rejoin_chats
;
399 sipe_core_chat_rejoin(sipe_public
, entry
->data
);
402 sipe_purple_chat_destroy_rejoin(purple_private
);
405 void sipe_backend_chat_remove(struct sipe_backend_chat_session
*backend_session
,
408 purple_conv_chat_remove_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
413 void sipe_backend_chat_show(struct sipe_backend_chat_session
*backend_session
)
415 /* Bring existing purple chat to the front */
416 /* @TODO: This seems to the trick, but is it the correct way? */
417 purple_conversation_update((PurpleConversation
*) backend_session
,
418 PURPLE_CONV_UPDATE_TOPIC
);
421 void sipe_backend_chat_topic(struct sipe_backend_chat_session
*backend_session
,
424 purple_conv_chat_set_topic(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),