/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-stream.c : abstract class for a stream
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* 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.
*
* 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, see .
*
* Authors: Bertrand Guiheneuf
*/
#include "camel-stream.h"
#include
#include
#include
#define CAMEL_STREAM_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_STREAM, CamelStreamPrivate))
struct _CamelStreamPrivate {
GIOStream *base_stream;
GMutex base_stream_lock;
};
enum {
PROP_0,
PROP_BASE_STREAM
};
/* Forward Declarations */
static void camel_stream_seekable_init (GSeekableIface *iface);
G_DEFINE_TYPE_WITH_CODE (
CamelStream,
camel_stream,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (
G_TYPE_SEEKABLE,
camel_stream_seekable_init))
static void
stream_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_BASE_STREAM:
camel_stream_set_base_stream (
CAMEL_STREAM (object),
g_value_get_object (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
stream_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_BASE_STREAM:
g_value_take_object (
value,
camel_stream_ref_base_stream (
CAMEL_STREAM (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
stream_dispose (GObject *object)
{
CamelStreamPrivate *priv;
priv = CAMEL_STREAM_GET_PRIVATE (object);
g_clear_object (&priv->base_stream);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (camel_stream_parent_class)->dispose (object);
}
static void
stream_finalize (GObject *object)
{
CamelStreamPrivate *priv;
priv = CAMEL_STREAM_GET_PRIVATE (object);
g_mutex_clear (&priv->base_stream_lock);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_stream_parent_class)->finalize (object);
}
static gssize
stream_read (CamelStream *stream,
gchar *buffer,
gsize n,
GCancellable *cancellable,
GError **error)
{
GIOStream *base_stream;
gssize n_bytes_read = 0;
base_stream = camel_stream_ref_base_stream (stream);
if (base_stream != NULL) {
GInputStream *input_stream;
input_stream = g_io_stream_get_input_stream (base_stream);
n_bytes_read = g_input_stream_read (
input_stream, buffer, n, cancellable, error);
g_object_unref (base_stream);
}
stream->eos = n_bytes_read <= 0;
return n_bytes_read;
}
static gssize
stream_write (CamelStream *stream,
const gchar *buffer,
gsize n,
GCancellable *cancellable,
GError **error)
{
GIOStream *base_stream;
gssize n_bytes_written = -1;
base_stream = camel_stream_ref_base_stream (stream);
if (base_stream != NULL) {
GOutputStream *output_stream;
gsize n_written = 0;
output_stream = g_io_stream_get_output_stream (base_stream);
stream->eos = FALSE;
if (g_output_stream_write_all (output_stream, buffer, n, &n_written, cancellable, error))
n_bytes_written = (gssize) n_written;
else
n_bytes_written = -1;
g_object_unref (base_stream);
} else {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot write with no base stream"));
}
return n_bytes_written;
}
static gint
stream_close (CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
GIOStream *base_stream;
gboolean success = TRUE;
base_stream = camel_stream_ref_base_stream (stream);
if (base_stream != NULL) {
success = g_io_stream_close (
base_stream, cancellable, error);
g_object_unref (base_stream);
}
return success ? 0 : -1;
}
static gint
stream_flush (CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
GIOStream *base_stream;
gboolean success = TRUE;
base_stream = camel_stream_ref_base_stream (stream);
if (base_stream != NULL) {
GOutputStream *output_stream;
output_stream = g_io_stream_get_output_stream (base_stream);
success = g_output_stream_flush (
output_stream, cancellable, error);
g_object_unref (base_stream);
}
return success ? 0 : -1;
}
static gboolean
stream_eos (CamelStream *stream)
{
return stream->eos;
}
static goffset
stream_tell (GSeekable *seekable)
{
CamelStream *stream;
GIOStream *base_stream;
goffset position = 0;
stream = CAMEL_STREAM (seekable);
base_stream = camel_stream_ref_base_stream (stream);
if (G_IS_SEEKABLE (base_stream)) {
position = g_seekable_tell (G_SEEKABLE (base_stream));
} else if (base_stream != NULL) {
g_critical (
"Stream type '%s' is not seekable",
G_OBJECT_TYPE_NAME (base_stream));
}
g_clear_object (&base_stream);
return position;
}
static gboolean
stream_can_seek (GSeekable *seekable)
{
CamelStream *stream;
GIOStream *base_stream;
gboolean can_seek = FALSE;
stream = CAMEL_STREAM (seekable);
base_stream = camel_stream_ref_base_stream (stream);
if (G_IS_SEEKABLE (base_stream))
can_seek = g_seekable_can_seek (G_SEEKABLE (base_stream));
g_clear_object (&base_stream);
return can_seek;
}
static gboolean
stream_seek (GSeekable *seekable,
goffset offset,
GSeekType type,
GCancellable *cancellable,
GError **error)
{
CamelStream *stream;
GIOStream *base_stream;
gboolean success = FALSE;
stream = CAMEL_STREAM (seekable);
base_stream = camel_stream_ref_base_stream (stream);
if (G_IS_SEEKABLE (base_stream)) {
stream->eos = FALSE;
success = g_seekable_seek (
G_SEEKABLE (base_stream),
offset, type, cancellable, error);
} else if (base_stream != NULL) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Stream type '%s' is not seekable"),
G_OBJECT_TYPE_NAME (base_stream));
} else {
g_warn_if_reached ();
}
g_clear_object (&base_stream);
return success;
}
static gboolean
stream_can_truncate (GSeekable *seekable)
{
CamelStream *stream;
GIOStream *base_stream;
gboolean can_truncate = FALSE;
stream = CAMEL_STREAM (seekable);
base_stream = camel_stream_ref_base_stream (stream);
if (G_IS_SEEKABLE (base_stream))
can_truncate = g_seekable_can_truncate (
G_SEEKABLE (base_stream));
g_clear_object (&base_stream);
return can_truncate;
}
static gboolean
stream_truncate (GSeekable *seekable,
goffset offset,
GCancellable *cancellable,
GError **error)
{
CamelStream *stream;
GIOStream *base_stream;
gboolean success = FALSE;
stream = CAMEL_STREAM (seekable);
base_stream = camel_stream_ref_base_stream (stream);
if (G_IS_SEEKABLE (base_stream)) {
success = g_seekable_truncate (
G_SEEKABLE (base_stream),
offset, cancellable, error);
} else if (base_stream != NULL) {
g_set_error (
error, G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Stream type '%s' is not seekable"),
G_OBJECT_TYPE_NAME (base_stream));
} else {
g_warn_if_reached ();
}
g_clear_object (&base_stream);
return success;
}
static void
camel_stream_class_init (CamelStreamClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelStreamPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = stream_set_property;
object_class->get_property = stream_get_property;
object_class->dispose = stream_dispose;
object_class->finalize = stream_finalize;
class->read = stream_read;
class->write = stream_write;
class->close = stream_close;
class->flush = stream_flush;
class->eos = stream_eos;
g_object_class_install_property (
object_class,
PROP_BASE_STREAM,
g_param_spec_object (
"base-stream",
"Base Stream",
"The base GIOStream",
G_TYPE_IO_STREAM,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
}
static void
camel_stream_seekable_init (GSeekableIface *iface)
{
iface->tell = stream_tell;
iface->can_seek = stream_can_seek;
iface->seek = stream_seek;
iface->can_truncate = stream_can_truncate;
iface->truncate_fn = stream_truncate;
}
static void
camel_stream_init (CamelStream *stream)
{
stream->priv = CAMEL_STREAM_GET_PRIVATE (stream);
g_mutex_init (&stream->priv->base_stream_lock);
}
/**
* camel_stream_new:
* @base_stream: a #GIOStream
*
* Creates a #CamelStream as a thin wrapper for @base_stream.
*
* Returns: a #CamelStream
*
* Since: 3.12
**/
CamelStream *
camel_stream_new (GIOStream *base_stream)
{
g_return_val_if_fail (G_IS_IO_STREAM (base_stream), NULL);
return g_object_new (
CAMEL_TYPE_STREAM, "base-stream", base_stream, NULL);
}
/**
* camel_stream_ref_base_stream:
* @stream: a #CamelStream
*
* Returns the #GIOStream for @stream. This is only valid if @stream was
* created with camel_stream_new(). For all other #CamelStream subclasses
* this function returns %NULL.
*
* The returned #GIOStream is referenced for thread-safety and should be
* unreferenced with g_object_unref() when finished with it.
*
* Returns: a #GIOStream, or %NULL
*
* Since: 3.12
**/
GIOStream *
camel_stream_ref_base_stream (CamelStream *stream)
{
GIOStream *base_stream = NULL;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
g_mutex_lock (&stream->priv->base_stream_lock);
if (stream->priv->base_stream != NULL)
base_stream = g_object_ref (stream->priv->base_stream);
g_mutex_unlock (&stream->priv->base_stream_lock);
return base_stream;
}
/**
* camel_stream_set_base_stream:
* @stream: a #CamelStream
* @base_stream: a #GIOStream
*
* Replaces the #GIOStream passed to camel_stream_new() with @base_stream.
* The new @base_stream should wrap the original #GIOStream, such as when
* adding Transport Layer Security after issuing a STARTTLS command.
*
* Since: 3.12
**/
void
camel_stream_set_base_stream (CamelStream *stream,
GIOStream *base_stream)
{
g_return_if_fail (CAMEL_IS_STREAM (stream));
g_return_if_fail (G_IS_IO_STREAM (base_stream));
g_mutex_lock (&stream->priv->base_stream_lock);
g_clear_object (&stream->priv->base_stream);
stream->priv->base_stream = g_object_ref (base_stream);
g_mutex_unlock (&stream->priv->base_stream_lock);
g_object_notify (G_OBJECT (stream), "base-stream");
}
/**
* camel_stream_read:
* @stream: a #CamelStream object.
* @buffer: output buffer
* @n: max number of bytes to read.
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Attempts to read up to @len bytes from @stream into @buf.
*
* Returns: the number of bytes actually read, or %-1 on error and set
* errno.
**/
gssize
camel_stream_read (CamelStream *stream,
gchar *buffer,
gsize n,
GCancellable *cancellable,
GError **error)
{
CamelStreamClass *class;
gssize n_bytes;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
g_return_val_if_fail (n == 0 || buffer, -1);
class = CAMEL_STREAM_GET_CLASS (stream);
g_return_val_if_fail (class->read != NULL, -1);
n_bytes = class->read (stream, buffer, n, cancellable, error);
CAMEL_CHECK_GERROR (stream, read, n_bytes >= 0, error);
return n_bytes;
}
/**
* camel_stream_write:
* @stream: a #CamelStream object
* @buffer: buffer to write.
* @n: number of bytes to write
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Attempts to write up to @n bytes of @buffer into @stream.
*
* Returns: the number of bytes written to the stream, or %-1 on error
* along with setting errno.
**/
gssize
camel_stream_write (CamelStream *stream,
const gchar *buffer,
gsize n,
GCancellable *cancellable,
GError **error)
{
CamelStreamClass *class;
gssize n_bytes;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
g_return_val_if_fail (n == 0 || buffer, -1);
class = CAMEL_STREAM_GET_CLASS (stream);
g_return_val_if_fail (class->write != NULL, -1);
n_bytes = class->write (stream, buffer, n, cancellable, error);
CAMEL_CHECK_GERROR (stream, write, n_bytes >= 0, error);
return n_bytes;
}
/**
* camel_stream_flush:
* @stream: a #CamelStream object
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Flushes any buffered data to the stream's backing store. Only
* meaningful for writable streams.
*
* Returns: %0 on success or %-1 on fail along with setting @error
**/
gint
camel_stream_flush (CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
CamelStreamClass *class;
gint retval;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
class = CAMEL_STREAM_GET_CLASS (stream);
g_return_val_if_fail (class->flush != NULL, -1);
retval = class->flush (stream, cancellable, error);
CAMEL_CHECK_GERROR (stream, flush, retval == 0, error);
return retval;
}
/**
* camel_stream_close:
* @stream: a #CamelStream object
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Closes the stream.
*
* Returns: %0 on success or %-1 on error.
**/
gint
camel_stream_close (CamelStream *stream,
GCancellable *cancellable,
GError **error)
{
CamelStreamClass *class;
gint retval;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
class = CAMEL_STREAM_GET_CLASS (stream);
g_return_val_if_fail (class->close != NULL, -1);
retval = class->close (stream, cancellable, error);
CAMEL_CHECK_GERROR (stream, close, retval == 0, error);
return retval;
}
/**
* camel_stream_eos:
* @stream: a #CamelStream object
*
* Tests if there are bytes left to read on the @stream object.
*
* Returns: %TRUE on EOS or %FALSE otherwise.
**/
gboolean
camel_stream_eos (CamelStream *stream)
{
CamelStreamClass *class;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), TRUE);
class = CAMEL_STREAM_GET_CLASS (stream);
g_return_val_if_fail (class->eos != NULL, TRUE);
return class->eos (stream);
}
/***************** Utility functions ********************/
/**
* camel_stream_write_string:
* @stream: a #CamelStream object
* @string: a string
* @error: return location for a #GError, or %NULL
*
* Writes the string to the stream.
*
* Returns: the number of characters written or %-1 on error.
**/
gssize
camel_stream_write_string (CamelStream *stream,
const gchar *string,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
g_return_val_if_fail (string != NULL, -1);
return camel_stream_write (
stream, string, strlen (string), cancellable, error);
}
/**
* camel_stream_write_to_stream:
* @stream: source #CamelStream object
* @output_stream: destination #CamelStream object
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Write all of a stream (until eos) into another stream, in a
* blocking fashion.
*
* Returns: %-1 on error, or the number of bytes succesfully
* copied across streams.
**/
gssize
camel_stream_write_to_stream (CamelStream *stream,
CamelStream *output_stream,
GCancellable *cancellable,
GError **error)
{
gchar tmp_buf[4096];
gssize total = 0;
gssize nb_read;
gssize nb_written;
g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
g_return_val_if_fail (CAMEL_IS_STREAM (output_stream), -1);
while (!camel_stream_eos (stream)) {
nb_read = camel_stream_read (
stream, tmp_buf, sizeof (tmp_buf),
cancellable, error);
if (nb_read < 0)
return -1;
else if (nb_read > 0) {
nb_written = 0;
while (nb_written < nb_read) {
gssize len = camel_stream_write (
output_stream,
tmp_buf + nb_written,
nb_read - nb_written,
cancellable, error);
if (len < 0)
return -1;
nb_written += len;
}
total += nb_written;
}
}
return total;
}