1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2000-2012 Jeffrey Stedfast
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1
8 * of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free
17 * Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
28 #include "gmime-stream-cat.h"
34 * SECTION: gmime-stream-cat
35 * @title: GMimeStreamCat
36 * @short_description: A concatenated stream
37 * @see_also: #GMimeStream
39 * A #GMimeStream which chains together any number of other streams.
43 static void g_mime_stream_cat_class_init (GMimeStreamCatClass *klass);
44 static void g_mime_stream_cat_init (GMimeStreamCat *stream, GMimeStreamCatClass *klass);
45 static void g_mime_stream_cat_finalize (GObject *object);
47 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
48 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
49 static int stream_flush (GMimeStream *stream);
50 static int stream_close (GMimeStream *stream);
51 static gboolean stream_eos (GMimeStream *stream);
52 static int stream_reset (GMimeStream *stream);
53 static gint64 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence);
54 static gint64 stream_tell (GMimeStream *stream);
55 static gint64 stream_length (GMimeStream *stream);
56 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
59 static GMimeStreamClass *parent_class = NULL;
63 struct _cat_node *next;
66 int id; /* for debugging */
70 g_mime_stream_cat_get_type (void)
72 static GType type = 0;
75 static const GTypeInfo info = {
76 sizeof (GMimeStreamCatClass),
77 NULL, /* base_class_init */
78 NULL, /* base_class_finalize */
79 (GClassInitFunc) g_mime_stream_cat_class_init,
80 NULL, /* class_finalize */
81 NULL, /* class_data */
82 sizeof (GMimeStreamCat),
84 (GInstanceInitFunc) g_mime_stream_cat_init,
87 type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamCat", &info, 0);
95 g_mime_stream_cat_class_init (GMimeStreamCatClass *klass)
97 GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
98 GObjectClass *object_class = G_OBJECT_CLASS (klass);
100 parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
102 object_class->finalize = g_mime_stream_cat_finalize;
104 stream_class->read = stream_read;
105 stream_class->write = stream_write;
106 stream_class->flush = stream_flush;
107 stream_class->close = stream_close;
108 stream_class->eos = stream_eos;
109 stream_class->reset = stream_reset;
110 stream_class->seek = stream_seek;
111 stream_class->tell = stream_tell;
112 stream_class->length = stream_length;
113 stream_class->substream = stream_substream;
117 g_mime_stream_cat_init (GMimeStreamCat *stream, GMimeStreamCatClass *klass)
119 stream->sources = NULL;
120 stream->current = NULL;
124 g_mime_stream_cat_finalize (GObject *object)
126 GMimeStreamCat *cat = (GMimeStreamCat *) object;
127 struct _cat_node *n, *nn;
132 g_object_unref (n->stream);
137 G_OBJECT_CLASS (parent_class)->finalize (object);
141 stream_read (GMimeStream *stream, char *buf, size_t len)
143 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
144 struct _cat_node *current;
148 /* check for end-of-stream */
149 if (stream->bound_end != -1 && stream->position >= stream->bound_end)
152 /* don't allow our caller to read past the end of the stream */
153 if (stream->bound_end != -1)
154 len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
156 if (!(current = cat->current))
159 /* make sure our stream position is where it should be */
160 offset = current->stream->bound_start + current->position;
161 if (g_mime_stream_seek (current->stream, offset, GMIME_STREAM_SEEK_SET) == -1)
165 if ((nread = g_mime_stream_read (current->stream, buf, len)) <= 0) {
166 cat->current = current = current->next;
167 if (current != NULL) {
168 if (g_mime_stream_reset (current->stream) == -1)
170 current->position = 0;
173 } else if (nread > 0) {
174 current->position += nread;
176 } while (nread == 0 && current != NULL);
179 stream->position += nread;
185 stream_write (GMimeStream *stream, const char *buf, size_t len)
187 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
188 struct _cat_node *current;
193 /* check for end-of-stream */
194 if (stream->bound_end != -1 && stream->position >= stream->bound_end)
197 /* don't allow our caller to write past the end of the stream */
198 if (stream->bound_end != -1)
199 len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
201 if (!(current = cat->current))
204 /* make sure our stream position is where it should be */
205 offset = current->stream->bound_start + current->position;
206 if (g_mime_stream_seek (current->stream, offset, GMIME_STREAM_SEEK_SET) == -1)
211 while (!g_mime_stream_eos (current->stream) && nwritten < len) {
212 if ((n = g_mime_stream_write (current->stream, buf + nwritten, len - nwritten)) <= 0)
215 current->position += n;
220 if (nwritten < len) {
221 /* try spilling over into the next stream */
222 current = current->next;
224 current->position = 0;
225 if (g_mime_stream_reset (current->stream) == -1)
231 } while (nwritten < len);
233 stream->position += nwritten;
235 cat->current = current;
237 if (n == -1 && nwritten == 0)
244 stream_flush (GMimeStream *stream)
246 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
247 struct _cat_node *node;
251 /* flush all streams up to and including the current stream */
255 if (g_mime_stream_flush (node->stream) == -1) {
261 if (node == cat->current)
271 stream_close (GMimeStream *stream)
273 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
274 struct _cat_node *n, *nn;
280 g_object_unref (n->stream);
291 stream_eos (GMimeStream *stream)
293 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
295 if (cat->current == NULL)
298 if (stream->bound_end != -1 && stream->position >= stream->bound_end)
305 stream_reset (GMimeStream *stream)
307 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
310 if (stream->position == stream->bound_start)
315 if (g_mime_stream_reset (n->stream) == -1)
322 cat->current = cat->sources;
328 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
330 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
331 struct _cat_node *current, *n;
332 gint64 real, off, len;
334 d(fprintf (stderr, "GMimeStreamCat::stream_seek (%p, %ld, %d)\n",
335 stream, offset, whence));
337 if (cat->sources == NULL)
341 case GMIME_STREAM_SEEK_SET:
343 /* sanity check our seek - make sure we don't under/over-seek our bounds */
345 d(fprintf (stderr, "offset %ld < 0, fail\n", offset));
349 /* sanity check our seek */
350 if (stream->bound_end != -1 && offset > stream->bound_end) {
351 d(fprintf (stderr, "offset %ld > bound_end %ld, fail\n",
352 offset, stream->bound_end));
356 /* short-cut if we are seeking to our current position */
357 if (offset == stream->position) {
358 d(fprintf (stderr, "offset %ld == stream->position %ld, no need to seek\n",
359 offset, stream->position));
365 current = cat->current;
367 while (n != current) {
368 if (real + n->position > offset)
375 /* offset not within our grasp... */
380 /* seeking to a previous stream (n->stream) */
381 if ((offset - real) != n->position) {
382 /* FIXME: could probably skip these seek checks... */
383 off = n->stream->bound_start + (offset - real);
384 if (g_mime_stream_seek (n->stream, off, GMIME_STREAM_SEEK_SET) == -1)
388 d(fprintf (stderr, "setting current stream to %i and updating cur->position to %ld\n",
389 n->id, offset - real));
392 current->position = offset - real;
396 /* seeking to someplace in our current (or next) stream */
397 d(fprintf (stderr, "seek offset %ld in current stream[%d] or after\n",
398 offset, current->id));
399 if ((offset - real) == current->position) {
400 /* exactly at our current position */
401 d(fprintf (stderr, "seek offset at cur position of stream[%d]\n",
403 stream->position = offset;
407 if ((offset - real) < current->position) {
408 /* in current stream, but before current position */
409 d(fprintf (stderr, "seeking backwards in cur stream[%d]\n",
411 /* FIXME: again, could probably skip seek checks... */
412 off = current->stream->bound_start + (offset - real);
413 if (g_mime_stream_seek (current->stream, off, GMIME_STREAM_SEEK_SET) == -1)
416 d(fprintf (stderr, "setting cur stream[%d] position to %ld\n",
417 current->id, offset - real));
418 current->position = offset - real;
423 /* after our current position */
424 d(fprintf (stderr, "after cur position in stream[%d] or in a later stream\n",
427 if (current->stream->bound_end != -1) {
428 len = current->stream->bound_end - current->stream->bound_start;
430 if ((len = g_mime_stream_length (current->stream)) == -1)
434 d(fprintf (stderr, "real = %lld, stream[%d] len = %lld\n",
435 real, current->id, len));
437 if ((real + len) > offset) {
438 /* within the bounds of the current stream */
439 d(fprintf (stderr, "offset within bounds of stream[%d]\n",
443 d(fprintf (stderr, "not within bounds of stream[%d]\n",
445 current->position = len;
448 current = current->next;
449 if (current == NULL) {
450 d(fprintf (stderr, "ran out of streams, failed\n"));
454 d(fprintf (stderr, "advanced to stream[%d]...\n", current->id));
456 if (g_mime_stream_reset (current->stream) == -1)
459 current->position = 0;
463 /* FIXME: another seek check... probably can skip this */
464 off = current->stream->bound_start + (offset - real);
465 if (g_mime_stream_seek (current->stream, off, GMIME_STREAM_SEEK_SET) == -1)
468 d(fprintf (stderr, "setting cur position of stream[%d] to %ld\n",
469 current->id, offset - real));
470 current->position = offset - real;
474 case GMIME_STREAM_SEEK_CUR:
476 return stream->position;
478 /* calculate offset relative to the beginning of the stream */
479 offset = stream->position + offset;
482 case GMIME_STREAM_SEEK_END:
486 /* calculate the offset of the end of the stream */
488 real = stream->bound_start;
490 if ((len = g_mime_stream_length (n->stream)) == -1)
497 /* calculate offset relative to the beginning of the stream */
498 offset = real + offset;
502 g_assert_not_reached ();
506 d(fprintf (stderr, "setting stream->offset to %ld and current stream to %d\n",
507 offset, current->id));
509 stream->position = offset;
510 cat->current = current;
512 /* reset all following streams */
515 if (g_mime_stream_reset (n->stream) == -1)
525 stream_tell (GMimeStream *stream)
527 return stream->position;
531 stream_length (GMimeStream *stream)
533 GMimeStreamCat *cat = GMIME_STREAM_CAT (stream);
534 gint64 len, total = 0;
537 if (stream->bound_end != -1)
538 return stream->bound_end - stream->bound_start;
542 if ((len = g_mime_stream_length (n->stream)) == -1)
553 struct _sub_node *next;
559 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
561 GMimeStreamCat *cat = (GMimeStreamCat *) stream;
562 struct _sub_node *streams, *tail, *s;
563 gint64 offset = 0, subend = 0;
564 GMimeStream *substream;
568 d(fprintf (stderr, "GMimeStreamCat::substream (%p, %ld, %ld)\n", stream, start, end));
570 /* find the first source stream that contains data we're interested in... */
572 while (offset < start && n != NULL) {
573 if (n->stream->bound_end == -1) {
574 if ((len = g_mime_stream_length (n->stream)) == -1)
577 len = n->stream->bound_end - n->stream->bound_start;
580 if ((offset + len) > start)
591 d(fprintf (stderr, "stream[%d] is the first stream containing data we want\n", n->id));
594 tail = (struct _sub_node *) &streams;
597 s = g_new (struct _sub_node, 1);
599 s->stream = n->stream;
603 s->start = n->stream->bound_start;
604 if (n == cat->sources)
606 else if (offset < start)
607 s->start += (start - offset);
609 d(fprintf (stderr, "added stream[%d] to our list\n", n->id));
611 if (n->stream->bound_end == -1) {
612 if ((len = g_mime_stream_length (n->stream)) == -1)
615 len = n->stream->bound_end - n->stream->bound_start;
618 d(fprintf (stderr, "stream[%d]: len = %ld, offset of beginning of stream is %ld\n",
619 n->id, len, offset));
621 if (end != -1 && (end <= (offset + len))) {
622 d(fprintf (stderr, "stream[%d]: requested end <= offset + len\n", n->id));
623 s->end = n->stream->bound_start + (end - offset);
624 d(fprintf (stderr, "stream[%d]: s->start = %ld, s->end = %ld; break\n",
625 n->id, s->start, s->end));
626 subend += (end - offset);
629 s->end = n->stream->bound_start + len;
630 d(fprintf (stderr, "stream[%d]: s->start = %ld, s->end = %ld\n",
631 n->id, s->start, s->end));
634 subend += (s->end - s->start);
640 d(fprintf (stderr, "returning a substream containing multiple source streams\n"));
641 cat = g_object_newv (GMIME_TYPE_STREAM_CAT, 0, NULL);
642 /* Note: we could pass -1 as bound_end, it should Just
643 * Work(tm) but setting absolute bounds is kinda
645 g_mime_stream_construct (GMIME_STREAM (cat), 0, subend);
647 while (streams != NULL) {
649 substream = g_mime_stream_substream (streams->stream, streams->start, streams->end);
650 g_mime_stream_cat_add_source (cat, substream);
651 g_object_unref (substream);
656 substream = (GMimeStream *) cat;
662 while (streams != NULL) {
673 * g_mime_stream_cat_new:
675 * Creates a new #GMimeStreamCat object.
677 * Returns: a new #GMimeStreamCat stream.
680 g_mime_stream_cat_new (void)
684 stream = g_object_newv (GMIME_TYPE_STREAM_CAT, 0, NULL);
685 g_mime_stream_construct (stream, 0, -1);
692 * g_mime_stream_cat_add_source:
693 * @cat: a #GMimeStreamCat
694 * @source: a source stream
696 * Adds the @source stream to the @cat.
698 * Returns: %0 on success or %-1 on fail.
701 g_mime_stream_cat_add_source (GMimeStreamCat *cat, GMimeStream *source)
703 struct _cat_node *node, *n;
705 g_return_val_if_fail (GMIME_IS_STREAM_CAT (cat), -1);
706 g_return_val_if_fail (GMIME_IS_STREAM (source), -1);
708 node = g_new (struct _cat_node, 1);
710 node->stream = source;
711 g_object_ref (source);
722 node->id = n->id + 1;