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