From 372bbc80202c590fadc55a32819cccc61a4e8f94 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 8 Oct 2019 17:52:30 +0100 Subject: [PATCH] paste stream.c back in revise stream.h, implement it next --- libvips/include/vips/Makefile.am | 1 + libvips/include/vips/stream.h | 213 ++++++++++ libvips/include/vips/vips.h | 1 + libvips/iofuncs/Makefile.am | 1 + libvips/iofuncs/stream.c | 683 +++++++++++++++++++++++++++++++ 5 files changed, 899 insertions(+) create mode 100644 libvips/include/vips/stream.h create mode 100644 libvips/iofuncs/stream.c diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 6d296905..f6c1e603 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -1,4 +1,5 @@ pkginclude_HEADERS = \ + stream.h \ basic.h \ type.h \ gate.h \ diff --git a/libvips/include/vips/stream.h b/libvips/include/vips/stream.h new file mode 100644 index 00000000..8dc905f7 --- /dev/null +++ b/libvips/include/vips/stream.h @@ -0,0 +1,213 @@ +/* A byte source/sink .. it can be a pipe, socket, or perhaps a node.js stream. + * + * J.Cupitt, 19/6/14 + */ + +/* + + This file is part of VIPS. + + VIPS 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 Lesser 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifndef VIPS_STREAM_H +#define VIPS_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +#define VIPS_TYPE_STREAM (vips_stream_get_type()) +#define VIPS_STREAM( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_STREAM, VipsStream )) +#define VIPS_STREAM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_STREAM, VipsStreamClass)) +#define VIPS_IS_STREAM( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_STREAM )) +#define VIPS_IS_STREAM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_STREAM )) +#define VIPS_STREAM_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_STREAM, VipsStreamClass )) + +/* Communicate with something like a socket or pipe. + */ +typedef struct _VipsStream { + VipsObject parent_object; + + /*< private >*/ + + /* Read/write this fd if connected to a system pipe/socket. Override + * ::read() and ::write() to do something else. + */ + int descriptor; + + /* If descriptor is a file, the filename we opened. Handy for error + * messages. + */ + const char *filename; + +} VipsStream; + +typedef struct _VipsStreamClass { + VipsObjectClass parent_class; + +} VipsStreamClass; + +GType vips_stream_get_type( void ); + +void vips_stream_attach( VipsStream *stream ); + +#define VIPS_TYPE_STREAM_INPUT (vips_stream_input_get_type()) +#define VIPS_STREAM_INPUT( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_STREAM_INPUT, VipsStreamInput )) +#define VIPS_STREAM_INPUT_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_STREAM_INPUT, VipsStreamInputClass)) +#define VIPS_IS_STREAM_INPUT( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_STREAM_INPUT )) +#define VIPS_IS_STREAM_INPUT_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_STREAM_INPUT )) +#define VIPS_STREAM_INPUT_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_STREAM_INPUT, VipsStreamInputClass )) + +/* Read from something like a socket, file or memory area and present the data + * with a simple seek / read interface. + * + * During the header phase, we save data from unseekable sources in a buffer + * so readers can rewind and read again. We don't buffer data during the + * decode stage. + */ +typedef struct _VipsStreamInput { + VipsStream parent_object; + + /* We have two phases: + * + * During the header phase, we save bytes read from the input (if this + * is an unseekable source) so that we can rewind and try again, if + * necessary. + * + * Once we reach decode pahse, we no longer support seek and the + * buffer of saved data is discarded. + */ + gboolean decode; + + /* TRUE is this input source suports seek. If not, then we save data + * read during header phase in a buffer so we can rewind. + */ + gboolean seekable; + + /*< private >*/ + + /* The current read point. + */ + off_t read_position; + + /* Save data read during header phase here. If we rewind and try + * again, serve data from this until it runs out. + */ + GByteArray *buffer; + + /* For a memory source, the blob we read from. + */ + VipsBlob *blob; + + /* Set on EOF. + */ + gboolean eof; + +} VipsStreamInput; + +typedef struct _VipsStreamInputClass { + VipsStreamClass parent_class; + + /* Subclasses can define these to implement other input methods. + */ + ssize_t (*read)( VipsStreamInput *, unsigned char *, size_t ); + void (*rewind)( VipsStreamInput * ); + +} VipsStreamInputClass; + +GType vips_stream_input_get_type( void ); + +VipsStreamInput *vips_stream_input_new_from_descriptor( int descriptor ); +VipsStreamInput *vips_stream_input_new_from_filename( const char *filename ); +VipsStreamInput *vips_stream_input_new_from_blob( VipsBlob *blob ); +VipsStreamInput *vips_stream_input_new_from_buffer( void *buf, size_t len ); + +ssize_t vips_stream_input_read( VipsStreamInput *input, + unsigned char *buffer, size_t length ); +void vips_stream_input_rewind( VipsStreamInput *input ); +void vips_stream_input_decode( VipsStreamInput *input ); +gboolean vips_stream_input_eof( VipsStreamInput *input ); +unsigned char *vips_stream_input_sniff( VipsStreamInput *input, size_t length ); + +#define VIPS_TYPE_STREAM_OUTPUT (vips_stream_output_get_type()) +#define VIPS_STREAM_OUTPUT( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_STREAM_OUTPUT, VipsStreamOutput )) +#define VIPS_STREAM_OUTPUT_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_STREAM_OUTPUT, VipsStreamOutputClass)) +#define VIPS_IS_STREAM_OUTPUT( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_STREAM_OUTPUT )) +#define VIPS_IS_STREAM_OUTPUT_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_STREAM_OUTPUT )) +#define VIPS_STREAM_OUTPUT_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_STREAM_OUTPUT, VipsStreamOutputClass )) + +/* Read or output to something like a socket or pipe. + */ +typedef struct _VipsStreamOutput { + VipsStream parent_object; + + /*< private >*/ + +} VipsStreamOutput; + +typedef struct _VipsStreamOutputClass { + VipsStreamClass parent_class; + + /* If defined, output some bytes with this. Otherwise use write(). + */ + ssize_t (*write)( VipsStreamOutput *, const unsigned char *, size_t ); + +} VipsStreamOutputClass; + +GType vips_stream_output_get_type( void ); + +VipsStreamOutput *vips_stream_output_new_from_descriptor( int descriptor ); +VipsStreamOutput *vips_stream_output_new_from_filename( const char *filename ); +int vips_stream_output_write( VipsStreamOutput *stream, + const unsigned char *buffer, size_t buffer_size ); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /*VIPS_STREAM_H*/ diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index 0bedf6f5..32146aa6 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -113,6 +113,7 @@ extern "C" { #include #include #include +#include #include #include diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index 0cbfdf9a..24a720d7 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -1,6 +1,7 @@ noinst_LTLIBRARIES = libiofuncs.la libiofuncs_la_SOURCES = \ + stream.c \ dbuf.c \ reorder.c \ vipsmarshal.h \ diff --git a/libvips/iofuncs/stream.c b/libvips/iofuncs/stream.c new file mode 100644 index 00000000..af74d140 --- /dev/null +++ b/libvips/iofuncs/stream.c @@ -0,0 +1,683 @@ +/* A byte source/sink .. it can be a pipe, file descriptor, memory area, + * socket, or perhaps a node.js stream. + * + * J.Cupitt, 19/6/14 + */ + +/* + + This file is part of VIPS. + + VIPS 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 Lesser 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include + +#include +#include + +/* Try to make an O_BINARY ... sometimes need the leading '_'. + */ +#ifdef BINARY_OPEN +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#endif /*_O_BINARY*/ +#endif /*!O_BINARY*/ +#endif /*BINARY_OPEN*/ + +/* If we have O_BINARY, add it to a mode flags set. + */ +#ifdef O_BINARY +#define BINARYIZE(M) ((M) | O_BINARY) +#else /*!O_BINARY*/ +#define BINARYIZE(M) (M) +#endif /*O_BINARY*/ + +#define MODE_READ BINARYIZE (O_RDONLY) +#define MODE_READWRITE BINARYIZE (O_RDWR) +#define MODE_WRITE BINARYIZE (O_WRONLY | O_CREAT | O_TRUNC) + +/** + * SECTION: stream + * @short_description: a source/sink of bytes, perhaps a network socket + * @stability: Stable + * @see_also: foreign + * @include: vips/vips.h + * + * A #VipsStream is a source or sink of bytes for something like jpeg loading. + * It can be connected to a network socket, for example, or perhaps a node.js + * stream, or to an area of memory. + * + * Subclass to add other input sources. + */ + +/** + * VipsStream: + * + * A #VipsStream is a source of bytes for something like jpeg loading. It can + * be connected to a network socket, for example. + */ + +G_DEFINE_ABSTRACT_TYPE( VipsStream, vips_stream, VIPS_TYPE_OBJECT ); + +static void +vips_stream_finalize( GObject *gobject ) +{ + VipsStream *stream = (VipsStream *) gobject; + +#ifdef VIPS_DEBUG + VIPS_DEBUG_MSG( "vips_stream_finalize: " ); + vips_object_print_name( VIPS_OBJECT( gobject ) ); + VIPS_DEBUG_MSG( "\n" ); +#endif /*VIPS_DEBUG*/ + + if( stream->descriptor >= 0 ) { + vips_tracked_close( stream->descriptor ); + stream->descriptor = -1; + } + VIPS_FREE( stream->filename ); + + G_OBJECT_CLASS( vips_stream_parent_class )->finalize( gobject ); +} + +static void +vips_stream_class_init( VipsStreamClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + + gobject_class->finalize = vips_stream_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + VIPS_ARG_INT( class, "descriptor", 1, + _( "Descriptor" ), + _( "File descriptor for read or write" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsStream, descriptor ), + -1, 1000000000, 0 ); + + VIPS_ARG_STRING( class, "filename", 2, + _( "Filename" ), + _( "Name of file to open" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsStream, filename ), + NULL ); + +} + +static void +vips_stream_init( VipsStream *stream ) +{ + stream->descriptor = -1; +} + +void +vips_stream_attach( VipsStream *stream ) +{ +#ifdef VIPS_DEBUG + VIPS_DEBUG_MSG( "vips_stream_attach: " ); + vips_object_print_name( VIPS_OBJECT( stream ) ); + VIPS_DEBUG_MSG( "\n" ); +#endif /*VIPS_DEBUG*/ + + g_assert( !stream->attached ); + stream->attached = TRUE; +} + +G_DEFINE_TYPE( VipsStreamInput, vips_stream_input, VIPS_TYPE_STREAM ); + +static void +vips_stream_input_finalize( GObject *gobject ) +{ + VipsStreamInput *stream = (VipsStreamInput *) gobject; + + VIPS_FREE( stream->buffer ); + + G_OBJECT_CLASS( vips_stream_input_parent_class )->finalize( gobject ); +} + +static int +vips_stream_input_build( VipsObject *object ) +{ + VipsStreamInput *stream = VIPS_STREAM_INPUT( object ); + + VIPS_DEBUG_MSG( "vips_stream_input_build: %p\n", stream ); + + if( VIPS_OBJECT_CLASS( vips_stream_input_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips_object_argument_isset( object, "filename" ) && + !vips_object_argument_isset( object, "descriptor" ) ) { + const char *filename = VIPS_STREAM( stream )->filename; + + int fd; + + if( (fd = vips_tracked_open( filename, MODE_READ )) == -1 ) { + vips_error_system( errno, filename, + "%s", _( "unable to open for read" ) ); + return( -1 ); + } + + g_object_set( object, "descriptor", fd, NULL ); + } + + g_assert( !stream->buffer ); + g_assert( stream->buffer_size > 0 && + stream->buffer_size < 1000000 ); + stream->buffer = g_new0( unsigned char, stream->buffer_size ); + stream->next_byte = NULL; + stream->bytes_available = 0; + + return( 0 ); +} + +static void +vips_stream_input_class_init( VipsStreamInputClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->finalize = vips_stream_input_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->build = vips_stream_input_build; + + VIPS_ARG_INT( class, "buffer_size", 2, + _( "Buffer size" ), + _( "Size of input buffer" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsStreamInput, buffer_size ), + 1, 10000000, 4096 ); + + VIPS_ARG_BOXED( class, "blob", 3, + _( "Blob" ), + _( "Memory area to read from" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsStreamInput, blob ), + VIPS_TYPE_BLOB ); +} + +static void +vips_stream_input_init( VipsStreamInput *stream ) +{ + stream->buffer_size = 4096; +} + +/** + * vips_stream_input_new_from_descriptor: + * @descriptor: read from this file descriptor + * + * Create a stream attached to a file descriptor. @descriptor is closed when + * the #VipsStream is finalized. + * + * #VipsStream s start out empty, you need to call + * vips_stream_input_refill() to fill them with bytes. + * + * See also: vips_stream_input_refill(). + * + * Returns: a new #VipsStream + */ +VipsStreamInput * +vips_stream_input_new_from_descriptor( int descriptor ) +{ + VipsStreamInput *stream; + + VIPS_DEBUG_MSG( "vips_stream_input_new_from_descriptor: %d\n", + descriptor ); + + stream = VIPS_STREAM_INPUT( + g_object_new( VIPS_TYPE_STREAM_INPUT, + "descriptor", descriptor, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( stream ) ) ) { + VIPS_UNREF( stream ); + return( NULL ); + } + + return( stream ); +} + +/** + * vips_stream_input_new_from_filename: + * @descriptor: read from this filename + * + * Create a stream attached to a file. + * + * #VipsStream s start out empty, you need to call + * vips_stream_input_refill() to fill them with bytes. + * + * See also: vips_stream_input_refill(). + * + * Returns: a new #VipsStream + */ +VipsStreamInput * +vips_stream_input_new_from_filename( const char *filename ) +{ + VipsStreamInput *stream; + + VIPS_DEBUG_MSG( "vips_stream_input_new_from_filename: %s\n", + filename ); + + stream = VIPS_STREAM_INPUT( + g_object_new( VIPS_TYPE_STREAM_INPUT, + "filename", filename, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( stream ) ) ) { + VIPS_UNREF( stream ); + return( NULL ); + } + + return( stream ); +} + +/** + * vips_stream_input_new_from_blob: + * @blob: memory area to load + * + * Create a stream attached to an area of memory. + * + * #VipsStream s start out empty, you need to call + * vips_stream_input_refill() to fill them with bytes. + * + * See also: vips_stream_input_refill(). + * + * Returns: a new #VipsStream + */ +VipsStreamInput * +vips_stream_input_new_from_blob( VipsBlob *blob ) +{ + VipsStreamInput *stream; + + VIPS_DEBUG_MSG( "vips_stream_input_new_from_blob: %p\n", blob ); + + stream = VIPS_STREAM_INPUT( + g_object_new( VIPS_TYPE_STREAM_INPUT, + "blob", blob, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( stream ) ) ) { + VIPS_UNREF( stream ); + return( NULL ); + } + + return( stream ); +} + +/** + * vips_stream_input_new_from_buffer: + * @buf: memory area to load + * @len: size of memory area + * + * Create a stream attached to an area of memory. + * + * You must not free @buf while the stream is active. + * + * #VipsStream s start out empty, you need to call + * vips_stream_input_refill() to fill them with bytes. + * + * See also: vips_stream_input_refill(). + * + * Returns: a new #VipsStream + */ +VipsStreamInput * +vips_stream_input_new_from_buffer( void *buf, size_t len ) +{ + VipsStreamInput *stream; + VipsBlob *blob; + + VIPS_DEBUG_MSG( "vips_stream_input_new_from_buffer: %p, len = %zd\n", + buf, len ); + + /* We don't take a copy of the data or free it. + */ + blob = vips_blob_new( NULL, buf, len ); + + stream = vips_stream_input_new_from_blob( blob ); + + vips_area_unref( VIPS_AREA( blob ) ); + + return( stream ); +} + +static ssize_t +vips_stream_input_read( VipsStreamInput *stream, + unsigned char *buffer, size_t buffer_size ) +{ + VipsStreamInputClass *class = VIPS_STREAM_INPUT_GET_CLASS( stream ); + + ssize_t len; + + if( class->read ) + len = class->read( stream, buffer, buffer_size ); + else if( VIPS_STREAM( stream )->descriptor > 0 ) + len = read( VIPS_STREAM( stream )->descriptor, + buffer, buffer_size ); + else + g_assert( 0 ); + + return( len ); +} + +/** + * vips_stream_input_refill: + * @stream: fill the stream buffer + * + * Reads data into the stream buffer. + * + * Returns: 0 on success, -1 on error or EOF. + */ +int +vips_stream_input_refill( VipsStreamInput *stream ) +{ + ssize_t len; + + /* If this is a memory source, we just set next_byte on the first + * call and we're done. + * + * If we're not attached, we can read even when the buffer isn't + * empty. Just move the unused bytes down and top up. + * + * If we're attached, we don't own the next_byte and bytes_available + * values (they are run by the load library) so we can't do this. + * + * We need to be able to refill the unattached buffer so we can do + * file format sniffing. + */ + if( stream->blob ) { + /* On the first call we read the whole of the input blob. On + * the second call, we EOF. + */ + if( stream->next_byte ) + len = 0; + else + stream->next_byte = (void *) + vips_blob_get( stream->blob, (size_t *) &len ); + } + else if( !VIPS_STREAM( stream )->attached ) { + memmove( stream->buffer, stream->next_byte, + stream->bytes_available ); + stream->next_byte = stream->buffer; + + len = vips_stream_input_read( stream, + stream->next_byte, + stream->buffer_size - stream->bytes_available ); + } + else { + len = vips_stream_input_read( stream, + stream->buffer, stream->buffer_size ); + stream->next_byte = stream->buffer; + + /* This is incremented below, after we check the return value. + */ + stream->bytes_available = 0; + } + +#ifdef VIPS_DEBUG + if( len > 0 ) + VIPS_DEBUG_MSG( "vips_stream_input_refill: " + "read %zd bytes\n", len ); +#endif /*VIPS_DEBUG*/ + + if( len <= 0 ) { + stream->eof = TRUE; + + if( len < 0 ) + vips_error_system( errno, "read", + "%s", _( "read error" ) ); + + return( -1 ); + } + + stream->bytes_available += len; + + return( 0 ); +} + +gboolean +vips_stream_input_eof( VipsStreamInput *stream ) +{ + if( !stream->eof && + stream->bytes_available == 0 && + !VIPS_STREAM( stream )->attached ) + vips_stream_input_refill( stream ); + + return( stream->eof ); +} + +void +vips_stream_input_detach( VipsStreamInput *stream, + unsigned char *next_byte, size_t bytes_available ) +{ + VIPS_DEBUG_MSG( "vips_stream_input_detach:\n" ); + + g_assert( VIPS_STREAM( stream )->attached ); + VIPS_STREAM( stream )->attached = FALSE; + + stream->next_byte = next_byte; + stream->bytes_available = bytes_available; +} + +/** + * vips_stream_input_sniff: + * @bytes: number of bytes to sniff + * + * Return a pointer to the start of the next @bytes bytes. This can only be + * used in detached mode. + */ +unsigned char * +vips_stream_input_sniff( VipsStreamInput *stream, int bytes ) +{ + if( VIPS_STREAM( stream )->attached ) { + g_warning( "%s", _( "cannot sniff attached streams" ) ); + return( NULL ); + } + + while( stream->bytes_available < bytes ) + if( vips_stream_input_refill( stream ) ) { + g_warning( "%s", + _( "not enough bytes in stream to sniff" ) ); + return( NULL ); + } + + return( stream->next_byte ); +} + +G_DEFINE_TYPE( VipsStreamOutput, vips_stream_output, VIPS_TYPE_STREAM ); + +static int +vips_stream_output_build( VipsObject *object ) +{ + VipsStreamOutput *stream = VIPS_STREAM_OUTPUT( object ); + + VIPS_DEBUG_MSG( "vips_stream_output_build: %p\n", stream ); + + if( VIPS_OBJECT_CLASS( vips_stream_output_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips_object_argument_isset( object, "filename" ) && + !vips_object_argument_isset( object, "descriptor" ) ) { + const char *filename = VIPS_STREAM( stream )->filename; + + int fd; + + if( (fd = vips_tracked_open( filename, MODE_WRITE )) == -1 ) { + vips_error_system( errno, filename, + "%s", _( "unable to open for write" ) ); + return( -1 ); + } + + g_object_set( object, "descriptor", fd, NULL ); + } + + return( 0 ); +} + +static void +vips_stream_output_class_init( VipsStreamOutputClass *class ) +{ + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + vobject_class->build = vips_stream_output_build; +} + +static void +vips_stream_output_init( VipsStreamOutput *stream ) +{ +} + +/** + * vips_stream_output_new_from_descriptor: + * @descriptor: write to this file descriptor + * + * Create a stream attached to a file descriptor. + * @descriptor is closed when + * the #VipsStream is finalized. + * + * See also: vips_stream_output_write(). + * + * Returns: a new #VipsStream + */ +VipsStreamOutput * +vips_stream_output_new_from_descriptor( int descriptor ) +{ + VipsStreamOutput *stream; + + VIPS_DEBUG_MSG( "vips_stream_output_new_from_descriptor: %d\n", + descriptor ); + + stream = VIPS_STREAM_OUTPUT( + g_object_new( VIPS_TYPE_STREAM_OUTPUT, + "descriptor", descriptor, + "filename", "descriptor", + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( stream ) ) ) { + VIPS_UNREF( stream ); + return( NULL ); + } + + return( stream ); +} + +/** + * vips_stream_output_new_from_filename: + * @filename: write to this file + * + * Create a stream attached to a file. + * + * See also: vips_stream_output_write(). + * + * Returns: a new #VipsStream + */ +VipsStreamOutput * +vips_stream_output_new_from_filename( const char *filename ) +{ + VipsStreamOutput *stream; + + VIPS_DEBUG_MSG( "vips_stream_output_new_from_filename: %s\n", + filename ); + + stream = VIPS_STREAM_OUTPUT( + g_object_new( VIPS_TYPE_STREAM_OUTPUT, + "filename", filename, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( stream ) ) ) { + VIPS_UNREF( stream ); + return( NULL ); + } + + return( stream ); +} + +void +vips_stream_output_detach( VipsStreamOutput *stream ) +{ + VIPS_DEBUG_MSG( "vips_stream_output_detach:\n" ); + + g_assert( VIPS_STREAM( stream )->attached ); + VIPS_STREAM( stream )->attached = FALSE; +} + +int +vips_stream_output_write( VipsStreamOutput *stream, + const unsigned char *buffer, size_t buffer_size ) +{ + VipsStreamOutputClass *class = VIPS_STREAM_OUTPUT_GET_CLASS( stream ); + + while( buffer_size > 0 ) { + ssize_t len; + + if( class->write ) + len = class->write( stream, buffer, buffer_size ); + else + len = write( VIPS_STREAM( stream )->descriptor, + buffer, buffer_size ); + +#ifdef VIPS_DEBUG + if( len > 0 ) + VIPS_DEBUG_MSG( "vips_stream_output_write: " + "written %zd bytes\n", len ); +#endif /*VIPS_DEBUG*/ + + /* len == 0 isn't strictly an error, but we treat it as one to + * make sure we don't get stuck in this loop. + */ + if( len <= 0 ) { + vips_error_system( errno, + VIPS_OBJECT( stream )->nickname, + "%s", _( "write error" ) ); + return( -1 ); + } + + buffer_size -= len; + buffer += len; + } + + return( 0 ); +} +