Added dynamic module loading: now the Glk program (i.e., the
[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 window's rock, or #NULL.
14  *
15  * Iterates over the list of file references; if @fref is #NULL, it returns the
16  * first file reference, otherwise the next file reference after @fref. If 
17  * there are no more, it returns #NULL. The file reference's rock is stored in
18  * @rockptr. If you don't want the rocks to be returned, you may set @rockptr 
19  * to #NULL.
20  *
21  * The order in which file references are returned is arbitrary. The order may
22  * change every time you create or destroy a file reference, invalidating the 
23  * iteration.
24  *
25  * Returns: the next file reference, or #NULL if there are no more.
26  */
27 frefid_t
28 glk_fileref_iterate(frefid_t fref, glui32 *rockptr)
29 {
30         GList *retnode;
31         
32         if(fref == NULL)
33                 retnode = glk_data->fileref_list;
34         else
35                 retnode = fref->fileref_list->next;
36         frefid_t retval = retnode? (frefid_t)retnode->data : NULL;
37                 
38         /* Store the fileref's rock in rockptr */
39         if(retval && rockptr)
40                 *rockptr = glk_fileref_get_rock(retval);
41                 
42         return retval;
43 }
44
45 /**
46  * glk_fileref_get_rock:
47  * @fref: A file reference.
48  * 
49  * Returns the file reference @fref's rock value.
50  *
51  * Returns: A rock value.
52  */
53 glui32
54 glk_fileref_get_rock(frefid_t fref)
55 {
56         g_return_val_if_fail(fref != NULL, 0);
57         return fref->rock;
58 }
59
60 /* Internal function: create a fileref using the given parameters. */
61 static 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         frefid_t f = g_new0(struct glk_fileref_struct, 1);
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 #fileusage_ 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  * A temporary file should never be used for long-term storage. It may be
89  * deleted automatically when the program exits, or at some later time, say
90  * when the machine is turned off or rebooted. You do not have to worry about
91  * deleting it yourself.
92  *
93  * Returns: A new fileref, or #NULL if the fileref creation failed.
94  */ 
95 frefid_t
96 glk_fileref_create_temp(glui32 usage, glui32 rock)
97 {
98         /* Get a temp file */
99         GError *error = NULL;
100         gchar *filename = NULL;
101         gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error);
102         if(handle == -1)
103         {
104                 g_warning("Error creating temporary file: %s", error->message);
105                 if(filename)
106                         g_free(filename);
107                 return NULL;
108         }
109         if(close(handle) == -1) /* There is no g_close()? */
110         {
111                 g_warning("Error closing temporary file.");
112                 if(filename)
113                         g_free(filename);
114                 return NULL;
115         }
116         
117         frefid_t f = fileref_new(filename, rock, usage, filemode_Write);
118         g_free(filename);
119         return f;
120 }
121
122 /**
123  * glk_fileref_create_by_prompt:
124  * @usage: Bitfield with one or more of the #fileusage_ constants.
125  * @fmode: File mode, contolling the dialog's behavior.
126  * @rock: The new fileref's rock value.
127  *
128  * Creates a reference to a file by opening a file chooser dialog. If @fmode is
129  * #filemode_Read, then the file must already exist and the user will be asked
130  * to select from existing files. If @fmode is #filemode_Write, then the file
131  * should not exist; if the user selects an existing file, he or she will be
132  * warned that it will be replaced. If @fmode is #filemode_ReadWrite, then the
133  * file may or may not exist; if it already exists, the user will be warned
134  * that it will be modified. The @fmode argument should generally match the
135  * @fmode which will be used to open the file.
136  *
137  * Returns: A new fileref, or #NULL if the fileref creation failed or the
138  * dialog was canceled.
139  */
140 frefid_t
141 glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
142 {
143         /* TODO: Remember current working directory and last used filename
144         for each usage */
145         GtkWidget *chooser;
146
147         gdk_threads_enter();
148
149         switch(fmode)
150         {
151                 case filemode_Read:
152                         chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL,
153                                 GTK_FILE_CHOOSER_ACTION_OPEN,
154                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
155                                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
156                                 NULL);
157                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser),
158                                 GTK_FILE_CHOOSER_ACTION_OPEN);
159                         break;
160                 case filemode_Write:
161                         chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
162                                 GTK_FILE_CHOOSER_ACTION_SAVE,
163                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
164                                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
165                                 NULL);
166                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser),
167                                 GTK_FILE_CHOOSER_ACTION_SAVE);
168                         gtk_file_chooser_set_do_overwrite_confirmation(
169                                 GTK_FILE_CHOOSER(chooser), TRUE);
170                         break;
171                 case filemode_ReadWrite:
172                 case filemode_WriteAppend:
173                         chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL,
174                                 GTK_FILE_CHOOSER_ACTION_SAVE,
175                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
176                                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
177                                 NULL);
178                         gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser),
179                                 GTK_FILE_CHOOSER_ACTION_SAVE);
180                         break;
181                 default:
182                         g_warning("glk_fileref_create_by_prompt: Unsupported mode");
183                         gdk_threads_leave();
184                         return NULL;
185         }
186         
187         if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT)
188         {
189                 gtk_widget_destroy(chooser);
190                 gdk_threads_leave();
191                 return NULL;
192         }
193         gchar *filename = 
194                 gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) );
195         frefid_t f = fileref_new(filename, rock, usage, fmode);
196         g_free(filename);
197         gtk_widget_destroy(chooser);
198
199         gdk_threads_leave();
200         return f;
201 }
202
203 /**
204  * glk_fileref_create_by_name:
205  * @usage: Bitfield with one or more of the #fileusage_ constants.
206  * @name: A filename.
207  * @rock: The new fileref's rock value.
208  *
209  * This creates a reference to a file with a specific name. The file will be
210  * in the same directory as your program, and visible to the player.
211  *
212  * Returns: A new fileref, or #NULL if the fileref creation failed. 
213  */
214 frefid_t
215 glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
216 {
217         g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL);
218
219         /* Find out what encoding filenames are in */
220         const gchar **charsets; /* Do not free */
221         g_get_filename_charsets(&charsets);
222
223         /* Convert name to that encoding */
224         GError *error = NULL;
225         gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL,
226                 &error);
227         if(osname == NULL)
228         {
229                 g_warning("Error during latin1->filename conversion: %s", error->message);
230                 return NULL;
231         }
232
233         /* Do any string-munging here to remove illegal characters from filename.
234         On ext3, the only illegal characters are '/' and '\0'. TODO: Should this
235         function be allowed to reference files in other directories, or should we
236         disallow '/'? */
237
238         frefid_t f = fileref_new(osname, rock, usage, filemode_ReadWrite);
239         g_free(osname);
240         return f;
241 }
242
243 /**
244  * glk_fileref_create_from_fileref:
245  * @usage: Bitfield with one or more of the #fileusage_ constants.
246  * @fref: Fileref to copy.
247  * @rock: The new fileref's rock value.
248  *
249  * This copies an existing file reference @fref, but changes the usage. (The
250  * original @fref is not modified.)
251  *
252  * If you write to a file in text mode and then read from it in binary mode,
253  * the results are platform-dependent.
254  *
255  * Returns: A new fileref, or #NULL if the fileref creation failed. 
256  */
257 frefid_t
258 glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock)
259 {
260         return fileref_new(fref->filename, rock, usage, fref->orig_filemode);
261 }
262
263 /**
264  * glk_fileref_destroy:
265  * @fref: Fileref to destroy.
266  * 
267  * Destroys a fileref which you have created. This does not affect the disk
268  * file.
269  *
270  * It is legal to destroy a fileref after opening a file with it (while the
271  * file is still open.) The fileref is only used for the opening operation,
272  * not for accessing the file stream.
273  */
274 void
275 glk_fileref_destroy(frefid_t fref)
276 {
277         glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list);
278         if(fref->filename)
279                 g_free(fref->filename);
280         g_free(fref);
281 }
282
283 /**
284  * glk_fileref_delete_file:
285  * @fref: A refrence to the file to delete.
286  *
287  * Deletes the file referred to by @fref. Does not destroy @fref itself.
288  */
289 void
290 glk_fileref_delete_file(frefid_t fref)
291 {
292         if( glk_fileref_does_file_exist(fref) )
293                 if(g_unlink(fref->filename) == -1)
294                         g_warning("Error deleting file %s", fref->filename);
295 }
296
297 /**
298  * glk_fileref_does_file_exist:
299  * @fref: A fileref to check.
300  *
301  * Checks whether the file referred to by @fref exists.
302  *
303  * Returns: #TRUE (1) if @fref refers to an existing file, #FALSE (0) if not.
304  */
305 glui32
306 glk_fileref_does_file_exist(frefid_t fref)
307 {
308         if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) )
309                 return 1;
310         return 0;
311 }
312