<commit_before>/*
 * gabble-media-channel.c - Source for GabbleMediaChannel
 * Copyright (C) 2006 Collabora Ltd.
 * Copyright (C) 2006 Nokia Corporation
 *   @author <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "gabble-media-channel.h"

#include <dbus/dbus-glib.h>
#include <stdio.h>
#include <stdlib.h>

#define DEBUG_FLAG GABBLE_DEBUG_MEDIA

#include "debug.h"
#include "extensions/extensions.h" /* for Hold */
#include "gabble-connection.h"
#include "gabble-media-session.h"
#include "presence.h"
#include "presence-cache.h"

#include <telepathy-glib/dbus.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/channel-iface.h>
#include <telepathy-glib/svc-channel.h>
#include <telepathy-glib/svc-properties-interface.h>

#include "gabble-media-session.h"
#include "gabble-media-stream.h"

#include "media-factory.h"

#define GABBLE_TP_SESSION_HANDLER_SET_TYPE (dbus_g_type_get_struct ("GValueArray", \
      DBUS_TYPE_G_OBJECT_PATH, \
      G_TYPE_STRING, \
      G_TYPE_INVALID))

#define GABBLE_TP_CHANNEL_STREAM_TYPE (dbus_g_type_get_struct ("GValueArray", \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_INVALID))

#ifdef WITH_CALL_STATE
#error Before enabling CallState, update configure.ac to require tp-glib 0.7.6
static void call_state_iface_init (gpointer, gpointer);
#define MAYBE_CALL_STATE() \
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CALL_STATE, \
      call_state_iface_init);
#else
#define MAYBE_CALL_STATE() G_STMT_START { } G_STMT_END
#endif

static void channel_iface_init (gpointer, gpointer);
static void hold_iface_init (gpointer, gpointer);
static void media_signalling_iface_init (gpointer, gpointer);
static void streamed_media_iface_init (gpointer, gpointer);

G_DEFINE_TYPE_WITH_CODE (GabbleMediaChannel, gabble_media_channel,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
    MAYBE_CALL_STATE ();
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
      tp_group_mixin_iface_init);
    G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
      hold_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
      media_signalling_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
      streamed_media_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
      tp_properties_mixin_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));

/* properties */
enum
{
  PROP_OBJECT_PATH = 1,
  PROP_CHANNEL_TYPE,
  PROP_HANDLE_TYPE,
  PROP_HANDLE,
  PROP_CONNECTION,
  PROP_CREATOR,
  PROP_FACTORY,
  /* TP properties (see also below) */
  PROP_NAT_TRAVERSAL,
  PROP_STUN_SERVER,
  PROP_STUN_PORT,
  PROP_GTALK_P2P_RELAY_TOKEN,
  LAST_PROPERTY
};

/* TP properties */
enum
{
  CHAN_PROP_NAT_TRAVERSAL = 0,
  CHAN_PROP_STUN_SERVER,
  CHAN_PROP_STUN_PORT,
  CHAN_PROP_GTALK_P2P_RELAY_TOKEN,
  NUM_CHAN_PROPS,
  INVALID_CHAN_PROP
};

const TpPropertySignature channel_property_signatures[NUM_CHAN_PROPS] = {
      { "nat-traversal",          G_TYPE_STRING },
      { "stun-server",            G_TYPE_STRING },
      { "stun-port",              G_TYPE_UINT   },
      { "gtalk-p2p-relay-token",  G_TYPE_STRING }
};

struct _GabbleMediaChannelPrivate
{
  GabbleConnection *conn;
  gchar *object_path;
  TpHandle creator;

  GabbleMediaFactory *factory;
  GabbleMediaSession *session;
  GPtrArray *streams;

  guint next_stream_id;

  GabbleLocalHoldState hold_state;
  GabbleLocalHoldStateReason hold_state_reason;

  gboolean closed:1;
  gboolean dispose_has_run:1;
};

#define GABBLE_MEDIA_CHANNEL_GET_PRIVATE(obj) (obj->priv)

