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