Initialize the gmime for upstream
[platform/upstream/gmime.git] / gmime / gmime-stream-cat.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2012 Jeffrey Stedfast
4  *
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.
9  *
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.
14  *
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
18  *  02110-1301, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27
28 #include "gmime-stream-cat.h"
29
30 #define d(x)
31
32
33 /**
34  * SECTION: gmime-stream-cat
35  * @title: GMimeStreamCat
36  * @short_description: A concatenated stream
37  * @see_also: #GMimeStream
38  *
39  * A #GMimeStream which chains together any number of other streams.
40  **/
41
42
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);
46
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);
57
58
59 static GMimeStreamClass *parent_class = NULL;
60
61
62 struct _cat_node {
63         struct _cat_node *next;
64         GMimeStream *stream;
65         gint64 position;
66         int id; /* for debugging */
67 };
68
69 GType
70 g_mime_stream_cat_get_type (void)
71 {
72         static GType type = 0;
73         
74         if (!type) {
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),
83                         0,    /* n_preallocs */
84                         (GInstanceInitFunc) g_mime_stream_cat_init,
85                 };
86                 
87                 type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamCat", &info, 0);
88         }
89         
90         return type;
91 }
92
93
94 static void
95 g_mime_stream_cat_class_init (GMimeStreamCatClass *klass)
96 {
97         GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
98         GObjectClass *object_class = G_OBJECT_CLASS (klass);
99         
100         parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
101         
102         object_class->finalize = g_mime_stream_cat_finalize;
103         
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;
114 }
115
116 static void
117 g_mime_stream_cat_init (GMimeStreamCat *stream, GMimeStreamCatClass *klass)
118 {
119         stream->sources = NULL;
120         stream->current = NULL;
121 }
122
123 static void
124 g_mime_stream_cat_finalize (GObject *object)
125 {
126         GMimeStreamCat *cat = (GMimeStreamCat *) object;
127         struct _cat_node *n, *nn;
128         
129         n = cat->sources;
130         while (n != NULL) {
131                 nn = n->next;
132                 g_object_unref (n->stream);
133                 g_free (n);
134                 n = nn;
135         }
136         
137         G_OBJECT_CLASS (parent_class)->finalize (object);
138 }
139
140 static ssize_t
141 stream_read (GMimeStream *stream, char *buf, size_t len)
142 {
143         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
144         struct _cat_node *current;
145         ssize_t nread = 0;
146         gint64 offset;
147         
148         /* check for end-of-stream */
149         if (stream->bound_end != -1 && stream->position >= stream->bound_end)
150                 return -1;
151         
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);
155         
156         if (!(current = cat->current))
157                 return -1;
158         
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)
162                 return -1;
163         
164         do {
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)
169                                         return -1;
170                                 current->position = 0;
171                         }
172                         nread = 0;
173                 } else if (nread > 0) {
174                         current->position += nread;
175                 }
176         } while (nread == 0 && current != NULL);
177         
178         if (nread > 0)
179                 stream->position += nread;
180         
181         return nread;
182 }
183  
184 static ssize_t
185 stream_write (GMimeStream *stream, const char *buf, size_t len)
186 {
187         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
188         struct _cat_node *current;
189         size_t nwritten = 0;
190         ssize_t n = -1;
191         gint64 offset;
192         
193         /* check for end-of-stream */
194         if (stream->bound_end != -1 && stream->position >= stream->bound_end)
195                 return -1;
196         
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);
200         
201         if (!(current = cat->current))
202                 return -1;
203         
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)
207                 return -1;
208         
209         do {
210                 n = -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)
213                                 break;
214                         
215                         current->position += n;
216                         
217                         nwritten += n;
218                 }
219                 
220                 if (nwritten < len) {
221                         /* try spilling over into the next stream */
222                         current = current->next;
223                         if (current) {
224                                 current->position = 0;
225                                 if (g_mime_stream_reset (current->stream) == -1)
226                                         break;
227                         } else {
228                                 break;
229                         }
230                 }
231         } while (nwritten < len);
232         
233         stream->position += nwritten;
234         
235         cat->current = current;
236         
237         if (n == -1 && nwritten == 0)
238                 return -1;
239         
240         return nwritten;
241 }
242
243 static int
244 stream_flush (GMimeStream *stream)
245 {
246         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
247         struct _cat_node *node;
248         int errnosav = 0;
249         int rv = 0;
250         
251         /* flush all streams up to and including the current stream */
252         
253         node = cat->sources;
254         while (node) {
255                 if (g_mime_stream_flush (node->stream) == -1) {
256                         if (errnosav == 0)
257                                 errnosav = errno;
258                         rv = -1;
259                 }
260                 
261                 if (node == cat->current)
262                         break;
263                 
264                 node = node->next;
265         }
266         
267         return rv;
268 }
269
270 static int
271 stream_close (GMimeStream *stream)
272 {
273         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
274         struct _cat_node *n, *nn;
275         
276         cat->current = NULL;
277         n = cat->sources;
278         while (n != NULL) {
279                 nn = n->next;
280                 g_object_unref (n->stream);
281                 g_free (n);
282                 n = nn;
283         }
284         
285         cat->sources = NULL;
286         
287         return 0;
288 }
289
290 static gboolean
291 stream_eos (GMimeStream *stream)
292 {
293         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
294         
295         if (cat->current == NULL)
296                 return TRUE;
297         
298         if (stream->bound_end != -1 && stream->position >= stream->bound_end)
299                 return TRUE;
300         
301         return FALSE;
302 }
303
304 static int
305 stream_reset (GMimeStream *stream)
306 {
307         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
308         struct _cat_node *n;
309         
310         if (stream->position == stream->bound_start)
311                 return 0;
312         
313         n = cat->sources;
314         while (n != NULL) {
315                 if (g_mime_stream_reset (n->stream) == -1)
316                         return -1;
317                 
318                 n->position = 0;
319                 n = n->next;
320         }
321         
322         cat->current = cat->sources;
323         
324         return 0;
325 }
326
327 static gint64
328 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
329 {
330         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
331         struct _cat_node *current, *n;
332         gint64 real, off, len;
333         
334         d(fprintf (stderr, "GMimeStreamCat::stream_seek (%p, %ld, %d)\n",
335                    stream, offset, whence));
336         
337         if (cat->sources == NULL)
338                 return -1;
339         
340         switch (whence) {
341         case GMIME_STREAM_SEEK_SET:
342         seek_set:
343                 /* sanity check our seek - make sure we don't under/over-seek our bounds */
344                 if (offset < 0) {
345                         d(fprintf (stderr, "offset %ld < 0, fail\n", offset));
346                         return -1;
347                 }
348                 
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));
353                         return -1;
354                 }
355                 
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));
360                         return offset;
361                 }
362                 
363                 real = 0;
364                 n = cat->sources;
365                 current = cat->current;
366                 
367                 while (n != current) {
368                         if (real + n->position > offset)
369                                 break;
370                         real += n->position;
371                         n = n->next;
372                 }
373                 
374                 if (n == NULL) {
375                         /* offset not within our grasp... */
376                         return -1;
377                 }
378                 
379                 if (n != current) {
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)
385                                         return -1;
386                         }
387                         
388                         d(fprintf (stderr, "setting current stream to %i and updating cur->position to %ld\n",
389                                    n->id, offset - real));
390                         
391                         current = n;
392                         current->position = offset - real;
393                         
394                         break;
395                 } else {
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",
402                                            current->id));
403                                 stream->position = offset;
404                                 return offset;
405                         }
406                         
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",
410                                            current->id));
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)
414                                         return -1;
415                                 
416                                 d(fprintf (stderr, "setting cur stream[%d] position to %ld\n",
417                                            current->id, offset - real));
418                                 current->position = offset - real;
419                                 
420                                 break;
421                         }
422                         
423                         /* after our current position */
424                         d(fprintf (stderr, "after cur position in stream[%d] or in a later stream\n",
425                                    current->id));
426                         do {
427                                 if (current->stream->bound_end != -1) {
428                                         len = current->stream->bound_end - current->stream->bound_start;
429                                 } else {
430                                         if ((len = g_mime_stream_length (current->stream)) == -1)
431                                                 return -1;
432                                 }
433                                 
434                                 d(fprintf (stderr, "real = %lld, stream[%d] len = %lld\n",
435                                            real, current->id, len));
436                                 
437                                 if ((real + len) > offset) {
438                                         /* within the bounds of the current stream */
439                                         d(fprintf (stderr, "offset within bounds of stream[%d]\n",
440                                                    current->id));
441                                         break;
442                                 } else {
443                                         d(fprintf (stderr, "not within bounds of stream[%d]\n",
444                                                    current->id));
445                                         current->position = len;
446                                         real += len;
447                                         
448                                         current = current->next;
449                                         if (current == NULL) {
450                                                 d(fprintf (stderr, "ran out of streams, failed\n"));
451                                                 return -1;
452                                         }
453                                         
454                                         d(fprintf (stderr, "advanced to stream[%d]...\n", current->id));
455                                         
456                                         if (g_mime_stream_reset (current->stream) == -1)
457                                                 return -1;
458                                         
459                                         current->position = 0;
460                                 }
461                         } while (1);
462                         
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)
466                                 return -1;
467                         
468                         d(fprintf (stderr, "setting cur position of stream[%d] to %ld\n",
469                                    current->id, offset - real));
470                         current->position = offset - real;
471                 }
472                 
473                 break;
474         case GMIME_STREAM_SEEK_CUR:
475                 if (offset == 0)
476                         return stream->position;
477                 
478                 /* calculate offset relative to the beginning of the stream */
479                 offset = stream->position + offset;
480                 goto seek_set;
481                 break;
482         case GMIME_STREAM_SEEK_END:
483                 if (offset > 0)
484                         return -1;
485                 
486                 /* calculate the offset of the end of the stream */
487                 n = cat->sources;
488                 real = stream->bound_start;
489                 while (n != NULL) {
490                         if ((len = g_mime_stream_length (n->stream)) == -1)
491                                 return -1;
492                         
493                         real += len;
494                         n = n->next;
495                 }
496                 
497                 /* calculate offset relative to the beginning of the stream */
498                 offset = real + offset;
499                 goto seek_set;
500                 break;
501         default:
502                 g_assert_not_reached ();
503                 return -1;
504         }
505         
506         d(fprintf (stderr, "setting stream->offset to %ld and current stream to %d\n",
507                    offset, current->id));
508         
509         stream->position = offset;
510         cat->current = current;
511         
512         /* reset all following streams */
513         n = current->next;
514         while (n != NULL) {
515                 if (g_mime_stream_reset (n->stream) == -1)
516                         return -1;
517                 n->position = 0;
518                 n = n->next;
519         }
520         
521         return offset;
522 }
523
524 static gint64
525 stream_tell (GMimeStream *stream)
526 {
527         return stream->position;
528 }
529
530 static gint64
531 stream_length (GMimeStream *stream)
532 {
533         GMimeStreamCat *cat = GMIME_STREAM_CAT (stream);
534         gint64 len, total = 0;
535         struct _cat_node *n;
536         
537         if (stream->bound_end != -1)
538                 return stream->bound_end - stream->bound_start;
539         
540         n = cat->sources;
541         while (n != NULL) {
542                 if ((len = g_mime_stream_length (n->stream)) == -1)
543                         return -1;
544                 
545                 total += len;
546                 n = n->next;
547         }
548         
549         return total;
550 }
551
552 struct _sub_node {
553         struct _sub_node *next;
554         GMimeStream *stream;
555         gint64 start, end;
556 };
557
558 static GMimeStream *
559 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
560 {
561         GMimeStreamCat *cat = (GMimeStreamCat *) stream;
562         struct _sub_node *streams, *tail, *s;
563         gint64 offset = 0, subend = 0;
564         GMimeStream *substream;
565         struct _cat_node *n;
566         gint64 len;
567         
568         d(fprintf (stderr, "GMimeStreamCat::substream (%p, %ld, %ld)\n", stream, start, end));
569         
570         /* find the first source stream that contains data we're interested in... */
571         n = cat->sources;
572         while (offset < start && n != NULL) {
573                 if (n->stream->bound_end == -1) {
574                         if ((len = g_mime_stream_length (n->stream)) == -1)
575                                 return NULL;
576                 } else {
577                         len = n->stream->bound_end - n->stream->bound_start;
578                 }
579                 
580                 if ((offset + len) > start)
581                         break;
582                 
583                 offset += len;
584                 
585                 n = n->next;
586         }
587         
588         if (n == NULL)
589                 return NULL;
590         
591         d(fprintf (stderr, "stream[%d] is the first stream containing data we want\n", n->id));
592         
593         streams = NULL;
594         tail = (struct _sub_node *) &streams;
595         
596         do {
597                 s = g_new (struct _sub_node, 1);
598                 s->next = NULL;
599                 s->stream = n->stream;
600                 tail->next = s;
601                 tail = s;
602                 
603                 s->start = n->stream->bound_start;
604                 if (n == cat->sources)
605                         s->start += start;
606                 else if (offset < start)
607                         s->start += (start - offset);
608                 
609                 d(fprintf (stderr, "added stream[%d] to our list\n", n->id));
610                 
611                 if (n->stream->bound_end == -1) {
612                         if ((len = g_mime_stream_length (n->stream)) == -1)
613                                 goto error;
614                 } else {
615                         len = n->stream->bound_end - n->stream->bound_start;
616                 }
617                 
618                 d(fprintf (stderr, "stream[%d]: len = %ld, offset of beginning of stream is %ld\n",
619                            n->id, len, offset));
620                 
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);
627                         break;
628                 } else {
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));
632                 }
633                 
634                 subend += (s->end - s->start);
635                 offset += len;
636                 
637                 n = n->next;
638         } while (n != NULL);
639         
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
644          * nice... */
645         g_mime_stream_construct (GMIME_STREAM (cat), 0, subend);
646         
647         while (streams != NULL) {
648                 s = streams->next;
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);
652                 g_free (streams);
653                 streams = s;
654         }
655         
656         substream = (GMimeStream *) cat;
657         
658         return substream;
659         
660  error:
661         
662         while (streams != NULL) {
663                 s = streams->next;
664                 g_free (streams);
665                 streams = s;
666         }
667         
668         return NULL;
669 }
670
671
672 /**
673  * g_mime_stream_cat_new:
674  *
675  * Creates a new #GMimeStreamCat object.
676  *
677  * Returns: a new #GMimeStreamCat stream.
678  **/
679 GMimeStream *
680 g_mime_stream_cat_new (void)
681 {
682         GMimeStream *stream;
683         
684         stream = g_object_newv (GMIME_TYPE_STREAM_CAT, 0, NULL);
685         g_mime_stream_construct (stream, 0, -1);
686         
687         return stream;
688 }
689
690
691 /**
692  * g_mime_stream_cat_add_source:
693  * @cat: a #GMimeStreamCat
694  * @source: a source stream
695  *
696  * Adds the @source stream to the @cat.
697  *
698  * Returns: %0 on success or %-1 on fail.
699  **/
700 int
701 g_mime_stream_cat_add_source (GMimeStreamCat *cat, GMimeStream *source)
702 {
703         struct _cat_node *node, *n;
704         
705         g_return_val_if_fail (GMIME_IS_STREAM_CAT (cat), -1);
706         g_return_val_if_fail (GMIME_IS_STREAM (source), -1);
707         
708         node = g_new (struct _cat_node, 1);
709         node->next = NULL;
710         node->stream = source;
711         g_object_ref (source);
712         node->position = 0;
713         
714         n = cat->sources;
715         while (n && n->next)
716                 n = n->next;
717         
718         if (n == NULL) {
719                 cat->sources = node;
720                 node->id = 0;
721         } else {
722                 node->id = n->id + 1;
723                 n->next = node;
724         }
725         
726         if (!cat->current)
727                 cat->current = node;
728         
729         return 0;
730 }