File en memory streams, en lezen van input streams. Nog niet getest. Alle
[rodin/chimara.git] / src / stream.c
1 #include "stream.h"
2 #include "fileref.h"
3 #include <glib.h>
4 #include <glib/gstdio.h>
5
6 /* Global current stream */
7 static strid_t current_stream = NULL;
8 /* List of streams currently in existence */
9 static GList *stream_list = NULL;
10
11 /* Internal function: create a window stream to go with window. */
12 strid_t
13 window_stream_new(winid_t window)
14 {
15         /* Create stream and connect it to window */
16         strid_t s = g_new0(struct glk_stream_struct, 1);
17         s->file_mode = filemode_Write;
18         s->stream_type = STREAM_TYPE_WINDOW;
19         s->window = window;
20         /* Add it to the global stream list */
21         stream_list = g_list_prepend(stream_list, s);
22         s->stream_list = stream_list;
23
24         return s;
25 }
26
27 /**
28  * glk_stream_iterate:
29  * @str: A stream, or #NULL.
30  * @rockptr: Return location for the next window's rock, or #NULL.
31  *
32  * Iterates over the list of streams; if @str is #NULL, it returns the first
33  * stream, otherwise the next stream after @str. If there are no more, it
34  * returns #NULL. The stream's rock is stored in @rockptr. If you don't want
35  * the rocks to be returned, you may set @rockptr to #NULL.
36  *
37  * The order in which streams are returned is arbitrary. The order may change
38  * every time you create or destroy a stream, invalidating the iteration.
39  *
40  * Returns: the next stream, or #NULL if there are no more.
41  */
42 strid_t
43 glk_stream_iterate(strid_t str, glui32 *rockptr)
44 {
45         GList *retnode;
46         
47         if(str == NULL)
48                 retnode = stream_list;
49         else
50                 retnode = str->stream_list->next;
51         strid_t retval = retnode? (strid_t)retnode->data : NULL;
52                 
53         /* Store the stream's rock in rockptr */
54         if(retval && rockptr)
55                 *rockptr = glk_stream_get_rock(retval);
56                 
57         return retval;
58 }
59
60 /**
61  * glk_stream_get_rock:
62  * @str: A stream.
63  * 
64  * Returns the stream @str's rock value.
65  *
66  * Returns: A rock value.
67  */
68 glui32
69 glk_stream_get_rock(strid_t str)
70 {
71         g_return_val_if_fail(str != NULL, 0);
72         return str->rock;
73 }
74
75 /**
76  * glk_stream_set_current:
77  * @str: An output stream, or NULL.
78  *
79  * Sets the current stream to @str, or to nothing if @str is #NULL.
80  */
81 void
82 glk_stream_set_current(strid_t str)
83 {
84         if(str != NULL && str->file_mode != filemode_Write)
85         {
86                 g_warning("glk_stream_set_current: "
87                         "Cannot set current stream to non output stream");
88                 return;
89         }
90
91         current_stream = str;
92 }
93
94 /**
95  * glk_stream_get_current:
96  * 
97  * Returns the current stream, or #NULL if there is none.
98  *
99  * Returns: A stream.
100  */
101 strid_t
102 glk_stream_get_current()
103 {
104         return current_stream;
105 }
106
107 /**
108  * glk_put_char:
109  * @ch: A character in Latin-1 encoding.
110  *
111  * Prints one character @ch to the current stream.
112  */
113 void
114 glk_put_char(unsigned char ch)
115 {
116         /* Illegal to print to the current stream if it is NULL */
117         g_return_if_fail(current_stream != NULL);
118         glk_put_char_stream(current_stream, ch);
119 }
120
121 /**
122  * glk_put_string:
123  * @s: A null-terminated string in Latin-1 encoding.
124  *
125  * Prints @s to the current stream.
126  */
127 void
128 glk_put_string(char *s)
129 {
130         /* Illegal to print to the current stream if it is NULL */
131         g_return_if_fail(current_stream != NULL);
132         glk_put_string_stream(current_stream, s);
133 }
134
135 /**
136  * glk_put_buffer:
137  * @buf: An array of characters in Latin-1 encoding.
138  * @len: Length of @buf.
139  *
140  * Prints @buf to the current stream.
141  */
142 void
143 glk_put_buffer(char *buf, glui32 len)
144 {
145         /* Illegal to print to the current stream if it is NULL */
146         g_return_if_fail(current_stream != NULL);
147         glk_put_buffer_stream(current_stream, buf, len);
148 }
149
150 /**
151  * glk_stream_open_memory:
152  * @buf: An allocated buffer, or %NULL.
153  * @buflen: Length of @buf.
154  * @fmode: Mode in which the buffer will be opened. Must be one of 
155  * #filemode_Read, #filemode_Write, or #filemode_ReadWrite.
156  * @rock: The new stream's rock value.
157  *
158  * Opens a stream which reads from or writes to a space in memory. @buf points
159  * to the buffer where output will be read from or written to. @buflen is the
160  * length of the buffer.
161  *
162  * When outputting, if more than @buflen characters are written to the stream,
163  * all of them beyond the buffer length will be thrown away, so as not to
164  * overwrite the buffer. (The character count of the stream will still be
165  * maintained correctly. That is, it will count the number of characters written
166  * into the stream, not the number that fit into the buffer.)
167  *
168  * If @buf is %NULL, or for that matter if @buflen is zero, then <emphasis>
169  * everything</emphasis> written to the stream is thrown away. This may be
170  * useful if you are interested in the character count.
171  *
172  * When inputting, if more than @buflen characters are read from the stream, the
173  * stream will start returning -1 (signalling end-of-file.) If @buf is %NULL,
174  * the stream will always return end-of-file.
175  *
176  * The data is written to the buffer exactly as it was passed to the printing
177  * functions (glk_put_char(), etc.); input functions will read the data exactly
178  * as it exists in memory. No platform-dependent cookery will be done on it.
179  * [You can write a disk file in text mode, but a memory stream is effectively
180  * always in binary mode.]
181  *
182  * Unicode values (characters greater than 255) cannot be written to the buffer.
183  * If you try, they will be stored as 0x3F ("?") characters.
184  *
185  * Whether reading or writing, the contents of the buffer are undefined until
186  * the stream is closed. The library may store the data there as it is written,
187  * or deposit it all in a lump when the stream is closed. It is illegal to
188  * change the contents of the buffer while the stream is open.
189  */
190 strid_t
191 glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock)
192 {
193         g_return_val_if_fail(fmode != filemode_WriteAppend, NULL);
194         
195         strid_t s = g_new0(struct glk_stream_struct, 1);
196         s->rock = rock;
197         s->file_mode = fmode;
198         s->stream_type = STREAM_TYPE_MEMORY;
199         s->buffer = buf;
200         s->mark = 0;
201         s->buflen = buflen;
202         s->unicode = FALSE;
203
204         /* Add it to the global stream list */
205         stream_list = g_list_prepend(stream_list, s);
206         s->stream_list = stream_list;
207
208         return s;
209 }
210
211 /**
212  * glk_stream_open_memory_uni:
213  * @buf: An allocated buffer, or %NULL.
214  * @buflen: Length of @buf.
215  * @fmode: Mode in which the buffer will be opened. Must be one of 
216  * #filemode_Read, #filemode_Write, or #filemode_ReadWrite.
217  * @rock: The new stream's rock value.
218  *
219  * Works just like glk_stream_open_memory(), except that the buffer is an array
220  * of 32-bit words, instead of bytes. This allows you to write and read any
221  * Unicode character. The @buflen is the number of words, not the number of
222  * bytes.
223  */
224 strid_t
225 glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, 
226         glui32 rock)
227 {
228         g_return_val_if_fail(fmode != filemode_WriteAppend, NULL);
229         
230         strid_t s = g_new0(struct glk_stream_struct, 1);
231         s->rock = rock;
232         s->file_mode = fmode;
233         s->stream_type = STREAM_TYPE_MEMORY;
234         s->ubuffer = buf;
235         s->mark = 0;
236         s->buflen = buflen;
237         s->unicode = TRUE;
238
239         /* Add it to the global stream list */
240         stream_list = g_list_prepend(stream_list, s);
241         s->stream_list = stream_list;
242
243         return s;
244 }
245
246 /* Internal function: create a stream using the given parameters. */
247 static strid_t
248 file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
249 {
250         g_return_val_if_fail(fileref != NULL, NULL);
251         
252         gchar *modestr;
253         gboolean binary = fileref->usage & fileusage_BinaryMode;
254         switch(fmode) 
255         {
256                 case filemode_Read:
257                         if(!g_file_test(fileref->filename, G_FILE_TEST_EXISTS)) {
258                                 g_warning("glk_stream_open_file: Tried to open a file in read "
259                                                   "mode that didn't exist!");
260                                 return NULL;
261                         }
262                         modestr = g_strdup(binary? "rb" : "r");
263                         break;
264                 case filemode_Write:
265                         modestr = g_strdup(binary? "wb" : "w");
266                         break;
267                 case filemode_WriteAppend:
268                         modestr = g_strdup(binary? "ab" : "a");
269                         break;
270                 case filemode_ReadWrite:
271                         modestr = g_strdup(binary? "r+b" : "r+");
272                         break;
273                 default:
274                         g_warning("glk_stream_open_file: Invalid file mode");
275                         return NULL;
276         }
277         
278         FILE *fp = g_fopen(fileref->filename, modestr);
279         g_free(modestr);
280         if(fp == NULL) {
281                 g_warning("glk_stream_open_file: Error opening file");
282                 return NULL;
283         }
284         
285         /* If they opened a file in write mode but didn't specifically get
286         permission to do so, complain if the file already exists */
287         if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) {
288                 GtkWidget *dialog = gtk_message_dialog_new(NULL, 0,
289                         GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
290                         "File %s already exists. Overwrite?", fileref->filename);
291                 gint response = gtk_dialog_run(GTK_DIALOG(dialog));
292                 gtk_widget_destroy(dialog);
293                 if(response != GTK_RESPONSE_YES) {
294                         fclose(fp);
295                         return NULL;
296                 }
297         }
298         
299         strid_t s = g_new0(struct glk_stream_struct, 1);
300         s->rock = rock;
301         s->file_mode = fmode;
302         s->stream_type = STREAM_TYPE_FILE;
303         s->file_pointer = fp;
304         s->binary = binary;
305         s->unicode = unicode;
306         s->filename = g_filename_to_utf8(fileref->filename, -1, NULL, NULL, NULL);
307         if(s->filename == NULL)
308                 s->filename = g_strdup("Unknown file name"); /* fail silently */
309         /* Add it to the global stream list */
310         stream_list = g_list_prepend(stream_list, s);
311         s->stream_list = stream_list;
312
313         return s;
314 }
315
316 /**
317  * glk_stream_open_file:
318  * @fileref: Indicates the file which will be opened.
319  * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read,
320  * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite.
321  * @rock: The new stream's rock value.
322  *
323  * Opens a stream which reads to or writes from a disk file. If @fmode is
324  * #filemode_Read, the file must already exist; for the other modes, an empty
325  * file is created if none exists. If @fmode is #filemode_Write, and the file
326  * already exists, it is truncated down to zero length (an empty file). If
327  * @fmode is #filemode_WriteAppend, the file mark is set to the end of the 
328  * file.
329  * 
330  * The file may be written in text or binary mode; this is determined by the
331  * @fileref argument. Similarly, platform-dependent attributes such as file 
332  * type are determined by @fileref.
333  *
334  * When writing in binary mode, Unicode values (characters greater than 255)
335  * cannot be written to the file. If you try, they will be stored as 0x3F ("?")
336  * characters. In text mode, Unicode values are stored in UTF-8.
337  *
338  * Returns: A new stream, or %NULL if the file operation failed.
339  */
340 strid_t
341 glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock)
342 {
343         return file_stream_new(fileref, fmode, rock, FALSE);
344 }
345
346 /**
347  * glk_stream_open_file_uni:
348  * @fileref: Indicates the file which will be opened.
349  * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read,
350  * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite.
351  * @rock: The new stream's rock value.
352  *
353  * This works just like glk_stream_open_file(), except that in binary mode,
354  * characters are written and read as four-byte (big-endian) values. This
355  * allows you to write any Unicode character.
356  *
357  * In text mode, the file is written and read in UTF-8.
358  *
359  * Returns: A new stream, or %NULL if the file operation failed.
360  */
361 strid_t
362 glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock)
363 {
364         return file_stream_new(fileref, fmode, rock, TRUE);
365 }
366
367 /**
368  * glk_stream_close:
369  * @str: Stream to close.
370  * @result: Pointer to a #stream_result_t, or %NULL.
371  *
372  * Closes the stream @str. The @result argument points to a structure which is
373  * filled in with the final character counts of the stream. If you do not care
374  * about these, you may pass %NULL as the @result argument.
375  *
376  * If @str is the current output stream, the current output stream is set to
377  * %NULL.
378  *
379  * You cannot close window streams; use glk_window_close() instead.
380  */
381 void 
382 glk_stream_close(strid_t str, stream_result_t *result)
383 {
384         g_return_if_fail(str != NULL);
385         
386         /* Free resources associated with one specific type of stream */
387         switch(str->stream_type)
388         {
389                 case STREAM_TYPE_WINDOW:
390                         g_warning("%s: Attempted to close a window stream. Use glk_window_"
391                                 "close() instead.", __func__);
392                         return;
393                         
394                 case STREAM_TYPE_MEMORY:
395                         /* Do nothing */
396                         break;
397                         
398                 case STREAM_TYPE_FILE:
399                         if(fclose(str->file_pointer) != 0)
400                                 g_warning("%s: Failed to close file '%s'.", __func__, 
401                                         str->filename);
402                         g_free(str->filename);
403                         break;
404                 default:
405                         g_warning("%s: Closing this type of stream not supported.", 
406                                 __func__);
407                         return;
408         }
409         
410         /* Remove the stream from the global stream list */
411         stream_list = g_list_delete_link(stream_list, str->stream_list);
412         /* If it was the current output stream, set that to NULL */
413         if(current_stream == str)
414                 current_stream = NULL;
415         /* If it was one or more windows' echo streams, set those to NULL */
416         winid_t win;
417         for(win = glk_window_iterate(NULL, NULL); win; 
418                 win = glk_window_iterate(win, NULL))
419                 if(win->echo_stream == str)
420                         win->echo_stream = NULL;
421         /* Return the character counts */
422         if(result) 
423         {
424                 result->readcount = str->read_count;
425                 result->writecount = str->write_count;
426         }
427         g_free(str);
428 }
429