Testen en debuggen van alle file en memory stream functies
[projects/chimara/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_Read)
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         /* Binary mode is 0x000, text mode 0x100 */
254         gboolean binary = !(fileref->usage & fileusage_TextMode);
255         switch(fmode) 
256         {
257                 case filemode_Read:
258                         if(!g_file_test(fileref->filename, G_FILE_TEST_EXISTS)) {
259                                 g_warning("glk_stream_open_file: Tried to open a file in read "
260                                                   "mode that didn't exist!");
261                                 return NULL;
262                         }
263                         modestr = g_strdup(binary? "rb" : "r");
264                         break;
265                 case filemode_Write:
266                         modestr = g_strdup(binary? "wb" : "w");
267                         break;
268                 case filemode_WriteAppend:
269                         modestr = g_strdup(binary? "ab" : "a");
270                         break;
271                 case filemode_ReadWrite:
272                         modestr = g_strdup(binary? "r+b" : "r+");
273                         break;
274                 default:
275                         g_warning("glk_stream_open_file: Invalid file mode");
276                         return NULL;
277         }
278         
279         FILE *fp = g_fopen(fileref->filename, modestr);
280         g_free(modestr);
281         if(fp == NULL) {
282                 g_warning("glk_stream_open_file: Error opening file");
283                 return NULL;
284         }
285         
286         /* If they opened a file in write mode but didn't specifically get
287         permission to do so, complain if the file already exists */
288         if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) {
289                 GtkWidget *dialog = gtk_message_dialog_new(NULL, 0,
290                         GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
291                         "File %s already exists. Overwrite?", fileref->filename);
292                 gint response = gtk_dialog_run(GTK_DIALOG(dialog));
293                 gtk_widget_destroy(dialog);
294                 if(response != GTK_RESPONSE_YES) {
295                         fclose(fp);
296                         return NULL;
297                 }
298         }
299         
300         strid_t s = g_new0(struct glk_stream_struct, 1);
301         s->rock = rock;
302         s->file_mode = fmode;
303         s->stream_type = STREAM_TYPE_FILE;
304         s->file_pointer = fp;
305         s->binary = binary;
306         s->unicode = unicode;
307         s->filename = g_filename_to_utf8(fileref->filename, -1, NULL, NULL, NULL);
308         if(s->filename == NULL)
309                 s->filename = g_strdup("Unknown file name"); /* fail silently */
310         /* Add it to the global stream list */
311         stream_list = g_list_prepend(stream_list, s);
312         s->stream_list = stream_list;
313
314         return s;
315 }
316
317 /**
318  * glk_stream_open_file:
319  * @fileref: Indicates the file which will be opened.
320  * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read,
321  * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite.
322  * @rock: The new stream's rock value.
323  *
324  * Opens a stream which reads to or writes from a disk file. If @fmode is
325  * #filemode_Read, the file must already exist; for the other modes, an empty
326  * file is created if none exists. If @fmode is #filemode_Write, and the file
327  * already exists, it is truncated down to zero length (an empty file). If
328  * @fmode is #filemode_WriteAppend, the file mark is set to the end of the 
329  * file.
330  * 
331  * The file may be written in text or binary mode; this is determined by the
332  * @fileref argument. Similarly, platform-dependent attributes such as file 
333  * type are determined by @fileref.
334  *
335  * When writing in binary mode, Unicode values (characters greater than 255)
336  * cannot be written to the file. If you try, they will be stored as 0x3F ("?")
337  * characters. In text mode, Unicode values are stored in UTF-8.
338  *
339  * Returns: A new stream, or %NULL if the file operation failed.
340  */
341 strid_t
342 glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock)
343 {
344         return file_stream_new(fileref, fmode, rock, FALSE);
345 }
346
347 /**
348  * glk_stream_open_file_uni:
349  * @fileref: Indicates the file which will be opened.
350  * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read,
351  * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite.
352  * @rock: The new stream's rock value.
353  *
354  * This works just like glk_stream_open_file(), except that in binary mode,
355  * characters are written and read as four-byte (big-endian) values. This
356  * allows you to write any Unicode character.
357  *
358  * In text mode, the file is written and read in UTF-8.
359  *
360  * Returns: A new stream, or %NULL if the file operation failed.
361  */
362 strid_t
363 glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock)
364 {
365         return file_stream_new(fileref, fmode, rock, TRUE);
366 }
367
368 /**
369  * glk_stream_close:
370  * @str: Stream to close.
371  * @result: Pointer to a #stream_result_t, or %NULL.
372  *
373  * Closes the stream @str. The @result argument points to a structure which is
374  * filled in with the final character counts of the stream. If you do not care
375  * about these, you may pass %NULL as the @result argument.
376  *
377  * If @str is the current output stream, the current output stream is set to
378  * %NULL.
379  *
380  * You cannot close window streams; use glk_window_close() instead.
381  */
382 void 
383 glk_stream_close(strid_t str, stream_result_t *result)
384 {
385         g_return_if_fail(str != NULL);
386         
387         /* Free resources associated with one specific type of stream */
388         switch(str->stream_type)
389         {
390                 case STREAM_TYPE_WINDOW:
391                         g_warning("%s: Attempted to close a window stream. Use glk_window_"
392                                 "close() instead.", __func__);
393                         return;
394                         
395                 case STREAM_TYPE_MEMORY:
396                         /* Do nothing */
397                         break;
398                         
399                 case STREAM_TYPE_FILE:
400                         if(fclose(str->file_pointer) != 0)
401                                 g_warning("%s: Failed to close file '%s'.", __func__, 
402                                         str->filename);
403                         g_free(str->filename);
404                         break;
405                 default:
406                         g_warning("%s: Closing this type of stream not supported.", 
407                                 __func__);
408                         return;
409         }
410         
411         /* Remove the stream from the global stream list */
412         stream_list = g_list_delete_link(stream_list, str->stream_list);
413         /* If it was the current output stream, set that to NULL */
414         if(current_stream == str)
415                 current_stream = NULL;
416         /* If it was one or more windows' echo streams, set those to NULL */
417         winid_t win;
418         for(win = glk_window_iterate(NULL, NULL); win; 
419                 win = glk_window_iterate(win, NULL))
420                 if(win->echo_stream == str)
421                         win->echo_stream = NULL;
422         /* Return the character counts */
423         if(result) 
424         {
425                 result->readcount = str->read_count;
426                 result->writecount = str->write_count;
427         }
428         g_free(str);
429 }
430