added assfile
[raydungeon] / libs / assfile / assfile.c
1 /*
2 assfile - library for accessing assets with an fopen/fread-like interface
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include "assfile_impl.h"
23
24 int ass_errno;
25
26 /* declared in assfile_impl.h */
27 int ass_mod_url_max_threads;
28 char ass_mod_url_cachedir[512];
29 int ass_verbose;
30
31 static int add_fop(const char *prefix, int type, struct ass_fileops *fop);
32 static const char *match_prefix(const char *str, const char *prefix);
33 static void upd_verbose_flag(void);
34
35 #define DEF_FLAGS       (1 << ASS_OPEN_FALLTHROUGH)
36
37 static unsigned int assflags = DEF_FLAGS;
38 static struct mount *mlist;
39
40 void ass_set_option(int opt, int val)
41 {
42         if(val) {
43                 assflags |= 1 << opt;
44         } else {
45                 assflags &= ~(1 << opt);
46         }
47 }
48
49 int ass_get_option(int opt)
50 {
51         return assflags & (1 << opt);
52 }
53
54 int ass_add_path(const char *prefix, const char *path)
55 {
56         return add_fop(prefix, MOD_PATH, ass_alloc_path(path));
57 }
58
59 int ass_add_archive(const char *prefix, const char *arfile)
60 {
61         return add_fop(prefix, MOD_ARCHIVE, ass_alloc_archive(arfile));
62 }
63
64 int ass_add_url(const char *prefix, const char *url)
65 {
66         return add_fop(prefix, MOD_URL, ass_alloc_url(url));
67 }
68
69 int ass_add_user(const char *prefix, struct ass_fileops *fop)
70 {
71         return add_fop(prefix, MOD_USER, fop);
72 }
73
74 static int add_fop(const char *prefix, int type, struct ass_fileops *fop)
75 {
76         struct mount *m;
77
78         upd_verbose_flag();
79
80         if(!fop) {
81                 fprintf(stderr, "assfile: failed to allocate asset source\n");
82                 return -1;
83         }
84
85         if(!(m = malloc(sizeof *m))) {
86                 perror("assfile: failed to allocate mount node");
87                 return -1;
88         }
89         if(prefix) {
90                 if(!(m->prefix = malloc(strlen(prefix) + 1))) {
91                         free(m);
92                         return -1;
93                 }
94                 strcpy(m->prefix, prefix);
95         } else {
96                 m->prefix = 0;
97         }
98         m->fop = fop;
99         m->type = type;
100
101         m->next = mlist;
102         mlist = m;
103         return 0;
104 }
105
106 void ass_clear(void)
107 {
108         while(mlist) {
109                 struct mount *m = mlist;
110                 mlist = mlist->next;
111
112                 switch(m->type) {
113                 case MOD_PATH:
114                         ass_free_path(m->fop);
115                         break;
116                 case MOD_ARCHIVE:
117                         ass_free_archive(m->fop);
118                         break;
119                 case MOD_URL:
120                         ass_free_url(m->fop);
121                         break;
122                 default:
123                         break;
124                 }
125
126                 free(m->prefix);
127                 free(m);
128         }
129 }
130
131 ass_file *ass_fopen(const char *fname, const char *mode)
132 {
133         struct mount *m;
134         void *mfile;
135         ass_file *file;
136         FILE *fp;
137         const char *after_prefix;
138
139         upd_verbose_flag();
140
141         m = mlist;
142         while(m) {
143                 if((after_prefix = match_prefix(fname, m->prefix))) {
144                         while(*after_prefix && (*after_prefix == '/' || *after_prefix == '\\')) {
145                                 after_prefix++;
146                         }
147                         if((mfile = m->fop->open(after_prefix, m->fop->udata))) {
148                                 if(!(file = malloc(sizeof *file))) {
149                                         perror("assfile: ass_fopen failed to allocate file structure");
150                                         m->fop->close(mfile, m->fop->udata);
151                                         return 0;
152                                 }
153                                 file->file = mfile;
154                                 file->fop = m->fop;
155                                 return file;
156                         } else {
157                                 if(!(assflags & (1 << ASS_OPEN_FALLTHROUGH))) {
158                                         return 0;
159                                 }
160                         }
161                 }
162                 m = m->next;
163         }
164
165         /* nothing matched, or failed to open, try the filesystem */
166         if((fp = fopen(fname, mode))) {
167                 if(!(file = malloc(sizeof *file))) {
168                         ass_errno = errno;
169                         perror("assfile: ass_fopen failed to allocate file structure");
170                         fclose(fp);
171                         return 0;
172                 }
173                 file->file = fp;
174                 file->fop = 0;
175                 return file;
176         }
177         ass_errno = errno;
178         return 0;
179 }
180
181 static const char *match_prefix(const char *str, const char *prefix)
182 {
183         if(!prefix || !*prefix) return str;     /* match on null or empty prefix */
184
185         while(*prefix) {
186                 if(*prefix++ != *str++) {
187                         return 0;
188                 }
189         }
190         return str;
191 }
192
193 void ass_fclose(ass_file *fp)
194 {
195         if(fp->fop) {
196                 fp->fop->close(fp->file, fp->fop->udata);
197         } else {
198                 fclose(fp->file);
199         }
200         free(fp);
201 }
202
203 long ass_fseek(ass_file *fp, long offs, int whence)
204 {
205         if(fp->fop) {
206                 return fp->fop->seek(fp->file, offs, whence, fp->fop->udata);
207         }
208
209         if(fseek(fp->file, offs, whence) == -1) {
210                 return -1;
211         }
212         return ftell(fp->file);
213 }
214
215 long ass_ftell(ass_file *fp)
216 {
217         return ass_fseek(fp, 0, SEEK_CUR);
218 }
219
220 size_t ass_fread(void *buf, size_t size, size_t count, ass_file *fp)
221 {
222         if(fp->fop) {
223                 long res = fp->fop->read(fp->file, buf, size * count, fp->fop->udata);
224                 if(res <= 0) return 0;
225                 return res / size;
226         }
227
228         return fread(buf, size, count, fp->file);
229 }
230
231
232 /* --- convenience functions --- */
233
234 int ass_fgetc(ass_file *fp)
235 {
236         unsigned char c;
237
238         if(ass_fread(&c, 1, 1, fp) < 1) {
239                 return -1;
240         }
241         return (int)c;
242 }
243
244 char *ass_fgets(char *s, int size, ass_file *fp)
245 {
246         int i, c;
247         char *ptr = s;
248
249         if(!size) return 0;
250
251         for(i=0; i<size - 1; i++) {
252                 if((c = ass_fgetc(fp)) == -1) {
253                         break;
254                 }
255                 *ptr++ = c;
256
257                 if(c == '\n') break;
258         }
259         *ptr = 0;
260         return ptr == s ? 0 : s;
261 }
262
263
264 static void upd_verbose_flag(void)
265 {
266         const char *env;
267
268         if((env = getenv("ASSFILE_VERBOSE"))) {
269                 ass_verbose = atoi(env);
270         }
271 }