rtsp-server: Run gst-indent
[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,
66       "GstRTSPSession");
67 }
68
69 static void
70 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
71 {
72   pool->lock = g_mutex_new ();
73   pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
74       NULL, g_object_unref);
75   pool->max_sessions = DEFAULT_MAX_SESSIONS;
76 }
77
78 static void
79 gst_rtsp_session_pool_finalize (GObject * object)
80 {
81   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
82
83   g_mutex_free (pool->lock);
84   g_hash_table_unref (pool->sessions);
85
86   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
87 }
88
89 static void
90 gst_rtsp_session_pool_get_property (GObject * object, guint propid,
91     GValue * value, GParamSpec * pspec)
92 {
93   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
94
95   switch (propid) {
96     case PROP_MAX_SESSIONS:
97       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
98       break;
99     default:
100       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
101       break;
102   }
103 }
104
105 static void
106 gst_rtsp_session_pool_set_property (GObject * object, guint propid,
107     const GValue * value, GParamSpec * pspec)
108 {
109   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
110
111   switch (propid) {
112     case PROP_MAX_SESSIONS:
113       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
114       break;
115     default:
116       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
117       break;
118   }
119 }
120
121 /**
122  * gst_rtsp_session_pool_new:
123  *
124  * Create a new #GstRTSPSessionPool instance.
125  *
126  * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
127  */
128 GstRTSPSessionPool *
129 gst_rtsp_session_pool_new (void)
130 {
131   GstRTSPSessionPool *result;
132
133   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
134
135   return result;
136 }
137
138 /**
139  * gst_rtsp_session_pool_set_max_sessions:
140  * @pool: a #GstRTSPSessionPool
141  * @max: the maximum number of sessions
142  *
143  * Configure the maximum allowed number of sessions in @pool to @max.
144  * A value of 0 means an unlimited amount of sessions.
145  */
146 void
147 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
148 {
149   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
150
151   g_mutex_lock (pool->lock);
152   pool->max_sessions = max;
153   g_mutex_unlock (pool->lock);
154 }
155
156 /**
157  * gst_rtsp_session_pool_get_max_sessions:
158  * @pool: a #GstRTSPSessionPool
159  *
160  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
161  * amount of sessions.
162  *
163  * Returns: the maximum allowed number of sessions.
164  */
165 guint
166 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
167 {
168   guint result;
169
170   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
171
172   g_mutex_lock (pool->lock);
173   result = pool->max_sessions;
174   g_mutex_unlock (pool->lock);
175
176   return result;
177 }
178
179 /**
180  * gst_rtsp_session_pool_get_n_sessions:
181  * @pool: a #GstRTSPSessionPool
182  *
183  * Get the amount of active sessions in @pool.
184  *
185  * Returns: the amount of active sessions in @pool.
186  */
187 guint
188 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
189 {
190   guint result;
191
192   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
193
194   g_mutex_lock (pool->lock);
195   result = g_hash_table_size (pool->sessions);
196   g_mutex_unlock (pool->lock);
197
198   return result;
199 }
200
201 /**
202  * gst_rtsp_session_pool_find:
203  * @pool: the pool to search
204  * @sessionid: the session id
205  *
206  * Find the session with @sessionid in @pool. The access time of the session
207  * will be updated with gst_rtsp_session_touch().
208  *
209  * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
210  * not exist. g_object_unref() after usage.
211  */
212 GstRTSPSession *
213 gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
214 {
215   GstRTSPSession *result;
216
217   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
218   g_return_val_if_fail (sessionid != NULL, NULL);
219
220   g_mutex_lock (pool->lock);
221   result = g_hash_table_lookup (pool->sessions, sessionid);
222   if (result) {
223     g_object_ref (result);
224     gst_rtsp_session_touch (result);
225   }
226   g_mutex_unlock (pool->lock);
227
228   return result;
229 }
230
231 static gchar *
232 create_session_id (GstRTSPSessionPool * pool)
233 {
234   gchar id[16];
235   gint i;
236
237   for (i = 0; i < 16; i++) {
238     id[i] = g_random_int_range ('a', 'z');
239   }
240
241   return g_strndup (id, 16);
242 }
243
244 /**
245  * gst_rtsp_session_pool_create:
246  * @pool: a #GstRTSPSessionPool
247  *
248  * Create a new #GstRTSPSession object in @pool.
249  *
250  * Returns: a new #GstRTSPSession.
251  */
252 GstRTSPSession *
253 gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
254 {
255   GstRTSPSession *result = NULL;
256   GstRTSPSessionPoolClass *klass;
257   gchar *id = NULL;
258   guint retry;
259
260   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
261
262   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
263
264   retry = 0;
265   do {
266     /* start by creating a new random session id, we assume that this is random
267      * enough to not cause a collision, which we will check later  */
268     if (klass->create_session_id)
269       id = klass->create_session_id (pool);
270     else
271       goto no_function;
272
273     if (id == NULL)
274       goto no_session;
275
276     g_mutex_lock (pool->lock);
277     /* check session limit */
278     if (pool->max_sessions > 0) {
279       if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
280         goto too_many_sessions;
281     }
282     /* check if the sessionid existed */
283     result = g_hash_table_lookup (pool->sessions, id);
284     if (result) {
285       /* found, retry with a different session id */
286       result = NULL;
287       retry++;
288       if (retry > 100)
289         goto collision;
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 =
382       g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func,
383       &now);
384   g_mutex_unlock (pool->lock);
385
386   return result;
387 }
388
389 typedef struct
390 {
391   GstRTSPSessionPool *pool;
392   GstRTSPSessionFilterFunc func;
393   gpointer user_data;
394   GList *list;
395 } FilterData;
396
397 static gboolean
398 filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
399 {
400   switch (data->func (data->pool, sess, data->user_data)) {
401     case GST_RTSP_FILTER_REMOVE:
402       return TRUE;
403     case GST_RTSP_FILTER_REF:
404       /* keep ref */
405       data->list = g_list_prepend (data->list, g_object_ref (sess));
406       /* fallthrough */
407     default:
408     case GST_RTSP_FILTER_KEEP:
409       return FALSE;
410   }
411 }
412
413 /**
414  * gst_rtsp_session_pool_filter:
415  * @pool: a #GstRTSPSessionPool
416  * @func: a callback
417  * @user_data: user data passed to @func
418  *
419  * Call @func for each session in @pool. The result value of @func determines
420  * what happens to the session. @func will be called with the session pool
421  * locked so no further actions on @pool can be performed from @func.
422  *
423  * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
424  * @pool.
425  *
426  * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
427  *
428  * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @pool but
429  * will also be added with an additional ref to the result GList of this
430  * function..
431  *
432  * Returns: a GList with all sessions for which @func returned
433  * #GST_RTSP_FILTER_REF. After usage, each element in the GList should be unreffed
434  * before the list is freed.
435  */
436 GList *
437 gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
438     GstRTSPSessionFilterFunc func, gpointer user_data)
439 {
440   FilterData data;
441
442   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
443   g_return_val_if_fail (func != NULL, NULL);
444
445   data.pool = pool;
446   data.func = func;
447   data.user_data = user_data;
448   data.list = NULL;
449
450   g_mutex_lock (pool->lock);
451   g_hash_table_foreach_remove (pool->sessions, (GHRFunc) filter_func, &data);
452   g_mutex_unlock (pool->lock);
453
454   return data.list;
455 }
456
457 typedef struct
458 {
459   GSource source;
460   GstRTSPSessionPool *pool;
461   gint timeout;
462 } GstPoolSource;
463
464 static void
465 collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
466 {
467   gint timeout;
468   GTimeVal now;
469
470   g_source_get_current_time ((GSource *) psrc, &now);
471
472   timeout = gst_rtsp_session_next_timeout (sess, &now);
473   GST_INFO ("%p: next timeout: %d", sess, timeout);
474   if (psrc->timeout == -1 || timeout < psrc->timeout)
475     psrc->timeout = timeout;
476 }
477
478 static gboolean
479 gst_pool_source_prepare (GSource * source, gint * timeout)
480 {
481   GstPoolSource *psrc;
482   gboolean result;
483
484   psrc = (GstPoolSource *) source;
485   psrc->timeout = -1;
486
487   g_mutex_lock (psrc->pool->lock);
488   g_hash_table_foreach (psrc->pool->sessions, (GHFunc) collect_timeout, psrc);
489   g_mutex_unlock (psrc->pool->lock);
490
491   if (timeout)
492     *timeout = psrc->timeout;
493
494   result = psrc->timeout == 0;
495
496   GST_INFO ("prepare %d, %d", psrc->timeout, result);
497
498   return result;
499 }
500
501 static gboolean
502 gst_pool_source_check (GSource * source)
503 {
504   GST_INFO ("check");
505
506   return gst_pool_source_prepare (source, NULL);
507 }
508
509 static gboolean
510 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
511     gpointer user_data)
512 {
513   gboolean res;
514   GstPoolSource *psrc = (GstPoolSource *) source;
515   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
516
517   GST_INFO ("dispatch");
518
519   if (func)
520     res = func (psrc->pool, user_data);
521   else
522     res = FALSE;
523
524   return res;
525 }
526
527 static void
528 gst_pool_source_finalize (GSource * source)
529 {
530   GstPoolSource *psrc = (GstPoolSource *) source;
531
532   GST_INFO ("finalize %p", psrc);
533
534   g_object_unref (psrc->pool);
535   psrc->pool = NULL;
536 }
537
538 static GSourceFuncs gst_pool_source_funcs = {
539   gst_pool_source_prepare,
540   gst_pool_source_check,
541   gst_pool_source_dispatch,
542   gst_pool_source_finalize
543 };
544
545 /**
546  * gst_rtsp_session_pool_create_watch:
547  * @pool: a #GstRTSPSessionPool
548  *
549  * A GSource that will be dispatched when the session should be cleaned up.
550  */
551 GSource *
552 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)
553 {
554   GstPoolSource *source;
555
556   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
557
558   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
559       sizeof (GstPoolSource));
560   source->pool = g_object_ref (pool);
561
562   return (GSource *) source;
563 }