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