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