File: stream.rst

package info (click to toggle)
libcork 1.0.0~rc3-4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 2,024 kB
  • sloc: ansic: 12,975; python: 605; makefile: 331; sh: 238
file content (228 lines) | stat: -rw-r--r-- 8,140 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
.. _stream:

*****************
Stream processing
*****************

.. highlight:: c

::

  #include <libcork/ds.h>


Stream producers
----------------

A *producer* of binary data should take in a pointer to a
:c:type:`cork_stream_consumer` instance.  Any data that is produced by the
stream is then sent into the consumer instance for processing.  Once the stream
has been exhausted (for instance, by reaching the end of a file), you signal
this to the consumer.  During both of these steps, the consumer is able to
signal error conditions; for instance, a stream consumer that parses a
particular file format might return an error condition if the stream of data is
malformed.  If possible, the stream producer can try to recover from the error
condition, but more often, the stream producer will simply pass the error back
up to its caller.

.. function:: int cork_stream_consumer_data(struct cork_stream_consumer *consumer, const void *buf, size_t size, bool is_first_chunk)

   Send the next chunk of data into a stream consumer.  You only have to ensure
   that *buf* is valid for the duration of this function call; the stream
   consumer is responsible for saving a copy of the data if it needs to be
   processed later.  In particular, this means that it's perfectly safe for
   *buf* to refer to a stack-allocated memory region.

.. function:: int cork_stream_consumer_eof(struct cork_stream_consumer *consumer)

   Notify the stream consumer that the end of the stream has been reached.  The
   stream consumer might perform some final validation and error detection at
   this point.

.. function:: void cork_stream_consumer_free(struct cork_stream_consumer *consumer)

   Finalize and deallocate a stream consumer.


Built-in stream producers
~~~~~~~~~~~~~~~~~~~~~~~~~

We provide several built-in stream producers:

.. function:: int cork_consume_fd(struct cork_stream_consumer *consumer, int fd)
              int cork_consume_file(struct cork_stream_consumer *consumer, FILE *fp)
              int cork_consume_file_from_path(struct cork_stream_consumer *consumer, const char *path, int flags)

   Read in a file, passing its contents into the given stream consumer.  The
   ``_fd`` and ``_file`` variants consume a file that you've already opened; you
   are responsible for closing the file after its been consumed.  The
   ``_file_from_path`` variant will open the file for you, using the standard
   ``open(2)`` function with the given *flags*.  This variant will close the
   file before returning, regardless of whether the file was successfully
   consumed or not.


File stream producer example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As an example, we could implement the :c:func:`cork_consume_file` stream
producer as follows::

  #include <stdio.h>
  #include <libcork/core.h>
  #include <libcork/helpers/errors.h>
  #include <libcork/ds.h>

  #define BUFFER_SIZE  65536

  int
  cork_consume_file(struct cork_stream_consumer *consumer, FILE *fp)
  {
      char  buf[BUFFER_SIZE];
      size_t  bytes_read;
      bool  first = true;

      while ((bytes_read = fread(buf, 1, BUFFER_SIZE, fp)) > 0) {
          rii_check(cork_stream_consumer_data(consumer, buf, bytes_read, first));
          first = false;
      }

      if (feof(fp)) {
          return cork_stream_consumer_eof(consumer);
      } else {
          cork_system_error_set();
          return -1;
      }
  }

Note that this stream producer does not take care of opening or closing
the ``FILE`` object, nor does it take care of freeing the consumer.  (Our actual
implementation of :c:func:`cork_consume_file` also correctly handles ``EINTR``
errors, and so is a bit more complex.  But this example still works as an
illustration of how to pass data into a stream consumer.)


.. _stream-consumers:

Stream consumers
----------------

To consume data from a stream, you must create a type that implements the
:c:type:`cork_stream_consumer` interface.

.. type:: struct cork_stream_consumer

   An interface for consumer a stream of binary data.  The producer of
   the stream will call the :c:func:`cork_stream_consumer_data()`
   function repeatedly, once for each successive chunk of data in the
   stream.  Once the stream has been exhausted, the producer will call
   :c:func:`cork_stream_consumer_eof()` to signal the end of the stream.

   .. member:: int (*data)(struct cork_stream_consumer *consumer, const void *buf, size_t size, bool is_first_chunk)

      Process the next chunk of data in the stream.  *buf* is only
      guaranteed to be valid for the duration of this function call.  If
      you need to access the contents of the slice later, you must save
      it somewhere yourself.

      If there is an error processing this chunk of data, you should
      return ``-1`` and fill in the current error condition using
      :c:func:`cork_error_set`.

   .. member:: int (*eof)(struct cork_stream_consumer *consumer)

      Handle the end of the stream.  This allows you to defer any final
      validation or error detection until all of the data has been
      processed.

      If there is an error detected at this point, you should return
      ``-1`` and fill in the current error condition using
      :c:func:`cork_error_set`.

   .. member:: void (*free)(struct cork_stream_consumer *consumer)

      Free the consumer object.


Built-in stream consumers
~~~~~~~~~~~~~~~~~~~~~~~~~

We provide several built-in stream consumers:

.. function:: struct cork_stream_consumer *cork_fd_consumer_new(int fd)
              struct cork_stream_consumer *cork_file_consumer_new(FILE *fp)
              struct cork_stream_consumer *cork_file_from_path_consumer_new(const char *path, int flags)

   Create a stream consumer that appends any data that it receives to a file.
   The ``_fd`` and ``_file`` variants append to a file that you've already
   opened; you are responsible for closing the file after the consumer has
   finished processing data.  The ``_file_from_path`` variant will open the file
   for you, using the standard ``open(2)`` function with the given *flags*.
   This variant will close the file before returning, regardless of whether the
   stream consumer successfully processed the data or not.


File stream consumer example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As an example, we could implement a stream consumer for the
:c:func:`cork_file_consumer_new` function as follows::

  #include <stdio.h>
  #include <libcork/core.h>
  #include <libcork/helpers/errors.h>
  #include <libcork/ds.h>

  struct cork_file_consumer {
      /* cork_file_consumer implements the cork_stream_consumer interface */
      struct cork_stream_consumer  parent;
      /* the file to write the data into */
      FILE  *fp;
  };

  static int
  cork_file_consumer__data(struct cork_stream_consumer *vself,
                           const void *buf, size_t size, bool is_first)
  {
      struct file_consumer  *self =
          cork_container_of(vself, struct cork_file_consumer, parent);
      size_t  bytes_written = fwrite(buf, 1, size, self->fp);
      /* If there was an error writing to the file, then signal this to
       * the producer */
      if (bytes_written == size) {
          return 0;
      } else {
          cork_system_error_set();
          return -1;
      }
  }

  static int
  cork_file_consumer__eof(struct cork_stream_consumer *vself)
  {
      /* We don't close the file, so there's nothing special to do at
       * end-of-stream. */
      return 0;
  }

  static void
  cork_file_consumer__free(struct cork_stream_consumer *vself)
  {
      struct file_consumer  *self =
          cork_container_of(vself, struct cork_file_consumer, parent);
      free(self);
  }

  struct cork_stream_consumer *
  cork_file_consumer_new(FILE *fp)
  {
      struct cork_file_consumer  *self = cork_new(struct cork_file_consumer);
      self->parent.data = cork_file_consumer__data;
      self->parent.eof = cork_file_consumer__eof;
      self->parent.free = cork_file_consumer__free;
      self->fp = fp;
      return &self->parent;
  }

Note that this stream consumer does not take care of opening or closing the
``FILE`` object.