static void
gabble_media_channel_init (GabbleMediaChannel *self)
{
  GabbleMediaChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GABBLE_TYPE_MEDIA_CHANNEL, GabbleMediaChannelPrivate);

  self->priv = priv;

  priv->next_stream_id = 1;

  /* initialize properties mixin */
  tp_properties_mixin_init (G_OBJECT (self), G_STRUCT_OFFSET (
        GabbleMediaChannel, properties));
}

static GObject *
gabble_media_channel_constructor (GType type, guint n_props,
                                  GObjectConstructParam *props)
{
  GObject *obj;
  GabbleMediaChannelPrivate *priv;
  TpBaseConnection *conn;
  DBusGConnection *bus;
  TpIntSet *set;
  TpHandleRepoIface *contact_handles;

  obj = G_OBJECT_CLASS (gabble_media_channel_parent_class)->
           constructor (type, n_props, props);

  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (GABBLE_MEDIA_CHANNEL (obj));
  conn = (TpBaseConnection *)priv->conn;
  contact_handles = tp_base_connection_get_handles (conn,
      TP_HANDLE_TYPE_CONTACT);

  /* register object on the bus */
  bus = tp_get_bus ();
  dbus_g_connection_register_g_object (bus, priv->object_path, obj);

  tp_group_mixin_init (obj, G_STRUCT_OFFSET (GabbleMediaChannel, group),
      contact_handles, conn->self_handle);

  /* automatically add creator to channel */
  set = tp_intset_new ();
  tp_intset_add (set, priv->creator);

  tp_group_mixin_change_members (obj, "", set, NULL, NULL, NULL, 0, 0);

  tp_intset_destroy (set);

  /* allow member adding */
  tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD, 0);

  return obj;
}

static void session_state_changed_cb (GabbleMediaSession *session,
    GParamSpec *arg1, GabbleMediaChannel *channel);
static void session_stream_added_cb (GabbleMediaSession *session,
    GabbleMediaStream  *stream, GabbleMediaChannel *chan);
static void session_terminated_cb (GabbleMediaSession *session,
    guint terminator, guint reason, gpointer user_data);

/**
 * create_session
 *
 * Creates a GabbleMediaSession object for given peer.
 *
 * If sid is set to NULL a unique sid is generated and
 * the "initiator" property of the newly created
 * GabbleMediaSession is set to our own handle.
 */
static GabbleMediaSession *
create_session (GabbleMediaChannel *channel,
                TpHandle peer,
                const gchar *peer_resource,
                const gchar *sid,
                GError **error)
{
  GabbleMediaChannelPrivate *priv;
  GabbleMediaSession *session;
  gchar *object_path;
  JingleInitiator initiator;

  g_assert (GABBLE_IS_MEDIA_CHANNEL (channel));

  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (channel);

  g_assert (priv->session == NULL);

  object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);

  if (sid == NULL)
    {
      /* We are the initiator */
      GabblePresence *presence;
#ifdef ENABLE_DEBUG
      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
      TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
          conn, TP_HANDLE_TYPE_CONTACT);
#endif

      initiator = INITIATOR_LOCAL;

      presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);

      if (presence == NULL)
        {
          DEBUG ("failed to add contact %d (%s) to media channel: "
              "no presence available", peer,
              tp_handle_inspect (contact_handles, peer));
          goto NO_CAPS;
        }

      if (!(presence->caps & PRESENCE_CAP_GOOGLE_VOICE ||
            presence->caps & PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO ||
            presence->caps & PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO))
        {
          DEBUG ("failed to add contact %d (%s) to media channel: "
              "caps %x aren't sufficient", peer,
              tp_handle_inspect (contact_handles, peer),
              presence->caps);
          goto NO_CAPS;
        }


      sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
    }
  else
    {
      initiator = INITIATOR_REMOTE;
      _gabble_media_factory_register_sid (priv->factory, sid, channel);
    }

  session = g_object_new (GABBLE_TYPE_MEDIA_SESSION,
                          "connection", priv->conn,
                          "media-channel", channel,
                          "object-path", object_path,
                          "session-id", sid,
                          "initiator", initiator,
                          "peer", peer,
                          "peer-resource", peer_resource,
                          NULL);

  g_signal_connect (session, "notify::state",
                    (GCallback) session_state_changed_cb, channel);
  g_signal_connect (session, "stream-added",
                    (GCallback) session_stream_added_cb, channel);
  g_signal_connect (session, "terminated",
                    (GCallback) session_terminated_cb, channel);

  priv->session = session;

  priv->streams = g_ptr_array_sized_new (1);

  tp_svc_channel_interface_media_signalling_emit_new_session_handler (
      channel, object_path, "rtp");

  g_free (object_path);

  return session;

