No need to include empty default signal handlers
[projects/chimara/chimara.git] / libchimara / stream.c
index f6abec64885e4718783485df6a72e14efd9d185d..46a2efc5cb077b8af39d54c86427d1c497178490 100644 (file)
@@ -1,20 +1,23 @@
+#include <config.h>
 #include "stream.h"
 #include "fileref.h"
 #include "magic.h"
+#include "gi_blorb.h"
 #include <errno.h>
 #include <stdio.h>
 #include <glib.h>
 #include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
 
 #include "chimara-glk-private.h"
-extern GPrivate *glk_data_key;
+extern GPrivate glk_data_key;
 
 /* Internal function: create a stream with a specified rock value */
 strid_t
 stream_new_common(glui32 rock)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+
        strid_t str = g_new0(struct glk_stream_struct, 1);
        str->magic = MAGIC_STREAM;
        str->rock = rock;
@@ -24,24 +27,8 @@ stream_new_common(glui32 rock)
        /* Add it to the global stream list */
        glk_data->stream_list = g_list_prepend(glk_data->stream_list, str);
        str->stream_list = glk_data->stream_list;
-       
-       return str;
-}
 
-/* Internal function: stream closing stuff that is safe to call from either the
- main thread or the Glk thread. */
-void
-trash_stream_thread_independent(ChimaraGlkPrivate *glk_data, strid_t str)
-{
-       /* Remove the stream from the global stream list */
-       glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list);
-       
-       /* If it was the current output stream, set that to NULL */
-       if(glk_data->current_stream == str)
-               glk_data->current_stream = NULL;
-       
-       str->magic = MAGIC_FREE;
-       g_free(str);
+       return str;
 }
 
 /* Internal function: Stuff to do upon closing any type of stream. Call only 
@@ -49,7 +36,7 @@ trash_stream_thread_independent(ChimaraGlkPrivate *glk_data, strid_t str)
 void
 stream_close_common(strid_t str, stream_result_t *result)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
 
        if(glk_data->unregister_obj)
        {
@@ -71,7 +58,15 @@ stream_close_common(strid_t str, stream_result_t *result)
                result->writecount = str->write_count;
        }
 
-       trash_stream_thread_independent(glk_data, str);
+       /* Remove the stream from the global stream list */
+       glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list);
+       
+       /* If it was the current output stream, set that to NULL */
+       if(glk_data->current_stream == str)
+               glk_data->current_stream = NULL;
+       
+       str->magic = MAGIC_FREE;
+       g_free(str);
 }
 
 /**
@@ -90,7 +85,7 @@ glk_stream_iterate(strid_t str, glui32 *rockptr)
 {
        VALID_STREAM_OR_NULL(str, return NULL);
 
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        GList *retnode;
        
        if(str == NULL)
@@ -111,7 +106,8 @@ glk_stream_iterate(strid_t str, glui32 *rockptr)
  * @str: A stream.
  * 
  * Retrieves the stream @str's rock value. See <link 
- * linkend="chimara-Rocks">Rocks</link>.
+ * linkend="chimara-Rocks">Rocks</link>. Window streams always have rock 0; all
+ * other streams return whatever rock you created them with.
  *
  * Returns: A rock value.
  */
@@ -135,8 +131,8 @@ glk_stream_set_current(strid_t str)
 {
        VALID_STREAM_OR_NULL(str, return);
 
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+
        if(str != NULL && str->file_mode == filemode_Read)
        {
                ILLEGAL("Cannot set current stream to non output stream");
@@ -156,7 +152,7 @@ glk_stream_set_current(strid_t str)
 strid_t
 glk_stream_get_current()
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        return glk_data->current_stream;
 }
 
@@ -171,7 +167,7 @@ glk_stream_get_current()
 void
 glk_put_char(unsigned char ch)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_char_stream(glk_data->current_stream, ch);
 }
@@ -187,7 +183,7 @@ glk_put_char(unsigned char ch)
 void
 glk_put_char_uni(glui32 ch)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_char_stream_uni(glk_data->current_stream, ch);
 }
@@ -200,14 +196,14 @@ glk_put_char_uni(glui32 ch)
  * equivalent to
  * |[
  * for (ptr = s; *ptr; ptr++)
- *     #glk_put_char(*ptr);
+ *     glk_put_char(*ptr);
  * ]|
  * However, it may be more efficient.
  */
 void
 glk_put_string(char *s)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_string_stream(glk_data->current_stream, s);
 }
@@ -223,7 +219,7 @@ glk_put_string(char *s)
 void
 glk_put_string_uni(glui32 *s)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_string_stream_uni(glk_data->current_stream, s);
 }
@@ -237,14 +233,14 @@ glk_put_string_uni(glui32 *s)
  * to:
  * |[
  * for (i = 0; i < len; i++)
- *     #glk_put_char(buf[i]);
+ *     glk_put_char(buf[i]);
  * ]|
  * However, it may be more efficient.
  */
 void
 glk_put_buffer(char *buf, glui32 len)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_buffer_stream(glk_data->current_stream, buf, len);
 }
