[farsight2/master] Make the FsRtpSubStream into a GObject of its own in its own file

Olivier Crête olivier.crete at collabora.co.uk
Tue Dec 23 15:19:54 PST 2008


---
 gst/fsrtpconference/Makefile.am        |    2 +
 gst/fsrtpconference/fs-rtp-session.c   |  143 ++-------------
 gst/fsrtpconference/fs-rtp-session.h   |    3 -
 gst/fsrtpconference/fs-rtp-stream.h    |    1 +
 gst/fsrtpconference/fs-rtp-substream.c |  314 ++++++++++++++++++++++++++++++++
 gst/fsrtpconference/fs-rtp-substream.h |   82 +++++++++
 6 files changed, 415 insertions(+), 130 deletions(-)
 create mode 100644 gst/fsrtpconference/fs-rtp-substream.c
 create mode 100644 gst/fsrtpconference/fs-rtp-substream.h

diff --git a/gst/fsrtpconference/Makefile.am b/gst/fsrtpconference/Makefile.am
index 9c7da5d..fee5bd4 100644
--- a/gst/fsrtpconference/Makefile.am
+++ b/gst/fsrtpconference/Makefile.am
@@ -5,6 +5,7 @@ libfsrtpconference_la_SOURCES = gstfsrtpconference.c \
 	fs-rtp-participant.c \
 	fs-rtp-session.c \
 	fs-rtp-stream.c \
+	fs-rtp-substream.c \
 	fs-rtp-discover-codecs.c \
 	fs-rtp-codec-cache.c \
 	fs-rtp-codec-negotiation.c \
@@ -21,6 +22,7 @@ noinst_HEADERS = \
 	fs-rtp-participant.h \
 	fs-rtp-session.h \
 	fs-rtp-stream.h \
+	fs-rtp-substream.h \
 	fs-rtp-discover-codecs.h \
 	fs-rtp-codec-cache.h \
 	fs-rtp-codec-negotiation.h \
diff --git a/gst/fsrtpconference/fs-rtp-session.c b/gst/fsrtpconference/fs-rtp-session.c
index 30ba233..66da591 100644
--- a/gst/fsrtpconference/fs-rtp-session.c
+++ b/gst/fsrtpconference/fs-rtp-session.c
@@ -42,7 +42,7 @@
 #include "fs-rtp-participant.h"
 #include "fs-rtp-discover-codecs.h"
 #include "fs-rtp-codec-negotiation.h"
-
+#include "fs-rtp-substream.h"
 
 /* Signals */
 enum
@@ -164,11 +164,6 @@ static FsStreamTransmitter *fs_rtp_session_get_new_stream_transmitter (
     FsRtpSession *self, gchar *transmitter_name, FsParticipant *participant,
     guint n_parameters, GParameter *parameters, GError **error);
 
-static FsRtpSubStream *fs_rtp_session_new_substream (FsRtpSession *self,
-  GstPad *pad, guint32 ssrc, guint pt);
-static void fs_rtp_session_destroy_substream (FsRtpSession *session,
-  FsRtpSubStream *substream);
-
 
 static GObjectClass *parent_class = NULL;
 
@@ -370,11 +365,7 @@ fs_rtp_session_dispose (GObject *object)
   }
 
   if (self->priv->free_substreams) {
-    GList *walk;
-    for (walk = g_list_first (self->priv->free_substreams);
-         walk;
-         walk = g_list_next (walk))
-      fs_rtp_session_destroy_substream (self, (FsRtpSubStream *)walk->data);
+    g_list_foreach (self->priv->free_substreams, (GFunc) g_object_unref, NULL);
     g_list_free (self->priv->free_substreams);
     self->priv->free_substreams = NULL;
   }
