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