X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=libchimara%2Fstream.c;h=46a2efc5cb077b8af39d54c86427d1c497178490;hb=HEAD;hp=4a1e004cfad42e9235b5a239efb62cd88617ca14;hpb=cfdddc22cc7aa7fcfaebb74102de03de8f4ad27a;p=projects%2Fchimara%2Fchimara.git diff --git a/libchimara/stream.c b/libchimara/stream.c index 4a1e004..46a2efc 100644 --- a/libchimara/stream.c +++ b/libchimara/stream.c @@ -1,40 +1,72 @@ +#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 ChimaraGlkPrivate *glk_data; +extern GPrivate glk_data_key; /* Internal function: create a stream with a specified rock value */ -static strid_t -stream_new_common(glui32 rock, glui32 fmode, enum StreamType type) +strid_t +stream_new_common(glui32 rock) { + 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; - str->file_mode = fmode; - str->type = type; + if(glk_data->register_obj) + str->disprock = (*glk_data->register_obj)(str, gidisp_Class_Stream); /* 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: create a window stream to go with window. */ -strid_t -window_stream_new(winid_t window) +/* Internal function: Stuff to do upon closing any type of stream. Call only + from Glk thread. */ +void +stream_close_common(strid_t str, stream_result_t *result) { - /* Create stream and connect it to window */ - strid_t str = stream_new_common(0, filemode_Write, STREAM_TYPE_WINDOW); - str->window = window; - str->style = "normal"; - return str; + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); + + if(glk_data->unregister_obj) + { + (*glk_data->unregister_obj)(str, gidisp_Class_Stream, str->disprock); + str->disprock.ptr = NULL; + } + + /* If the stream was one or more windows' echo streams, set those to NULL */ + winid_t win; + for(win = glk_window_iterate(NULL, NULL); win; + win = glk_window_iterate(win, NULL)) + if(win->echo_stream == str) + win->echo_stream = NULL; + + /* Return the character counts */ + if(result) + { + result->readcount = str->read_count; + result->writecount = str->write_count; + } + + /* 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); } /** @@ -52,7 +84,8 @@ strid_t glk_stream_iterate(strid_t str, glui32 *rockptr) { VALID_STREAM_OR_NULL(str, return NULL); - + + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); GList *retnode; if(str == NULL) @@ -73,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. */ @@ -96,7 +130,9 @@ void glk_stream_set_current(strid_t str) { VALID_STREAM_OR_NULL(str, return); - + + 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"); @@ -116,6 +152,7 @@ glk_stream_set_current(strid_t str) strid_t glk_stream_get_current() { + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); return glk_data->current_stream; } @@ -130,6 +167,7 @@ glk_stream_get_current() void glk_put_char(unsigned char ch) { + 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); } @@ -145,6 +183,7 @@ glk_put_char(unsigned char ch) void glk_put_char_uni(glui32 ch) { + 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); } @@ -156,14 +195,15 @@ glk_put_char_uni(glui32 ch) * Prints a null-terminated string to the current stream. It is exactly * equivalent to * |[ - * for (ptr = @s; *ptr; ptr++) - * #glk_put_char(*ptr); + * for (ptr = s; *ptr; 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); VALID_STREAM(glk_data->current_stream, return); glk_put_string_stream(glk_data->current_stream, s); } @@ -179,6 +219,7 @@ glk_put_string(char *s) void glk_put_string_uni(glui32 *s) { + 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); } @@ -191,14 +232,15 @@ glk_put_string_uni(glui32 *s) * Prints a block of characters to the current stream. It is exactly equivalent * to: * |[ - * for (i = 0; i < @len; i++) - * #glk_put_char(@buf[i]); + * for (i = 0; i < len; 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); VALID_STREAM(glk_data->current_stream, return); glk_put_buffer_stream(glk_data->current_stream, buf, len); } @@ -214,6 +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); VALID_STREAM(glk_data->current_stream, return); glk_put_buffer_stream_uni(glk_data->current_stream, buf, len); } @@ -223,7 +266,7 @@ glk_put_buffer_uni(glui32 *buf, glui32 len) * @buf: An allocated buffer, or %NULL. * @buflen: Length of @buf. * @fmode: Mode in which the buffer will be opened. Must be one of - * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. + * %filemode_Read, %filemode_Write, or %filemode_ReadWrite. * @rock: The new stream's rock value. * * Opens a stream which reads from or writes to a space in memory. @buf points @@ -240,11 +283,22 @@ glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock) { g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); - str->buffer = buf; + strid_t str = stream_new_common(rock); + str->file_mode = fmode; + str->type = STREAM_TYPE_MEMORY; str->mark = 0; - str->buflen = buflen; + str->endmark = 0; str->unicode = FALSE; + + if(buf && buflen) + { + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); + str->buffer = buf; + str->buflen = buflen; + if(glk_data->register_arr) + str->buffer_rock = (*glk_data->register_arr)(buf, buflen, "&+#!Cn"); + } + return str; } @@ -253,7 +307,7 @@ glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock) * @buf: An allocated buffer, or %NULL. * @buflen: Length of @buf. * @fmode: Mode in which the buffer will be opened. Must be one of - * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. + * %filemode_Read, %filemode_Write, or %filemode_ReadWrite. * @rock: The new stream's rock value. * * Works just like glk_stream_open_memory(), except that the buffer is an array @@ -268,21 +322,32 @@ glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock { g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); - str->ubuffer = buf; + strid_t str = stream_new_common(rock); + str->file_mode = fmode; + str->type = STREAM_TYPE_MEMORY; str->mark = 0; - str->buflen = buflen; + str->endmark = 0; str->unicode = TRUE; + + if(buf && buflen) + { + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); + str->ubuffer = buf; + str->buflen = buflen; + if(glk_data->register_arr) + str->buffer_rock = (*glk_data->register_arr)(buf, buflen, "&+#!Iu"); + } + return str; } /* Internal function: create a stream using the given parameters. */ -static strid_t +strid_t 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) @@ -292,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); @@ -313,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) { @@ -339,7 +412,9 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) } } - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_FILE); + strid_t str = stream_new_common(rock); + str->file_mode = fmode; + str->type = STREAM_TYPE_FILE; str->file_pointer = fp; str->binary = binary; str->unicode = unicode; @@ -353,16 +428,29 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) /** * glk_stream_open_file: * @fileref: Indicates the file which will be opened. - * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, - * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. + * @fmode: Mode in which the file will be opened. Can be any of %filemode_Read, + * %filemode_Write, %filemode_WriteAppend, or %filemode_ReadWrite. * @rock: The new stream's rock value. * * 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. + * %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); 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 @@ -381,8 +469,8 @@ glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock) /** * glk_stream_open_file_uni: * @fileref: Indicates the file which will be opened. - * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, - * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. + * @fmode: Mode in which the file will be opened. Can be any of %filemode_Read, + * %filemode_Write, %filemode_WriteAppend, or %filemode_ReadWrite. * @rock: The new stream's rock value. * * This works just like glk_stream_open_file(), except that in binary mode, @@ -403,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. @@ -432,14 +636,29 @@ glk_stream_close(strid_t str, stream_result_t *result) return; case STREAM_TYPE_MEMORY: - /* Do nothing */ + { + ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key); + if(glk_data->unregister_arr) + { + if(str->unicode) + (*glk_data->unregister_arr)(str->ubuffer, str->buflen, "&+#!Iu", str->buffer_rock); + else + (*glk_data->unregister_arr)(str->buffer, str->buflen, "&+#!Cn", str->buffer_rock); + } + } break; - + case STREAM_TYPE_FILE: if(fclose(str->file_pointer) != 0) 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; @@ -447,41 +666,3 @@ glk_stream_close(strid_t str, stream_result_t *result) stream_close_common(str, result); } - -/* Internal function: Stuff to do upon closing any type of stream. */ -void -stream_close_common(strid_t str, stream_result_t *result) -{ - /* 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; - - /* If it was one or more windows' echo streams, set those to NULL */ - winid_t win; - for(win = glk_window_iterate(NULL, NULL); win; - win = glk_window_iterate(win, NULL)) - if(win->echo_stream == str) - win->echo_stream = NULL; - - /* Return the character counts */ - if(result) - { - result->readcount = str->read_count; - result->writecount = str->write_count; - } - - str->magic = MAGIC_FREE; - g_free(str); -} - -strid_t -glkunix_stream_open_pathname(char *pathname, glui32 usage, glui32 rock) -{ - printf("making new fileref: %s\n", pathname); - frefid_t fileref = fileref_new(pathname, rock, usage, filemode_ReadWrite); - printf("makeing new stream:\n"); - return file_stream_new(fileref, filemode_ReadWrite, rock, FALSE); -}