/*
* e-data-capture.c
*
* 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) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*/
/**
* SECTION: e-data-capture
* @include: e-util/e-util.h
* @short_description: Capture data from streams
*
* #EDataCapture is a #GConverter that captures data until the end of
* the input data is seen, then emits a #EDataCapture:finished signal
* with the captured data in a #GBytes instance.
*
* When used with #GConverterInputStream or #GConverterOutputStream,
* an #EDataCapture can discreetly capture the stream content for the
* purpose of caching.
**/
#include "e-data-capture.h"
#include <string.h>
#define E_DATA_CAPTURE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_DATA_CAPTURE, EDataCapturePrivate))
typedef struct _SignalClosure SignalClosure;
struct _EDataCapturePrivate {
GMainContext *main_context;
GByteArray *byte_array;
GMutex byte_array_lock;
};
struct _SignalClosure {
GWeakRef data_capture;
GBytes *data;
};
enum {
PROP_0,
PROP_MAIN_CONTEXT
};
enum {
FINISHED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
/* Forward Declarations */
static void e_data_capture_converter_init (GConverterIface *interface);
G_DEFINE_TYPE_WITH_CODE (
EDataCapture,
e_data_capture,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (
G_TYPE_CONVERTER,
e_data_capture_converter_init))
static void
signal_closure_free (SignalClosure *signal_closure)
{
g_weak_ref_set (&signal_closure->data_capture, NULL);
g_bytes_unref (signal_closure->data);
g_slice_free (SignalClosure, signal_closure);
}
static gboolean
data_capture_emit_finished_idle_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
EDataCapture *data_capture;
data_capture = g_weak_ref_get (&signal_closure->data_capture);
if (data_capture != NULL) {
g_signal_emit (
data_capture,
signals[FINISHED], 0,
signal_closure->data);
g_object_unref (data_capture);
}
return FALSE;
}
static void
data_capture_set_main_context (EDataCapture *data_capture,
GMainContext *main_context)
{
g_return_if_fail (data_capture->priv->main_context == NULL);
if (main_context != NULL)
g_main_context_ref (main_context);
else
main_context = g_main_context_ref_thread_default ();
data_capture->priv->main_context = main_context;
}
static void
data_capture_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_MAIN_CONTEXT:
data_capture_set_main_context (
E_DATA_CAPTURE (object),
g_value_get_boxed (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
data_capture_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_MAIN_CONTEXT:
g_value_take_boxed (
value,
e_data_capture_ref_main_context (
E_DATA_CAPTURE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
data_capture_finalize (GObject *object)
{
EDataCapturePrivate *priv;
priv = E_DATA_CAPTURE_GET_PRIVATE (object);
g_main_context_unref (priv->main_context);
g_byte_array_free (priv->byte_array, TRUE);
g_mutex_clear (&priv->byte_array_lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_data_capture_parent_class)->finalize (object);
}
static GConverterResult
data_capture_convert (GConverter *converter,
gconstpointer inbuf,
gsize inbuf_size,
gpointer outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
EDataCapture *data_capture;
GConverterResult result;
data_capture = E_DATA_CAPTURE (converter);
/* Output buffer needs to be at least as large as the input buffer.
* The error message should never make it to the user interface so
* no need to translate. */
if (outbuf_size < inbuf_size) {
g_set_error_literal (
error, G_IO_ERROR,
G_IO_ERROR_NO_SPACE,
"EDataCapture needs more space");
return G_CONVERTER_ERROR;
}
memcpy (outbuf, inbuf, inbuf_size);
*bytes_read = *bytes_written = inbuf_size;
g_mutex_lock (&data_capture->priv->byte_array_lock);
g_byte_array_append (
data_capture->priv->byte_array, inbuf, inbuf_size);
if ((flags & G_CONVERTER_INPUT_AT_END) != 0) {
GSource *idle_source;
GMainContext *main_context;
SignalClosure *signal_closure;
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_set (&signal_closure->data_capture, data_capture);
signal_closure->data = g_bytes_new (
data_capture->priv->byte_array->data,
data_capture->priv->byte_array->len);
main_context = e_data_capture_ref_main_context (data_capture);
idle_source = g_idle_source_new ();
g_source_set_callback (
idle_source,
data_capture_emit_finished_idle_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
g_source_attach (idle_source, main_context);
g_source_unref (idle_source);
g_main_context_unref (main_context);
}
g_mutex_unlock (&data_capture->priv->byte_array_lock);
if ((flags & G_CONVERTER_INPUT_AT_END) != 0)
result = G_CONVERTER_FINISHED;
else if ((flags & G_CONVERTER_FLUSH) != 0)
result = G_CONVERTER_FLUSHED;
else
result = G_CONVERTER_CONVERTED;
return result;
}
static void
data_capture_reset (GConverter *converter)
{
EDataCapture *data_capture;
data_capture = E_DATA_CAPTURE (converter);
g_mutex_lock (&data_capture->priv->byte_array_lock);
g_byte_array_set_size (data_capture->priv->byte_array, 0);
g_mutex_unlock (&data_capture->priv->byte_array_lock);
}
static void
e_data_capture_class_init (EDataCaptureClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (EDataCapturePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = data_capture_set_property;
object_class->get_property = data_capture_get_property;
object_class->finalize = data_capture_finalize;
/**
* EDataCapture:main-context:
*
* The #GMainContext from which to emit the #EDataCapture::finished
* signal.
**/
g_object_class_install_property (
object_class,
PROP_MAIN_CONTEXT,
g_param_spec_boxed (
"main-context",
"Main Context",
"The main loop context from "
"which to emit the 'finished' signal",
G_TYPE_MAIN_CONTEXT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* EDataCapture::finished:
* @data_capture: the #EDataCapture that received the signal
* @data: the captured data
*
* The ::finished signal is emitted when there is no more input
* data to be captured.
**/
signals[FINISHED] = g_signal_new (
"finished",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EDataCaptureClass, finished),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_BYTES);
}
static void
e_data_capture_converter_init (GConverterIface *interface)
{
interface->convert = data_capture_convert;
interface->reset = data_capture_reset;
}
static void
e_data_capture_init (EDataCapture *data_capture)
{
data_capture->priv = E_DATA_CAPTURE_GET_PRIVATE (data_capture);
data_capture->priv->byte_array = g_byte_array_new ();
g_mutex_init (&data_capture->priv->byte_array_lock);
}
/**
* e_data_capture_new:
* @main_context: a #GMainContext, or %NULL
*
* Creates a new #EDataCapture. If @main_context is %NULL, then the
* #EDataCapture:finished signal will be emitted from the thread-default
* #GMainContext for this thread.
*
* Returns: an #EDataCapture
**/
EDataCapture *
e_data_capture_new (GMainContext *main_context)
{
return g_object_new (
E_TYPE_DATA_CAPTURE,
"main-context", main_context, NULL);
}
/**
* e_data_capture_ref_main_context:
* @data_capture: an #EDataCapture
*
* Returns the #GMainContext from which the #EDataCapture:finished signal
* is emitted.
*
* The returned #GMainContext is referenced for thread-safety and must be
* unreferenced with g_main_context_unref() when finished with it.
*
* Returns: a #GMainContext
**/
GMainContext *
e_data_capture_ref_main_context (EDataCapture *data_capture)
{
g_return_val_if_fail (E_IS_DATA_CAPTURE (data_capture), NULL);
return g_main_context_ref (data_capture->priv->main_context);
}