NO_CAPS:
  g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
      "handle %u has no media capabilities", peer);
  return NULL;
}

gboolean
_gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
                                               TpHandle peer,
                                               const gchar *peer_resource,
                                               const gchar *sid,
                                               LmMessage *message,
                                               LmMessageNode *session_node,
                                               const gchar *action,
                                               GError **error)
{
  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
  GabbleMediaSession *session = priv->session;
  gboolean session_is_new = FALSE;

  /* If this assertion fails, create_session() would think we're the
   * initiator. However, GabbleMediaFactory checks this, so it can't fail */
  g_return_val_if_fail (sid != NULL, FALSE);

  if (session == NULL)
    {
      TpGroupMixin *mixin = TP_GROUP_MIXIN (chan);
      TpIntSet *set;

      session = create_session (chan, peer, peer_resource, sid, NULL);
      g_assert (session != NULL);
      session_is_new = TRUE;

      /* make us local pending */
      set = tp_intset_new ();
      tp_intset_add (set, mixin->self_handle);

      tp_group_mixin_change_members ((GObject *)chan,
          "", NULL, NULL, set, NULL, 0, 0);<commit_msg>When adding ourselves to the local-pending set for an incoming call, make the actor the handle of the caller (fd.o #14806)<commit_after>/*
 * gabble-media-channel.c - Source for GabbleMediaChannel
 * Copyright (C) 2006 Collabora Ltd.
 * Copyright (C) 2006 Nokia Corporation
 *   @author <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "gabble-media-channel.h"

#include <dbus/dbus-glib.h>
#include <stdio.h>
#include <stdlib.h>

#define DEBUG_FLAG GABBLE_DEBUG_MEDIA

#include "debug.h"
#include "extensions/extensions.h" /* for Hold */
#include "gabble-connection.h"
#include "gabble-media-session.h"
#include "presence.h"
#include "presence-cache.h"

#include <telepathy-glib/dbus.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/channel-iface.h>
#include <telepathy-glib/svc-channel.h>
#include <telepathy-glib/svc-properties-interface.h>

#include "gabble-media-session.h"
#include "gabble-media-stream.h"

#include "media-factory.h"

#define GABBLE_TP_SESSION_HANDLER_SET_TYPE (dbus_g_type_get_struct ("GValueArray", \
      DBUS_TYPE_G_OBJECT_PATH, \
      G_TYPE_STRING, \
      G_TYPE_INVALID))

#define GABBLE_TP_CHANNEL_STREAM_TYPE (dbus_g_type_get_struct ("GValueArray", \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_UINT, \
      G_TYPE_INVALID))

#ifdef WITH_CALL_STATE
#error Before enabling CallState, update configure.ac to require tp-glib 0.7.6
static void call_state_iface_init (gpointer, gpointer);
#define MAYBE_CALL_STATE() \
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CALL_STATE, \
      call_state_iface_init);
#else
#define MAYBE_CALL_STATE() G_STMT_START { } G_STMT_END
#endif

static void channel_iface_init (gpointer, gpointer);
static void hold_iface_init (gpointer, gpointer);
static void media_signalling_iface_init (gpointer, gpointer);
static void streamed_media_iface_init (gpointer, gpointer);

G_DEFINE_TYPE_WITH_CODE (GabbleMediaChannel, gabble_media_channel,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
    MAYBE_CALL_STATE ();
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
      tp_group_mixin_iface_init);
    G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
      hold_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
      media_signalling_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
      streamed_media_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
      tp_properties_mixin_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));

