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