Bug fixes
[rodin/chimara.git] / libchimara / stream.c
1 #include "stream.h"
2 #include "fileref.h"
3 #include "magic.h"
4 #include <errno.h>
5 #include <stdio.h>
6 #include <glib.h>
7 #include <glib/gstdio.h>
8
9 #include "chimara-glk-private.h"
10 extern GPrivate *glk_data_key;
11
12 /* Internal function: create a stream with a specified rock value */
13 strid_t
14 stream_new_common(glui32 rock)
15 {
16         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
17         
18         strid_t str = g_new0(struct glk_stream_struct, 1);
19         str->magic = MAGIC_STREAM;
20         str->rock = rock;
21         if(glk_data->register_obj)
22                 str->disprock = (*glk_data->register_obj)(str, gidisp_Class_Stream);
23                 
24         /* Add it to the global stream list */
25         glk_data->stream_list = g_list_prepend(glk_data->stream_list, str);
26         str->stream_list = glk_data->stream_list;
27         
28         return str;
29 }
30
31 /* Internal function: stream closing stuff that is safe to call from either the
32  main thread or the Glk thread. */
33 void
34 trash_stream_thread_independent(ChimaraGlkPrivate *glk_data, strid_t str)
35 {
36         /* Remove the stream from the global stream list */
37         glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list);
38         
39         /* If it was the current output stream, set that to NULL */
40         if(glk_data->current_stream == str)
41                 glk_data->current_stream = NULL;
42         
43         str->magic = MAGIC_FREE;
44         g_free(str);
45 }
46
47 /* Internal function: Stuff to do upon closing any type of stream. Call only 
48  from Glk thread. */
49 void
50 stream_close_common(strid_t str, stream_result_t *result)
51 {
52         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
53
54         if(glk_data->unregister_obj)
55         {
56                 (*glk_data->unregister_obj)(str, gidisp_Class_Stream, str->disprock);
57                 str->disprock.ptr = NULL;
58         }
59         
60         /* If the stream was one or more windows' echo streams, set those to NULL */
61         winid_t win;
62         for(win = glk_window_iterate(NULL, NULL); win; 
63                 win = glk_window_iterate(win, NULL))
64                 if(win->echo_stream == str)
65                         win->echo_stream = NULL;
66         
67         /* Return the character counts */
68         if(result) 
69         {
70                 result->readcount = str->read_count;
71                 result->writecount = str->write_count;
72         }
73
74         trash_stream_thread_independent(glk_data, str);
75 }
76
77 /**
78  * glk_stream_iterate:
79  * @str: A stream, or %NULL.
80  * @rockptr: Return location for the next window's rock, or %NULL.
81  *
82  * Iterates through all the existing streams. See <link
83  * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
84  * Objects</link>.
85  *
86  * Returns: the next stream, or %NULL if there are no more.
87  */
88 strid_t
89 glk_stream_iterate(strid_t str, glui32 *rockptr)
90 {
91         VALID_STREAM_OR_NULL(str, return NULL);
92
93         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
94         GList *retnode;
95         
96         if(str == NULL)
97                 retnode = glk_data->stream_list;
98         else
99                 retnode = str->stream_list->next;
100         strid_t retval = retnode? (strid_t)retnode->data : NULL;
101                 
102         /* Store the stream's rock in rockptr */
103         if(retval && rockptr)
104                 *rockptr = glk_stream_get_rock(retval);
105                 
106         return retval;
107 }
108
109 /**
110  * glk_stream_get_rock:
111  * @str: A stream.
112  * 
113  * Retrieves the stream @str's rock value. See <link 
114  * linkend="chimara-Rocks">Rocks</link>.
115  *
116  * Returns: A rock value.
117  */
118 glui32
119 glk_stream_get_rock(strid_t str)
120 {
121         VALID_STREAM(str, return 0);
122         return str->rock;
123 }
124
125 /**
126  * glk_stream_set_current:
127  * @str: An output stream, or %NULL.
128  *
129  * Sets the current stream to @str, which must be an output stream. You may set
130  * the current stream to %NULL, which means the current stream is not set to
131  * anything. 
132  */
133 void
134 glk_stream_set_current(strid_t str)
135 {
136         VALID_STREAM_OR_NULL(str, return);
137
138         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
139         
140         if(str != NULL && str->file_mode == filemode_Read)
141         {
142                 ILLEGAL("Cannot set current stream to non output stream");
143                 return;
144         }
145
146         glk_data->current_stream = str;
147 }
148
149 /**
150  * glk_stream_get_current:
151  * 
152  * Returns the current stream, or %NULL if there is none.
153  *
154  * Returns: A stream, or %NULL.
155  */
156 strid_t
157 glk_stream_get_current()
158 {
159         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
160         return glk_data->current_stream;
161 }
162
163 /**
164  * glk_put_char:
165  * @ch: A character in Latin-1 encoding.
166  *
167  * Prints one character to the current stream. As with all basic functions, the
168  * character is assumed to be in the Latin-1 character encoding. See <link
169  * linkend="chimara-Character-Encoding">Character Encoding</link>.
170  */
171 void
172 glk_put_char(unsigned char ch)
173 {
174         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
175         VALID_STREAM(glk_data->current_stream, return);
176         glk_put_char_stream(glk_data->current_stream, ch);
177 }
178
179 /**
180  * glk_put_char_uni:
181  * @ch: A Unicode code point.
182  *
183  * Prints one character to the current stream. The character is assumed to be a
184  * Unicode code point. See <link linkend="chimara-Character-Encoding">Character
185  * Encoding</link>.
186  */
187 void
188 glk_put_char_uni(glui32 ch)
189 {
190         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
191         VALID_STREAM(glk_data->current_stream, return);
192         glk_put_char_stream_uni(glk_data->current_stream, ch);
193 }
194
195 /**
196  * glk_put_string:
197  * @s: A null-terminated string in Latin-1 encoding.
198  *
199  * Prints a null-terminated string to the current stream. It is exactly
200  * equivalent to
201  * |[
202  * for (ptr = s; *ptr; ptr++)
203  *      #glk_put_char(*ptr);
204  * ]|
205  * However, it may be more efficient.
206  */
207 void
208 glk_put_string(char *s)
209 {
210         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
211         VALID_STREAM(glk_data->current_stream, return);
212         glk_put_string_stream(glk_data->current_stream, s);
213 }
214
215 /**
216  * glk_put_string_uni:
217  * @s: A zero-terminated string of Unicode code points.
218  * 
219  * Prints a string of Unicode characters to the current stream. It is equivalent
220  * to a series of glk_put_char_uni() calls. A string ends on a #glui32 whose
221  * value is 0.
222  */
223 void
224 glk_put_string_uni(glui32 *s)
225 {
226         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
227         VALID_STREAM(glk_data->current_stream, return);
228         glk_put_string_stream_uni(glk_data->current_stream, s);
229 }
230
231 /**
232  * glk_put_buffer:
233  * @buf: An array of characters in Latin-1 encoding.
234  * @len: Length of @buf.
235  *
236  * Prints a block of characters to the current stream. It is exactly equivalent
237  * to:
238  * |[
239  * for (i = 0; i < len; i++)
240  *      #glk_put_char(buf[i]);
241  * ]|
242  * However, it may be more efficient.
243  */
244 void
245 glk_put_buffer(char *buf, glui32 len)
246 {
247         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
248         VALID_STREAM(glk_data->current_stream, return);
249         glk_put_buffer_stream(glk_data->current_stream, buf, len);
250 }
251
252 /**
253  * glk_put_buffer_uni:
254  * @buf: An array of Unicode code points.
255  * @len: Length of @buf.
256  *
257  * Prints a block of Unicode characters to the current stream. It is equivalent
258  * to a series of glk_put_char_uni() calls.
259  */
260 void
261 glk_put_buffer_uni(glui32 *buf, glui32 len)
262 {
263         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
264         VALID_STREAM(glk_data->current_stream, return);
265         glk_put_buffer_stream_uni(glk_data->current_stream, buf, len);
266 }
267
268 /**
269  * glk_stream_open_memory:
270  * @buf: An allocated buffer, or %NULL.
271  * @buflen: Length of @buf.
272  * @fmode: Mode in which the buffer will be opened. Must be one of 
273  * %filemode_Read, %filemode_Write, or %filemode_ReadWrite.
274  * @rock: The new stream's rock value.
275  *
276  * Opens a stream which reads from or writes to a space in memory. @buf points
277  * to the buffer where output will be read from or written to. @buflen is the
278  * length of the buffer.
279  *
280  * Unicode values (characters greater than 255) cannot be written to the buffer.
281  * If you try, they will be stored as 0x3F (<code>"?"</code>) characters.
282  *
283  * Returns: the new stream, or %NULL on error.
284  */
285 strid_t
286 glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock)
287 {
288         g_return_val_if_fail(fmode != filemode_WriteAppend, NULL);
289         
290         strid_t str = stream_new_common(rock);
291         str->file_mode = fmode;
292         str->type = STREAM_TYPE_MEMORY;
293         str->mark = 0;
294         str->unicode = FALSE;
295
296         if(buf && buflen) 
297         {
298                 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
299                 str->buffer = buf;
300                 str->buflen = buflen;
301                 if(glk_data->register_arr) 
302                         str->buffer_rock = (*glk_data->register_arr)(buf, buflen, "&+#!Cn");
303         }
304         
305         return str;
306 }
307
308 /**
309  * glk_stream_open_memory_uni:
310  * @buf: An allocated buffer, or %NULL.
311  * @buflen: Length of @buf.
312  * @fmode: Mode in which the buffer will be opened. Must be one of 
313  * %filemode_Read, %filemode_Write, or %filemode_ReadWrite.
314  * @rock: The new stream's rock value.
315  *
316  * Works just like glk_stream_open_memory(), except that the buffer is an array
317  * of 32-bit words, instead of bytes. This allows you to write and read any
318  * Unicode character. The @buflen is the number of words, not the number of
319  * bytes.
320  * 
321  * Returns: the new stream, or %NULL on error.
322  */
323 strid_t
324 glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock)
325 {
326         g_return_val_if_fail(fmode != filemode_WriteAppend, NULL);
327         
328         strid_t str = stream_new_common(rock);
329         str->file_mode = fmode;
330         str->type = STREAM_TYPE_MEMORY;
331         str->mark = 0;
332         str->unicode = TRUE;
333
334         if(buf && buflen) 
335         {
336                 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
337                 str->ubuffer = buf;
338                 str->buflen = buflen;
339                 if(glk_data->register_arr) 
340                         str->buffer_rock = (*glk_data->register_arr)(buf, buflen, "&+#!Iu");
341         }
342         
343         return str;
344 }
345
346 /* Internal function: create a stream using the given parameters. */
347 strid_t
348 file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
349 {
350         VALID_FILEREF(fileref, return NULL);
351         
352         gchar *modestr;
353         /* Binary mode is 0x000, text mode 0x100 */
354         gboolean binary = !(fileref->usage & fileusage_TextMode);
355         switch(fmode) 
356         {
357                 case filemode_Read:
358                         if(!g_file_test(fileref->filename, G_FILE_TEST_EXISTS)) {
359                                 ILLEGAL_PARAM("Tried to open a nonexistent file, '%s', in read mode", fileref->filename);
360                                 return NULL;
361                         }
362                         modestr = g_strdup(binary? "rb" : "r");
363                         break;
364                 case filemode_Write:
365                         modestr = g_strdup(binary? "wb" : "w");
366                         break;
367                 case filemode_WriteAppend:
368                         modestr = g_strdup(binary? "ab" : "a");
369                         break;
370                 case filemode_ReadWrite:
371                         if( g_file_test(fileref->filename, G_FILE_TEST_EXISTS) ) {
372                                 modestr = g_strdup(binary? "r+b" : "r+");
373                         } else {
374                                 modestr = g_strdup(binary? "w+b" : "w+");
375                         }
376                         break;
377                 default:
378                         ILLEGAL_PARAM("Invalid file mode: %u", fmode);
379                         return NULL;
380         }
381         
382         FILE *fp = g_fopen(fileref->filename, modestr);
383         g_free(modestr);
384         if(fp == NULL) {
385                 IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) );
386                 return NULL;
387         }
388         
389         /* If they opened a file in write mode but didn't specifically get
390         permission to do so, complain if the file already exists */
391         if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) {
392                 gdk_threads_enter();
393
394                 GtkWidget *dialog = gtk_message_dialog_new(NULL, 0,
395                         GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
396                         "File '%s' already exists. Overwrite?", fileref->filename);
397                 gint response = gtk_dialog_run(GTK_DIALOG(dialog));
398                 gtk_widget_destroy(dialog);
399
400                 gdk_threads_leave();
401
402                 if(response != GTK_RESPONSE_YES) {
403                         if(fclose(fp) != 0)
404                                 IO_WARNING( "Error closing file", fileref->filename, g_strerror(errno) );
405                         return NULL;
406                 }
407         }
408         
409         strid_t str = stream_new_common(rock);
410         str->file_mode = fmode;
411         str->type = STREAM_TYPE_FILE;
412         str->file_pointer = fp;
413         str->binary = binary;
414         str->unicode = unicode;
415         str->filename = g_filename_to_utf8(fileref->filename, -1, NULL, NULL, NULL);
416         if(str->filename == NULL)
417                 str->filename = g_strdup("Unknown file name"); /* fail silently */
418
419         return str;
420 }
421
422 /**
423  * glk_stream_open_file:
424  * @fileref: Indicates the file which will be opened.
425  * @fmode: Mode in which the file will be opened. Can be any of %filemode_Read,
426  * %filemode_Write, %filemode_WriteAppend, or %filemode_ReadWrite.
427  * @rock: The new stream's rock value.
428  *
429  * Opens a stream which reads to or writes from a disk file. If @fmode is
430  * %filemode_Read, the file must already exist; for the other modes, an empty
431  * file is created if none exists. If @fmode is %filemode_Write, and the file
432  * already exists, it is truncated down to zero length (an empty file). If
433  * @fmode is %filemode_WriteAppend, the file mark is set to the end of the 
434  * file.
435  *
436  * When writing in binary mode, Unicode values (characters greater than 255)
437  * cannot be written to the file. If you try, they will be stored as 0x3F
438  * (<code>"?"</code>) characters. In text mode, Unicode values may be stored
439  * exactly, approximated, or abbreviated, depending on what the platform's text
440  * files support.
441  *
442  * Returns: A new stream, or %NULL if the file operation failed.
443  */
444 strid_t
445 glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock)
446 {
447         return file_stream_new(fileref, fmode, rock, FALSE);
448 }
449
450 /**
451  * glk_stream_open_file_uni:
452  * @fileref: Indicates the file which will be opened.
453  * @fmode: Mode in which the file will be opened. Can be any of %filemode_Read,
454  * %filemode_Write, %filemode_WriteAppend, or %filemode_ReadWrite.
455  * @rock: The new stream's rock value.
456  *
457  * This works just like glk_stream_open_file(), except that in binary mode,
458  * characters are written and read as four-byte (big-endian) values. This
459  * allows you to write any Unicode character.
460  *
461  * In text mode, the file is written and read in a platform-dependent way, which
462  * may or may not handle all Unicode characters. A text-mode file created with
463  * glk_stream_open_file_uni() may have the same format as a text-mode file
464  * created with glk_stream_open_file(); or it may use a more Unicode-friendly
465  * format.
466  *
467  * Returns: A new stream, or %NULL if the file operation failed.
468  */
469 strid_t
470 glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock)
471 {
472         return file_stream_new(fileref, fmode, rock, TRUE);
473 }
474
475 /**
476  * glk_stream_close:
477  * @str: Stream to close.
478  * @result: Pointer to a #stream_result_t, or %NULL.
479  *
480  * Closes the stream @str. The @result argument points to a structure which is
481  * filled in with the final character counts of the stream. If you do not care
482  * about these, you may pass %NULL as the @result argument.
483  *
484  * If @str is the current output stream, the current output stream is set to
485  * %NULL.
486  *
487  * You cannot close window streams; use glk_window_close() instead. See <link
488  * linkend="chimara-Window-Opening-Closing-and-Constraints">Window Opening,
489  * Closing, and Constraints</link>.
490  */
491 void 
492 glk_stream_close(strid_t str, stream_result_t *result)
493 {
494         VALID_STREAM(str, return);
495         
496         /* Free resources associated with one specific type of stream */
497         switch(str->type)
498         {
499                 case STREAM_TYPE_WINDOW:
500                         ILLEGAL("Attempted to close a window stream. Use glk_window_close() instead.");
501                         return;
502                         
503                 case STREAM_TYPE_MEMORY:
504                 {
505                         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
506                         if(glk_data->unregister_arr) 
507                         {
508                                 if(str->unicode)
509                                         (*glk_data->unregister_arr)(str->ubuffer, str->buflen, "&+#!Iu", str->buffer_rock);
510                                 else
511                                         (*glk_data->unregister_arr)(str->buffer, str->buflen, "&+#!Cn", str->buffer_rock);
512             }
513                 }
514                         break;
515                 
516                 case STREAM_TYPE_FILE:
517                         if(fclose(str->file_pointer) != 0)
518                                 IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) );
519                         g_free(str->filename);
520                         break;
521                 default:
522                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
523                         return;
524         }
525
526         stream_close_common(str, result);
527 }