/* properties */
enum
{
  PROP_OBJECT_PATH = 1,
  PROP_CHANNEL_TYPE,
  PROP_HANDLE_TYPE,
  PROP_HANDLE,
  PROP_CONNECTION,
  PROP_CREATOR,
  PROP_FACTORY,
  /* TP properties (see also below) */
  PROP_NAT_TRAVERSAL,
  PROP_STUN_SERVER,
  PROP_STUN_PORT,
  PROP_GTALK_P2P_RELAY_TOKEN,
  LAST_PROPERTY
};

/* TP properties */
enum
{
  CHAN_PROP_NAT_TRAVERSAL = 0,
  CHAN_PROP_STUN_SERVER,
  CHAN_PROP_STUN_PORT,
  CHAN_PROP_GTALK_P2P_RELAY_TOKEN,
  NUM_CHAN_PROPS,
  INVALID_CHAN_PROP
};

const TpPropertySignature channel_property_signatures[NUM_CHAN_PROPS] = {
      { "nat-traversal",          G_TYPE_STRING },
      { "stun-server",            G_TYPE_STRING },
      { "stun-port",              G_TYPE_UINT   },
      { "gtalk-p2p-relay-token",  G_TYPE_STRING }
};

struct _GabbleMediaChannelPrivate
{
  GabbleConnection *conn;
  gchar *object_path;
  TpHandle creator;

  GabbleMediaFactory *factory;
  GabbleMediaSession *session;
  GPtrArray *streams;

  guint next_stream_id;

  GabbleLocalHoldState hold_state;
  GabbleLocalHoldStateReason hold_state_reason;

  gboolean closed:1;
  gboolean dispose_has_run:1;
};

#define GABBLE_MEDIA_CHANNEL_GET_PRIVATE(obj) (obj->priv)

static void
gabble_media_channel_init (GabbleMediaChannel *self)
{
  GabbleMediaChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GABBLE_TYPE_MEDIA_CHANNEL, GabbleMediaChannelPrivate);

  self->priv = priv;

  priv->next_stream_id = 1;

  /* initialize properties mixin */
  tp_properties_mixin_init (G_OBJECT (self), G_STRUCT_OFFSET (
        GabbleMediaChannel, properties));
}

static GObject *
gabble_media_channel_constructor (GType type, guint n_props,
                                  GObjectConstructParam *props)
{
  GObject *obj;
  GabbleMediaChannelPrivate *priv;
  TpBaseConnection *conn;
  DBusGConnection *bus;
  TpIntSet *set;
  TpHandleRepoIface *contact_handles;

  obj = G_OBJECT_CLASS (gabble_media_channel_parent_class)->
           constructor (type, n_props, props);

  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (GABBLE_MEDIA_CHANNEL (obj));
  conn = (TpBaseConnection *)priv->conn;
  contact_handles = tp_base_connection_get_handles (conn,
      TP_HANDLE_TYPE_CONTACT);

  /* register object on the bus */
  bus = tp_get_bus ();
  dbus_g_connection_register_g_object (bus, priv->object_path, obj);

  tp_group_mixin_init (obj, G_STRUCT_OFFSET (GabbleMediaChannel, group),
      contact_handles, conn->self_handle);

  /* automatically add creator to channel */
  set = tp_intset_new ();
  tp_intset_add (set, priv->creator);

  tp_group_mixin_change_members (obj, "", set, NULL, NULL, NULL, 0, 0);

  tp_intset_destroy (set);

  /* allow member adding */
  tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD, 0);

  return obj;
}

static void session_state_changed_cb (GabbleMediaSession *session,
    GParamSpec *arg1, GabbleMediaChannel *channel);
static void session_stream_added_cb (GabbleMediaSession *session,
    GabbleMediaStream  *stream, GabbleMediaChannel *chan);
static void session_terminated_cb (GabbleMediaSession *session,
    guint terminator, guint reason, gpointer user_data);

