Stream Class Overview

There are a number of stream classes included with GMime, but we are only going to go over the more widely useful stream classes. You should be able to figure out the others on your own.

We've already seen GMimeStreamFile and GMimeStreamFs in action in the prevous chapter, so lets skip them and start with GMimeStreamMem.

GMimeStremMem is a stream abstraction that reads and writes to a memory buffer. Like any other stream, the basic stream functions (read, write, seek, substream, eos, etc) apply here as well. Internally, GMimeStreamMem uses the GLib GByteArray structure for storage so you may want to read up on that.

There are several ways to instantiate a GMimeStreamMem object. You will probably use g_mime_stream_mem_new() most of the time. There may be times, however, when you will already have a memory buffer that you'd like to use as a stream. There are several ways to create a GMimeStreamMem object to use this buffer (or a copy of it).

The first is g_mime_stream_mem_new_with_byte_array(). This assumes that you are already using a GByteArray and want to use it as a stream. As explained in the previous chapter about GMimeStreamFile and ownership, the same applies here. When the GMimeStreamMem is destroyed, so is the GByteArray structure and the memory buffer it contained. To get around this, create a new GMimeStreamMem object using g_mime_stream_mem_new() and then use g_mime_stream_mem_set_byte_array() to set the GByteArray as the memory buffer. This will make it so that GMimeStreamMem does not own the GByteArray, so when the GMimeStremMem object is destroyed, the GByteArray will remain.

Also at your disposal for creating GMimeStreamMem objects with an initial buffer is g_mime_stream_mem_new_with_buffer(). This function, however, will duplicate the buffer passed to it so if you have memory quotas you are trying to keep, you may wish to find a way to use one of the above methods.

That pretty much sums up how to use GMimeStreamMem. The next most widely used stream class is probably GMimeStreamBuffer. This stream class actually wraps another stream object adding additional functionality such as read and write buffering and a few additional read methods.

As you may or may not know, buffering reads and writes is a great way to improve I/O performance in applications. The time it takes to do a lot of small reads and writes accumulates fast.

When using a GMimeStreamBuffer in GMIME_STREAM_BUFFER_BLOCK_READ mode, a block of 4K (4096 bytes) will be read into an intermediate buffer. Each time your application performs a read on this GMimeStreamBuffer stream, a chunk of that intermediate buffer will be copied to your read buffer until all 4K have been read, at which point GMimeStreamBuffer will pre-scan the next 4K and so on.

Similarly, using mode GMIME_STREAM_BUFFER_BLOCK_WRITE will copy each of your application write-buffers into an intermediate 4K buffer. When that 4K buffer fills up, it will be flushed to the underlying stream. You may also use g_mime_stream_flush() to force the intermediate buffer to be written to the underlying stream.

Note that the intermediate buffer size is 4096 bytes. You should be aware that if you will mostly be reading and writing blocks of larger than 4K, it is probably best to avoid using GMimeStreamBuffer as it will not likely gain you any performance and will likely decrease performance instead.

GMimeStreamBuffer also adds 2 convenience functions for reading. While they will both work with any stream class, they are obviously much faster if used with a GMimeStreamBuffer in mode GMIME_STREAM_BUFFER_BLOCK_READ. These functions are:

ssize_t g_mime_stream_buffer_gets (GMimeStream *stream, char *buf, size_t max);

void    g_mime_stream_buffer_readln (GMimeStream *stream, GByteArray *buffer);
      

The first function is similar to Standard C's fgets() function (although the arguments are in a slightly different order). It reads up to the first max - 1 bytes, stopping after a \n character if found. buf will always be nul-terminated.

The second function, g_mime_stream_buffer_readln(), has no Standard C equivalent that I am aware of, but you should get the idea of what it does based on the function name (I hope). It reads exactly one (1) line (including the \n character) and appends it to the end of buffer.

The last stream class you really need to know (and the last one I have the patience to explain) is GMimeStreamFilter. This is a special stream class which you can attach GMimeFilters to so that reading/writing to this stream will automagically convert the stream from one form to another. GMime uses this stream internally for converting base64 encoded attachments into their raw form and vice versa.

Note

As previously mentioned in the last chapter concerning g_mime_stream_reset(), resetting a GMimeStreamFilter stream will also reset all of the filters applied.

A great example usage of GMimeStreamFilter can be found in the src/uuencode.c source file found in the source distribution. Here's a clip of that source file illustrating how to use stream filters:

	GMimeStream *istream, *ostream, *fstream;
	GMimeFilter *filter;
	int fd;
	
	...
	
	if (g_mime_stream_printf (ostream, "begin %.3o %s\n", st.st_mode & 0777, name) == -1) {
		fprintf (stderr, "%s: %s\n", progname, strerror (errno));
		g_object_unref (ostream);
		exit (1);
	}
	
	istream = g_mime_stream_fs_new (fd);
	
	fstream = g_mime_stream_filter_new_with_stream (ostream);
	
	filter = g_mime_filter_basic_new_type (GMIME_FILTER_BASIC_UU_ENC);
	g_mime_stream_filter_add ((GMimeStreamFilter *) fstream, filter);
	g_object_unref (filter);
	
	if (g_mime_stream_write_to_stream (istream, fstream) == -1) {
		fprintf (stderr, "%s: %s\n", progname, strerror (errno));
		g_object_unref (fstream);
		g_object_unref (istream);
		g_object_unref (ostream);
		exit (1);
	}
	
	g_mime_stream_flush (fstream);
	g_object_unref (fstream);
	g_object_unref (istream);
	
	if (g_mime_stream_write_string (ostream, "end\n") == -1) {
		fprintf (stderr, "%s: %s\n", progname, strerror (errno));
		g_object_unref (ostream);
		exit (1);
	}
	
	g_object_unref (ostream);
      

The above snippet of code will read the contents of the input stream (istream) and write it to our output stream (ostream), but only after it has passed through our filter-stream (fstream). The filter attached to fstream is one of the basic MIME filters that encodes data in the traditional UUCP format. You have probably run a program to do this many times in the past using the Unix command uuencode. Never thought writing a replacement for uuencode could be so easy, did you? Well, it is. And not only is it that easy, but it also runs three times faster than the uuencode shipped with GNU Sharutils (at least up to and including the 4.2.1 release).