X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=libchimara%2Fstream.c;h=46a2efc5cb077b8af39d54c86427d1c497178490;hb=0c5e51127206d26290a8e1e1349ab37d847d1d84;hp=f6abec64885e4718783485df6a72e14efd9d185d;hpb=1f983431c87bfc86307c790f519409672f2d0e5a;p=projects%2Fchimara%2Fchimara.git diff --git a/libchimara/stream.c b/libchimara/stream.c index f6abec6..46a2efc 100644 --- a/libchimara/stream.c +++ b/libchimara/stream.c @@ -1,20 +1,23 @@ +#include #include "stream.h" #include "fileref.h" #include "magic.h" +#include "gi_blorb.h" #include #include #include #include +#include #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 Rocks. + * linkend="chimara-Rocks">Rocks. 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, again, that this doesn't match stdio's fopen() call very well. See + * the file mode constants. + * + * + * 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 File References. * * 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 that there is no notion of file usage — the resource does not + * have to be specified as saved game or whatever. + * + * + * 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. + * + * + * Thus, the difference between text and binary resources is only important + * when opened as a Unicode stream. + * + * + * 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;