X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=libchimara%2Ffileref.c;fp=libchimara%2Ffileref.c;h=2b5cf53efd65d9f8c04bc32732699e16c6db33a4;hb=78652af29a2f39e626febd5f4213da57d3a13901;hp=0000000000000000000000000000000000000000;hpb=91214934fbcdfd363202a65c142194506604ff7b;p=projects%2Fchimara%2Fchimara.git diff --git a/libchimara/fileref.c b/libchimara/fileref.c new file mode 100644 index 0000000..2b5cf53 --- /dev/null +++ b/libchimara/fileref.c @@ -0,0 +1,406 @@ +#include +#include +#include +#include +#include +#include "fileref.h" +#include "magic.h" +#include "chimara-glk-private.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * glk_fileref_iterate: + * @fref: A file reference, or %NULL. + * @rockptr: Return location for the next fileref's rock, or %NULL. + * + * Iterates through all the existing filerefs. See Iterating Through Opaque + * Objects. + * + * Returns: the next file reference, or %NULL if there are no more. + */ +frefid_t +glk_fileref_iterate(frefid_t fref, glui32 *rockptr) +{ + VALID_FILEREF_OR_NULL(fref, return NULL); + + GList *retnode; + + if(fref == NULL) + retnode = glk_data->fileref_list; + else + retnode = fref->fileref_list->next; + frefid_t retval = retnode? (frefid_t)retnode->data : NULL; + + /* Store the fileref's rock in rockptr */ + if(retval && rockptr) + *rockptr = glk_fileref_get_rock(retval); + + return retval; +} + +/** + * glk_fileref_get_rock: + * @fref: A file reference. + * + * Retrieves the file reference @fref's rock value. See Rocks. + * + * Returns: A rock value. + */ +glui32 +glk_fileref_get_rock(frefid_t fref) +{ + VALID_FILEREF(fref, return 0); + return fref->rock; +} + +/* Internal function: create a fileref using the given parameters. */ +static frefid_t +fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode) +{ + g_return_val_if_fail(filename != NULL, NULL); + + frefid_t f = g_new0(struct glk_fileref_struct, 1); + f->magic = MAGIC_FILEREF; + f->rock = rock; + f->filename = g_strdup(filename); + f->usage = usage; + f->orig_filemode = orig_filemode; + + /* Add it to the global fileref list */ + glk_data->fileref_list = g_list_prepend(glk_data->fileref_list, f); + f->fileref_list = glk_data->fileref_list; + + return f; +} + +/** + * glk_fileref_create_temp: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @rock: The new fileref's rock value. + * + * Creates a reference to a temporary file. It is always a new file (one which + * does not yet exist). The file (once created) will be somewhere out of the + * player's way. + * + * + * This is why no name is specified; the player will never need to know it. + * + * + * A temporary file should never be used for long-term storage. It may be + * deleted automatically when the program exits, or at some later time, say + * when the machine is turned off or rebooted. You do not have to worry about + * deleting it yourself. + * + * Returns: A new fileref, or #NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_temp(glui32 usage, glui32 rock) +{ + /* Get a temp file */ + GError *error = NULL; + gchar *filename = NULL; + gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error); + if(handle == -1) + { + WARNING_S("Error creating temporary file", error->message); + if(filename) + g_free(filename); + return NULL; + } + if(close(handle) == -1) /* There is no g_close() */ + { + IO_WARNING( "Error closing temporary file", filename, g_strerror(errno) ); + if(filename) + g_free(filename); + return NULL; + } + + frefid_t f = fileref_new(filename, rock, usage, filemode_Write); + g_free(filename); + return f; +} + +/** + * glk_fileref_create_by_prompt: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @fmode: File mode, contolling the dialog's behavior. + * @rock: The new fileref's rock value. + * + * Creates a reference to a file by asking the player to locate it. The library + * may simply prompt the player to type a name, or may use a platform-native + * file navigation tool. (The prompt, if any, is inferred from the usage + * argument.) + * + * Chimara + * + * Chimara uses a GtkFileChooserDialog. + * + * + * @fmode must be one of these values: + * + * + * #filemode_Read + * The file must already exist; and the player will be asked + * to select from existing files which match the usage. + * + * + * #filemode_Write + * The file should not exist; if the player selects an + * existing file, he will be warned that it will be replaced. + * + * + * + * #filemode_ReadWrite + * The file may or may not exist; if it already exists, the + * player will be warned that it will be modified. + * + * + * #filemode_WriteAppend + * Same behavior as #filemode_ReadWrite. + * + * + * + * The @fmode argument should generally match the @fmode which will be used to + * open the file. + * + * + * It is possible that the prompt or file tool will have a + * cancel option. If the player chooses this, + * glk_fileref_create_by_prompt() will return %NULL. This is a major reason + * why you should make sure the return value is valid before you use it. + * + * + * Returns: A new fileref, or #NULL if the fileref creation failed or the + * dialog was canceled. + */ +frefid_t +glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) +{ + /* TODO: Remember current working directory and last used filename + for each usage */ + GtkWidget *chooser; + + gdk_threads_enter(); + + switch(fmode) + { + case filemode_Read: + chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_OPEN); + break; + case filemode_Write: + chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_SAVE); + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(chooser), TRUE); + break; + case filemode_ReadWrite: + case filemode_WriteAppend: + chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_SAVE); + break; + default: + ILLEGAL_PARAM("Unknown file mode: %u", fmode); + gdk_threads_leave(); + return NULL; + } + + if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT) + { + gtk_widget_destroy(chooser); + gdk_threads_leave(); + return NULL; + } + gchar *filename = + gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) ); + frefid_t f = fileref_new(filename, rock, usage, fmode); + g_free(filename); + gtk_widget_destroy(chooser); + + gdk_threads_leave(); + return f; +} + +/** + * glk_fileref_create_by_name: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @name: A filename. + * @rock: The new fileref's rock value. + * + * This creates a reference to a file with a specific name. The file will be + * in a fixed location relevant to your program, and visible to the player. + * + * + * This usually means in the same directory as your program. + * + * Chimara + * + * In Chimara, the file is created in the current working directory. + * + * + * Since filenames are highly platform-specific, you should use + * glk_fileref_create_by_name() with care. It is legal to pass any string in the + * name argument. However, the library may have to mangle, transform, or + * truncate the string to make it a legal native filename. + * + * + * For example, if you create two filerefs with the names File + * and FILE, they may wind up pointing to the same file; the + * platform may not support case distinctions in file names. Another example: + * on a platform where file type is specified by filename suffix, the library + * will add an appropriate suffix based on the usage; any suffix in the string + * will be overwritten or added to. For that matter, remember that the period + * is not a legal character in Acorn filenames... + * + * + * The most conservative approach is to pass a string of no more than 8 + * characters, consisting entirely of upper-case letters and numbers, starting + * with a letter. You can then be reasonably sure that the resulting filename + * will display all the characters you specify — in some form. + * + * Returns: A new fileref, or %NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) +{ + g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL); + + /* Find out what encoding filenames are in */ + const gchar **charsets; /* Do not free */ + g_get_filename_charsets(&charsets); + + /* Convert name to that encoding */ + GError *error = NULL; + gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL, + &error); + if(osname == NULL) + { + WARNING_S("Error during latin1->filename conversion", error->message); + return NULL; + } + + /* Do any string-munging here to remove illegal characters from filename. + On ext3, the only illegal characters are '/' and '\0'. TODO: Should this + function be allowed to reference files in other directories, or should we + disallow '/'? */ + + frefid_t f = fileref_new(osname, rock, usage, filemode_ReadWrite); + g_free(osname); + return f; +} + +/** + * glk_fileref_create_from_fileref: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @fref: Fileref to copy. + * @rock: The new fileref's rock value. + * + * This copies an existing file reference @fref, but changes the usage. (The + * original fileref is not modified.) + * + * The use of this function can be tricky. If you change the type of the fileref + * (#fileusage_Data, #fileusage_SavedGame, etc), the new reference may or may + * not point to the same actual disk file. + * + * + * This generally depends on whether the platform uses suffixes to indicate + * file type. + * + * + * If you do this, and open both file references for writing, the results are + * unpredictable. It is safest to change the type of a fileref only if it refers + * to a nonexistent file. + * + * If you change the mode of a fileref (#fileusage_TextMode, + * #fileusage_BinaryMode), but leave the rest of the type unchanged, the new + * fileref will definitely point to the same disk file as the old one. + * + * Obviously, if you write to a file in text mode and then read from it in + * binary mode, the results are platform-dependent. + * + * Returns: A new fileref, or %NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock) +{ + VALID_FILEREF(fref, return NULL); + return fileref_new(fref->filename, rock, usage, fref->orig_filemode); +} + +/** + * glk_fileref_destroy: + * @fref: Fileref to destroy. + * + * Destroys a fileref which you have created. This does not + * affect the disk file; it just reclaims the resources allocated by the + * glk_fileref_create... function. + * + * It is legal to destroy a fileref after opening a file with it (while the + * file is still open.) The fileref is only used for the opening operation, + * not for accessing the file stream. + */ +void +glk_fileref_destroy(frefid_t fref) +{ + VALID_FILEREF(fref, return); + + glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list); + if(fref->filename) + g_free(fref->filename); + + fref->magic = MAGIC_FREE; + g_free(fref); +} + +/** + * glk_fileref_delete_file: + * @fref: A refrence to the file to delete. + * + * Deletes the file referred to by @fref. It does not destroy @fref itself. + */ +void +glk_fileref_delete_file(frefid_t fref) +{ + VALID_FILEREF(fref, return); + if( glk_fileref_does_file_exist(fref) ) + if(g_unlink(fref->filename) == -1) + IO_WARNING( "Error deleting file", fref->filename, g_strerror(errno) ); +} + +/** + * glk_fileref_does_file_exist: + * @fref: A fileref to check. + * + * Checks whether the file referred to by @fref exists. + * + * Returns: %TRUE (1) if @fref refers to an existing file, and %FALSE (0) if + * not. + */ +glui32 +glk_fileref_does_file_exist(frefid_t fref) +{ + VALID_FILEREF(fref, return 0); + if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) ) + return 1; + return 0; +} +