rtsp-server: port to new thread API
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-session-pool.c
1 /* GStreamer
2  * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "rtsp-session-pool.h"
21
22 #undef DEBUG
23
24 #define DEFAULT_MAX_SESSIONS 0
25
26 enum
27 {
28   PROP_0,
29   PROP_MAX_SESSIONS,
30   PROP_LAST
31 };
32
33 static const gchar session_id_charset[] =
34     { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
35   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
36   'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
37   'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7',
38   '8', '9', '$', '-', '_', '.', '+'
39 };
40
41 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
42 #define GST_CAT_DEFAULT rtsp_session_debug
43
44 static void gst_rtsp_session_pool_get_property (GObject * object, guint propid,
45     GValue * value, GParamSpec * pspec);
46 static void gst_rtsp_session_pool_set_property (GObject * object, guint propid,
47     const GValue * value, GParamSpec * pspec);
48 static void gst_rtsp_session_pool_finalize (GObject * object);
49
50 static gchar *create_session_id (GstRTSPSessionPool * pool);
51
52 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
53
54 static void
55 gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
56 {
57   GObjectClass *gobject_class;
58
59   gobject_class = G_OBJECT_CLASS (klass);
60
61   gobject_class->get_property = gst_rtsp_session_pool_get_property;
62   gobject_class->set_property = gst_rtsp_session_pool_set_property;
63   gobject_class->finalize = gst_rtsp_session_pool_finalize;
64
65   g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
66       g_param_spec_uint ("max-sessions", "Max Sessions",
67           "the maximum amount of sessions (0 = unlimited)",
68           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
69           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
70
71   klass->create_session_id = create_session_id;
72
73   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
74       "GstRTSPSessionPool");
75 }
76
77 static void
78 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
79 {
80   g_mutex_init (&pool->lock);
81   pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
82       NULL, g_object_unref);
83   pool->max_sessions = DEFAULT_MAX_SESSIONS;
84 }
85
86 static void
87 gst_rtsp_session_pool_finalize (GObject * object)
88 {
89   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
90
91   g_mutex_clear (&pool->lock);
92   g_hash_table_unref (pool->sessions);
93
94   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
95 }
96
97 static void
98 gst_rtsp_session_pool_get_property (GObject * object, guint propid,
99     GValue * value, GParamSpec * pspec)
100 {
101   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
102
103   switch (propid) {
104     case PROP_MAX_SESSIONS:
105       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
106       break;
107     default:
108       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
109       break;
110   }
111 }
112
113 static void
114 gst_rtsp_session_pool_set_property (GObject * object, guint propid,
115     const GValue * value, GParamSpec * pspec)
116 {
117   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
118
119   switch (propid) {
120     case PROP_MAX_SESSIONS:
121       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
122       break;
123     default:
124       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
125       break;
126   }
127 }
128
129 /**
130  * gst_rtsp_session_pool_new:
131  *
132  * Create a new #GstRTSPSessionPool instance.
133  *
134  * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
135  */
136 GstRTSPSessionPool *
137 gst_rtsp_session_pool_new (void)
138 {
139   GstRTSPSessionPool *result;
140
141   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
142
143   return result;
144 }
145
146 /**
147  * gst_rtsp_session_pool_set_max_sessions:
148  * @pool: a #GstRTSPSessionPool
149  * @max: the maximum number of sessions
150  *
151  * Configure the maximum allowed number of sessions in @pool to @max.
152  * A value of 0 means an unlimited amount of sessions.
153  */
154 void
155 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
156 {
157   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
158
159   g_mutex_lock (&pool->lock);
160   pool->max_sessions = max;
161   g_mutex_unlock (&pool->lock);
162 }
163
164 /**
165  * gst_rtsp_session_pool_get_max_sessions:
166  * @pool: a #GstRTSPSessionPool
167  *
168  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
169  * amount of sessions.
170  *
171  * Returns: the maximum allowed number of sessions.
172  */
173 guint
174 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
175 {
176   guint result;
177
178   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
179
180   g_mutex_lock (&pool->lock);
181   result = pool->max_sessions;
182   g_mutex_unlock (&pool->lock);
183
184   return result;
185 }
186
187 /**
188  * gst_rtsp_session_pool_get_n_sessions:
189  * @pool: a #GstRTSPSessionPool
190  *
191  * Get the amount of active sessions in @pool.
192  *
193  * Returns: the amount of active sessions in @pool.
194  */
195 guint
196 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
197 {
198   guint result;
199
200   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
201
202   g_mutex_lock (&pool->lock);
203   result = g_hash_table_size (pool->sessions);
204   g_mutex_unlock (&pool->lock);
205
206   return result;
207 }
208
209 /**
210  * gst_rtsp_session_pool_find:
211  * @pool: the pool to search
212  * @sessionid: the session id
213  *
214  * Find the session with @sessionid in @pool. The access time of the session
215  * will be updated with gst_rtsp_session_touch().
216  *
217  * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
218  * not exist. g_object_unref() after usage.
219  */
220 GstRTSPSession *
221 gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
222 {
223   GstRTSPSession *result;
224
225   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
226   g_return_val_if_fail (sessionid != NULL, NULL);
227
228   g_mutex_lock (&pool->lock);
229   result = g_hash_table_lookup (pool->sessions, sessionid);
230   if (result) {
231     g_object_ref (result);
232     gst_rtsp_session_touch (result);
233   }
234   g_mutex_unlock (&pool->lock);
235
236   return result;
237 }
238
239 static gchar *
240 create_session_id (GstRTSPSessionPool * pool)
241 {
242   gchar id[16];
243   gint i;
244
245   for (i = 0; i < 16; i++) {
246     id[i] =
247         session_id_charset[g_random_int_range (0,
248             G_N_ELEMENTS (session_id_charset))];
249   }
250
251   return g_strndup (id, 16);
252 }
253
254 /**
255  * gst_rtsp_session_pool_create:
256  * @pool: a #GstRTSPSessionPool
257  *
258  * Create a new #GstRTSPSession object in @pool.
259  *
260  * Returns: a new #GstRTSPSession.
261  */
262 GstRTSPSession *
263 gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
264 {
265   GstRTSPSession *result = NULL;
266   GstRTSPSessionPoolClass *klass;
267   gchar *id = NULL;
268   guint retry;
269
270   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
271
272   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
273
274   retry = 0;
275   do {
276     /* start by creating a new random session id, we assume that this is random
277      * enough to not cause a collision, which we will check later  */
278     if (klass->create_session_id)
279       id = klass->create_session_id (pool);
280     else
281       goto no_function;
282
283     if (id == NULL)
284       goto no_session;
285
286     g_mutex_lock (&pool->lock);
287     /* check session limit */
288     if (pool->max_sessions > 0) {
289       if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
290         goto too_many_sessions;
291     }
292     /* check if the sessionid existed */
293     result = g_hash_table_lookup (pool->sessions, id);
294     if (result) {
295       /* found, retry with a different session id */
296       result = NULL;
297       retry++;
298       if (retry > 100)
299         goto collision;
300     } else {
301       /* not found, create session and insert it in the pool */
302       result = gst_rtsp_session_new (id);
303       /* take additional ref for the pool */
304       g_object_ref (result);
305       g_hash_table_insert (pool->sessions, result->sessionid, result);
306     }
307     g_mutex_unlock (&pool->lock);
308
309     g_free (id);
310   } while (result == NULL);
311
312   return result;
313
314   /* ERRORS */
315 no_function:
316   {
317     GST_WARNING ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
318     return NULL;
319   }
320 no_session:
321   {
322     GST_WARNING ("can't create session id with GstRTSPSessionPool %p", pool);
323     return NULL;
324   }
325 collision:
326   {
327     GST_WARNING ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
328     g_mutex_unlock (&pool->lock);
329     g_free (id);
330     return NULL;
331   }
332 too_many_sessions:
333   {
334     GST_WARNING ("session pool reached max sessions of %d", pool->max_sessions);
335     g_mutex_unlock (&pool->lock);
336     g_free (id);
337     return NULL;
338   }
339 }
340
341 /**
342  * gst_rtsp_session_pool_remove:
343  * @pool: a #GstRTSPSessionPool
344  * @sess: a #GstRTSPSession
345  *
346  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
347  *
348  * Returns: %TRUE if the session was found and removed.
349  */
350 gboolean
351 gst_rtsp_session_pool_remove (GstRTSPSessionPool * pool, GstRTSPSession * sess)
352 {
353   gboolean found;
354
355   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
356   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
357
358   g_mutex_lock (&pool->lock);
359   found = g_hash_table_remove (pool->sessions, sess->sessionid);
360   g_mutex_unlock (&pool->lock);
361
362   return found;
363 }
364
365 static gboolean
366 cleanup_func (gchar * sessionid, GstRTSPSession * sess, GTimeVal * now)
367 {
368   return gst_rtsp_session_is_expired (sess, now);
369 }
370
371 /**
372  * gst_rtsp_session_pool_cleanup:
373  * @pool: a #GstRTSPSessionPool
374  *
375  * Inspect all the sessions in @pool and remove the sessions that are inactive
376  * for more than their timeout.
377  *
378  * Returns: the amount of sessions that got removed.
379  */
380 guint
381 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool * pool)
382 {
383   guint result;
384   GTimeVal now;
385
386   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
387
388   g_get_current_time (&now);
389
390   g_mutex_lock (&pool->lock);
391   result =
392       g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func,
393       &now);
394   g_mutex_unlock (&pool->lock);
395
396   return result;
397 }
398
399 typedef struct
400 {
401   GstRTSPSessionPool *pool;
402   GstRTSPSessionFilterFunc func;
403   gpointer user_data;
404   GList *list;
405 } FilterData;
406
407 static gboolean
408 filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
409 {
410   switch (data->func (data->pool, sess, data->user_data)) {
411     case GST_RTSP_FILTER_REMOVE:
412       return TRUE;
413     case GST_RTSP_FILTER_REF:
414       /* keep ref */
415       data->list = g_list_prepend (data->list, g_object_ref (sess));
416       /* fallthrough */
417     default:
418     case GST_RTSP_FILTER_KEEP:
419       return FALSE;
420   }
421 }
422
423 /**
424  * gst_rtsp_session_pool_filter:
425  * @pool: a #GstRTSPSessionPool
426  * @func: a callback
427  * @user_data: user data passed to @func
428  *
429  * Call @func for each session in @pool. The result value of @func determines
430  * what happens to the session. @func will be called with the session pool
431  * locked so no further actions on @pool can be performed from @func.
432  *
433  * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
434  * @pool.
435  *
436  * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
437  *
438  * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @pool but
439  * will also be added with an additional ref to the result GList of this
440  * function..
441  *
442  * Returns: a GList with all sessions for which @func returned
443  * #GST_RTSP_FILTER_REF. After usage, each element in the GList should be unreffed
444  * before the list is freed.
445  */
446 GList *
447 gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
448     GstRTSPSessionFilterFunc func, gpointer user_data)
449 {
450   FilterData data;
451
452   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
453   g_return_val_if_fail (func != NULL, NULL);
454
455   data.pool = pool;
456   data.func = func;
457   data.user_data = user_data;
458   data.list = NULL;
459
460   g_mutex_lock (&pool->lock);
461   g_hash_table_foreach_remove (pool->sessions, (GHRFunc) filter_func, &data);
462   g_mutex_unlock (&pool->lock);
463
464   return data.list;
465 }
466
467 typedef struct
468 {
469   GSource source;
470   GstRTSPSessionPool *pool;
471   gint timeout;
472 } GstPoolSource;
473
474 static void
475 collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
476 {
477   gint timeout;
478   GTimeVal now;
479   gint64 tmp;
480
481   tmp = g_source_get_time ((GSource *) psrc);
482   now.tv_sec = tmp / G_USEC_PER_SEC;
483   now.tv_usec = tmp % G_USEC_PER_SEC;
484
485   timeout = gst_rtsp_session_next_timeout (sess, &now);
486   GST_INFO ("%p: next timeout: %d", sess, timeout);
487   if (psrc->timeout == -1 || timeout < psrc->timeout)
488     psrc->timeout = timeout;
489 }
490
491 static gboolean
492 gst_pool_source_prepare (GSource * source, gint * timeout)
493 {
494   GstPoolSource *psrc;
495   gboolean result;
496
497   psrc = (GstPoolSource *) source;
498   psrc->timeout = -1;
499
500   g_mutex_lock (&psrc->pool->lock);
501   g_hash_table_foreach (psrc->pool->sessions, (GHFunc) collect_timeout, psrc);
502   g_mutex_unlock (&psrc->pool->lock);
503
504   if (timeout)
505     *timeout = psrc->timeout;
506
507   result = psrc->timeout == 0;
508
509   GST_INFO ("prepare %d, %d", psrc->timeout, result);
510
511   return result;
512 }
513
514 static gboolean
515 gst_pool_source_check (GSource * source)
516 {
517   GST_INFO ("check");
518
519   return gst_pool_source_prepare (source, NULL);
520 }
521
522 static gboolean
523 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
524     gpointer user_data)
525 {
526   gboolean res;
527   GstPoolSource *psrc = (GstPoolSource *) source;
528   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
529
530   GST_INFO ("dispatch");
531
532   if (func)
533     res = func (psrc->pool, user_data);
534   else
535     res = FALSE;
536
537   return res;
538 }
539
540 static void
541 gst_pool_source_finalize (GSource * source)
542 {
543   GstPoolSource *psrc = (GstPoolSource *) source;
544
545   GST_INFO ("finalize %p", psrc);
546
547   g_object_unref (psrc->pool);
548   psrc->pool = NULL;
549 }
550
551 static GSourceFuncs gst_pool_source_funcs = {
552   gst_pool_source_prepare,
553   gst_pool_source_check,
554   gst_pool_source_dispatch,
555   gst_pool_source_finalize
556 };
557
558 /**
559  * gst_rtsp_session_pool_create_watch:
560  * @pool: a #GstRTSPSessionPool
561  *
562  * A GSource that will be dispatched when the session should be cleaned up.
563  */
564 GSource *
565 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)
566 {
567   GstPoolSource *source;
568
569   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
570
571   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
572       sizeof (GstPoolSource));
573   source->pool = g_object_ref (pool);
574
575   return (GSource *) source;
576 }