@@ -260,7 +256,7 @@ glk_put_buffer(char *buf, glui32 len)
 void
 glk_put_buffer_uni(glui32 *buf, glui32 len)
 {
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        VALID_STREAM(glk_data->current_stream, return);
        glk_put_buffer_stream_uni(glk_data->current_stream, buf, len);
 }
@@ -291,11 +287,12 @@ glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock)
        str->file_mode = fmode;
        str->type = STREAM_TYPE_MEMORY;
        str->mark = 0;
+       str->endmark = 0;
        str->unicode = FALSE;
 
        if(buf && buflen) 
        {
-               ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+               ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
                str->buffer = buf;
                str->buflen = buflen;
                if(glk_data->register_arr) 
@@ -329,11 +326,12 @@ glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock
        str->file_mode = fmode;
        str->type = STREAM_TYPE_MEMORY;
        str->mark = 0;
+       str->endmark = 0;
        str->unicode = TRUE;
 
        if(buf && buflen) 
        {
-               ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+               ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
                str->ubuffer = buf;
                str->buflen = buflen;
                if(glk_data->register_arr) 
@@ -349,7 +347,7 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
 {
        VALID_FILEREF(fileref, return NULL);
        
-       gchar *modestr;
+       const gchar *modestr;
        /* Binary mode is 0x000, text mode 0x100 */
        gboolean binary = !(fileref->usage & fileusage_TextMode);
        switch(fmode) 
@@ -359,20 +357,23 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
                                ILLEGAL_PARAM("Tried to open a nonexistent file, '%s', in read mode", fileref->filename);
                                return NULL;
                        }
-                       modestr = g_strdup(binary? "rb" : "r");
+                       modestr = binary? "rb" : "r";
                        break;
                case filemode_Write:
-                       modestr = g_strdup(binary? "wb" : "w");
+                       modestr = binary? "wb" : "w";
                        break;
                case filemode_WriteAppend:
-                       modestr = g_strdup(binary? "ab" : "a");
-                       break;
                case filemode_ReadWrite:
-                       if( g_file_test(fileref->filename, G_FILE_TEST_EXISTS) ) {
-                               modestr = g_strdup(binary? "r+b" : "r+");
-                       } else {
-                               modestr = g_strdup(binary? "w+b" : "w+");
+               {
+                       /* We have to open the file first and then close it, in order to
+                        both make sure it exists and be able to seek in it later */
+                       FILE *fp = g_fopen(fileref->filename, binary? "ab" : "a");
+                       if(fclose(fp) != 0) {
+                               IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) );
+                               return NULL;
                        }
+                       modestr = binary? "r+b" : "r+";
+               }
                        break;
                default:
                        ILLEGAL_PARAM("Invalid file mode: %u", fmode);
@@ -380,12 +381,17 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
        }
        
        FILE *fp = g_fopen(fileref->filename, modestr);
-       g_free(modestr);
        if(fp == NULL) {
                IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) );
                return NULL;
        }
        
+       /* Fast-forward to the end if we are appending */
+       if(fmode == filemode_WriteAppend && fseek(fp, 0, SEEK_END) != 0) {
+               IO_WARNING("Error fast-forwarding file to end", fileref->filename, g_strerror(errno));
+               return NULL;
+       }
+
        /* If they opened a file in write mode but didn't specifically get
        permission to do so, complain if the file already exists */
        if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) {
@@ -429,9 +435,22 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
  * Opens a stream which reads to or writes from a disk file. If @fmode is
  * %filemode_Read, the file must already exist; for the other modes, an empty
  * file is created if none exists. If @fmode is %filemode_Write, and the file
- * already exists, it is truncated down to zero length (an empty file). If
- * @fmode is %filemode_WriteAppend, the file mark is set to the end of the 
- * file.
+ * already exists, it is truncated down to zero length (an empty file); the
+ * other modes do not truncate. If @fmode is %filemode_WriteAppend, the file
+ * mark is set to the end of the file.
+ *
+ * <note><para>
+ *   Note, again, that this doesn't match stdio's fopen() call very well. See
+ *   <link linkend="filemode-WriteAppend">the file mode constants</link>.
+ * </para></note>
+ *
+ * If the filemode requires the file to exist, but the file does not exist,
+ * glk_stream_open_file() returns %NULL.
+ *
+ * The file may be read or written in text or binary mode; this is determined
+ * by the @fileref argument. Similarly, platform-dependent attributes such as
+ * file type are determined by @fileref. See <link
+ * linkend="chimara-File-References">File References</link>.
  *
  * When writing in binary mode, Unicode values (characters greater than 255)
  * cannot be written to the file. If you try, they will be stored as 0x3F
@@ -472,6 +491,122 @@ glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock)
        return file_stream_new(fileref, fmode, rock, TRUE);
 }
 
