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