@@ -1305,12 +1296,23 @@ fs_rtp_session_new_recv_pad (FsRtpSession *session, GstPad *new_pad,
 {
   FsRtpSubStream *substream = NULL;
   FsRtpStream *stream = NULL;
+  GError *error = NULL;
 
-  substream = fs_rtp_session_new_substream (session, new_pad,
-    ssrc, pt);
+  substream = fs_rtp_substream_new (session->priv->conference, new_pad,
+    ssrc, pt, &error);
 
-  if (substream == NULL)
+  if (substream == NULL) {
+    if (error && error->domain == FS_ERROR)
+      fs_session_emit_error (FS_SESSION (session), error->code,
+        "Could not create a substream for the new pad", error->message);
+    else
+      fs_session_emit_error (FS_SESSION (session), FS_ERROR_CONSTRUCTION,
+        "Could not create a substream for the new pad",
+        "No error details returned");
+
+    g_clear_error (&error);
     return;
+  }
 
 
   /* Lets find the FsRtpStream for this substream, if no Stream claims it
@@ -1330,116 +1332,3 @@ fs_rtp_session_new_recv_pad (FsRtpSession *session, GstPad *new_pad,
   }
 }
 
-
-struct _FsRtpSubStream {
-  guint32 ssrc;
-  guint pt;
-
-  GstPad *rtpbin_pad;
-
-  GstElement *valve;
-
-  /* This only exists if the codec is valid,
-   * otherwise the rtpbin_pad is blocked */
-  GstElement *codecbin;
-
-  /* This is only created when the substream is associated with a FsRtpStream */
-  GstPad *output_pad;
-};
-
-
-/**
- * fs_rtp_session_add_codecbin:
- *
- * Creates, add, links the rtpbin for a given substream.
- * It will block the substream if there is no known info on the PT.
- */
-
-static void
-fs_rtp_session_add_codecbin (FsRtpSession *self, FsRtpSubStream *substream)
-{
-}
-
-static FsRtpSubStream *
-fs_rtp_session_new_substream (FsRtpSession *self, GstPad *pad,
-  guint32 ssrc, guint pt)
-{
-  FsRtpSubStream *substream = g_new0 (FsRtpSubStream, 1);
-
-  substream->ssrc = ssrc;
-  substream->pt = pt;
-  substream->rtpbin_pad = pad;
-
-  substream->valve = gst_element_factory_make ("fsvalve", NULL);
-
-  if (!substream->valve) {
-    gchar *str = g_strdup_printf ("Could not create a fsvalve element for"
-      " session substream with ssrc: %x and pt:%d", ssrc, pt);
-    fs_session_emit_error (FS_SESSION (self), FS_ERROR_CONSTRUCTION,
-      "Could not create required fsvalve element for reception", str);
-    g_free (str);
-    goto error;
-  }
-
-  if (!gst_bin_add (GST_BIN (self->priv->conference), substream->valve)) {
-    gchar *str = g_strdup_printf ("Could not add the fsvalve element for"
-      " session substream with ssrc: %x and pt:%d to the conference bin",
-      ssrc, pt);
-    fs_session_emit_error (FS_SESSION (self), FS_ERROR_CONSTRUCTION,
-      "Could not add the required fsvalve element for reception", str);
-    g_free (str);
-    goto error;
-  }
-
-  /* We set the valve to dropping, the stream will unblock it when its linked */
-  g_object_set (substream->valve, "drop", TRUE, NULL);
-
-  if (gst_element_set_state (substream->valve, GST_STATE_PLAYING) ==
-    GST_STATE_CHANGE_FAILURE) {
-    gchar *str = g_strdup_printf ("Could not set the fsvalve element for"
-      " session substream with ssrc: %x and pt:%d to the playing state",
-      ssrc, pt);
-    fs_session_emit_error (FS_SESSION (self), FS_ERROR_CONSTRUCTION,
-      "Could not set the required fsvalve element to playing", str);
-    g_free (str);
-    goto error;
-  }
-
-  fs_rtp_session_add_codecbin (self, substream);
-
-  return substream;
- error:
-  g_free (substream);
-  return NULL;
-}
-
-
-static void
-fs_rtp_session_destroy_substream (FsRtpSession *session,
-  FsRtpSubStream *substream)
-{
-  if (substream->output_pad) {
-    gst_element_remove_pad (GST_ELEMENT (session->priv->conference),
-      substream->output_pad);
-  }
-
-  if (substream->valve) {
-    gst_object_ref (substream->valve);
-    gst_bin_remove (GST_BIN (session->priv->conference), substream->valve);
-    gst_element_set_state (substream->valve, GST_STATE_NULL);
-    gst_object_unref (substream->valve);
-    substream->valve = NULL;
-  }
-
-
-  if (substream->codecbin) {
-    gst_object_ref (substream->codecbin);
-    gst_bin_remove (GST_BIN (session->priv->conference), substream->codecbin);
-    gst_element_set_state (substream->codecbin, GST_STATE_NULL);
-    gst_object_unref (substream->codecbin);
-    substream->codecbin = NULL;
-  }
-}
-
-
-
diff --git a/gst/fsrtpconference/fs-rtp-session.h b/gst/fsrtpconference/fs-rtp-session.h
index 8fef2b8..96863fb 100644
--- a/gst/fsrtpconference/fs-rtp-session.h
+++ b/gst/fsrtpconference/fs-rtp-session.h
@@ -72,9 +72,6 @@ struct _FsRtpSession
   FsRtpSessionPrivate *priv;
 };
 
