EDataCapture is a GConverter that captures data until the end of the input data is seen, then emits a "finished" signal with the captured data in a GBytes instance. When used with GConverterInputStream or GConverterOutputStream, an EDataCapture can discreetly capture stream content for the purpose of caching.
360 lines
9.0 KiB
C
360 lines
9.0 KiB
C
/*
|
|
* 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);
|
|
}
|
|
|