Rewrite fs_rename to do a real rename rather than VFS copy+remove
[neverball] / share / fs.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <string.h>
5 #include <physfs.h>
6
7 #include "fs.h"
8 #include "dir.h"
9 #include "array.h"
10 #include "common.h"
11
12 /*
13  * This file implements the virtual file system layer.  Most file
14  * system and input/output operations are handled here.  There are
15  * basically two groups of functions here: low-level functions
16  * implemented directly using the PhysicsFS 1.0 API and higher-level
17  * functions implemented using the former group.
18  */
19
20 /* -------------------------------------------------------------------------- */
21
22 struct fs_file
23 {
24     PHYSFS_file *handle;
25 };
26
27 int fs_init(const char *argv0)
28 {
29     return PHYSFS_init(argv0);
30 }
31
32 int fs_quit(void)
33 {
34     return PHYSFS_deinit();
35 }
36
37 /* -------------------------------------------------------------------------- */
38
39 const char *fs_base_dir(void)
40 {
41     return PHYSFS_getBaseDir();
42 }
43
44 int fs_add_path(const char *path)
45 {
46     return PHYSFS_addToSearchPath(path, 0);
47 }
48
49 int fs_set_write_dir(const char *path)
50 {
51     return PHYSFS_setWriteDir(path);
52 }
53
54 const char *fs_get_write_dir(void)
55 {
56     return PHYSFS_getWriteDir();
57 }
58
59 /* -------------------------------------------------------------------------- */
60
61 Array fs_dir_scan(const char *path, int (*filter)(struct dir_item *))
62 {
63     return dir_scan(path, filter, PHYSFS_enumerateFiles, PHYSFS_freeList);
64 }
65
66 void fs_dir_free(Array items)
67 {
68     dir_free(items);
69 }
70
71 /* -------------------------------------------------------------------------- */
72
73 fs_file fs_open(const char *path, const char *mode)
74 {
75     fs_file fh;
76
77     assert((mode[0] == 'r' && !mode[1]) ||
78            (mode[0] == 'w' && (!mode[1] || mode[1] == '+')));
79
80     if ((fh = malloc(sizeof (*fh))))
81     {
82         switch (mode[0])
83         {
84         case 'r':
85             fh->handle = PHYSFS_openRead(path);
86             break;
87
88         case 'w':
89             fh->handle = (mode[1] == '+' ?
90                           PHYSFS_openAppend(path) :
91                           PHYSFS_openWrite(path));
92             break;
93         }
94
95         if (fh->handle)
96         {
97             PHYSFS_setBuffer(fh->handle, 0x2000);
98         }
99         else
100         {
101             free(fh);
102             fh = NULL;
103         }
104     }
105
106     return fh;
107 }
108
109 int fs_close(fs_file fh)
110 {
111     if (PHYSFS_close(fh->handle))
112     {
113         free(fh);
114         return 1;
115     }
116     return 0;
117 }
118
119 /* -------------------------------------------------------------------------- */
120
121 int fs_mkdir(const char *path)
122 {
123     return PHYSFS_mkdir(path);
124 }
125
126 int fs_exists(const char *path)
127 {
128     return PHYSFS_exists(path);
129 }
130
131 int fs_remove(const char *path)
132 {
133     return PHYSFS_delete(path);
134 }
135
136 /* -------------------------------------------------------------------------- */
137
138 int fs_read(void *data, int size, int count, fs_file fh)
139 {
140     return PHYSFS_read(fh->handle, data, size, count);
141 }
142
143 int fs_write(const void *data, int size, int count, fs_file fh)
144 {
145     return PHYSFS_write(fh->handle, data, size, count);
146 }
147
148 int fs_flush(fs_file fh)
149 {
150     return PHYSFS_flush(fh->handle);
151 }
152
153 long fs_tell(fs_file fh)
154 {
155     return PHYSFS_tell(fh->handle);
156 }
157
158 int fs_seek(fs_file fh, long offset, int whence)
159 {
160     PHYSFS_uint64 pos = 0;
161     PHYSFS_sint64 cur = PHYSFS_tell(fh->handle);
162     PHYSFS_sint64 len = PHYSFS_fileLength(fh->handle);
163
164     switch (whence)
165     {
166     case SEEK_SET:
167         pos = offset;
168         break;
169
170     case SEEK_CUR:
171         if (cur < 0)
172             return -1;
173         pos = cur + offset;
174         break;
175
176     case SEEK_END:
177         if (len < 0)
178             return -1;
179         pos = len + offset;
180         break;
181     }
182
183     return PHYSFS_seek(fh->handle, pos);
184 }
185
186 int fs_eof(fs_file fh)
187 {
188     return PHYSFS_eof(fh->handle);
189 }
190
191 /* -------------------------------------------------------------------------- */
192
193 /*
194  * The code below does not use the PhysicsFS API.
195  */
196
197 /* -------------------------------------------------------------------------- */
198
199 static int cmp_dir_items(const void *A, const void *B)
200 {
201     const struct dir_item *a = A, *b = B;
202     return strcmp(a->path, b->path);
203 }
204
205 static int is_archive(struct dir_item *item)
206 {
207     return strcmp(item->path + strlen(item->path) - 4, ".zip") == 0;
208 }
209
210 static void add_archives(const char *path)
211 {
212     Array archives;
213     int i;
214
215     if ((archives = dir_scan(path, is_archive, NULL, NULL)))
216     {
217         array_sort(archives, cmp_dir_items);
218
219         for (i = 0; i < array_len(archives); i++)
220             fs_add_path(DIR_ITEM_GET(archives, i)->path);
221
222         dir_free(archives);
223     }
224 }
225
226 int fs_add_path_with_archives(const char *path)
227 {
228     add_archives(path);
229     return fs_add_path(path);
230 }
231
232 /* -------------------------------------------------------------------------- */
233
234 int fs_rename(const char *src, const char *dst)
235 {
236     const char *write_dir;
237     char *real_src, *real_dst;
238     int rc = 0;
239
240     if ((write_dir = fs_get_write_dir()))
241     {
242         real_src = concat_string(write_dir, "/", src, NULL);
243         real_dst = concat_string(write_dir, "/", dst, NULL);
244
245         rc = file_rename(real_src, real_dst);
246
247         free(real_src);
248         free(real_dst);
249     }
250
251     return rc;
252 }
253
254 /* -------------------------------------------------------------------------- */
255
256 int fs_getc(fs_file fh)
257 {
258     unsigned char c;
259
260     if (fs_read(&c, 1, 1, fh) != 1)
261         return -1;
262
263     return (int) c;
264 }
265
266 int fs_putc(int c, fs_file fh)
267 {
268     unsigned char b = (unsigned char) c;
269
270     if (fs_write(&b, 1, 1, fh) != 1)
271         return -1;
272
273     return b;
274 }
275
276 int fs_puts(const char *src, fs_file fh)
277 {
278     while (*src)
279         if (fs_putc(*src++, fh) < 0)
280             return -1;
281
282     return 0;
283 }
284
285 char *fs_gets(char *dst, int count, fs_file fh)
286 {
287     char *s = dst;
288     char *cr = NULL;
289     int c = 0;
290
291     if (fs_eof(fh))
292         return NULL;
293
294     while (count > 1 && (c = fs_getc(fh)) >= 0)
295     {
296         count--;
297
298         *s = c;
299
300         /* Normalize possible CRLF and break. */
301
302         if (*s == '\n')
303         {
304             if (cr + 1 == s)
305                 *cr = '\n';
306             else
307                 s++;
308
309             break;
310         }
311
312         /* Note carriage return. */
313
314         if (*s == '\r')
315             cr = s;
316
317         s++;
318     }
319
320     if (count > 0)
321         *s = '\0';
322
323     return c < 0 ? NULL : dst;
324 }
325
326 /* -------------------------------------------------------------------------- */
327
328 /*
329  * Write out a multiline string to a file with appropriately converted
330  * linefeed characters.
331  */
332 static int write_lines(const char *start, int length, fs_file fh)
333 {
334 #ifdef _WIN32
335     static const char crlf[] = "\r\n";
336 #else
337     static const char crlf[] = "\n";
338 #endif
339
340     int total_written = 0;
341
342     int datalen;
343     int written;
344     char *lf;
345
346     while (total_written < length)
347     {
348         lf = strchr(start, '\n');
349
350         datalen = lf ? (int) (lf - start) : length - total_written;
351         written = fs_write(start, 1, datalen, fh);
352
353         if (written < 0)
354             break;
355
356         total_written += written;
357
358         if (written < datalen)
359             break;
360
361         if (lf)
362         {
363             if (fs_puts(crlf, fh) < 0)
364                 break;
365
366             total_written += 1;
367             start = lf + 1;
368         }
369     }
370
371     return total_written;
372 }
373
374 /* -------------------------------------------------------------------------- */
375
376 /*
377  * Trying to avoid defining a feature test macro for every platform by
378  * declaring vsnprintf with the C99 signature.  This is probably bad.
379  */
380
381 #include <stdio.h>
382 #include <stdarg.h>
383
384 extern int vsnprintf(char *, size_t, const char *, va_list);
385
386 int fs_printf(fs_file fh, const char *fmt, ...)
387 {
388     char *buff;
389     int len;
390
391     va_list ap;
392
393     va_start(ap, fmt);
394     len = vsnprintf(NULL, 0, fmt, ap) + 1;
395     va_end(ap);
396
397     if ((buff = malloc(len)))
398     {
399         int written;
400
401         va_start(ap, fmt);
402         vsnprintf(buff, len, fmt, ap);
403         va_end(ap);
404
405         /*
406          * HACK.  This assumes fs_printf is always called with the
407          * intention of writing text, and not arbitrary data.
408          */
409
410         written = write_lines(buff, strlen(buff), fh);
411
412         free(buff);
413
414         return written;
415     }
416
417     return 0;
418 }
419
420 /* -------------------------------------------------------------------------- */