6 #include <glib/gstdio.h>
7 #include <glib/gi18n-lib.h>
10 #include "chimara-glk-private.h"
13 extern GPrivate *glk_data_key;
15 /* Internal function: create a fileref using the given parameters. If @basename
16 is NULL, compute a basename from @filename. */
18 fileref_new(char *filename, char *basename, glui32 rock, glui32 usage, glui32 orig_filemode)
20 g_return_val_if_fail(filename != NULL, NULL);
22 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
24 frefid_t f = g_new0(struct glk_fileref_struct, 1);
25 f->magic = MAGIC_FILEREF;
27 if(glk_data->register_obj)
28 f->disprock = (*glk_data->register_obj)(f, gidisp_Class_Fileref);
30 f->filename = g_strdup(filename);
32 f->basename = g_strdup(basename);
34 f->basename = g_path_get_basename(filename);
36 f->orig_filemode = orig_filemode;
38 /* Add it to the global fileref list */
39 glk_data->fileref_list = g_list_prepend(glk_data->fileref_list, f);
40 f->fileref_list = glk_data->fileref_list;
46 fileref_close_common(frefid_t fref)
48 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
50 glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list);
52 if(glk_data->unregister_obj)
54 (*glk_data->unregister_obj)(fref, gidisp_Class_Fileref, fref->disprock);
55 fref->disprock.ptr = NULL;
58 g_free(fref->filename);
59 g_free(fref->basename);
61 fref->magic = MAGIC_FREE;
66 * glk_fileref_iterate:
67 * @fref: A file reference, or %NULL.
68 * @rockptr: Return location for the next fileref's rock, or %NULL.
70 * Iterates through all the existing filerefs. See <link
71 * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
74 * Returns: the next file reference, or %NULL if there are no more.
77 glk_fileref_iterate(frefid_t fref, glui32 *rockptr)
79 VALID_FILEREF_OR_NULL(fref, return NULL);
81 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
85 retnode = glk_data->fileref_list;
87 retnode = fref->fileref_list->next;
88 frefid_t retval = retnode? (frefid_t)retnode->data : NULL;
90 /* Store the fileref's rock in rockptr */
92 *rockptr = glk_fileref_get_rock(retval);
98 * glk_fileref_get_rock:
99 * @fref: A file reference.
101 * Retrieves the file reference @fref's rock value. See <link
102 * linkend="chimara-Rocks">Rocks</link>.
104 * Returns: A rock value.
107 glk_fileref_get_rock(frefid_t fref)
109 VALID_FILEREF(fref, return 0);
114 * glk_fileref_create_temp:
115 * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
116 * @rock: The new fileref's rock value.
118 * Creates a reference to a temporary file. It is always a new file (one which
119 * does not yet exist). The file (once created) will be somewhere out of the
123 * This is why no name is specified; the player will never need to know it.
126 * A temporary file should never be used for long-term storage. It may be
127 * deleted automatically when the program exits, or at some later time, say
128 * when the machine is turned off or rebooted. You do not have to worry about
129 * deleting it yourself.
131 * Returns: A new fileref, or #NULL if the fileref creation failed.
134 glk_fileref_create_temp(glui32 usage, glui32 rock)
136 /* Get a temp file */
137 GError *error = NULL;
138 gchar *filename = NULL;
139 gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error);
142 WARNING_S("Error creating temporary file", error->message);
147 if(close(handle) == -1) /* There is no g_close() */
149 IO_WARNING( "Error closing temporary file", filename, g_strerror(errno) );
155 /* Pass a basename of "" to ensure that this file can't be repurposed */
156 frefid_t f = fileref_new(filename, "", rock, usage, filemode_Write);
162 * glk_fileref_create_by_prompt:
163 * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
164 * @fmode: File mode, contolling the dialog's behavior.
165 * @rock: The new fileref's rock value.
167 * Creates a reference to a file by asking the player to locate it. The library
168 * may simply prompt the player to type a name, or may use a platform-native
169 * file navigation tool. (The prompt, if any, is inferred from the usage
172 * <note><title>Chimara</title>
174 * Chimara uses a <link
175 * linkend="GtkFileChooserDialog">GtkFileChooserDialog</link>. The default
176 * starting location for the dialog may be set with glkunix_set_base_file().
179 * @fmode must be one of these values:
182 * <term>%filemode_Read</term>
183 * <listitem><para>The file must already exist; and the player will be asked
184 * to select from existing files which match the usage.</para></listitem>
187 * <term>%filemode_Write</term>
188 * <listitem><para>The file should not exist; if the player selects an
189 * existing file, he will be warned that it will be replaced.
193 * <term>%filemode_ReadWrite</term>
194 * <listitem><para>The file may or may not exist; if it already exists, the
195 * player will be warned that it will be modified.</para></listitem>
198 * <term>%filemode_WriteAppend</term>
199 * <listitem><para>Same behavior as %filemode_ReadWrite.</para></listitem>
203 * The @fmode argument should generally match the @fmode which will be used to
207 * It is likely that the prompt or file tool will have a <quote>cancel</quote>
208 * option. If the player chooses this, glk_fileref_create_by_prompt() will
209 * return %NULL. This is a major reason why you should make sure the return
210 * value is valid before you use it.
213 * The recommended file suffixes for files are <filename>.glkdata</filename> for
214 * %fileusage_Data, <filename>.glksave</filename> for %fileusage_SavedGame,
215 * <filename>.txt</filename> for %fileusage_Transcript and
216 * %fileusage_InputRecord.
218 * Returns: A new fileref, or #NULL if the fileref creation failed or the
219 * dialog was canceled.
222 glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
224 /* TODO: Remember current working directory and last used filename
228 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
235 chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL,
236 GTK_FILE_CHOOSER_ACTION_OPEN,
237 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
238 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
240 gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_OPEN);
243 chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
244 GTK_FILE_CHOOSER_ACTION_SAVE,
245 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
246 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
248 gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_SAVE);
251 #if GTK_CHECK_VERSION(2,8,0)
252 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
255 case filemode_ReadWrite:
256 case filemode_WriteAppend:
257 chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
258 GTK_FILE_CHOOSER_ACTION_SAVE,
259 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
260 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
262 gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_SAVE);
265 ILLEGAL_PARAM("Unknown file mode: %u", fmode);
270 /* Set up a file filter with suggested extensions */
271 GtkFileFilter *filter = gtk_file_filter_new();
272 switch(usage & fileusage_TypeMask)
275 gtk_file_filter_set_name(filter, _("Data files (*.glkdata)"));
276 gtk_file_filter_add_pattern(filter, "*.glkdata");
278 case fileusage_SavedGame:
279 gtk_file_filter_set_name(filter, _("Saved games (*.glksave)"));
280 gtk_file_filter_add_pattern(filter, "*.glksave");
282 case fileusage_InputRecord:
283 gtk_file_filter_set_name(filter, _("Text files (*.txt)"));
284 gtk_file_filter_add_pattern(filter, "*.txt");
286 case fileusage_Transcript:
287 gtk_file_filter_set_name(filter, _("Transcript files (*.txt)"));
288 gtk_file_filter_add_pattern(filter, "*.txt");
291 ILLEGAL_PARAM("Unknown file usage: %u", usage);
295 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
297 /* Add a "text mode" filter for text files */
298 if((usage & fileusage_TypeMask) == fileusage_InputRecord || (usage & fileusage_TypeMask) == fileusage_Transcript)
300 filter = gtk_file_filter_new();
301 gtk_file_filter_set_name(filter, _("All text files"));
302 gtk_file_filter_add_mime_type(filter, "text/plain");
303 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
306 /* Add another non-restricted filter */
307 filter = gtk_file_filter_new();
308 gtk_file_filter_set_name(filter, _("All files"));
309 gtk_file_filter_add_pattern(filter, "*");
310 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
312 if(glk_data->current_dir)
313 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), glk_data->current_dir);
315 if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT)
317 gtk_widget_destroy(chooser);
321 gchar *filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) );
322 frefid_t f = fileref_new(filename, NULL, rock, usage, fmode);
324 gtk_widget_destroy(chooser);
331 * glk_fileref_create_by_name:
332 * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
334 * @rock: The new fileref's rock value.
336 * This creates a reference to a file with a specific name. The file will be
337 * in a fixed location relevant to your program, and visible to the player.
340 * This usually means <quote>in the same directory as your program.</quote>
342 * <note><title>Chimara</title>
344 * In Chimara, the file is created in the directory last set by
345 * glkunix_set_base_file(), and otherwise in the current working directory.
348 * Earlier versions of the Glk spec specified that the library may have to
349 * extend, truncate, or change your name argument in order to produce a legal
350 * native filename. This remains true. However, since Glk was originally
351 * proposed, the world has largely reached consensus about what a filename looks
352 * like. Therefore, it is worth including some recommended library behavior
353 * here. Libraries that share this behavior will more easily be able to exchange
354 * files, which may be valuable both to authors (distributing data files for
355 * games) and for players (moving data between different computers or different
358 * The library should take the given filename argument, and delete any
359 * characters illegal for a filename. This will include all of the following
360 * characters (and more, if the OS requires it): slash, backslash, angle
361 * brackets (less-than and greater-than), colon, double-quote, pipe (vertical
362 * bar), question-mark, asterisk. The library should also truncate the argument
363 * at the first period (delete the first period and any following characters).
364 * If the result is the empty string, change it to the string
365 * <code>"null"</code>.
367 * It should then append an appropriate suffix, depending on the usage:
368 * <filename>.glkdata</filename> for %fileusage_Data,
369 * <filename>.glksave</filename> for %fileusage_SavedGame,
370 * <filename>.txt</filename> for %fileusage_Transcript and
371 * %fileusage_InputRecord.
373 * The above behavior is not a requirement of the Glk spec. Older
374 * implementations can continue doing what they do. Some programs (e.g.
375 * web-based interpreters) may not have access to a traditional filesystem at
376 * all, and to them these recommendations will be meaningless.
378 * On the other side of the coin, the game file should not press these
379 * limitations. Best practice is for the game to pass a filename containing only
380 * letters and digits, beginning with a letter, and not mixing upper and lower
381 * case. Avoid overly-long filenames.
384 * The earlier Glk spec gave more stringent recommendations: <quote>No more
385 * than 8 characters, consisting entirely of upper-case letters and numbers,
386 * starting with a letter</quote>. The DOS era is safely contained, if not
387 * over, so this has been relaxed. The I7 manual recommends <quote>23
388 * characters or fewer</quote>.
392 * To address other complications:</para>
395 * Some filesystems are case-insensitive. If you create two filerefs with
396 * the names <filename>File</filename> and <filename>FILE</filename>, they
397 * may wind up pointing to the same file, or they may not. Avoid doing
401 * Some programs will look for all files in the same directory as the
402 * program itself (or, for interpreted games, in the same directory as the
403 * game file). Others may keep files in a data-specific directory
404 * appropriate for the user (e.g., <filename
405 * class="directory">~/Library</filename> on MacOS).
408 * If a game interpreter uses a data-specific directory, there is a
409 * question of whether to use a common location, or divide it into
410 * game-specific subdirectories. (Or to put it another way: should the
411 * namespace of named files be per-game or app-wide?) Since data files may
412 * be exchanged between games, they should be given an app-wide namespace.
413 * In contrast, saved games should be per-game, as they can never be
414 * exchanged. Transcripts and input records can go either way.
417 * When updating an older library to follow these recommendations,
418 * consider backwards compatibility for games already installed. When
419 * opening an existing file (that is, not in a write-only mode) it may be
420 * worth looking under the older name (suffix) if the newer one does not
424 * Game-save files are already stored with a variety of file suffixes,
425 * since that usage goes back to the oldest IF interpreters, long
426 * predating Glk. It is reasonable to treat them in some special way,
427 * while hewing closer to these recommendations for data files.
432 * Returns: A new fileref, or %NULL if the fileref creation failed.
435 glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
437 g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL);
439 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
441 /* Do any string-munging here to remove illegal Latin-1 characters from
442 filename. On ext3, the only illegal characters are '/' and '\0', but the Glk
443 spec calls for removing any other tricky characters. */
444 char *buf = g_malloc(strlen(name));
445 char *ptr, *filename, *extension;
447 for(ptr = name, len = 0; *ptr && *ptr != '.'; ptr++)
451 case '"': case '\\': case '/': case '>': case '<':
452 case ':': case '|': case '?': case '*':
460 /* If there is nothing left, make the name "null" */
466 switch(usage & fileusage_TypeMask)
469 extension = ".glkdata";
471 case fileusage_SavedGame:
472 extension = ".glksave";
474 case fileusage_InputRecord:
475 case fileusage_Transcript:
479 ILLEGAL_PARAM("Unknown file usage: %u", usage);
482 filename = g_strconcat(buf, extension, NULL);
484 /* Find out what encoding filenames are in */
485 const gchar **charsets; /* Do not free */
486 g_get_filename_charsets(&charsets);
488 /* Convert name to that encoding */
489 GError *error = NULL;
490 char *osname = g_convert(filename, -1, charsets[0], "ISO-8859-1", NULL, NULL, &error);
493 WARNING_S("Error during latin1->filename conversion", error->message);
498 if(glk_data->current_dir)
499 path = g_build_filename(glk_data->current_dir, osname, NULL);
501 path = g_strdup(osname);
504 frefid_t f = fileref_new(path, buf, rock, usage, filemode_ReadWrite);
511 * glk_fileref_create_from_fileref:
512 * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
513 * @fref: Fileref to copy.
514 * @rock: The new fileref's rock value.
516 * This copies an existing file reference @fref, but changes the usage. (The
517 * original fileref is not modified.)
519 * The use of this function can be tricky. If you change the type of the fileref
520 * (%fileusage_Data, %fileusage_SavedGame, etc), the new reference may or may
521 * not point to the same actual disk file.
524 * Most platforms use suffixes to indicate file type, so it typically will
525 * not. See the earlier comments about recommended file suffixes.
528 * If you do this, and open both file references for writing, the results are
529 * unpredictable. It is safest to change the type of a fileref only if it refers
530 * to a nonexistent file.
532 * If you change the mode of a fileref (%fileusage_TextMode,
533 * %fileusage_BinaryMode), but leave the rest of the type unchanged, the new
534 * fileref will definitely point to the same disk file as the old one.
536 * Obviously, if you write to a file in text mode and then read from it in
537 * binary mode, the results are platform-dependent.
539 * Returns: A new fileref, or %NULL if the fileref creation failed.
542 glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock)
544 VALID_FILEREF(fref, return NULL);
545 return fileref_new(fref->filename, fref->basename, rock, usage, fref->orig_filemode);
549 * glk_fileref_destroy:
550 * @fref: Fileref to destroy.
552 * Destroys a fileref which you have created. This does <emphasis>not</emphasis>
553 * affect the disk file; it just reclaims the resources allocated by the
554 * <code>glk_fileref_create...</code> function.
556 * It is legal to destroy a fileref after opening a file with it (while the
557 * file is still open.) The fileref is only used for the opening operation,
558 * not for accessing the file stream.
561 glk_fileref_destroy(frefid_t fref)
563 VALID_FILEREF(fref, return);
564 fileref_close_common(fref);
568 * glk_fileref_delete_file:
569 * @fref: A refrence to the file to delete.
571 * Deletes the file referred to by @fref. It does not destroy @fref itself.
573 * You should only call this with a fileref that refers to an existing file.
576 glk_fileref_delete_file(frefid_t fref)
578 VALID_FILEREF(fref, return);
579 if( glk_fileref_does_file_exist(fref) )
581 if(g_unlink(fref->filename) == -1)
582 IO_WARNING( "Error deleting file", fref->filename, g_strerror(errno) );
586 ILLEGAL(_("Tried to delete a fileref that does not refer to an existing file."));
592 * glk_fileref_does_file_exist:
593 * @fref: A fileref to check.
595 * Checks whether the file referred to by @fref exists.
597 * Returns: %TRUE (1) if @fref refers to an existing file, and %FALSE (0) if
601 glk_fileref_does_file_exist(frefid_t fref)
603 VALID_FILEREF(fref, return 0);
604 if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) )