janus-gateway / plugins / janus_textroom.c @ 5f0f0227
History | View | Annotate | Download (71.9 KB)
1 |
/*! \file janus_textroom.c
|
---|---|
2 |
* \author Lorenzo Miniero <lorenzo@meetecho.com>
|
3 |
* \copyright GNU General Public License v3
|
4 |
* \brief Janus TextRoom plugin
|
5 |
* \details This is a plugin implementing a DataChannel only text room.
|
6 |
* As such, it does NOT support or negotiate audio or video, but only
|
7 |
* data channels, in order to provide text broadcasting features. The
|
8 |
* plugin allows users to join multiple text-only rooms via a single
|
9 |
* PeerConnection. Users can send messages either to a room in general
|
10 |
* (broadcasting), or to individual users (whispers). This plugin can be
|
11 |
* used within the context of any application that needs real-time text
|
12 |
* broadcasting (e.g., chatrooms, but not only).
|
13 |
*
|
14 |
* The only message that is typically sent to the plugin through the Janus API is
|
15 |
* a "setup" message, by which the user initializes the PeerConnection
|
16 |
* itself. Apart from that, all other messages can be exchanged directly
|
17 |
* via Data Channels. For room management purposes, though, requests like
|
18 |
* "create", "destroy", "list" and "exists" are available through the
|
19 |
* Janus API as well: notice that in this case you'll have to use "request"
|
20 |
* and not "textroom" as the name of the request.
|
21 |
*
|
22 |
* Each room can also be configured with an HTTP backend to contact for
|
23 |
* incoming messages. If configured, messages addressed to that room will
|
24 |
* also be forwarded, by means of an HTTP POST, to the specified address.
|
25 |
* Notice that this will only work if libcurl was available when
|
26 |
* configuring and installing Janus.
|
27 |
*
|
28 |
* \note This plugin is only meant to showcase what you can do with
|
29 |
* data channels involving multiple participants at the same time. While
|
30 |
* functional, it's not inherently better or faster than doing the same
|
31 |
* thing using the Janus API messaging itself (e.g., as part of the
|
32 |
* plugin API messaging) or using existing instant messaging protocols
|
33 |
* (e.g., Jabber). In fact, while data channels are being used, you're
|
34 |
* still going through a server, so it's not really peer-to-peer. That
|
35 |
* said, the plugin can be useful if you don't plan to use any other
|
36 |
* infrastructure than Janus, and yet you also want to have text-based
|
37 |
* communication (e.g., to add a chatroom to an audio or video conference).
|
38 |
*
|
39 |
* Notice that, in general, all users can create rooms. If you want to
|
40 |
* limit this functionality, you can configure an admin \c admin_key in
|
41 |
* the plugin settings. When configured, only "create" requests that
|
42 |
* include the correct \c admin_key value in an "admin_key" property
|
43 |
* will succeed, and will be rejected otherwise.
|
44 |
*
|
45 |
* \section textroomapi Text Room API
|
46 |
* TBD.
|
47 |
*
|
48 |
* \ingroup plugins
|
49 |
* \ref plugins
|
50 |
*/
|
51 |
|
52 |
#include "plugins/plugin.h" |
53 |
|
54 |
#include <jansson.h> |
55 |
|
56 |
#ifdef HAVE_LIBCURL
|
57 |
#include <curl/curl.h> |
58 |
#endif
|
59 |
|
60 |
#include "debug.h" |
61 |
#include "apierror.h" |
62 |
#include "config.h" |
63 |
#include "mutex.h" |
64 |
#include "utils.h" |
65 |
|
66 |
|
67 |
/* Plugin information */
|
68 |
#define JANUS_TEXTROOM_VERSION 2 |
69 |
#define JANUS_TEXTROOM_VERSION_STRING "0.0.2" |
70 |
#define JANUS_TEXTROOM_DESCRIPTION "This is a plugin implementing a text-only room for Janus, using DataChannels." |
71 |
#define JANUS_TEXTROOM_NAME "JANUS TextRoom plugin" |
72 |
#define JANUS_TEXTROOM_AUTHOR "Meetecho s.r.l." |
73 |
#define JANUS_TEXTROOM_PACKAGE "janus.plugin.textroom" |
74 |
|
75 |
/* Plugin methods */
|
76 |
janus_plugin *create(void);
|
77 |
int janus_textroom_init(janus_callbacks *callback, const char *config_path); |
78 |
void janus_textroom_destroy(void); |
79 |
int janus_textroom_get_api_compatibility(void); |
80 |
int janus_textroom_get_version(void); |
81 |
const char *janus_textroom_get_version_string(void); |
82 |
const char *janus_textroom_get_description(void); |
83 |
const char *janus_textroom_get_name(void); |
84 |
const char *janus_textroom_get_author(void); |
85 |
const char *janus_textroom_get_package(void); |
86 |
void janus_textroom_create_session(janus_plugin_session *handle, int *error); |
87 |
struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep); |
88 |
void janus_textroom_setup_media(janus_plugin_session *handle);
|
89 |
void janus_textroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len); |
90 |
void janus_textroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len); |
91 |
void janus_textroom_incoming_data(janus_plugin_session *handle, char *buf, int len); |
92 |
void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video); |
93 |
void janus_textroom_hangup_media(janus_plugin_session *handle);
|
94 |
void janus_textroom_destroy_session(janus_plugin_session *handle, int *error); |
95 |
json_t *janus_textroom_query_session(janus_plugin_session *handle); |
96 |
|
97 |
/* Plugin setup */
|
98 |
static janus_plugin janus_textroom_plugin =
|
99 |
JANUS_PLUGIN_INIT ( |
100 |
.init = janus_textroom_init, |
101 |
.destroy = janus_textroom_destroy, |
102 |
|
103 |
.get_api_compatibility = janus_textroom_get_api_compatibility, |
104 |
.get_version = janus_textroom_get_version, |
105 |
.get_version_string = janus_textroom_get_version_string, |
106 |
.get_description = janus_textroom_get_description, |
107 |
.get_name = janus_textroom_get_name, |
108 |
.get_author = janus_textroom_get_author, |
109 |
.get_package = janus_textroom_get_package, |
110 |
|
111 |
.create_session = janus_textroom_create_session, |
112 |
.handle_message = janus_textroom_handle_message, |
113 |
.setup_media = janus_textroom_setup_media, |
114 |
.incoming_rtp = janus_textroom_incoming_rtp, |
115 |
.incoming_rtcp = janus_textroom_incoming_rtcp, |
116 |
.incoming_data = janus_textroom_incoming_data, |
117 |
.slow_link = janus_textroom_slow_link, |
118 |
.hangup_media = janus_textroom_hangup_media, |
119 |
.destroy_session = janus_textroom_destroy_session, |
120 |
.query_session = janus_textroom_query_session, |
121 |
); |
122 |
|
123 |
/* Plugin creator */
|
124 |
janus_plugin *create(void) {
|
125 |
JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_TEXTROOM_NAME);
|
126 |
return &janus_textroom_plugin;
|
127 |
} |
128 |
|
129 |
|
130 |
/* Parameter validation */
|
131 |
static struct janus_json_parameter request_parameters[] = { |
132 |
{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
|
133 |
}; |
134 |
static struct janus_json_parameter transaction_parameters[] = { |
135 |
{"textroom", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
|
136 |
{"transaction", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
|
137 |
}; |
138 |
static struct janus_json_parameter room_parameters[] = { |
139 |
{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
|
140 |
}; |
141 |
static struct janus_json_parameter adminkey_parameters[] = { |
142 |
{"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
|
143 |
}; |
144 |
static struct janus_json_parameter create_parameters[] = { |
145 |
{"description", JSON_STRING, 0}, |
146 |
{"secret", JSON_STRING, 0}, |
147 |
{"pin", JSON_STRING, 0}, |
148 |
{"post", JSON_STRING, 0}, |
149 |
{"is_private", JANUS_JSON_BOOL, 0}, |
150 |
{"allowed", JSON_ARRAY, 0} |
151 |
}; |
152 |
static struct janus_json_parameter allowed_parameters[] = { |
153 |
{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
|
154 |
{"secret", JSON_STRING, 0}, |
155 |
{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
|
156 |
{"allowed", JSON_ARRAY, 0} |
157 |
}; |
158 |
static struct janus_json_parameter kick_parameters[] = { |
159 |
{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
|
160 |
{"secret", JSON_STRING, 0}, |
161 |
{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
|
162 |
}; |
163 |
static struct janus_json_parameter join_parameters[] = { |
164 |
{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
|
165 |
{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
|
166 |
{"display", JSON_STRING, 0} |
167 |
}; |
168 |
static struct janus_json_parameter message_parameters[] = { |
169 |
{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
|
170 |
{"text", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
|
171 |
{"to", JSON_STRING, 0}, |
172 |
{"tos", JSON_ARRAY, 0}, |
173 |
{"ack", JANUS_JSON_BOOL, 0} |
174 |
}; |
175 |
|
176 |
/* Static configuration instance */
|
177 |
static janus_config *config = NULL; |
178 |
static const char *config_folder = NULL; |
179 |
static janus_mutex config_mutex;
|
180 |
|
181 |
/* Useful stuff */
|
182 |
static volatile gint initialized = 0, stopping = 0; |
183 |
static gboolean notify_events = TRUE;
|
184 |
static janus_callbacks *gateway = NULL; |
185 |
static GThread *handler_thread;
|
186 |
static GThread *watchdog;
|
187 |
static void *janus_textroom_handler(void *data); |
188 |
|
189 |
/* JSON serialization options */
|
190 |
static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; |
191 |
|
192 |
|
193 |
typedef struct janus_textroom_message { |
194 |
janus_plugin_session *handle; |
195 |
char *transaction;
|
196 |
json_t *message; |
197 |
json_t *jsep; |
198 |
} janus_textroom_message; |
199 |
static GAsyncQueue *messages = NULL; |
200 |
static janus_textroom_message exit_message;
|
201 |
|
202 |
static void janus_textroom_message_free(janus_textroom_message *msg) { |
203 |
if(!msg || msg == &exit_message)
|
204 |
return;
|
205 |
|
206 |
msg->handle = NULL;
|
207 |
|
208 |
g_free(msg->transaction); |
209 |
msg->transaction = NULL;
|
210 |
if(msg->message)
|
211 |
json_decref(msg->message); |
212 |
msg->message = NULL;
|
213 |
if(msg->jsep)
|
214 |
json_decref(msg->jsep); |
215 |
msg->jsep = NULL;
|
216 |
|
217 |
g_free(msg); |
218 |
} |
219 |
|
220 |
typedef struct janus_textroom_room { |
221 |
guint64 room_id; /* Unique room ID */
|
222 |
gchar *room_name; /* Room description */
|
223 |
gchar *room_secret; /* Secret needed to manipulate (e.g., destroy) this room */
|
224 |
gchar *room_pin; /* Password needed to join this room, if any */
|
225 |
gboolean is_private; /* Whether this room is 'private' (as in hidden) or not */
|
226 |
gchar *http_backend; /* Server to contact via HTTP POST for incoming messages, if any */
|
227 |
GHashTable *participants; /* Map of participants */
|
228 |
gboolean check_tokens; /* Whether to check tokens when participants join (see below) */
|
229 |
GHashTable *allowed; /* Map of participants (as tokens) allowed to join */
|
230 |
gint64 destroyed; /* When this room has been destroyed */
|
231 |
janus_mutex mutex; /* Mutex to lock this room instance */
|
232 |
} janus_textroom_room; |
233 |
static GHashTable *rooms;
|
234 |
static janus_mutex rooms_mutex;
|
235 |
static GList *old_rooms;
|
236 |
static char *admin_key = NULL; |
237 |
|
238 |
typedef struct janus_textroom_session { |
239 |
janus_plugin_session *handle; |
240 |
GHashTable *rooms; /* Map of rooms this user is in, and related participant instance */
|
241 |
janus_mutex mutex; /* Mutex to lock this session */
|
242 |
volatile gint setup;
|
243 |
volatile gint hangingup;
|
244 |
gint64 destroyed; /* Time at which this session was marked as destroyed */
|
245 |
} janus_textroom_session; |
246 |
static GHashTable *sessions;
|
247 |
static GList *old_sessions;
|
248 |
static janus_mutex sessions_mutex;
|
249 |
|
250 |
typedef struct janus_textroom_participant { |
251 |
janus_textroom_session *session; |
252 |
janus_textroom_room *room; /* Room this participant is in */
|
253 |
gchar *username; /* Unique username in the room */
|
254 |
gchar *display; /* Display name in the room, if any */
|
255 |
janus_mutex mutex; /* Mutex to lock this session */
|
256 |
gint64 destroyed; /* When this participant was destroyed */
|
257 |
} janus_textroom_participant; |
258 |
|
259 |
|
260 |
/* SDP template: we only offer data channels */
|
261 |
#define sdp_template \
|
262 |
"v=0\r\n" \
|
263 |
"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n" /* We need current time here */ \ |
264 |
"s=Janus TextRoom plugin\r\n" \
|
265 |
"t=0 0\r\n" \
|
266 |
"m=application 1 DTLS/SCTP 5000\r\n" \
|
267 |
"c=IN IP4 1.1.1.1\r\n" \
|
268 |
"a=sctpmap:5000 webrtc-datachannel 16\r\n"
|
269 |
|
270 |
|
271 |
/* Error codes */
|
272 |
#define JANUS_TEXTROOM_ERROR_NO_MESSAGE 411 |
273 |
#define JANUS_TEXTROOM_ERROR_INVALID_JSON 412 |
274 |
#define JANUS_TEXTROOM_ERROR_MISSING_ELEMENT 413 |
275 |
#define JANUS_TEXTROOM_ERROR_INVALID_ELEMENT 414 |
276 |
#define JANUS_TEXTROOM_ERROR_INVALID_REQUEST 415 |
277 |
#define JANUS_TEXTROOM_ERROR_ALREADY_SETUP 416 |
278 |
#define JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM 417 |
279 |
#define JANUS_TEXTROOM_ERROR_ROOM_EXISTS 418 |
280 |
#define JANUS_TEXTROOM_ERROR_UNAUTHORIZED 419 |
281 |
#define JANUS_TEXTROOM_ERROR_USERNAME_EXISTS 420 |
282 |
#define JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM 421 |
283 |
#define JANUS_TEXTROOM_ERROR_NOT_IN_ROOM 422 |
284 |
#define JANUS_TEXTROOM_ERROR_NO_SUCH_USER 423 |
285 |
#define JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR 499 |
286 |
|
287 |
/* TextRoom watchdog/garbage collector (sort of) */
|
288 |
static void *janus_textroom_watchdog(void *data) { |
289 |
JANUS_LOG(LOG_INFO, "TextRoom watchdog started\n");
|
290 |
gint64 now = 0;
|
291 |
while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
|
292 |
janus_mutex_lock(&sessions_mutex); |
293 |
/* Iterate on all the sessions */
|
294 |
now = janus_get_monotonic_time(); |
295 |
if(old_sessions != NULL) { |
296 |
GList *sl = old_sessions; |
297 |
JANUS_LOG(LOG_HUGE, "Checking %d old TextRoom sessions...\n", g_list_length(old_sessions));
|
298 |
while(sl) {
|
299 |
janus_textroom_session *session = (janus_textroom_session *)sl->data; |
300 |
if(!session) {
|
301 |
sl = sl->next; |
302 |
continue;
|
303 |
} |
304 |
if(now-session->destroyed >= 5*G_USEC_PER_SEC) { |
305 |
/* We're lazy and actually get rid of the stuff only after a few seconds */
|
306 |
JANUS_LOG(LOG_VERB, "Freeing old TextRoom session\n");
|
307 |
GList *rm = sl->next; |
308 |
old_sessions = g_list_delete_link(old_sessions, sl); |
309 |
sl = rm; |
310 |
session->handle = NULL;
|
311 |
/* TODO Free session stuff */
|
312 |
g_free(session); |
313 |
session = NULL;
|
314 |
continue;
|
315 |
} |
316 |
sl = sl->next; |
317 |
} |
318 |
} |
319 |
janus_mutex_unlock(&sessions_mutex); |
320 |
janus_mutex_lock(&rooms_mutex); |
321 |
if(old_rooms != NULL) { |
322 |
GList *rl = old_rooms; |
323 |
now = janus_get_monotonic_time(); |
324 |
while(rl) {
|
325 |
janus_textroom_room *textroom = (janus_textroom_room*)rl->data; |
326 |
if(!g_atomic_int_get(&initialized) || g_atomic_int_get(&stopping)){
|
327 |
break;
|
328 |
} |
329 |
if(!textroom) {
|
330 |
rl = rl->next; |
331 |
continue;
|
332 |
} |
333 |
if(now - textroom->destroyed >= 5*G_USEC_PER_SEC) { |
334 |
/* Free resources */
|
335 |
JANUS_LOG(LOG_VERB, "Freeing old TextRoom room %"SCNu64"\n", textroom->room_id); |
336 |
g_free(textroom->room_name); |
337 |
g_free(textroom->room_secret); |
338 |
g_free(textroom->room_pin); |
339 |
g_hash_table_destroy(textroom->participants); |
340 |
g_hash_table_destroy(textroom->allowed); |
341 |
g_free(textroom); |
342 |
/* Move on */
|
343 |
GList *rm = rl->next; |
344 |
old_rooms = g_list_delete_link(old_rooms, rl); |
345 |
rl = rm; |
346 |
continue;
|
347 |
} |
348 |
rl = rl->next; |
349 |
} |
350 |
} |
351 |
janus_mutex_unlock(&rooms_mutex); |
352 |
g_usleep(500000);
|
353 |
} |
354 |
JANUS_LOG(LOG_INFO, "TextRoom watchdog stopped\n");
|
355 |
return NULL; |
356 |
} |
357 |
|
358 |
#ifdef HAVE_LIBCURL
|
359 |
static size_t janus_textroom_write_data(void *buffer, size_t size, size_t nmemb, void *userp) { |
360 |
return size*nmemb;
|
361 |
} |
362 |
#endif
|
363 |
|
364 |
/* We use this method to handle incoming requests. Since most of the requests
|
365 |
* will arrive from data channels, but some may also arrive from the regular
|
366 |
* plugin messaging (e.g., room management), we have the ability to pass
|
367 |
* parsed JSON objects instead of strings, which explains why we specify a
|
368 |
* janus_plugin_result pointer as a return value; messages handles via
|
369 |
* datachannels would simply return NULL. Besides, some requests are actually
|
370 |
* originated internally, and don't need any response to be sent to anyone,
|
371 |
* which is what the additional boolean "internal" value is for */
|
372 |
janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle, |
373 |
char *text, json_t *json, gboolean internal);
|
374 |
|
375 |
|
376 |
/* Plugin implementation */
|
377 |
int janus_textroom_init(janus_callbacks *callback, const char *config_path) { |
378 |
if(g_atomic_int_get(&stopping)) {
|
379 |
/* Still stopping from before */
|
380 |
return -1; |
381 |
} |
382 |
if(callback == NULL || config_path == NULL) { |
383 |
/* Invalid arguments */
|
384 |
return -1; |
385 |
} |
386 |
|
387 |
/* Read configuration */
|
388 |
char filename[255]; |
389 |
g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_TEXTROOM_PACKAGE); |
390 |
JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
|
391 |
config = janus_config_parse(filename); |
392 |
config_folder = config_path; |
393 |
if(config != NULL) |
394 |
janus_config_print(config); |
395 |
janus_mutex_init(&config_mutex); |
396 |
|
397 |
rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
|
398 |
janus_mutex_init(&rooms_mutex); |
399 |
sessions = g_hash_table_new(NULL, NULL); |
400 |
messages = g_async_queue_new_full((GDestroyNotify) janus_textroom_message_free); |
401 |
janus_mutex_init(&sessions_mutex); |
402 |
/* This is the callback we'll need to invoke to contact the gateway */
|
403 |
gateway = callback; |
404 |
|
405 |
/* Parse configuration to populate the rooms list */
|
406 |
if(config != NULL) { |
407 |
janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json"); |
408 |
if(item && item->value) {
|
409 |
/* Check how we need to format/serialize the JSON output */
|
410 |
if(!strcasecmp(item->value, "indented")) { |
411 |
/* Default: indented, we use three spaces for that */
|
412 |
json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
|
413 |
} else if(!strcasecmp(item->value, "plain")) { |
414 |
/* Not indented and no new lines, but still readable */
|
415 |
json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER;
|
416 |
} else if(!strcasecmp(item->value, "compact")) { |
417 |
/* Compact, so no spaces between separators */
|
418 |
json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; |
419 |
} else {
|
420 |
JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value);
|
421 |
json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
|
422 |
} |
423 |
} |
424 |
/* Any admin key to limit who can "create"? */
|
425 |
janus_config_item *key = janus_config_get_item_drilldown(config, "general", "admin_key"); |
426 |
if(key != NULL && key->value != NULL) |
427 |
admin_key = g_strdup(key->value); |
428 |
janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); |
429 |
if(events != NULL && events->value != NULL) |
430 |
notify_events = janus_is_true(events->value); |
431 |
if(!notify_events && callback->events_is_enabled()) {
|
432 |
JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_TEXTROOM_NAME);
|
433 |
} |
434 |
/* Iterate on all rooms */
|
435 |
GList *cl = janus_config_get_categories(config); |
436 |
while(cl != NULL) { |
437 |
janus_config_category *cat = (janus_config_category *)cl->data; |
438 |
if(cat->name == NULL || !strcasecmp(cat->name, "general")) { |
439 |
cl = cl->next; |
440 |
continue;
|
441 |
} |
442 |
JANUS_LOG(LOG_VERB, "Adding text room '%s'\n", cat->name);
|
443 |
janus_config_item *desc = janus_config_get_item(cat, "description");
|
444 |
janus_config_item *priv = janus_config_get_item(cat, "is_private");
|
445 |
janus_config_item *secret = janus_config_get_item(cat, "secret");
|
446 |
janus_config_item *pin = janus_config_get_item(cat, "pin");
|
447 |
janus_config_item *post = janus_config_get_item(cat, "post");
|
448 |
/* Create the text room */
|
449 |
janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
|
450 |
textroom->room_id = g_ascii_strtoull(cat->name, NULL, 0); |
451 |
char *description = NULL; |
452 |
if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0) |
453 |
description = g_strdup(desc->value); |
454 |
else
|
455 |
description = g_strdup(cat->name); |
456 |
textroom->room_name = description; |
457 |
textroom->is_private = priv && priv->value && janus_is_true(priv->value); |
458 |
if(secret != NULL && secret->value != NULL) { |
459 |
textroom->room_secret = g_strdup(secret->value); |
460 |
} |
461 |
if(pin != NULL && pin->value != NULL) { |
462 |
textroom->room_pin = g_strdup(pin->value); |
463 |
} |
464 |
if(post != NULL && post->value != NULL) { |
465 |
#ifdef HAVE_LIBCURL
|
466 |
/* FIXME Should we check if this is a valid HTTP address? */
|
467 |
textroom->http_backend = g_strdup(post->value); |
468 |
#else
|
469 |
JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
|
470 |
#endif
|
471 |
} |
472 |
textroom->participants = g_hash_table_new(g_str_hash, g_str_equal); |
473 |
textroom->check_tokens = FALSE; /* Static rooms can't have an "allowed" list yet, no hooks to the configuration file */
|
474 |
textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
|
475 |
textroom->destroyed = 0;
|
476 |
janus_mutex_init(&textroom->mutex); |
477 |
JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n", |
478 |
textroom->room_id, textroom->room_name, |
479 |
textroom->is_private ? "private" : "public", |
480 |
textroom->room_secret ? textroom->room_secret : "no secret",
|
481 |
textroom->room_pin ? textroom->room_pin : "no pin");
|
482 |
g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom); |
483 |
cl = cl->next; |
484 |
} |
485 |
/* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
|
486 |
} |
487 |
|
488 |
/* Show available rooms */
|
489 |
janus_mutex_lock(&rooms_mutex); |
490 |
GHashTableIter iter; |
491 |
gpointer value; |
492 |
g_hash_table_iter_init(&iter, rooms); |
493 |
while (g_hash_table_iter_next(&iter, NULL, &value)) { |
494 |
janus_textroom_room *tr = value; |
495 |
JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name); |
496 |
} |
497 |
janus_mutex_unlock(&rooms_mutex); |
498 |
|
499 |
#ifdef HAVE_LIBCURL
|
500 |
curl_global_init(CURL_GLOBAL_ALL); |
501 |
#endif
|
502 |
|
503 |
g_atomic_int_set(&initialized, 1);
|
504 |
|
505 |
GError *error = NULL;
|
506 |
/* Start the sessions watchdog */
|
507 |
watchdog = g_thread_try_new("textroom watchdog", &janus_textroom_watchdog, NULL, &error); |
508 |
if(error != NULL) { |
509 |
g_atomic_int_set(&initialized, 0);
|
510 |
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom watchdog thread...\n", error->code, error->message ? error->message : "??"); |
511 |
return -1; |
512 |
} |
513 |
/* Launch the thread that will handle incoming messages */
|
514 |
handler_thread = g_thread_try_new("textroom handler", janus_textroom_handler, NULL, &error); |
515 |
if(error != NULL) { |
516 |
g_atomic_int_set(&initialized, 0);
|
517 |
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom handler thread...\n", error->code, error->message ? error->message : "??"); |
518 |
return -1; |
519 |
} |
520 |
JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_TEXTROOM_NAME);
|
521 |
return 0; |
522 |
} |
523 |
|
524 |
void janus_textroom_destroy(void) { |
525 |
if(!g_atomic_int_get(&initialized))
|
526 |
return;
|
527 |
g_atomic_int_set(&stopping, 1);
|
528 |
|
529 |
g_async_queue_push(messages, &exit_message); |
530 |
if(handler_thread != NULL) { |
531 |
g_thread_join(handler_thread); |
532 |
handler_thread = NULL;
|
533 |
} |
534 |
if(watchdog != NULL) { |
535 |
g_thread_join(watchdog); |
536 |
watchdog = NULL;
|
537 |
} |
538 |
|
539 |
/* FIXME We should destroy the sessions cleanly */
|
540 |
janus_mutex_lock(&sessions_mutex); |
541 |
g_hash_table_destroy(sessions); |
542 |
janus_mutex_unlock(&sessions_mutex); |
543 |
janus_mutex_lock(&rooms_mutex); |
544 |
g_hash_table_destroy(rooms); |
545 |
janus_mutex_unlock(&rooms_mutex); |
546 |
g_async_queue_unref(messages); |
547 |
messages = NULL;
|
548 |
sessions = NULL;
|
549 |
|
550 |
#ifdef HAVE_LIBCURL
|
551 |
curl_global_cleanup(); |
552 |
#endif
|
553 |
|
554 |
janus_config_destroy(config); |
555 |
g_free(admin_key); |
556 |
|
557 |
g_atomic_int_set(&initialized, 0);
|
558 |
g_atomic_int_set(&stopping, 0);
|
559 |
JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_TEXTROOM_NAME);
|
560 |
} |
561 |
|
562 |
int janus_textroom_get_api_compatibility(void) { |
563 |
/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
|
564 |
return JANUS_PLUGIN_API_VERSION;
|
565 |
} |
566 |
|
567 |
int janus_textroom_get_version(void) { |
568 |
return JANUS_TEXTROOM_VERSION;
|
569 |
} |
570 |
|
571 |
const char *janus_textroom_get_version_string(void) { |
572 |
return JANUS_TEXTROOM_VERSION_STRING;
|
573 |
} |
574 |
|
575 |
const char *janus_textroom_get_description(void) { |
576 |
return JANUS_TEXTROOM_DESCRIPTION;
|
577 |
} |
578 |
|
579 |
const char *janus_textroom_get_name(void) { |
580 |
return JANUS_TEXTROOM_NAME;
|
581 |
} |
582 |
|
583 |
const char *janus_textroom_get_author(void) { |
584 |
return JANUS_TEXTROOM_AUTHOR;
|
585 |
} |
586 |
|
587 |
const char *janus_textroom_get_package(void) { |
588 |
return JANUS_TEXTROOM_PACKAGE;
|
589 |
} |
590 |
|
591 |
void janus_textroom_create_session(janus_plugin_session *handle, int *error) { |
592 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
|
593 |
*error = -1;
|
594 |
return;
|
595 |
} |
596 |
janus_textroom_session *session = (janus_textroom_session *)g_malloc0(sizeof(janus_textroom_session));
|
597 |
session->handle = handle; |
598 |
session->rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
|
599 |
session->destroyed = 0;
|
600 |
janus_mutex_init(&session->mutex); |
601 |
g_atomic_int_set(&session->setup, 0);
|
602 |
g_atomic_int_set(&session->hangingup, 0);
|
603 |
handle->plugin_handle = session; |
604 |
janus_mutex_lock(&sessions_mutex); |
605 |
g_hash_table_insert(sessions, handle, session); |
606 |
janus_mutex_unlock(&sessions_mutex); |
607 |
|
608 |
return;
|
609 |
} |
610 |
|
611 |
void janus_textroom_destroy_session(janus_plugin_session *handle, int *error) { |
612 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
|
613 |
*error = -1;
|
614 |
return;
|
615 |
} |
616 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
617 |
if(!session) {
|
618 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
619 |
*error = -2;
|
620 |
return;
|
621 |
} |
622 |
JANUS_LOG(LOG_VERB, "Removing Echo Test session...\n");
|
623 |
janus_mutex_lock(&sessions_mutex); |
624 |
if(!session->destroyed) {
|
625 |
g_hash_table_remove(sessions, handle); |
626 |
janus_textroom_hangup_media(handle); |
627 |
session->destroyed = janus_get_monotonic_time(); |
628 |
/* Cleaning up and removing the session is done in a lazy way */
|
629 |
old_sessions = g_list_append(old_sessions, session); |
630 |
} |
631 |
janus_mutex_unlock(&sessions_mutex); |
632 |
return;
|
633 |
} |
634 |
|
635 |
json_t *janus_textroom_query_session(janus_plugin_session *handle) { |
636 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
|
637 |
return NULL; |
638 |
} |
639 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
640 |
if(!session) {
|
641 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
642 |
return NULL; |
643 |
} |
644 |
/* TODO Return meaningful info: participant details, rooms they're in, etc. */
|
645 |
json_t *info = json_object(); |
646 |
json_object_set_new(info, "destroyed", json_integer(session->destroyed));
|
647 |
return info;
|
648 |
} |
649 |
|
650 |
struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) { |
651 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
|
652 |
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL); |
653 |
|
654 |
/* Pre-parse the message */
|
655 |
int error_code = 0; |
656 |
char error_cause[512]; |
657 |
json_t *root = message; |
658 |
json_t *response = NULL;
|
659 |
|
660 |
if(message == NULL) { |
661 |
JANUS_LOG(LOG_ERR, "No message??\n");
|
662 |
error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE; |
663 |
g_snprintf(error_cause, 512, "%s", "No message??"); |
664 |
goto plugin_response;
|
665 |
} |
666 |
|
667 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
668 |
if(!session) {
|
669 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
670 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
671 |
g_snprintf(error_cause, 512, "%s", "session associated with this handle..."); |
672 |
goto plugin_response;
|
673 |
} |
674 |
if(session->destroyed) {
|
675 |
JANUS_LOG(LOG_ERR, "Session has already been destroyed...\n");
|
676 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
677 |
g_snprintf(error_cause, 512, "%s", "Session has already been destroyed..."); |
678 |
goto plugin_response;
|
679 |
} |
680 |
if(!json_is_object(root)) {
|
681 |
JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
|
682 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON; |
683 |
g_snprintf(error_cause, 512, "JSON error: not an object"); |
684 |
goto plugin_response;
|
685 |
} |
686 |
/* Get the request first */
|
687 |
JANUS_VALIDATE_JSON_OBJECT(root, request_parameters, |
688 |
error_code, error_cause, TRUE, |
689 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
690 |
if(error_code != 0) |
691 |
goto plugin_response;
|
692 |
json_t *request = json_object_get(root, "request");
|
693 |
/* Some requests (e.g., 'create' and 'destroy') can be handled synchronously */
|
694 |
const char *request_text = json_string_value(request); |
695 |
if(!strcasecmp(request_text, "list") |
696 |
|| !strcasecmp(request_text, "exists")
|
697 |
|| !strcasecmp(request_text, "create")
|
698 |
|| !strcasecmp(request_text, "destroy")) {
|
699 |
/* These requests typically only belong to the datachannel
|
700 |
* messaging, but for admin purposes we might use them on
|
701 |
* the Janus API as well: add the properties the datachannel
|
702 |
* processor would expect and handle everything there */
|
703 |
json_object_set_new(root, "textroom", json_string(request_text));
|
704 |
json_object_set_new(root, "transaction", json_string(transaction));
|
705 |
janus_plugin_result *result = janus_textroom_handle_incoming_request(session->handle, NULL, root, FALSE);
|
706 |
if(result == NULL) { |
707 |
JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
|
708 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON; |
709 |
g_snprintf(error_cause, 512, "JSON error: not an object"); |
710 |
goto plugin_response;
|
711 |
} |
712 |
if(root != NULL) |
713 |
json_decref(root); |
714 |
if(jsep != NULL) |
715 |
json_decref(jsep); |
716 |
g_free(transaction); |
717 |
return result;
|
718 |
} else if(!strcasecmp(request_text, "setup") || !strcasecmp(request_text, "ack")) { |
719 |
/* These messages are handled asynchronously */
|
720 |
janus_textroom_message *msg = g_malloc0(sizeof(janus_textroom_message));
|
721 |
msg->handle = handle; |
722 |
msg->transaction = transaction; |
723 |
msg->message = root; |
724 |
msg->jsep = jsep; |
725 |
|
726 |
g_async_queue_push(messages, msg); |
727 |
|
728 |
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL); |
729 |
} else {
|
730 |
JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
|
731 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST; |
732 |
g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); |
733 |
} |
734 |
|
735 |
plugin_response:
|
736 |
{ |
737 |
if(error_code == 0 && !response) { |
738 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
739 |
g_snprintf(error_cause, 512, "Invalid response"); |
740 |
} |
741 |
if(error_code != 0) { |
742 |
/* Prepare JSON error event */
|
743 |
json_t *event = json_object(); |
744 |
json_object_set_new(event, "textroom", json_string("event")); |
745 |
json_object_set_new(event, "error_code", json_integer(error_code));
|
746 |
json_object_set_new(event, "error", json_string(error_cause));
|
747 |
response = event; |
748 |
} |
749 |
if(root != NULL) |
750 |
json_decref(root); |
751 |
if(jsep != NULL) |
752 |
json_decref(jsep); |
753 |
g_free(transaction); |
754 |
|
755 |
return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response); |
756 |
} |
757 |
|
758 |
} |
759 |
|
760 |
void janus_textroom_setup_media(janus_plugin_session *handle) {
|
761 |
JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
|
762 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
|
763 |
return;
|
764 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
765 |
if(!session) {
|
766 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
767 |
return;
|
768 |
} |
769 |
if(session->destroyed)
|
770 |
return;
|
771 |
g_atomic_int_set(&session->hangingup, 0);
|
772 |
} |
773 |
|
774 |
void janus_textroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) { |
775 |
/* We don't do audio/video */
|
776 |
} |
777 |
|
778 |
void janus_textroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) { |
779 |
/* We don't do audio/video */
|
780 |
} |
781 |
|
782 |
void janus_textroom_incoming_data(janus_plugin_session *handle, char *buf, int len) { |
783 |
if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) |
784 |
return;
|
785 |
/* Incoming request from this user: what should we do? */
|
786 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
787 |
if(!session) {
|
788 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
789 |
return;
|
790 |
} |
791 |
if(session->destroyed)
|
792 |
return;
|
793 |
if(buf == NULL || len <= 0) |
794 |
return;
|
795 |
char *text = g_malloc0(len+1); |
796 |
memcpy(text, buf, len); |
797 |
*(text+len) = '\0';
|
798 |
JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes): %s\n", strlen(text), text);
|
799 |
janus_textroom_handle_incoming_request(handle, text, NULL, FALSE);
|
800 |
} |
801 |
|
802 |
/* Helper method to handle incoming messages from the data channel */
|
803 |
janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle, char *text, json_t *json, gboolean internal) {
|
804 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
805 |
/* Parse JSON, if needed */
|
806 |
json_error_t error; |
807 |
json_t *root = text ? json_loads(text, 0, &error) : json;
|
808 |
g_free(text); |
809 |
if(!root) {
|
810 |
JANUS_LOG(LOG_ERR, "Error parsing data channel message (JSON error: on line %d: %s)\n", error.line, error.text);
|
811 |
return NULL; |
812 |
} |
813 |
/* Handle request */
|
814 |
int error_code = 0; |
815 |
char error_cause[512]; |
816 |
JANUS_VALIDATE_JSON_OBJECT(root, transaction_parameters, |
817 |
error_code, error_cause, TRUE, |
818 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
819 |
const char *transaction_text = NULL; |
820 |
json_t *reply = NULL;
|
821 |
if(error_code != 0) |
822 |
goto msg_response;
|
823 |
json_t *request = json_object_get(root, "textroom");
|
824 |
json_t *transaction = json_object_get(root, "transaction");
|
825 |
const char *request_text = json_string_value(request); |
826 |
transaction_text = json_string_value(transaction); |
827 |
if(!strcasecmp(request_text, "message")) { |
828 |
JANUS_VALIDATE_JSON_OBJECT(root, message_parameters, |
829 |
error_code, error_cause, TRUE, |
830 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
831 |
if(error_code != 0) |
832 |
goto msg_response;
|
833 |
json_t *room = json_object_get(root, "room");
|
834 |
guint64 room_id = json_integer_value(room); |
835 |
janus_mutex_lock(&rooms_mutex); |
836 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
837 |
if(textroom == NULL) { |
838 |
janus_mutex_unlock(&rooms_mutex); |
839 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
840 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
841 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
842 |
goto msg_response;
|
843 |
} |
844 |
janus_mutex_lock(&textroom->mutex); |
845 |
janus_mutex_unlock(&rooms_mutex); |
846 |
janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id); |
847 |
if(participant == NULL) { |
848 |
janus_mutex_unlock(&textroom->mutex); |
849 |
JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id); |
850 |
error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM; |
851 |
g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id); |
852 |
goto msg_response;
|
853 |
} |
854 |
json_t *username = json_object_get(root, "to");
|
855 |
json_t *usernames = json_object_get(root, "tos");
|
856 |
if(username && usernames) {
|
857 |
janus_mutex_unlock(&textroom->mutex); |
858 |
JANUS_LOG(LOG_ERR, "Both to and tos array provided\n");
|
859 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT; |
860 |
g_snprintf(error_cause, 512, "Both to and tos array provided"); |
861 |
goto msg_response;
|
862 |
} |
863 |
json_t *text = json_object_get(root, "text");
|
864 |
const char *message = json_string_value(text); |
865 |
/* Prepare outgoing message */
|
866 |
json_t *msg = json_object(); |
867 |
json_object_set_new(msg, "textroom", json_string("message")); |
868 |
json_object_set_new(msg, "room", json_integer(room_id));
|
869 |
json_object_set_new(msg, "from", json_string(participant->username));
|
870 |
time_t timer; |
871 |
time(&timer); |
872 |
struct tm *tm_info = localtime(&timer);
|
873 |
char msgTime[64]; |
874 |
strftime(msgTime, sizeof(msgTime), "%FT%T%z", tm_info); |
875 |
json_object_set_new(msg, "date", json_string(msgTime));
|
876 |
json_object_set_new(msg, "text", json_string(message));
|
877 |
if(username || usernames)
|
878 |
json_object_set_new(msg, "whisper", json_true());
|
879 |
char *msg_text = json_dumps(msg, json_format);
|
880 |
json_decref(msg); |
881 |
/* Start preparing the response too */
|
882 |
reply = json_object(); |
883 |
json_object_set_new(reply, "textroom", json_string("success")); |
884 |
/* Who should we send this message to? */
|
885 |
if(username) {
|
886 |
/* A single user */
|
887 |
json_t *sent = json_object(); |
888 |
const char *to = json_string_value(username); |
889 |
JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message); |
890 |
janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to); |
891 |
if(top) {
|
892 |
gateway->relay_data(top->session->handle, msg_text, strlen(msg_text)); |
893 |
json_object_set_new(sent, to, json_true()); |
894 |
} else {
|
895 |
JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id); |
896 |
json_object_set_new(sent, to, json_false()); |
897 |
} |
898 |
json_object_set_new(reply, "sent", sent);
|
899 |
} else if(usernames) { |
900 |
/* A limited number of users */
|
901 |
json_t *sent = json_object(); |
902 |
size_t i = 0;
|
903 |
for(i=0; i<json_array_size(usernames); i++) { |
904 |
json_t *u = json_array_get(usernames, i); |
905 |
const char *to = json_string_value(u); |
906 |
JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message); |
907 |
janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to); |
908 |
if(top) {
|
909 |
gateway->relay_data(top->session->handle, msg_text, strlen(msg_text)); |
910 |
json_object_set_new(sent, to, json_true()); |
911 |
} else {
|
912 |
JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id); |
913 |
json_object_set_new(sent, to, json_false()); |
914 |
} |
915 |
} |
916 |
json_object_set_new(reply, "sent", sent);
|
917 |
} else {
|
918 |
/* Everybody in the room */
|
919 |
JANUS_LOG(LOG_VERB, "To everybody in %"SCNu64": %s\n", room_id, message); |
920 |
if(textroom->participants) {
|
921 |
GHashTableIter iter; |
922 |
gpointer value; |
923 |
g_hash_table_iter_init(&iter, textroom->participants); |
924 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
925 |
janus_textroom_participant *top = value; |
926 |
JANUS_LOG(LOG_VERB, " >> To %s in %"SCNu64": %s\n", top->username, room_id, message); |
927 |
gateway->relay_data(top->session->handle, msg_text, strlen(msg_text)); |
928 |
} |
929 |
} |
930 |
#ifdef HAVE_LIBCURL
|
931 |
/* Is there a backend waiting for this message too? */
|
932 |
if(textroom->http_backend) {
|
933 |
/* Prepare the libcurl context */
|
934 |
CURLcode res; |
935 |
CURL *curl = curl_easy_init(); |
936 |
if(curl == NULL) { |
937 |
JANUS_LOG(LOG_ERR, "Error initializing CURL context\n");
|
938 |
} else {
|
939 |
curl_easy_setopt(curl, CURLOPT_URL, textroom->http_backend); |
940 |
struct curl_slist *headers = NULL; |
941 |
headers = curl_slist_append(headers, "Accept: application/json");
|
942 |
headers = curl_slist_append(headers, "Content-Type: application/json");
|
943 |
headers = curl_slist_append(headers, "charsets: utf-8");
|
944 |
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); |
945 |
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg_text); |
946 |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_textroom_write_data); |
947 |
/* Send the request */
|
948 |
res = curl_easy_perform(curl); |
949 |
if(res != CURLE_OK) {
|
950 |
JANUS_LOG(LOG_ERR, "Couldn't relay event to the backend: %s\n", curl_easy_strerror(res));
|
951 |
} else {
|
952 |
JANUS_LOG(LOG_DBG, "Event sent!\n");
|
953 |
} |
954 |
curl_easy_cleanup(curl); |
955 |
curl_slist_free_all(headers); |
956 |
} |
957 |
} |
958 |
#endif
|
959 |
} |
960 |
free(msg_text); |
961 |
janus_mutex_unlock(&textroom->mutex); |
962 |
/* By default we send a confirmation back to the user that sent this message:
|
963 |
* if the user passed an ack=false, though, we don't do that */
|
964 |
json_t *ack = json_object_get(root, "ack");
|
965 |
if(!internal && (ack == NULL || json_is_true(ack))) { |
966 |
/* Send response back */
|
967 |
} else {
|
968 |
internal = TRUE; |
969 |
json_decref(reply); |
970 |
reply = NULL;
|
971 |
} |
972 |
} else if(!strcasecmp(request_text, "join")) { |
973 |
JANUS_VALIDATE_JSON_OBJECT(root, join_parameters, |
974 |
error_code, error_cause, TRUE, |
975 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
976 |
if(error_code != 0) |
977 |
goto msg_response;
|
978 |
json_t *room = json_object_get(root, "room");
|
979 |
guint64 room_id = json_integer_value(room); |
980 |
janus_mutex_lock(&rooms_mutex); |
981 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
982 |
if(textroom == NULL) { |
983 |
janus_mutex_unlock(&rooms_mutex); |
984 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
985 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
986 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
987 |
goto msg_response;
|
988 |
} |
989 |
janus_mutex_lock(&textroom->mutex); |
990 |
janus_mutex_unlock(&rooms_mutex); |
991 |
/* A PIN may be required for this action */
|
992 |
JANUS_CHECK_SECRET(textroom->room_pin, root, "pin", error_code, error_cause,
|
993 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED); |
994 |
if(error_code != 0) { |
995 |
janus_mutex_unlock(&textroom->mutex); |
996 |
goto msg_response;
|
997 |
} |
998 |
janus_mutex_lock(&session->mutex); |
999 |
if(g_hash_table_lookup(session->rooms, &room_id) != NULL) { |
1000 |
janus_mutex_unlock(&session->mutex); |
1001 |
janus_mutex_unlock(&textroom->mutex); |
1002 |
JANUS_LOG(LOG_ERR, "Already in room %"SCNu64"\n", room_id); |
1003 |
error_code = JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM; |
1004 |
g_snprintf(error_cause, 512, "Already in room %"SCNu64, room_id); |
1005 |
goto msg_response;
|
1006 |
} |
1007 |
json_t *username = json_object_get(root, "username");
|
1008 |
const char *username_text = json_string_value(username); |
1009 |
janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, username_text); |
1010 |
if(participant != NULL) { |
1011 |
janus_mutex_unlock(&session->mutex); |
1012 |
janus_mutex_unlock(&textroom->mutex); |
1013 |
JANUS_LOG(LOG_ERR, "Username already taken\n");
|
1014 |
error_code = JANUS_TEXTROOM_ERROR_USERNAME_EXISTS; |
1015 |
g_snprintf(error_cause, 512, "Username already taken"); |
1016 |
goto msg_response;
|
1017 |
} |
1018 |
json_t *display = json_object_get(root, "display");
|
1019 |
const char *display_text = json_string_value(display); |
1020 |
/* Create a participant instance */
|
1021 |
participant = g_malloc0(sizeof(janus_textroom_participant));
|
1022 |
participant->session = session; |
1023 |
participant->room = textroom; |
1024 |
participant->username = g_strdup(username_text); |
1025 |
participant->display = display_text ? g_strdup(display_text) : NULL;
|
1026 |
participant->destroyed = 0;
|
1027 |
janus_mutex_init(&participant->mutex); |
1028 |
g_hash_table_insert(session->rooms, janus_uint64_dup(textroom->room_id), participant); |
1029 |
g_hash_table_insert(textroom->participants, participant->username, participant); |
1030 |
/* Notify all participants */
|
1031 |
JANUS_LOG(LOG_VERB, "Notifying all participants about the new join\n");
|
1032 |
json_t *list = json_array(); |
1033 |
if(textroom->participants) {
|
1034 |
/* Prepare event */
|
1035 |
json_t *event = json_object(); |
1036 |
json_object_set_new(event, "textroom", json_string("join")); |
1037 |
json_object_set_new(event, "room", json_integer(textroom->room_id));
|
1038 |
json_object_set_new(event, "username", json_string(username_text));
|
1039 |
if(display_text != NULL) |
1040 |
json_object_set_new(event, "display", json_string(display_text));
|
1041 |
char *event_text = json_dumps(event, json_format);
|
1042 |
json_decref(event); |
1043 |
gateway->relay_data(handle, event_text, strlen(event_text)); |
1044 |
/* Broadcast */
|
1045 |
GHashTableIter iter; |
1046 |
gpointer value; |
1047 |
g_hash_table_iter_init(&iter, textroom->participants); |
1048 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1049 |
janus_textroom_participant *top = value; |
1050 |
if(top == participant)
|
1051 |
continue; /* Skip us */ |
1052 |
JANUS_LOG(LOG_VERB, " >> To %s in %"SCNu64"\n", top->username, room_id); |
1053 |
gateway->relay_data(top->session->handle, event_text, strlen(event_text)); |
1054 |
/* Take note of this user */
|
1055 |
json_t *p = json_object(); |
1056 |
json_object_set_new(p, "username", json_string(top->username));
|
1057 |
if(top->display != NULL) |
1058 |
json_object_set_new(p, "display", json_string(top->display));
|
1059 |
json_array_append_new(list, p); |
1060 |
} |
1061 |
free(event_text); |
1062 |
} |
1063 |
janus_mutex_unlock(&session->mutex); |
1064 |
janus_mutex_unlock(&textroom->mutex); |
1065 |
if(!internal) {
|
1066 |
/* Send response back */
|
1067 |
reply = json_object(); |
1068 |
json_object_set_new(reply, "textroom", json_string("success")); |
1069 |
json_object_set_new(reply, "participants", list);
|
1070 |
} |
1071 |
/* Also notify event handlers */
|
1072 |
if(notify_events && gateway->events_is_enabled()) {
|
1073 |
json_t *info = json_object(); |
1074 |
json_object_set_new(info, "event", json_string("join")); |
1075 |
json_object_set_new(info, "room", json_integer(room_id));
|
1076 |
json_object_set_new(info, "username", json_string(username_text));
|
1077 |
if(display_text)
|
1078 |
json_object_set_new(info, "display", json_string(display_text));
|
1079 |
gateway->notify_event(&janus_textroom_plugin, session->handle, info); |
1080 |
} |
1081 |
} else if(!strcasecmp(request_text, "leave")) { |
1082 |
JANUS_VALIDATE_JSON_OBJECT(root, room_parameters, |
1083 |
error_code, error_cause, TRUE, |
1084 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1085 |
if(error_code != 0) |
1086 |
goto msg_response;
|
1087 |
json_t *room = json_object_get(root, "room");
|
1088 |
guint64 room_id = json_integer_value(room); |
1089 |
janus_mutex_lock(&rooms_mutex); |
1090 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
1091 |
if(textroom == NULL) { |
1092 |
janus_mutex_unlock(&rooms_mutex); |
1093 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
1094 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
1095 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
1096 |
goto msg_response;
|
1097 |
} |
1098 |
janus_mutex_lock(&textroom->mutex); |
1099 |
janus_mutex_unlock(&rooms_mutex); |
1100 |
janus_mutex_lock(&session->mutex); |
1101 |
janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id); |
1102 |
if(participant == NULL) { |
1103 |
janus_mutex_unlock(&session->mutex); |
1104 |
janus_mutex_unlock(&textroom->mutex); |
1105 |
JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id); |
1106 |
error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM; |
1107 |
g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id); |
1108 |
goto msg_response;
|
1109 |
} |
1110 |
g_hash_table_remove(session->rooms, &room_id); |
1111 |
g_hash_table_remove(textroom->participants, participant->username); |
1112 |
participant->session = NULL;
|
1113 |
participant->room = NULL;
|
1114 |
/* Notify all participants */
|
1115 |
JANUS_LOG(LOG_VERB, "Notifying all participants about the new leave\n");
|
1116 |
if(textroom->participants) {
|
1117 |
/* Prepare event */
|
1118 |
json_t *event = json_object(); |
1119 |
json_object_set_new(event, "textroom", json_string("leave")); |
1120 |
json_object_set_new(event, "room", json_integer(textroom->room_id));
|
1121 |
json_object_set_new(event, "username", json_string(participant->username));
|
1122 |
char *event_text = json_dumps(event, json_format);
|
1123 |
json_decref(event); |
1124 |
gateway->relay_data(handle, event_text, strlen(event_text)); |
1125 |
/* Broadcast */
|
1126 |
GHashTableIter iter; |
1127 |
gpointer value; |
1128 |
g_hash_table_iter_init(&iter, textroom->participants); |
1129 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1130 |
janus_textroom_participant *top = value; |
1131 |
if(top == participant)
|
1132 |
continue; /* Skip us */ |
1133 |
JANUS_LOG(LOG_VERB, " >> To %s in %"SCNu64"\n", top->username, room_id); |
1134 |
gateway->relay_data(top->session->handle, event_text, strlen(event_text)); |
1135 |
} |
1136 |
free(event_text); |
1137 |
} |
1138 |
/* Also notify event handlers */
|
1139 |
if(notify_events && gateway->events_is_enabled()) {
|
1140 |
json_t *info = json_object(); |
1141 |
json_object_set_new(info, "event", json_string("leave")); |
1142 |
json_object_set_new(info, "room", json_integer(room_id));
|
1143 |
json_object_set_new(info, "username", json_string(participant->username));
|
1144 |
gateway->notify_event(&janus_textroom_plugin, session->handle, info); |
1145 |
} |
1146 |
g_free(participant->username); |
1147 |
g_free(participant->display); |
1148 |
g_free(participant); |
1149 |
janus_mutex_unlock(&session->mutex); |
1150 |
janus_mutex_unlock(&textroom->mutex); |
1151 |
if(!internal) {
|
1152 |
/* Send response back */
|
1153 |
reply = json_object(); |
1154 |
json_object_set_new(reply, "textroom", json_string("success")); |
1155 |
} |
1156 |
} else if(!strcasecmp(request_text, "list")) { |
1157 |
/* List all rooms (but private ones) and their details (except for the secret, of course...) */
|
1158 |
json_t *list = json_array(); |
1159 |
JANUS_LOG(LOG_VERB, "Request for the list for all text rooms\n");
|
1160 |
janus_mutex_lock(&rooms_mutex); |
1161 |
GHashTableIter iter; |
1162 |
gpointer value; |
1163 |
g_hash_table_iter_init(&iter, rooms); |
1164 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1165 |
janus_textroom_room *room = value; |
1166 |
if(!room)
|
1167 |
continue;
|
1168 |
janus_mutex_lock(&room->mutex); |
1169 |
if(room->is_private) {
|
1170 |
/* Skip private room */
|
1171 |
JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
|
1172 |
janus_mutex_unlock(&room->mutex); |
1173 |
continue;
|
1174 |
} |
1175 |
json_t *rl = json_object(); |
1176 |
json_object_set_new(rl, "room", json_integer(room->room_id));
|
1177 |
json_object_set_new(rl, "description", json_string(room->room_name));
|
1178 |
json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false());
|
1179 |
/* TODO: Possibly list participant details... or make it a separate API call for a specific room */
|
1180 |
json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
|
1181 |
json_array_append_new(list, rl); |
1182 |
janus_mutex_unlock(&room->mutex); |
1183 |
} |
1184 |
janus_mutex_unlock(&rooms_mutex); |
1185 |
if(!internal) {
|
1186 |
/* Send response back */
|
1187 |
reply = json_object(); |
1188 |
json_object_set_new(reply, "textroom", json_string("success")); |
1189 |
json_object_set_new(reply, "list", list);
|
1190 |
} |
1191 |
} else if(!strcasecmp(request_text, "allowed")) { |
1192 |
JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing textroom room\n");
|
1193 |
JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters, |
1194 |
error_code, error_cause, TRUE, |
1195 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1196 |
if(error_code != 0) |
1197 |
goto msg_response;
|
1198 |
json_t *action = json_object_get(root, "action");
|
1199 |
json_t *room = json_object_get(root, "room");
|
1200 |
json_t *allowed = json_object_get(root, "allowed");
|
1201 |
const char *action_text = json_string_value(action); |
1202 |
if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") && |
1203 |
strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) { |
1204 |
JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
|
1205 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT; |
1206 |
g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text); |
1207 |
goto msg_response;
|
1208 |
} |
1209 |
guint64 room_id = json_integer_value(room); |
1210 |
janus_mutex_lock(&rooms_mutex); |
1211 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
1212 |
if(textroom == NULL) { |
1213 |
janus_mutex_unlock(&rooms_mutex); |
1214 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
1215 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
1216 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
1217 |
goto msg_response;
|
1218 |
} |
1219 |
janus_mutex_lock(&textroom->mutex); |
1220 |
/* A secret may be required for this action */
|
1221 |
JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
|
1222 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED); |
1223 |
if(error_code != 0) { |
1224 |
janus_mutex_unlock(&textroom->mutex); |
1225 |
janus_mutex_unlock(&rooms_mutex); |
1226 |
goto msg_response;
|
1227 |
} |
1228 |
if(!strcasecmp(action_text, "enable")) { |
1229 |
JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %"SCNu64"\n", room_id); |
1230 |
textroom->check_tokens = TRUE; |
1231 |
} else if(!strcasecmp(action_text, "disable")) { |
1232 |
JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %"SCNu64" (free entry)\n", room_id); |
1233 |
textroom->check_tokens = FALSE; |
1234 |
} else {
|
1235 |
gboolean add = !strcasecmp(action_text, "add");
|
1236 |
if(allowed) {
|
1237 |
/* Make sure the "allowed" array only contains strings */
|
1238 |
gboolean ok = TRUE; |
1239 |
if(json_array_size(allowed) > 0) { |
1240 |
size_t i = 0;
|
1241 |
for(i=0; i<json_array_size(allowed); i++) { |
1242 |
json_t *a = json_array_get(allowed, i); |
1243 |
if(!a || !json_is_string(a)) {
|
1244 |
ok = FALSE; |
1245 |
break;
|
1246 |
} |
1247 |
} |
1248 |
} |
1249 |
if(!ok) {
|
1250 |
JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
|
1251 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT; |
1252 |
g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)"); |
1253 |
janus_mutex_unlock(&textroom->mutex); |
1254 |
janus_mutex_unlock(&rooms_mutex); |
1255 |
goto msg_response;
|
1256 |
} |
1257 |
size_t i = 0;
|
1258 |
for(i=0; i<json_array_size(allowed); i++) { |
1259 |
const char *token = json_string_value(json_array_get(allowed, i)); |
1260 |
if(add) {
|
1261 |
if(!g_hash_table_lookup(textroom->allowed, token))
|
1262 |
g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE)); |
1263 |
} else {
|
1264 |
g_hash_table_remove(textroom->allowed, token); |
1265 |
} |
1266 |
} |
1267 |
} |
1268 |
} |
1269 |
if(!internal) {
|
1270 |
/* Send response back */
|
1271 |
reply = json_object(); |
1272 |
json_object_set_new(reply, "textroom", json_string("success")); |
1273 |
json_object_set_new(reply, "room", json_integer(textroom->room_id));
|
1274 |
json_t *list = json_array(); |
1275 |
if(strcasecmp(action_text, "disable")) { |
1276 |
if(g_hash_table_size(textroom->allowed) > 0) { |
1277 |
GHashTableIter iter; |
1278 |
gpointer key; |
1279 |
g_hash_table_iter_init(&iter, textroom->allowed); |
1280 |
while(g_hash_table_iter_next(&iter, &key, NULL)) { |
1281 |
char *token = key;
|
1282 |
json_array_append_new(list, json_string(token)); |
1283 |
} |
1284 |
} |
1285 |
json_object_set_new(reply, "allowed", list);
|
1286 |
} |
1287 |
janus_mutex_unlock(&textroom->mutex); |
1288 |
janus_mutex_unlock(&rooms_mutex); |
1289 |
JANUS_LOG(LOG_VERB, "TextRoom room allowed list updated\n");
|
1290 |
} |
1291 |
} else if(!strcasecmp(request_text, "kick")) { |
1292 |
JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing textroom room\n");
|
1293 |
JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters, |
1294 |
error_code, error_cause, TRUE, |
1295 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1296 |
if(error_code != 0) |
1297 |
goto msg_response;
|
1298 |
json_t *room = json_object_get(root, "room");
|
1299 |
json_t *username = json_object_get(root, "username");
|
1300 |
guint64 room_id = json_integer_value(room); |
1301 |
janus_mutex_lock(&rooms_mutex); |
1302 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
1303 |
if(textroom == NULL) { |
1304 |
janus_mutex_unlock(&rooms_mutex); |
1305 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
1306 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
1307 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
1308 |
goto msg_response;
|
1309 |
} |
1310 |
janus_mutex_lock(&textroom->mutex); |
1311 |
/* A secret may be required for this action */
|
1312 |
JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
|
1313 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED); |
1314 |
if(error_code != 0) { |
1315 |
janus_mutex_unlock(&textroom->mutex); |
1316 |
janus_mutex_unlock(&rooms_mutex); |
1317 |
goto msg_response;
|
1318 |
} |
1319 |
const char *user_id = json_string_value(username); |
1320 |
janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, user_id); |
1321 |
if(participant == NULL) { |
1322 |
janus_mutex_unlock(&textroom->mutex); |
1323 |
janus_mutex_unlock(&rooms_mutex); |
1324 |
JANUS_LOG(LOG_ERR, "No such participant %s in room %"SCNu64"\n", user_id, room_id); |
1325 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_USER; |
1326 |
g_snprintf(error_cause, 512, "No such user %s in room %"SCNu64, user_id, room_id); |
1327 |
goto msg_response;
|
1328 |
} |
1329 |
/* Notify all participants */
|
1330 |
JANUS_LOG(LOG_VERB, "Notifying all participants about the new kick\n");
|
1331 |
if(textroom->participants) {
|
1332 |
/* Prepare event */
|
1333 |
json_t *event = json_object(); |
1334 |
json_object_set_new(event, "textroom", json_string("kicked")); |
1335 |
json_object_set_new(event, "room", json_integer(textroom->room_id));
|
1336 |
json_object_set_new(event, "username", json_string(participant->username));
|
1337 |
char *event_text = json_dumps(event, json_format);
|
1338 |
json_decref(event); |
1339 |
/* Broadcast */
|
1340 |
GHashTableIter iter; |
1341 |
gpointer value; |
1342 |
g_hash_table_iter_init(&iter, textroom->participants); |
1343 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1344 |
janus_textroom_participant *top = value; |
1345 |
JANUS_LOG(LOG_VERB, " >> To %s in %"SCNu64"\n", top->username, room_id); |
1346 |
gateway->relay_data(top->session->handle, event_text, strlen(event_text)); |
1347 |
} |
1348 |
free(event_text); |
1349 |
} |
1350 |
/* Also notify event handlers */
|
1351 |
if(notify_events && gateway->events_is_enabled()) {
|
1352 |
json_t *info = json_object(); |
1353 |
json_object_set_new(info, "textroom", json_string("kicked")); |
1354 |
json_object_set_new(info, "room", json_integer(textroom->room_id));
|
1355 |
json_object_set_new(info, "username", json_string(participant->username));
|
1356 |
gateway->notify_event(&janus_textroom_plugin, session->handle, info); |
1357 |
} |
1358 |
/* Remove user from list */
|
1359 |
g_hash_table_remove(participant->session->rooms, &room_id); |
1360 |
g_hash_table_remove(textroom->participants, participant->username); |
1361 |
participant->session = NULL;
|
1362 |
participant->room = NULL;
|
1363 |
g_free(participant->username); |
1364 |
g_free(participant->display); |
1365 |
g_free(participant); |
1366 |
/* Done */
|
1367 |
janus_mutex_unlock(&textroom->mutex); |
1368 |
janus_mutex_unlock(&rooms_mutex); |
1369 |
if(!internal) {
|
1370 |
/* Send response back */
|
1371 |
reply = json_object(); |
1372 |
json_object_set_new(reply, "textbridge", json_string("success")); |
1373 |
} |
1374 |
} else if(!strcasecmp(request_text, "create")) { |
1375 |
JANUS_VALIDATE_JSON_OBJECT(root, create_parameters, |
1376 |
error_code, error_cause, TRUE, |
1377 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1378 |
if(error_code != 0) |
1379 |
goto msg_response;
|
1380 |
if(admin_key != NULL) { |
1381 |
/* An admin key was specified: make sure it was provided, and that it's valid */
|
1382 |
JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters, |
1383 |
error_code, error_cause, TRUE, |
1384 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1385 |
if(error_code != 0) |
1386 |
goto msg_response;
|
1387 |
JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
|
1388 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED); |
1389 |
if(error_code != 0) |
1390 |
goto msg_response;
|
1391 |
} |
1392 |
json_t *room = json_object_get(root, "room");
|
1393 |
json_t *desc = json_object_get(root, "description");
|
1394 |
json_t *is_private = json_object_get(root, "is_private");
|
1395 |
json_t *allowed = json_object_get(root, "allowed");
|
1396 |
json_t *secret = json_object_get(root, "secret");
|
1397 |
json_t *pin = json_object_get(root, "pin");
|
1398 |
json_t *post = json_object_get(root, "post");
|
1399 |
json_t *permanent = json_object_get(root, "permanent");
|
1400 |
if(allowed) {
|
1401 |
/* Make sure the "allowed" array only contains strings */
|
1402 |
gboolean ok = TRUE; |
1403 |
if(json_array_size(allowed) > 0) { |
1404 |
size_t i = 0;
|
1405 |
for(i=0; i<json_array_size(allowed); i++) { |
1406 |
json_t *a = json_array_get(allowed, i); |
1407 |
if(!a || !json_is_string(a)) {
|
1408 |
ok = FALSE; |
1409 |
break;
|
1410 |
} |
1411 |
} |
1412 |
} |
1413 |
if(!ok) {
|
1414 |
JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
|
1415 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT; |
1416 |
g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)"); |
1417 |
goto msg_response;
|
1418 |
} |
1419 |
} |
1420 |
gboolean save = permanent ? json_is_true(permanent) : FALSE; |
1421 |
if(save && config == NULL) { |
1422 |
JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
|
1423 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
1424 |
g_snprintf(error_cause, 512, "No configuration file, can't create permanent room"); |
1425 |
goto msg_response;
|
1426 |
} |
1427 |
guint64 room_id = 0;
|
1428 |
room_id = json_integer_value(room); |
1429 |
if(room_id == 0) { |
1430 |
JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
|
1431 |
} |
1432 |
janus_mutex_lock(&rooms_mutex); |
1433 |
if(room_id > 0) { |
1434 |
/* Let's make sure the room doesn't exist already */
|
1435 |
if(g_hash_table_lookup(rooms, &room_id) != NULL) { |
1436 |
/* It does... */
|
1437 |
janus_mutex_unlock(&rooms_mutex); |
1438 |
JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id); |
1439 |
error_code = JANUS_TEXTROOM_ERROR_ROOM_EXISTS; |
1440 |
g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id); |
1441 |
goto msg_response;
|
1442 |
} |
1443 |
} |
1444 |
/* Create the text room */
|
1445 |
janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
|
1446 |
/* Generate a random ID */
|
1447 |
if(room_id == 0) { |
1448 |
while(room_id == 0) { |
1449 |
room_id = janus_random_uint64(); |
1450 |
if(g_hash_table_lookup(rooms, &room_id) != NULL) { |
1451 |
/* Room ID already taken, try another one */
|
1452 |
room_id = 0;
|
1453 |
} |
1454 |
} |
1455 |
} |
1456 |
textroom->room_id = room_id; |
1457 |
char *description = NULL; |
1458 |
if(desc != NULL && strlen(json_string_value(desc)) > 0) { |
1459 |
description = g_strdup(json_string_value(desc)); |
1460 |
} else {
|
1461 |
char roomname[255]; |
1462 |
g_snprintf(roomname, 255, "Room %"SCNu64"", textroom->room_id); |
1463 |
description = g_strdup(roomname); |
1464 |
} |
1465 |
textroom->room_name = description; |
1466 |
textroom->is_private = is_private ? json_is_true(is_private) : FALSE; |
1467 |
if(secret)
|
1468 |
textroom->room_secret = g_strdup(json_string_value(secret)); |
1469 |
if(pin)
|
1470 |
textroom->room_pin = g_strdup(json_string_value(pin)); |
1471 |
if(post) {
|
1472 |
#ifdef HAVE_LIBCURL
|
1473 |
/* FIXME Should we check if this is a valid HTTP address? */
|
1474 |
textroom->http_backend = g_strdup(json_string_value(post)); |
1475 |
#else
|
1476 |
JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
|
1477 |
#endif
|
1478 |
} |
1479 |
textroom->participants = g_hash_table_new(g_str_hash, g_str_equal); |
1480 |
textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
|
1481 |
if(allowed != NULL) { |
1482 |
/* Populate the "allowed" list as an ACL for people trying to join */
|
1483 |
if(json_array_size(allowed) > 0) { |
1484 |
size_t i = 0;
|
1485 |
for(i=0; i<json_array_size(allowed); i++) { |
1486 |
const char *token = json_string_value(json_array_get(allowed, i)); |
1487 |
if(!g_hash_table_lookup(textroom->allowed, token))
|
1488 |
g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE)); |
1489 |
} |
1490 |
} |
1491 |
textroom->check_tokens = TRUE; |
1492 |
} |
1493 |
textroom->destroyed = 0;
|
1494 |
janus_mutex_init(&textroom->mutex); |
1495 |
g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom); |
1496 |
JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n", |
1497 |
textroom->room_id, textroom->room_name, |
1498 |
textroom->is_private ? "private" : "public", |
1499 |
textroom->room_secret ? textroom->room_secret : "no secret",
|
1500 |
textroom->room_pin ? textroom->room_pin : "no pin");
|
1501 |
if(save) {
|
1502 |
/* This room is permanent: save to the configuration file too
|
1503 |
* FIXME: We should check if anything fails... */
|
1504 |
JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", textroom->room_id); |
1505 |
janus_mutex_lock(&config_mutex); |
1506 |
char cat[BUFSIZ];
|
1507 |
/* The room ID is the category */
|
1508 |
g_snprintf(cat, BUFSIZ, "%"SCNu64, textroom->room_id);
|
1509 |
janus_config_add_category(config, cat); |
1510 |
/* Now for the values */
|
1511 |
janus_config_add_item(config, cat, "description", textroom->room_name);
|
1512 |
if(textroom->is_private)
|
1513 |
janus_config_add_item(config, cat, "is_private", "yes"); |
1514 |
if(textroom->room_secret)
|
1515 |
janus_config_add_item(config, cat, "secret", textroom->room_secret);
|
1516 |
if(textroom->room_pin)
|
1517 |
janus_config_add_item(config, cat, "pin", textroom->room_pin);
|
1518 |
if(textroom->http_backend)
|
1519 |
janus_config_add_item(config, cat, "post", textroom->http_backend);
|
1520 |
/* Save modified configuration */
|
1521 |
if(janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE) < 0) |
1522 |
save = FALSE; /* This will notify the user the room is not permanent */
|
1523 |
janus_mutex_unlock(&config_mutex); |
1524 |
} |
1525 |
/* Show updated rooms list */
|
1526 |
GHashTableIter iter; |
1527 |
gpointer value; |
1528 |
g_hash_table_iter_init(&iter, rooms); |
1529 |
while (g_hash_table_iter_next(&iter, NULL, &value)) { |
1530 |
janus_textroom_room *tr = value; |
1531 |
JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name); |
1532 |
} |
1533 |
janus_mutex_unlock(&rooms_mutex); |
1534 |
if(!internal) {
|
1535 |
/* Send response back */
|
1536 |
reply = json_object(); |
1537 |
/* Notice that we reply differently if the request came via Janus API */
|
1538 |
json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "created")); |
1539 |
json_object_set_new(reply, "room", json_integer(textroom->room_id));
|
1540 |
json_object_set_new(reply, "permanent", save ? json_true() : json_false());
|
1541 |
} |
1542 |
/* Also notify event handlers */
|
1543 |
if(notify_events && gateway->events_is_enabled()) {
|
1544 |
json_t *info = json_object(); |
1545 |
json_object_set_new(info, "event", json_string("created")); |
1546 |
json_object_set_new(info, "room", json_integer(room_id));
|
1547 |
gateway->notify_event(&janus_textroom_plugin, session->handle, info); |
1548 |
} |
1549 |
} else if(!strcasecmp(request_text, "exists")) { |
1550 |
JANUS_VALIDATE_JSON_OBJECT(root, room_parameters, |
1551 |
error_code, error_cause, TRUE, |
1552 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1553 |
if(error_code != 0) |
1554 |
goto msg_response;
|
1555 |
json_t *room = json_object_get(root, "room");
|
1556 |
guint64 room_id = json_integer_value(room); |
1557 |
janus_mutex_lock(&rooms_mutex); |
1558 |
gboolean room_exists = g_hash_table_contains(rooms, &room_id); |
1559 |
janus_mutex_unlock(&rooms_mutex); |
1560 |
if(!internal) {
|
1561 |
/* Send response back */
|
1562 |
reply = json_object(); |
1563 |
json_object_set_new(reply, "textroom", json_string("success")); |
1564 |
json_object_set_new(reply, "room", json_integer(room_id));
|
1565 |
json_object_set_new(reply, "exists", room_exists ? json_true() : json_false());
|
1566 |
} |
1567 |
} else if(!strcasecmp(request_text, "destroy")) { |
1568 |
JANUS_VALIDATE_JSON_OBJECT(root, room_parameters, |
1569 |
error_code, error_cause, TRUE, |
1570 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1571 |
if(error_code != 0) |
1572 |
goto msg_response;
|
1573 |
json_t *room = json_object_get(root, "room");
|
1574 |
json_t *permanent = json_object_get(root, "permanent");
|
1575 |
gboolean save = permanent ? json_is_true(permanent) : FALSE; |
1576 |
if(save && config == NULL) { |
1577 |
JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
|
1578 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
1579 |
g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently"); |
1580 |
goto msg_response;
|
1581 |
} |
1582 |
guint64 room_id = json_integer_value(room); |
1583 |
janus_mutex_lock(&rooms_mutex); |
1584 |
janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id); |
1585 |
if(textroom == NULL) { |
1586 |
janus_mutex_unlock(&rooms_mutex); |
1587 |
JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id); |
1588 |
error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM; |
1589 |
g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id); |
1590 |
goto msg_response;
|
1591 |
} |
1592 |
janus_mutex_lock(&textroom->mutex); |
1593 |
/* A secret may be required for this action */
|
1594 |
JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
|
1595 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED); |
1596 |
if(error_code != 0) { |
1597 |
janus_mutex_unlock(&textroom->mutex); |
1598 |
janus_mutex_unlock(&rooms_mutex); |
1599 |
goto msg_response;
|
1600 |
} |
1601 |
/* Remove room */
|
1602 |
g_hash_table_remove(rooms, &room_id); |
1603 |
if(save) {
|
1604 |
/* This change is permanent: save to the configuration file too
|
1605 |
* FIXME: We should check if anything fails... */
|
1606 |
JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id); |
1607 |
janus_mutex_lock(&config_mutex); |
1608 |
char cat[BUFSIZ];
|
1609 |
/* The room ID is the category */
|
1610 |
g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
|
1611 |
janus_config_remove_category(config, cat); |
1612 |
/* Save modified configuration */
|
1613 |
janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE); |
1614 |
janus_mutex_unlock(&config_mutex); |
1615 |
} |
1616 |
/* Notify all participants */
|
1617 |
JANUS_LOG(LOG_VERB, "Notifying all participants about the destroy\n");
|
1618 |
if(textroom->participants) {
|
1619 |
/* Prepare event */
|
1620 |
json_t *event = json_object(); |
1621 |
json_object_set_new(event, "textroom", json_string("destroyed")); |
1622 |
json_object_set_new(event, "room", json_integer(textroom->room_id));
|
1623 |
char *event_text = json_dumps(event, json_format);
|
1624 |
json_decref(event); |
1625 |
gateway->relay_data(handle, event_text, strlen(event_text)); |
1626 |
/* Broadcast */
|
1627 |
GHashTableIter iter; |
1628 |
gpointer value; |
1629 |
g_hash_table_iter_init(&iter, textroom->participants); |
1630 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1631 |
janus_textroom_participant *top = value; |
1632 |
JANUS_LOG(LOG_VERB, " >> To %s in %"SCNu64"\n", top->username, room_id); |
1633 |
gateway->relay_data(top->session->handle, event_text, strlen(event_text)); |
1634 |
janus_mutex_unlock(&top->session->mutex); |
1635 |
g_hash_table_remove(top->session->rooms, &room_id); |
1636 |
janus_mutex_unlock(&top->session->mutex); |
1637 |
g_free(top->username); |
1638 |
g_free(top->display); |
1639 |
g_free(top); |
1640 |
} |
1641 |
free(event_text); |
1642 |
} |
1643 |
janus_mutex_unlock(&textroom->mutex); |
1644 |
janus_mutex_unlock(&rooms_mutex); |
1645 |
if(!internal) {
|
1646 |
/* Send response back */
|
1647 |
reply = json_object(); |
1648 |
/* Notice that we reply differently if the request came via Janus API */
|
1649 |
json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "destroyed")); |
1650 |
} |
1651 |
/* We'll let the watchdog worry about freeing resources */
|
1652 |
old_rooms = g_list_append(old_rooms, textroom); |
1653 |
/* Also notify event handlers */
|
1654 |
if(notify_events && gateway->events_is_enabled()) {
|
1655 |
json_t *info = json_object(); |
1656 |
json_object_set_new(info, "event", json_string("destroyed")); |
1657 |
json_object_set_new(info, "room", json_integer(room_id));
|
1658 |
gateway->notify_event(&janus_textroom_plugin, session->handle, info); |
1659 |
} |
1660 |
} else {
|
1661 |
JANUS_LOG(LOG_ERR, "Unsupported request %s\n", request_text);
|
1662 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST; |
1663 |
g_snprintf(error_cause, 512, "Unsupported request %s", request_text); |
1664 |
goto msg_response;
|
1665 |
} |
1666 |
|
1667 |
msg_response:
|
1668 |
{ |
1669 |
if(!internal) {
|
1670 |
if(error_code == 0 && !reply) { |
1671 |
error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR; |
1672 |
g_snprintf(error_cause, 512, "Invalid response"); |
1673 |
} |
1674 |
if(error_code != 0) { |
1675 |
/* Prepare JSON error event */
|
1676 |
json_t *event = json_object(); |
1677 |
json_object_set_new(event, "textroom", json_string("error")); |
1678 |
json_object_set_new(event, "error_code", json_integer(error_code));
|
1679 |
json_object_set_new(event, "error", json_string(error_cause));
|
1680 |
reply = event; |
1681 |
} |
1682 |
if(transaction_text && json == NULL) |
1683 |
json_object_set_new(reply, "transaction", json_string(transaction_text));
|
1684 |
if(json == NULL) { |
1685 |
/* Reply via data channels */
|
1686 |
char *reply_text = json_dumps(reply, json_format);
|
1687 |
json_decref(reply); |
1688 |
gateway->relay_data(handle, reply_text, strlen(reply_text)); |
1689 |
free(reply_text); |
1690 |
} else {
|
1691 |
/* Reply via Janus API */
|
1692 |
return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, reply); |
1693 |
} |
1694 |
} |
1695 |
if(root != NULL) |
1696 |
json_decref(root); |
1697 |
} |
1698 |
return NULL; |
1699 |
} |
1700 |
|
1701 |
void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video) { |
1702 |
/* We don't do audio/video */
|
1703 |
} |
1704 |
|
1705 |
void janus_textroom_hangup_media(janus_plugin_session *handle) {
|
1706 |
JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
|
1707 |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
|
1708 |
return;
|
1709 |
janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle; |
1710 |
if(!session) {
|
1711 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
1712 |
return;
|
1713 |
} |
1714 |
if(session->destroyed)
|
1715 |
return;
|
1716 |
if(g_atomic_int_add(&session->hangingup, 1)) |
1717 |
return;
|
1718 |
/* Get rid of all participants */
|
1719 |
janus_mutex_lock(&session->mutex); |
1720 |
GList *list = NULL;
|
1721 |
if(session->rooms) {
|
1722 |
GHashTableIter iter; |
1723 |
gpointer value; |
1724 |
g_hash_table_iter_init(&iter, session->rooms); |
1725 |
while(g_hash_table_iter_next(&iter, NULL, &value)) { |
1726 |
janus_textroom_participant *p = value; |
1727 |
janus_mutex_lock(&p->mutex); |
1728 |
if(p->room)
|
1729 |
list = g_list_append(list, janus_uint64_dup(p->room->room_id)); |
1730 |
janus_mutex_unlock(&p->mutex); |
1731 |
} |
1732 |
janus_mutex_unlock(&rooms_mutex); |
1733 |
} |
1734 |
janus_mutex_unlock(&session->mutex); |
1735 |
JANUS_LOG(LOG_VERB, "Leaving %d rooms\n", g_list_length(list));
|
1736 |
char request[100]; |
1737 |
GList *first = list; |
1738 |
while(list) {
|
1739 |
guint64 room_id = *((guint64 *)list->data); |
1740 |
g_snprintf(request, sizeof(request), "{\"textroom\":\"leave\",\"transaction\":\"internal\",\"room\":%"SCNu64"}", room_id); |
1741 |
janus_textroom_handle_incoming_request(handle, g_strdup(request), NULL, TRUE);
|
1742 |
list = list->next; |
1743 |
} |
1744 |
g_list_free_full(first, (GDestroyNotify)g_free); |
1745 |
} |
1746 |
|
1747 |
/* Thread to handle incoming messages */
|
1748 |
static void *janus_textroom_handler(void *data) { |
1749 |
JANUS_LOG(LOG_VERB, "Joining TextRoom handler thread\n");
|
1750 |
janus_textroom_message *msg = NULL;
|
1751 |
int error_code = 0; |
1752 |
char error_cause[512]; |
1753 |
json_t *root = NULL;
|
1754 |
gboolean do_offer = FALSE; |
1755 |
while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
|
1756 |
msg = g_async_queue_pop(messages); |
1757 |
if(msg == NULL) |
1758 |
continue;
|
1759 |
if(msg == &exit_message)
|
1760 |
break;
|
1761 |
if(msg->handle == NULL) { |
1762 |
janus_textroom_message_free(msg); |
1763 |
continue;
|
1764 |
} |
1765 |
janus_textroom_session *session = NULL;
|
1766 |
janus_mutex_lock(&sessions_mutex); |
1767 |
if(g_hash_table_lookup(sessions, msg->handle) != NULL ) { |
1768 |
session = (janus_textroom_session *)msg->handle->plugin_handle; |
1769 |
} |
1770 |
janus_mutex_unlock(&sessions_mutex); |
1771 |
if(!session) {
|
1772 |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
|
1773 |
janus_textroom_message_free(msg); |
1774 |
continue;
|
1775 |
} |
1776 |
if(session->destroyed) {
|
1777 |
janus_textroom_message_free(msg); |
1778 |
continue;
|
1779 |
} |
1780 |
/* Handle request */
|
1781 |
error_code = 0;
|
1782 |
root = msg->message; |
1783 |
if(msg->message == NULL) { |
1784 |
JANUS_LOG(LOG_ERR, "No message??\n");
|
1785 |
error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE; |
1786 |
g_snprintf(error_cause, 512, "%s", "No message??"); |
1787 |
goto error;
|
1788 |
} |
1789 |
if(!json_is_object(root)) {
|
1790 |
JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
|
1791 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON; |
1792 |
g_snprintf(error_cause, 512, "JSON error: not an object"); |
1793 |
goto error;
|
1794 |
} |
1795 |
/* Parse request */
|
1796 |
JANUS_VALIDATE_JSON_OBJECT(root, request_parameters, |
1797 |
error_code, error_cause, TRUE, |
1798 |
JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT); |
1799 |
if(error_code != 0) |
1800 |
goto error;
|
1801 |
json_t *request = json_object_get(root, "request");
|
1802 |
const char *request_text = json_string_value(request); |
1803 |
do_offer = FALSE; |
1804 |
if(!strcasecmp(request_text, "setup")) { |
1805 |
if(!g_atomic_int_compare_and_exchange(&session->setup, 0, 1)) { |
1806 |
JANUS_LOG(LOG_ERR, "PeerConnection already setup\n");
|
1807 |
error_code = JANUS_TEXTROOM_ERROR_ALREADY_SETUP; |
1808 |
g_snprintf(error_cause, 512, "PeerConnection already setup"); |
1809 |
goto error;
|
1810 |
} |
1811 |
do_offer = TRUE; |
1812 |
} else if(!strcasecmp(request_text, "ack")) { |
1813 |
/* The peer sent their answer back: do nothing */
|
1814 |
} else {
|
1815 |
JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
|
1816 |
error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST; |
1817 |
g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); |
1818 |
goto error;
|
1819 |
} |
1820 |
|
1821 |
/* Prepare JSON event */
|
1822 |
json_t *event = json_object(); |
1823 |
json_object_set_new(event, "textroom", json_string("event")); |
1824 |
json_object_set_new(event, "result", json_string("ok")); |
1825 |
if(!do_offer) {
|
1826 |
int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL); |
1827 |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
|
1828 |
} else {
|
1829 |
/* Send an offer */
|
1830 |
char sdp[500]; |
1831 |
g_snprintf(sdp, sizeof(sdp), sdp_template,
|
1832 |
janus_get_real_time(), /* We need current time here */
|
1833 |
janus_get_real_time()); /* We need current time here */
|
1834 |
json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp); |
1835 |
/* How long will the gateway take to push the event? */
|
1836 |
g_atomic_int_set(&session->hangingup, 0);
|
1837 |
gint64 start = janus_get_monotonic_time(); |
1838 |
int res = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, jsep);
|
1839 |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", |
1840 |
res, janus_get_monotonic_time()-start); |
1841 |
json_decref(jsep); |
1842 |
} |
1843 |
json_decref(event); |
1844 |
janus_textroom_message_free(msg); |
1845 |
continue;
|
1846 |
|
1847 |
error:
|
1848 |
{ |
1849 |
/* Prepare JSON error event */
|
1850 |
json_t *event = json_object(); |
1851 |
json_object_set_new(event, "textroom", json_string("error")); |
1852 |
json_object_set_new(event, "error_code", json_integer(error_code));
|
1853 |
json_object_set_new(event, "error", json_string(error_cause));
|
1854 |
int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL); |
1855 |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
|
1856 |
json_decref(event); |
1857 |
janus_textroom_message_free(msg); |
1858 |
} |
1859 |
} |
1860 |
JANUS_LOG(LOG_VERB, "Leaving TextRoom handler thread\n");
|
1861 |
return NULL; |
1862 |
} |