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