+/**
+ * glk_stream_open_resource:
+ * @filenum: Resource chunk number to open.
+ * @rock: The new stream's rock value.
+ *
+ * Open the given data resource for reading (only), as a normal stream.
+ *
+ * <note><para>
+ *   Note that there is no notion of file usage &mdash; the resource does not
+ *   have to be specified as <quote>saved game</quote> or whatever.
+ * </para></note>
+ *
+ * If no resource chunk of the given number exists, the open function returns
+ * %NULL.
+ *
+ * As with file streams, a binary resource stream reads the resource as bytes. A
+ * text resource stream reads characters encoded as Latin-1.
+ *
+ * When reading from a resource stream, newlines are not remapped, even if they
+ * normally would be when reading from a text file on the host OS. If you read a
+ * line (glk_get_line_stream() or glk_get_line_stream_uni()), a Unix newline
+ * (0x0A) terminates the line.
+ *
+ * Returns: the new stream, or %NULL.
+ */
+strid_t
+glk_stream_open_resource(glui32 filenum, glui32 rock)
+{
+       /* Adapted from CheapGlk */
+       strid_t str;
+       gboolean isbinary;
+       giblorb_err_t err;
+       giblorb_result_t res;
+       giblorb_map_t *map = giblorb_get_resource_map();
+       if(map == NULL) {
+               WARNING(_("Could not create resource stream, because there was no "
+                       "resource map."));
+               return NULL; /* Not running from a blorb file */
+       }
+
+       err = giblorb_load_resource(map, giblorb_method_Memory, &res, giblorb_ID_Data, filenum);
+       if(err) {
+               WARNING_S(_("Could not create resource stream, because the resource "
+                       "could not be loaded"), giblorb_get_error_message(err));
+               return 0; /* Not found, or some other error */
+       }
+
+       /* We'll use the in-memory copy of the chunk data as the basis for
+       our new stream. It's important to not call chunk_unload() until
+       the stream is closed (and we won't).
+
+       This will be memory-hoggish for giant data chunks, but I don't
+       expect giant data chunks at this point. A more efficient model
+       would be to use the file on disk, but this requires some hacking
+       into the file stream code (we'd need to open a new FILE*) and
+       I don't feel like doing that. */
+
+       if(res.chunktype == giblorb_ID_TEXT)
+               isbinary = FALSE;
+       else if(res.chunktype == giblorb_ID_BINA)
+               isbinary = TRUE;
+       else {
+               WARNING(_("Could not create resource stream, because chunk was of "
+                       "unknown type."));
+               return NULL; /* Unknown chunk type */
+       }
+
+       str = stream_new_common(rock);
+       str->type = STREAM_TYPE_RESOURCE;
+       str->file_mode = filemode_Read;
+       str->binary = isbinary;
+
+       if (res.data.ptr && res.length) {
+               str->buffer = res.data.ptr;
+               str->mark = 0;
+               str->buflen = res.length;
+               str->endmark = str->buflen;
+       }
+
+       return str;
+}
+
+/**
+ * glk_stream_open_resource_uni:
+ * @filenum: Resource chunk number to open.
+ * @rock: The new stream's rock value.
+ *
+ * Open the given data resource for reading (only), as a Unicode stream. See
+ * glk_stream_open_resource() for more information.
+ *
+ * As with file streams, a binary resource stream reads the resource as
+ * four-byte (big-endian) words. A text resource stream reads characters encoded
+ * as UTF-8.
+ *
+ * <note><para>
+ *   Thus, the difference between text and binary resources is only important
+ *   when opened as a Unicode stream.
+ * </para></note>
+ *
+ * Returns: the new stream, or %NULL.
+ */
+strid_t
+glk_stream_open_resource_uni(glui32 filenum, glui32 rock)
+{
+       /* Adapted from CheapGlk */
+       /* We have been handed an array of bytes. (They're big-endian
+       four-byte chunks, or perhaps a UTF-8 byte sequence, rather than
+       native-endian four-byte integers). So we drop it into str->buffer,
+       rather than str->ubuffer -- we'll have to do the translation in the
+       get() functions. */
+       strid_t str = glk_stream_open_resource(filenum, rock);
+       if(str != NULL)
+               str->unicode = TRUE;
+       return str;
+}
+
 /**
  * glk_stream_close:
  * @str: Stream to close.
@@ -502,7 +637,7 @@ glk_stream_close(strid_t str, stream_result_t *result)
                        
                case STREAM_TYPE_MEMORY:
                {
-                       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+                       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
                        if(glk_data->unregister_arr) 
                        {
                                if(str->unicode)
@@ -518,6 +653,12 @@ glk_stream_close(strid_t str, stream_result_t *result)
                                IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) );
                        g_free(str->filename);
                        break;
+
+               case STREAM_TYPE_RESOURCE:
+                       /* Shouldn't free the chunk; someone else might be using it. It will
+                       be freed when the resource map is freed. */
+                       break;
+
                default:
                        ILLEGAL_PARAM("Unknown stream type: %u", str->type);
                        return;