/**
 * create_session
 *
 * Creates a GabbleMediaSession object for given peer.
 *
 * If sid is set to NULL a unique sid is generated and
 * the "initiator" property of the newly created
 * GabbleMediaSession is set to our own handle.
 */
static GabbleMediaSession *
create_session (GabbleMediaChannel *channel,
                TpHandle peer,
                const gchar *peer_resource,
                const gchar *sid,
                GError **error)
{
  GabbleMediaChannelPrivate *priv;
  GabbleMediaSession *session;
  gchar *object_path;
  JingleInitiator initiator;

  g_assert (GABBLE_IS_MEDIA_CHANNEL (channel));

  priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (channel);

  g_assert (priv->session == NULL);

  object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);

  if (sid == NULL)
    {
      /* We are the initiator */
      GabblePresence *presence;
#ifdef ENABLE_DEBUG
      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
      TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
          conn, TP_HANDLE_TYPE_CONTACT);
#endif

      initiator = INITIATOR_LOCAL;

      presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);

      if (presence == NULL)
        {
          DEBUG ("failed to add contact %d (%s) to media channel: "
              "no presence available", peer,
              tp_handle_inspect (contact_handles, peer));
          goto NO_CAPS;
        }

      if (!(presence->caps & PRESENCE_CAP_GOOGLE_VOICE ||
            presence->caps & PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO ||
            presence->caps & PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO))
        {
          DEBUG ("failed to add contact %d (%s) to media channel: "
              "caps %x aren't sufficient", peer,
              tp_handle_inspect (contact_handles, peer),
              presence->caps);
          goto NO_CAPS;
        }


      sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
    }
  else
    {
      initiator = INITIATOR_REMOTE;
      _gabble_media_factory_register_sid (priv->factory, sid, channel);
    }

  session = g_object_new (GABBLE_TYPE_MEDIA_SESSION,
                          "connection", priv->conn,
                          "media-channel", channel,
                          "object-path", object_path,
                          "session-id", sid,
                          "initiator", initiator,
                          "peer", peer,
                          "peer-resource", peer_resource,
                          NULL);

  g_signal_connect (session, "notify::state",
                    (GCallback) session_state_changed_cb, channel);
  g_signal_connect (session, "stream-added",
                    (GCallback) session_stream_added_cb, channel);
  g_signal_connect (session, "terminated",
                    (GCallback) session_terminated_cb, channel);

  priv->session = session;

  priv->streams = g_ptr_array_sized_new (1);

  tp_svc_channel_interface_media_signalling_emit_new_session_handler (
      channel, object_path, "rtp");

  g_free (object_path);

  return session;

NO_CAPS:
  g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
      "handle %u has no media capabilities", peer);
  return NULL;
}

gboolean
_gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
                                               TpHandle peer,
                                               const gchar *peer_resource,
                                               const gchar *sid,
                                               LmMessage *message,
                                               LmMessageNode *session_node,
                                               const gchar *action,
                                               GError **error)
{
  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
  GabbleMediaSession *session = priv->session;
  gboolean session_is_new = FALSE;

  /* If this assertion fails, create_session() would think we're the
   * initiator. However, GabbleMediaFactory checks this, so it can't fail */
  g_return_val_if_fail (sid != NULL, FALSE);

  if (session == NULL)
    {
      TpGroupMixin *mixin = TP_GROUP_MIXIN (chan);
      TpIntSet *set;

      session = create_session (chan, peer, peer_resource, sid, NULL);
      g_assert (session != NULL);
      session_is_new = TRUE;

      /* make us local pending */
      set = tp_intset_new ();
      tp_intset_add (set, mixin->self_handle);

      tp_group_mixin_change_members ((GObject *)chan,
          "", NULL, NULL, set, NULL, peer, 0);
{'fraction_non_alphanumeric': 0.08631863186318632, 'fraction_numerical': 0.0036903690369036904, 'mean_word_length': 3.2093199469596514, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4523415', 'n_tokens_mistral': 7358, 'n_tokens_neox': 7138, 'n_words': 1599}