* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
+/**
+ * SECTION:rtsp-session-pool
+ * @short_description: An object for managing sessions
+ * @see_also: #GstRTSPSession
+ *
+ * The #GstRTSPSessionPool object manages a list of #GstRTSPSession objects.
+ *
+ * The maximum number of sessions can be configured with
+ * gst_rtsp_session_pool_set_max_sessions(). The current number of sessions can
+ * be retrieved with gst_rtsp_session_pool_get_n_sessions().
+ *
+ * Use gst_rtsp_session_pool_create() to create a new #GstRTSPSession object.
+ * The session object can be found again with its id and
+ * gst_rtsp_session_pool_find().
+ *
+ * All sessions can be iterated with gst_rtsp_session_pool_filter().
+ *
+ * Run gst_rtsp_session_pool_cleanup() periodically to remove timed out sessions
+ * or use gst_rtsp_session_pool_create_watch() to be notified when session
+ * cleanup should be performed.
+ *
+ * Last reviewed on 2013-07-11 (1.0.0)
+ */
#include "rtsp-session-pool.h"
struct _GstRTSPSessionPoolPrivate
{
+ GMutex lock; /* protects everything in this struct */
guint max_sessions;
-
- GMutex lock;
GHashTable *sessions;
+ guint sessions_cookie;
};
#define DEFAULT_MAX_SESSIONS 0
'8', '9', '$', '-', '_', '.', '+'
};
+enum
+{
+ SIGNAL_SESSION_REMOVED,
+ SIGNAL_LAST
+};
+
+static guint gst_rtsp_session_pool_signals[SIGNAL_LAST] = { 0 };
+
GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
#define GST_CAT_DEFAULT rtsp_session_debug
static void gst_rtsp_session_pool_finalize (GObject * object);
static gchar *create_session_id (GstRTSPSessionPool * pool);
+static GstRTSPSession *create_session (GstRTSPSessionPool * pool,
+ const gchar * id);
G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED] =
+ g_signal_new ("session-removed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPSessionPoolClass,
+ session_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
+ 1, GST_TYPE_RTSP_SESSION);
+
klass->create_session_id = create_session_id;
+ klass->create_session = create_session;
GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
"GstRTSPSessionPool");
priv->max_sessions = DEFAULT_MAX_SESSIONS;
}
+static GstRTSPFilterResult
+remove_sessions_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
+ gpointer user_data)
+{
+ return GST_RTSP_FILTER_REMOVE;
+}
+
static void
gst_rtsp_session_pool_finalize (GObject * object)
{
GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
GstRTSPSessionPoolPrivate *priv = pool->priv;
- g_mutex_clear (&priv->lock);
+ gst_rtsp_session_pool_filter (pool, remove_sessions_func, NULL);
g_hash_table_unref (priv->sessions);
+ g_mutex_clear (&priv->lock);
G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
}
*
* Create a new #GstRTSPSessionPool instance.
*
- * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
+ * Returns: (transfer full): A new #GstRTSPSessionPool. g_object_unref() after
+ * usage.
*/
GstRTSPSessionPool *
gst_rtsp_session_pool_new (void)
* Find the session with @sessionid in @pool. The access time of the session
* will be updated with gst_rtsp_session_touch().
*
- * Returns: (transfer full): the #GstRTSPSession with @sessionid or %NULL when the session did
- * not exist. g_object_unref() after usage.
+ * Returns: (transfer full) (nullable): the #GstRTSPSession with @sessionid
+ * or %NULL when the session did not exist. g_object_unref() after usage.
*/
GstRTSPSession *
gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
return g_strndup (id, 16);
}
+static GstRTSPSession *
+create_session (GstRTSPSessionPool * pool, const gchar * id)
+{
+ return gst_rtsp_session_new (id);
+}
+
/**
* gst_rtsp_session_pool_create:
* @pool: a #GstRTSPSessionPool
*
* Create a new #GstRTSPSession object in @pool.
*
- * Returns: (transfer none): a new #GstRTSPSession.
+ * Returns: (transfer full): a new #GstRTSPSession.
*/
GstRTSPSession *
gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
goto collision;
} else {
/* not found, create session and insert it in the pool */
- result = gst_rtsp_session_new (id);
+ if (klass->create_session)
+ result = create_session (pool, id);
+ if (result == NULL)
+ goto too_many_sessions;
/* take additional ref for the pool */
g_object_ref (result);
g_hash_table_insert (priv->sessions,
(gchar *) gst_rtsp_session_get_sessionid (result), result);
+ priv->sessions_cookie++;
}
g_mutex_unlock (&priv->lock);
/**
* gst_rtsp_session_pool_remove:
* @pool: a #GstRTSPSessionPool
- * @sess: a #GstRTSPSession
+ * @sess: (transfer none): a #GstRTSPSession
*
* Remove @sess from @pool, releasing the ref that the pool has on @sess.
*
priv = pool->priv;
g_mutex_lock (&priv->lock);
+ g_object_ref (sess);
found =
g_hash_table_remove (priv->sessions,
gst_rtsp_session_get_sessionid (sess));
+ if (found)
+ priv->sessions_cookie++;
g_mutex_unlock (&priv->lock);
+ if (found)
+ g_signal_emit (pool, gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED],
+ 0, sess);
+
+ g_object_unref (sess);
+
return found;
}
+typedef struct
+{
+ GTimeVal now;
+ GstRTSPSessionPool *pool;
+ GList *removed;
+} CleanupData;
+
static gboolean
-cleanup_func (gchar * sessionid, GstRTSPSession * sess, GTimeVal * now)
+cleanup_func (gchar * sessionid, GstRTSPSession * sess, CleanupData * data)
{
- return gst_rtsp_session_is_expired (sess, now);
+ gboolean expired;
+
+ expired = gst_rtsp_session_is_expired (sess, &data->now);
+ if (expired) {
+ GST_DEBUG ("session expired");
+ data->removed = g_list_prepend (data->removed, g_object_ref (sess));
+ }
+ return expired;
}
/**
{
GstRTSPSessionPoolPrivate *priv;
guint result;
- GTimeVal now;
+ CleanupData data;
+ GList *walk;
g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
priv = pool->priv;
- g_get_current_time (&now);
+ g_get_current_time (&data.now);
+ data.pool = pool;
+ data.removed = NULL;
g_mutex_lock (&priv->lock);
result =
g_hash_table_foreach_remove (priv->sessions, (GHRFunc) cleanup_func,
- &now);
+ &data);
+ if (result > 0)
+ priv->sessions_cookie++;
g_mutex_unlock (&priv->lock);
- return result;
-}
+ for (walk = data.removed; walk; walk = walk->next) {
+ GstRTSPSession *sess = walk->data;
-typedef struct
-{
- GstRTSPSessionPool *pool;
- GstRTSPSessionPoolFilterFunc func;
- gpointer user_data;
- GList *list;
-} FilterData;
+ g_signal_emit (pool,
+ gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0, sess);
-static gboolean
-filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
-{
- switch (data->func (data->pool, sess, data->user_data)) {
- case GST_RTSP_FILTER_REMOVE:
- return TRUE;
- case GST_RTSP_FILTER_REF:
- /* keep ref */
- data->list = g_list_prepend (data->list, g_object_ref (sess));
- /* fallthrough */
- default:
- case GST_RTSP_FILTER_KEEP:
- return FALSE;
+ g_object_unref (sess);
}
+ g_list_free (data.removed);
+
+ return result;
}
/**
* gst_rtsp_session_pool_filter:
* @pool: a #GstRTSPSessionPool
- * @func: (scope call): a callback
- * @user_data: user data passed to @func
+ * @func: (scope call) (allow-none): a callback
+ * @user_data: (closure): user data passed to @func
*
* Call @func for each session in @pool. The result value of @func determines
* what happens to the session. @func will be called with the session pool
* locked so no further actions on @pool can be performed from @func.
*
- * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
+ * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be set to the
+ * expired state with gst_rtsp_session_set_expired() and removed from
* @pool.
*
* If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
* will also be added with an additional ref to the result GList of this
* function..
*
+ * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all sessions.
+ *
* Returns: (element-type GstRTSPSession) (transfer full): a GList with all
* sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
* element in the GList should be unreffed before the list is freed.
GstRTSPSessionPoolFilterFunc func, gpointer user_data)
{
GstRTSPSessionPoolPrivate *priv;
- FilterData data;
+ GHashTableIter iter;
+ gpointer key, value;
+ GList *result;
+ GHashTable *visited;
+ guint cookie;
g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
- g_return_val_if_fail (func != NULL, NULL);
priv = pool->priv;
- data.pool = pool;
- data.func = func;
- data.user_data = user_data;
- data.list = NULL;
+ result = NULL;
+ if (func)
+ visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
g_mutex_lock (&priv->lock);
- g_hash_table_foreach_remove (priv->sessions, (GHRFunc) filter_func, &data);
+restart:
+ g_hash_table_iter_init (&iter, priv->sessions);
+ cookie = priv->sessions_cookie;
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GstRTSPSession *session = value;
+ GstRTSPFilterResult res;
+ gboolean changed;
+
+ if (func) {
+ /* only visit each session once */
+ if (g_hash_table_contains (visited, session))
+ continue;
+
+ g_hash_table_add (visited, g_object_ref (session));
+ g_mutex_unlock (&priv->lock);
+
+ res = func (pool, session, user_data);
+
+ g_mutex_lock (&priv->lock);
+ } else
+ res = GST_RTSP_FILTER_REF;
+
+ changed = (cookie != priv->sessions_cookie);
+
+ switch (res) {
+ case GST_RTSP_FILTER_REMOVE:
+ {
+ gboolean removed = TRUE;
+
+ if (changed)
+ /* something changed, check if we still have the session */
+ removed = g_hash_table_remove (priv->sessions, key);
+ else
+ g_hash_table_iter_remove (&iter);
+
+ if (removed) {
+ /* if we managed to remove the session, update the cookie and
+ * signal */
+ cookie = ++priv->sessions_cookie;
+ g_mutex_unlock (&priv->lock);
+
+ g_signal_emit (pool,
+ gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0,
+ session);
+
+ g_mutex_lock (&priv->lock);
+ /* cookie could have changed again, make sure we restart */
+ changed |= (cookie != priv->sessions_cookie);
+ }
+ break;
+ }
+ case GST_RTSP_FILTER_REF:
+ /* keep ref */
+ result = g_list_prepend (result, g_object_ref (session));
+ break;
+ case GST_RTSP_FILTER_KEEP:
+ default:
+ break;
+ }
+ if (changed)
+ goto restart;
+ }
g_mutex_unlock (&priv->lock);
- return data.list;
+ if (func)
+ g_hash_table_unref (visited);
+
+ return result;
}
typedef struct
{
gint timeout;
GTimeVal now;
- gint64 tmp;
- tmp = g_source_get_time ((GSource *) psrc);
- now.tv_sec = tmp / G_USEC_PER_SEC;
- now.tv_usec = tmp % G_USEC_PER_SEC;
+ g_get_current_time (&now);
timeout = gst_rtsp_session_next_timeout (sess, &now);
GST_INFO ("%p: next timeout: %d", sess, timeout);
* gst_rtsp_session_pool_create_watch:
* @pool: a #GstRTSPSessionPool
*
- * A GSource that will be dispatched when the session should be cleaned up.
+ * Create a #GSource that will be dispatched when the session should be cleaned
+ * up.
+ *
+ * Returns: (transfer full): a #GSource
*/
GSource *
gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)