-/* All members are private */
-typedef struct _FsRtpSubStream FsRtpSubStream;
-
 
 GType fs_rtp_session_get_type (void);
 
diff --git a/gst/fsrtpconference/fs-rtp-stream.h b/gst/fsrtpconference/fs-rtp-stream.h
index e9cffe6..868c90c 100644
--- a/gst/fsrtpconference/fs-rtp-stream.h
+++ b/gst/fsrtpconference/fs-rtp-stream.h
@@ -30,6 +30,7 @@
 
 #include "fs-rtp-participant.h"
 #include "fs-rtp-session.h"
+#include "fs-rtp-substream.h"
 
 G_BEGIN_DECLS
 
diff --git a/gst/fsrtpconference/fs-rtp-substream.c b/gst/fsrtpconference/fs-rtp-substream.c
new file mode 100644
index 0000000..a65493d
--- /dev/null
+++ b/gst/fsrtpconference/fs-rtp-substream.c
@@ -0,0 +1,314 @@
+/*
+ * Farsight2 - Farsight RTP Sub Stream
+ *
+ * Copyright 2007 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete at collabora.co.uk>
+ * Copyright 2007 Nokia Corp.
+ *
+ * fs-rtp-substream.c - A Farsight RTP Substream gobject
+ *
+ * This program 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fs-rtp-substream.h"
+
+
+/* props */
+enum
+{
+  PROP_0,
+  PROP_CONFERENCE,
+  PROP_RTPBIN_PAD,
+  PROP_SSRC,
+  PROP_PT,
+};
+
+struct _FsRtpSubStreamPrivate {
+  gboolean disposed;
+
+  FsRtpConference *conference;
+
+  guint32 ssrc;
+  guint pt;
+
+  GstPad *rtpbin_pad;
+
+  GstElement *valve;
+
+  /* This only exists if the codec is valid,
+   * otherwise the rtpbin_pad is blocked */
+  GstElement *codecbin;
+
+  /* This is only created when the substream is associated with a FsRtpStream */
+  GstPad *output_pad;
+
+  GError *construction_error;
+};
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE(FsRtpSubStream, fs_rtp_sub_stream, G_TYPE_OBJECT);
+
+#define FS_RTP_SUB_STREAM_GET_PRIVATE(o)                                 \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_RTP_SUB_STREAM,             \
+   FsRtpSubStreamPrivate))
+
+static void fs_rtp_sub_stream_dispose (GObject *object);
+static void fs_rtp_sub_stream_finalize (GObject *object);
+static void fs_rtp_sub_stream_constructed (GObject *object);
+
+static void fs_rtp_sub_stream_get_property (GObject *object, guint prop_id,
+  GValue *value, GParamSpec *pspec);
+static void fs_rtp_sub_stream_set_property (GObject *object, guint prop_id,
+  const GValue *value, GParamSpec *pspec);
+
+static void
+fs_rtp_sub_stream_class_init (FsRtpSubStreamClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  parent_class = fs_rtp_sub_stream_parent_class;
+
+  gobject_class->constructed = fs_rtp_sub_stream_constructed;
+  gobject_class->dispose = fs_rtp_sub_stream_dispose;
+  gobject_class->finalize = fs_rtp_sub_stream_finalize;
+  gobject_class->set_property = fs_rtp_sub_stream_set_property;
+  gobject_class->get_property = fs_rtp_sub_stream_get_property;
+
+  g_object_class_install_property (gobject_class,
+    PROP_CONFERENCE,
+    g_param_spec_object ("conference",
+      "The Conference this substream stream refers to",
+      "This is a convience pointer for the Conference",
+      FS_TYPE_RTP_CONFERENCE,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+
+  g_object_class_install_property (gobject_class,
+    PROP_RTPBIN_PAD,
+    g_param_spec_object ("rtpbin-pad",
+      "The GstPad this substrea is linked to",
+      "This is the pad on which this substream will attach itself",
+      GST_TYPE_PAD,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+
+  g_object_class_install_property (gobject_class,
+    PROP_SSRC,
+    g_param_spec_uint ("ssrc",
+      "The ssrc this stream is used for",
+      "This is the SSRC from the pad",
+      0, G_MAXUINT32, 0,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+    PROP_PT,
+    g_param_spec_uint ("pt",
+      "The payload type this stream is used for",
+      "This is the payload type from the pad",
+      0, 128, 0,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+  g_type_class_add_private (klass, sizeof (FsRtpSubStreamPrivate));
+}
+
+
+static void
+fs_rtp_sub_stream_init (FsRtpSubStream *self)
+{
+  self->priv = FS_RTP_SUB_STREAM_GET_PRIVATE (self);
+  self->priv->disposed = FALSE;
+}
+
+
+static void
+fs_rtp_sub_stream_constructed (GObject *object)
+{
+  FsRtpSubStream *self = FS_RTP_SUB_STREAM (object);
+
+  if (!self->priv->conference) {
+    self->priv->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_INVALID_ARGUMENTS, "A Substream needs a conference object");
+    return;
+  }
+
+  self->priv->valve = gst_element_factory_make ("fsvalve", NULL);
+
+  if (!self->priv->valve) {
+    self->priv->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION, "Could not create a fsvalve element for"
+      " session substream with ssrc: %x and pt:%d", self->priv->ssrc,
+      self->priv->pt);
+    return;
+  }
+
+
+  if (!gst_bin_add (GST_BIN (self->priv->conference), self->priv->valve)) {
+    self->priv->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION, "Could not add the fsvalve element for session"
+      " substream with ssrc: %x and pt:%d to the conference bin",
+      self->priv->ssrc, self->priv->pt);
+    return;
+  }
+
+  /* We set the valve to dropping, the stream will unblock it when its linked */
+  g_object_set (self->priv->valve, "drop", TRUE, NULL);
+
+  if (gst_element_set_state (self->priv->valve, GST_STATE_PLAYING) ==
+    GST_STATE_CHANGE_FAILURE) {
+    self->priv->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION, "Could not set the fsvalve element for session"
+      " substream with ssrc: %x and pt:%d to the playing state",
+      self->priv->ssrc, self->priv->pt);
+    return;
+  }
+}
+
+
+static void
+fs_rtp_sub_stream_dispose (GObject *object)
+{
+  FsRtpSubStream *self = FS_RTP_SUB_STREAM (object);
+
+  if (self->priv->disposed)
+    return;
+
+  if (self->priv->output_pad) {
+    gst_element_remove_pad (GST_ELEMENT (self->priv->conference),
+      self->priv->output_pad);
+    self->priv->output_pad = NULL;
+  }
+
+  if (self->priv->valve) {
+    gst_object_ref (self->priv->valve);
+    gst_bin_remove (GST_BIN (self->priv->conference), self->priv->valve);
+    gst_element_set_state (self->priv->valve, GST_STATE_NULL);
+    gst_object_unref (self->priv->valve);
+    self->priv->valve = NULL;
+  }
+
+
+  if (self->priv->codecbin) {
+    gst_object_ref (self->priv->codecbin);
+    gst_bin_remove (GST_BIN (self->priv->conference), self->priv->codecbin);
+    gst_element_set_state (self->priv->codecbin, GST_STATE_NULL);
+    gst_object_unref (self->priv->codecbin);
+    self->priv->codecbin = NULL;
+  }
+
+  if (self->priv->rtpbin_pad) {
+    gst_object_unref (self->priv->rtpbin_pad);
+    self->priv->rtpbin_pad = NULL;
+  }
+
+  self->priv->disposed = TRUE;
+  G_OBJECT_CLASS (fs_rtp_sub_stream_parent_class)->dispose (object);
+}
+
+static void
+fs_rtp_sub_stream_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (fs_rtp_sub_stream_parent_class)->finalize (object);
+}
+
+
+
+static void
+fs_rtp_sub_stream_set_property (GObject *object,
+                                guint prop_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+  FsRtpSubStream *self = FS_RTP_SUB_STREAM (object);
+
+  switch (prop_id) {
+    case PROP_CONFERENCE:
+      self->priv->conference = g_value_get_object (value);
+      break;
+    case PROP_RTPBIN_PAD:
+      self->priv->rtpbin_pad = g_value_dup_object (value);
+      break;
+    case PROP_SSRC:
+      self->priv->ssrc = g_value_get_uint (value);
+     break;
+    case PROP_PT:
+      self->priv->pt = g_value_get_uint (value);
+      break;
+  }
+}
+
+
+static void
+fs_rtp_sub_stream_get_property (GObject *object,
+                                guint prop_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+  FsRtpSubStream *self = FS_RTP_SUB_STREAM (object);
+
+  switch (prop_id) {
+    case PROP_CONFERENCE:
+      g_value_set_object (value, self->priv->conference);
+      break;
+    case PROP_RTPBIN_PAD:
+      g_value_set_object (value, self->priv->rtpbin_pad);
+      break;
+    case PROP_SSRC:
+      g_value_set_uint (value, self->priv->ssrc);
+     break;
+    case PROP_PT:
+      g_value_set_uint (value, self->priv->pt);
+      break;
+  }
+}
+
+
+/**
+ * fs_rtp_session_add_codecbin:
+ *
+ * Creates, add, links the rtpbin for a given substream.
+ * It will block the substream if there is no known info on the PT.
+ */
+
+gboolean
+fs_rtp_sub_stream_try_add_codecbin (FsRtpSubStream *substream)
+{
+  return FALSE;
+}
+
+FsRtpSubStream *
+fs_rtp_sub_stream_new (FsRtpConference *conference, GstPad *rtpbin_pad,
+  guint32 ssrc, guint pt, GError **error)
+{
+  FsRtpSubStream *substream = g_object_new (FS_TYPE_RTP_SUB_STREAM,
+    "conference", conference,
+    "rtpbin-pad", rtpbin_pad,
+    "ssrc", ssrc,
+    "pt", pt,
+    NULL);
+
+  if (substream->priv->construction_error) {
+    g_propagate_error (error, substream->priv->construction_error);
+    g_object_unref (substream);
+    return NULL;
+  }
+
+  return substream;
+}
diff --git a/gst/fsrtpconference/fs-rtp-substream.h b/gst/fsrtpconference/fs-rtp-substream.h
new file mode 100644
index 0000000..3e557d4
--- /dev/null
+++ b/gst/fsrtpconference/fs-rtp-substream.h
@@ -0,0 +1,82 @@
+/*
+ * Farsight2 - Farsight RTP Sub Stream
+ *
+ * Copyright 2007 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete at collabora.co.uk>
+ * Copyright 2007 Nokia Corp.
+ *
+ * fs-rtp-substream.h - A Farsight RTP Substream gobject
+ *
+ * This program 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef __FS_RTP_SUBSTREAM_H__
+#define __FS_RTP_SUBSTREAM_H__
+
+#include <gst/gst.h>
+
+#include "fs-rtp-conference.h"
+
+G_BEGIN_DECLS
+
+/* TYPE MACROS */
+#define FS_TYPE_RTP_SUB_STREAM \
+  (fs_rtp_sub_stream_get_type())
+#define FS_RTP_SUB_STREAM(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), FS_TYPE_RTP_SUB_STREAM, FsRtpSubStream))
+#define FS_RTP_SUB_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), FS_TYPE_RTP_SUB_STREAM, FsRtpSubStreamClass))
+#define FS_IS_RTP_SUB_STREAM(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FS_TYPE_RTP_SUB_STREAM))
+#define FS_IS_RTP_SUB_STREAM_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), FS_TYPE_RTP_SUB_STREAM))
+#define FS_RTP_SUB_STREAM_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), FS_TYPE_RTP_SUB_STREAM,   \
+    FsRtpSubStreamClass))
+#define FS_RTP_SUB_STREAM_CAST(obj) ((FsRtpSubStream*) (obj))
+
+typedef struct _FsRtpSubStream FsRtpSubStream;
+typedef struct _FsRtpSubStreamClass FsRtpSubStreamClass;
+typedef struct _FsRtpSubStreamPrivate FsRtpSubStreamPrivate;
+
+struct _FsRtpSubStreamClass
+{
+  GObjectClass parent_class;
+
+};
+
+/**
+ * FsRtpSubStream:
+ *
+ */
+struct _FsRtpSubStream
+{
+  GObject parent;
+  FsRtpSubStreamPrivate *priv;
+};
+
+GType fs_rtp_sub_stream_get_type (void);
+
+FsRtpSubStream *fs_rtp_substream_new ( FsRtpConference *conference, GstPad *pad,
+  guint32 ssrc, guint pt, GError **error);
+
+
+gboolean fs_rtp_sub_stream_try_add_codecbin (FsRtpSubStream *substream);
+
+
+G_END_DECLS
+
+#endif /* __FS_RTP_SUBSTREAM_H__ */
-- 
1.5.6.5




More information about the farsight-commits mailing list