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