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