Iliad port
[rodin/chimara.git] / libchimara / fileref.c
1 #include <errno.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <gtk/gtk.h>
5 #include <glib/gstdio.h>
6 #include "fileref.h"
7 #include "magic.h"
8 #include "chimara-glk-private.h"
9 #include "gi_dispa.h"
10
11 extern GPrivate *glk_data_key;
12
13 /* Internal function: create a fileref using the given parameters. */
14 frefid_t
15 fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode)
16 {
17         g_return_val_if_fail(filename != NULL, NULL);
18
19         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
20         
21         frefid_t f = g_new0(struct glk_fileref_struct, 1);
22         f->magic = MAGIC_FILEREF;
23         f->rock = rock;
24         if(glk_data->register_obj)
25                 f->disprock = (*glk_data->register_obj)(f, gidisp_Class_Fileref);
26         
27         f->filename = g_strdup(filename);
28         f->usage = usage;
29         f->orig_filemode = orig_filemode;
30         
31         /* Add it to the global fileref list */
32         glk_data->fileref_list = g_list_prepend(glk_data->fileref_list, f);
33         f->fileref_list = glk_data->fileref_list;
34         
35         return f;
36 }
37
38 static void
39 fileref_close_common(frefid_t fref)
40 {
41         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
42         
43         glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list);
44
45         if(glk_data->unregister_obj)
46         {
47                 (*glk_data->unregister_obj)(fref, gidisp_Class_Fileref, fref->disprock);
48                 fref->disprock.ptr = NULL;
49         }
50         
51         if(fref->filename)
52                 g_free(fref->filename);
53         
54         fref->magic = MAGIC_FREE;
55         g_free(fref);
56 }
57
58 /**
59  * glk_fileref_iterate:
60  * @fref: A file reference, or %NULL.
61  * @rockptr: Return location for the next fileref's rock, or %NULL.
62  *
63  * Iterates through all the existing filerefs. See <link
64  * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
65  * Objects</link>.
66  *
67  * Returns: the next file reference, or %NULL if there are no more.
68  */
69 frefid_t
70 glk_fileref_iterate(frefid_t fref, glui32 *rockptr)
71 {
72         VALID_FILEREF_OR_NULL(fref, return NULL);
73
74         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
75         GList *retnode;
76         
77         if(fref == NULL)
78                 retnode = glk_data->fileref_list;
79         else
80                 retnode = fref->fileref_list->next;
81         frefid_t retval = retnode? (frefid_t)retnode->data : NULL;
82                 
83         /* Store the fileref's rock in rockptr */
84         if(retval && rockptr)
85                 *rockptr = glk_fileref_get_rock(retval);
86                 
87         return retval;
88 }
89
90 /**
91  * glk_fileref_get_rock:
92  * @fref: A file reference.
93  * 
94  * Retrieves the file reference @fref's rock value. See <link 
95  * linkend="chimara-Rocks">Rocks</link>.
96  *
97  * Returns: A rock value.
98  */
99 glui32
100 glk_fileref_get_rock(frefid_t fref)
101 {
102         VALID_FILEREF(fref, return 0);
103         return fref->rock;
104 }
105
106 /**
107  * glk_fileref_create_temp:
108  * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
109  * @rock: The new fileref's rock value.
110  *
111  * Creates a reference to a temporary file. It is always a new file (one which
112  * does not yet exist). The file (once created) will be somewhere out of the
113  * player's way.
114  *
115  * <note><para>
116  *   This is why no name is specified; the player will never need to know it.
117  * </para></note>
118  *
119  * A temporary file should never be used for long-term storage. It may be
120  * deleted automatically when the program exits, or at some later time, say
121  * when the machine is turned off or rebooted. You do not have to worry about
122  * deleting it yourself.
123  *
124  * Returns: A new fileref, or #NULL if the fileref creation failed.
125  */ 
126 frefid_t
127 glk_fileref_create_temp(glui32 usage, glui32 rock)
128 {
129         /* Get a temp file */
130         GError *error = NULL;
131         gchar *filename = NULL;
132         gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error);
133         if(handle == -1)
134         {
135                 WARNING_S("Error creating temporary file", error->message);
136                 if(filename)
137                         g_free(filename);
138                 return NULL;
139         }
140         if(close(handle) == -1) /* There is no g_close() */
141         {
142                 IO_WARNING( "Error closing temporary file", filename, g_strerror(errno) );
143                 if(filename)
144                         g_free(filename);
145                 return NULL;
146         }
147         
148         frefid_t f = fileref_new(filename, rock, usage, filemode_Write);
149         g_free(filename);
150         return f;
151 }
152
153 /**
154  * glk_fileref_create_by_prompt:
155  * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
156  * @fmode: File mode, contolling the dialog's behavior.
157  * @rock: The new fileref's rock value.
158  *
159  * Creates a reference to a file by asking the player to locate it. The library
160  * may simply prompt the player to type a name, or may use a platform-native
161  * file navigation tool. (The prompt, if any, is inferred from the usage
162  * argument.)
163  *
164  * <note><title>Chimara</title>
165  * <para>
166  * Chimara uses a <link 
167  * linkend="gtk-GtkFileChooserDialog">GtkFileChooserDialog</link>. The default
168  * starting location for the dialog may be set with glkunix_set_base_file().
169  * </para></note>
170  *
171  * @fmode must be one of these values:
172  * <variablelist>
173  * <varlistentry>
174  *   <term>%filemode_Read</term>
175  *   <listitem><para>The file must already exist; and the player will be asked
176  *   to select from existing files which match the usage.</para></listitem>
177  * </varlistentry>
178  * <varlistentry>
179  *   <term>%filemode_Write</term>
180  *   <listitem><para>The file should not exist; if the player selects an
181  *   existing file, he will be warned that it will be replaced.
182  *   </para></listitem>
183  * </varlistentry>
184  * <varlistentry>
185  *   <term>%filemode_ReadWrite</term>
186  *   <listitem><para>The file may or may not exist; if it already exists, the
187  *   player will be warned that it will be modified.</para></listitem>
188  * </varlistentry>
189  * <varlistentry>
190  *   <term>%filemode_WriteAppend</term>
191  *   <listitem><para>Same behavior as %filemode_ReadWrite.</para></listitem>
192  * </varlistentry>
193  * </variablelist>
194  *
195  * The @fmode argument should generally match the @fmode which will be used to
196  * open the file.
197  *
198  * <note><para>
199  *   It is possible that the prompt or file tool will have a 
200  *   <quote>cancel</quote> option. If the player chooses this,
201  *   glk_fileref_create_by_prompt() will return %NULL. This is a major reason
202  *   why you should make sure the return value is valid before you use it.
203  * </para></note>
204  *
205  * Returns: A new fileref, or #NULL if the fileref creation failed or the
206  * dialog was canceled.
207  */
208 frefid_t
209 glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
210 {
211         /* TODO: Remember current working directory and last used filename
212         for each usage */
213         GtkWidget *chooser;
214
215         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
216         
217         gdk_threads_enter();
218
219         switch(fmode)
220         {
221                 case filemode_Read:
222                         chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL,
223                                 GTK_FILE_CHOOSER_ACTION_OPEN,
224                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
225                                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
226                                 NULL);
227                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_OPEN);
228                         break;
229                 case filemode_Write:
230                         chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
231                                 GTK_FILE_CHOOSER_ACTION_SAVE,
232                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
233                                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
234                                 NULL);
235                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_SAVE);
236
237                         /* COMPAT: */
238 #if GTK_CHECK_VERSION(2,8,0)
239                         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
240 #endif
241                         break;
242                 case filemode_ReadWrite:
243                 case filemode_WriteAppend:
244                         chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
245                                 GTK_FILE_CHOOSER_ACTION_SAVE,
246                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
247                                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
248                                 NULL);
249                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_SAVE);
250                         break;
251                 default:
252                         ILLEGAL_PARAM("Unknown file mode: %u", fmode);
253                         gdk_threads_leave();
254                         return NULL;
255         }
256         
257         if(glk_data->current_dir)
258                 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), glk_data->current_dir);
259         
260         if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT)
261         {
262                 gtk_widget_destroy(chooser);
263                 gdk_threads_leave();
264                 return NULL;
265         }
266         gchar *filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) );
267         frefid_t f = fileref_new(filename, rock, usage, fmode);
268         g_free(filename);
269         gtk_widget_destroy(chooser);
270
271         gdk_threads_leave();
272         return f;
273 }
274
275 /**
276  * glk_fileref_create_by_name:
277  * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
278  * @name: A filename.
279  * @rock: The new fileref's rock value.
280  *
281  * This creates a reference to a file with a specific name. The file will be
282  * in a fixed location relevant to your program, and visible to the player.
283  *
284  * <note><para>
285  *   This usually means <quote>in the same directory as your program.</quote>
286  * </para></note>
287  * <note><title>Chimara</title>
288  * <para>
289  * In Chimara, the file is created in the directory last set by 
290  * glkunix_set_base_file(), and otherwise in the current working directory.
291  * </para></note>
292  *
293  * Since filenames are highly platform-specific, you should use
294  * glk_fileref_create_by_name() with care. It is legal to pass any string in the
295  * name argument. However, the library may have to mangle, transform, or
296  * truncate the string to make it a legal native filename. 
297  *
298  * <note><para>
299  *   For example, if you create two filerefs with the names <quote>File</quote>
300  *   and <quote>FILE</quote>, they may wind up pointing to the same file; the
301  *   platform may not support case distinctions in file names. Another example:
302  *   on a platform where file type is specified by filename suffix, the library
303  *   will add an appropriate suffix based on the usage; any suffix in the string
304  *   will be overwritten or added to. For that matter, remember that the period
305  *   is not a legal character in Acorn filenames...
306  * </para></note>
307  *
308  * The most conservative approach is to pass a string of no more than 8
309  * characters, consisting entirely of upper-case letters and numbers, starting
310  * with a letter. You can then be reasonably sure that the resulting filename
311  * will display all the characters you specify &mdash; in some form. 
312  *
313  * Returns: A new fileref, or %NULL if the fileref creation failed. 
314  */
315 frefid_t
316 glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
317 {
318         g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL);
319
320         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
321         
322         /* Do any string-munging here to remove illegal Latin-1 characters from 
323         filename. On ext3, the only illegal characters are '/' and '\0'. */
324         g_strdelimit(name, "/", '_');
325         
326         /* Find out what encoding filenames are in */
327         const gchar **charsets; /* Do not free */
328         g_get_filename_charsets(&charsets);
329
330         /* Convert name to that encoding */
331         GError *error = NULL;
332         gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL,
333                 &error);
334         if(osname == NULL)
335         {
336                 WARNING_S("Error during latin1->filename conversion", error->message);
337                 return NULL;
338         }
339         
340         gchar *path;
341         if(glk_data->current_dir)
342                 path = g_build_filename(glk_data->current_dir, osname, NULL);
343         else
344                 path = g_strdup(osname);
345         g_free(osname);
346         
347         frefid_t f = fileref_new(path, rock, usage, filemode_ReadWrite);
348         g_free(path);
349         return f;
350 }
351
352 /**
353  * glk_fileref_create_from_fileref:
354  * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
355  * @fref: Fileref to copy.
356  * @rock: The new fileref's rock value.
357  *
358  * This copies an existing file reference @fref, but changes the usage. (The
359  * original fileref is not modified.)
360  *
361  * The use of this function can be tricky. If you change the type of the fileref
362  * (%fileusage_Data, %fileusage_SavedGame, etc), the new reference may or may
363  * not point to the same actual disk file.
364  *
365  * <note><para>
366  *   This generally depends on whether the platform uses suffixes to indicate
367  *   file type.
368  * </para></note>
369  *
370  * If you do this, and open both file references for writing, the results are
371  * unpredictable. It is safest to change the type of a fileref only if it refers
372  * to a nonexistent file.
373  *
374  * If you change the mode of a fileref (%fileusage_TextMode,
375  * %fileusage_BinaryMode), but leave the rest of the type unchanged, the new
376  * fileref will definitely point to the same disk file as the old one.
377  * 
378  * Obviously, if you write to a file in text mode and then read from it in
379  * binary mode, the results are platform-dependent. 
380  *
381  * Returns: A new fileref, or %NULL if the fileref creation failed. 
382  */
383 frefid_t
384 glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock)
385 {
386         VALID_FILEREF(fref, return NULL);
387         return fileref_new(fref->filename, rock, usage, fref->orig_filemode);
388 }
389
390 /**
391  * glk_fileref_destroy:
392  * @fref: Fileref to destroy.
393  * 
394  * Destroys a fileref which you have created. This does <emphasis>not</emphasis>
395  * affect the disk file; it just reclaims the resources allocated by the
396  * <code>glk_fileref_create...</code> function.
397  *
398  * It is legal to destroy a fileref after opening a file with it (while the
399  * file is still open.) The fileref is only used for the opening operation,
400  * not for accessing the file stream.
401  */
402 void
403 glk_fileref_destroy(frefid_t fref)
404 {
405         VALID_FILEREF(fref, return);
406         fileref_close_common(fref);
407 }
408
409 /**
410  * glk_fileref_delete_file:
411  * @fref: A refrence to the file to delete.
412  *
413  * Deletes the file referred to by @fref. It does not destroy @fref itself.
414  */
415 void
416 glk_fileref_delete_file(frefid_t fref)
417 {
418         VALID_FILEREF(fref, return);
419         if( glk_fileref_does_file_exist(fref) )
420                 if(g_unlink(fref->filename) == -1)
421                         IO_WARNING( "Error deleting file", fref->filename, g_strerror(errno) );
422 }
423
424 /**
425  * glk_fileref_does_file_exist:
426  * @fref: A fileref to check.
427  *
428  * Checks whether the file referred to by @fref exists.
429  *
430  * Returns: %TRUE (1) if @fref refers to an existing file, and %FALSE (0) if 
431  * not.
432  */
433 glui32
434 glk_fileref_does_file_exist(frefid_t fref)
435 {
436         VALID_FILEREF(fref, return 0);
437         if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) )
438                 return 1;
439         return 0;
440 }
441