1 /*
2 ZIP VFS plugin for DeaDBeeF Player
3 Copyright (C) 2009-2014 Alexey Yakovenko
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source distribution.
22 */
23
24 #include <string.h>
25 #include <zip.h>
26 #include <stdlib.h>
27 #include <assert.h>
28 #include "../../deadbeef.h"
29
30 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
31 #define trace(fmt,...)
32
33 #define ENABLE_CACHE 1
34
35 #define min(x,y) ((x)<(y)?(x):(y))
36
37 static DB_functions_t *deadbeef;
38 static DB_vfs_t plugin;
39
40 #if ENABLE_CACHE
41 #define ZIP_BUFFER_SIZE 8192
42 #endif
43
44 typedef struct {
45 DB_FILE file;
46 struct zip* z;
47 struct zip_file *zf;
48 int64_t offset;
49 int index;
50 int64_t size;
51
52 #if ENABLE_CACHE
53 uint8_t buffer[ZIP_BUFFER_SIZE];
54 int buffer_remaining;
55 int buffer_pos;
56 #endif
57 } ddb_zip_file_DB;
58
59 static const char *scheme_names[] = { "zip://", NULL };
60
61 const char **
vfs_zip_get_schemes(void)62 vfs_zip_get_schemes (void) {
63 return scheme_names;
64 }
65
66 int
vfs_zip_is_streaming(void)67 vfs_zip_is_streaming (void) {
68 return 0;
69 }
70
71 // fname must have form of zip://full_filepath.zip:full_filepath_in_zip
72 DB_FILE*
vfs_zip_open(const char * fname)73 vfs_zip_open (const char *fname) {
74 trace ("vfs_zip: open %s\n", fname);
75 if (strncasecmp (fname, "zip://", 6)) {
76 return NULL;
77 }
78
79 fname += 6;
80
81 struct zip *z = NULL;
82 struct zip_stat st;
83
84 const char *colon = fname;
85
86 for (;;) {
87 colon = strchr (colon, ':');
88 if (!colon) {
89 break;
90 }
91
92 char zipname[colon-fname+1];
93 memcpy (zipname, fname, colon-fname);
94 zipname[colon-fname] = 0;
95
96 colon = colon+1;
97
98 z = zip_open (zipname, 0, NULL);
99 if (!z) {
100 continue;
101 }
102 memset (&st, 0, sizeof (st));
103
104 int res = zip_stat(z, colon, 0, &st);
105 if (res != 0) {
106 zip_close (z);
107 return NULL;
108 }
109
110 break;
111 }
112
113 if (!z) {
114 return NULL;
115 }
116
117 fname = colon;
118
119 struct zip_file *zf = zip_fopen_index (z, st.index, 0);
120 if (!zf) {
121 zip_close (z);
122 return NULL;
123 }
124
125 ddb_zip_file_DB *f = malloc (sizeof (ddb_zip_file_DB));
126 memset (f, 0, sizeof (ddb_zip_file_DB));
127 f->file.vfs = &plugin;
128 f->z = z;
129 f->zf = zf;
130 f->index = st.index;
131 f->size = st.size;
132 trace ("vfs_zip: end open %s\n", fname);
133 return (DB_FILE*)f;
134 }
135
136 void
vfs_zip_close(DB_FILE * f)137 vfs_zip_close (DB_FILE *f) {
138 trace ("vfs_zip: close\n");
139 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
140 if (zf->zf) {
141 zip_fclose (zf->zf);
142 }
143 if (zf->z) {
144 zip_close (zf->z);
145 }
146 free (zf);
147 }
148
149 size_t
vfs_zip_read(void * ptr,size_t size,size_t nmemb,DB_FILE * f)150 vfs_zip_read (void *ptr, size_t size, size_t nmemb, DB_FILE *f) {
151 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
152 // printf ("read: %d\n", size*nmemb);
153
154 size_t sz = size * nmemb;
155 #if ENABLE_CACHE
156 while (sz) {
157 if (zf->buffer_remaining == 0) {
158 zf->buffer_pos = 0;
159 int rb = zip_fread (zf->zf, zf->buffer, ZIP_BUFFER_SIZE);
160 if (rb <= 0) {
161 break;
162 }
163 zf->buffer_remaining = rb;
164 }
165 int from_buf = min (sz, zf->buffer_remaining);
166 memcpy (ptr, zf->buffer+zf->buffer_pos, from_buf);
167 zf->buffer_remaining -= from_buf;
168 zf->buffer_pos += from_buf;
169 zf->offset += from_buf;
170 sz -= from_buf;
171 ptr += from_buf;
172 }
173 #else
174 rb = zip_fread (zf->zf, ptr, sz);
175 sz -= rb;
176 zf->offset += rb;
177 #endif
178
179 return (size * nmemb - sz) / size;
180 }
181
182 int
vfs_zip_seek(DB_FILE * f,int64_t offset,int whence)183 vfs_zip_seek (DB_FILE *f, int64_t offset, int whence) {
184 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
185 // printf ("seek: %lld (%d)\n", offset, whence);
186
187 if (whence == SEEK_CUR) {
188 offset = zf->offset + offset;
189 }
190 else if (whence == SEEK_END) {
191 offset = zf->size + offset;
192 }
193
194 #if ENABLE_CACHE
195 int64_t offs = offset - zf->offset;
196 if ((offs < 0 && -offs <= zf->buffer_pos) || (offs >= 0 && offs < zf->buffer_remaining)) {
197 if (offs != 0) {
198 //printf ("cache success\n");
199
200 //printf ("[before] absoffs: %lld, offs: %lld, rem: %d, pos: %d\n", offset, offs, zf->buffer_remaining, zf->buffer_pos);
201
202 // test cases:
203 // fail: offs = -3, pos = 0, rem = 100
204 // fail: offs = 10, pos = 95, rem = 5
205 // succ: offs = -3, pos = 3, rem = 97 ----> pos = 0, rem=100
206 // succ: offs = 10, pos = 0, rem = 100 ---> pos = 10, rem = 90
207
208 zf->buffer_pos += offs;
209 zf->buffer_remaining -= offs;
210 //printf ("[after] offs: %lld, rem: %d, pos: %d\n", offs, zf->buffer_remaining, zf->buffer_pos);
211 zf->offset = offset;
212 assert (zf->buffer_pos < ZIP_BUFFER_SIZE);
213 return 0;
214 }
215 else {
216 // printf ("cache double success\n");
217 return 0;
218 }
219 }
220 // else {
221 // printf ("cache miss: abs_offs: %lld, offs: %lld, rem: %d, pos: %d\n", offset, offs, zf->buffer_remaining, zf->buffer_pos);
222 // }
223
224 zf->offset += zf->buffer_remaining;
225 #endif
226 if (offset < zf->offset) {
227 // reopen
228 zip_fclose (zf->zf);
229 zf->zf = zip_fopen_index (zf->z, zf->index, 0);
230 if (!zf->zf) {
231 return -1;
232 }
233 zf->offset = 0;
234 }
235 #if ENABLE_CACHE
236 zf->buffer_pos = 0;
237 zf->buffer_remaining = 0;
238 #endif
239 char buf[4096];
240 int64_t n = offset - zf->offset;
241 while (n > 0) {
242 int sz = min (n, sizeof (buf));
243 ssize_t rb = zip_fread (zf->zf, buf, sz);
244 n -= rb;
245 assert (n >= 0);
246 zf->offset += rb;
247 if (rb != sz) {
248 break;
249 }
250 }
251 if (n > 0) {
252 return -1;
253 }
254 return 0;
255 }
256
257 int64_t
vfs_zip_tell(DB_FILE * f)258 vfs_zip_tell (DB_FILE *f) {
259 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
260 return zf->offset;
261 }
262
263 void
vfs_zip_rewind(DB_FILE * f)264 vfs_zip_rewind (DB_FILE *f) {
265 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
266 zip_fclose (zf->zf);
267 zf->zf = zip_fopen_index (zf->z, zf->index, 0);
268 assert (zf->zf); // FIXME: better error handling?
269 zf->offset = 0;
270 #if ENABLE_CACHE
271 zf->buffer_remaining = 0;
272 #endif
273 }
274
275 int64_t
vfs_zip_getlength(DB_FILE * f)276 vfs_zip_getlength (DB_FILE *f) {
277 ddb_zip_file_DB *zf = (ddb_zip_file_DB *)f;
278 return zf->size;
279 }
280
281 int
vfs_zip_scandir(const char * dir,struct dirent *** namelist,int (* selector)(const struct dirent *),int (* cmp)(const struct dirent **,const struct dirent **))282 vfs_zip_scandir (const char *dir, struct dirent ***namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **)) {
283 trace ("vfs_zip_scandir: %s\n", dir);
284 int error;
285 struct zip *z = zip_open (dir, 0, &error);
286 if (!z) {
287 trace ("zip_open failed (code: %d)\n", error);
288 return -1;
289 }
290
291 int num_files = 0;
292 const int n = zip_get_num_files(z);
293 *namelist = malloc(sizeof(void *) * n);
294 for (int i = 0; i < n; i++) {
295 const char *nm = zip_get_name(z, i, 0);
296 struct dirent entry;
297 strncpy(entry.d_name, nm, sizeof(entry.d_name)-1);
298 entry.d_name[sizeof(entry.d_name)-1] = '\0';
299 if (!selector || selector && selector(&entry)) {
300 (*namelist)[num_files] = calloc(1, sizeof(struct dirent));
301 strcpy((*namelist)[num_files]->d_name, entry.d_name);
302 num_files++;
303 trace("vfs_zip: %s\n", nm);
304 }
305 }
306
307 zip_close (z);
308 trace ("vfs_zip: scandir done\n");
309 return num_files;
310 }
311
312 int
vfs_zip_is_container(const char * fname)313 vfs_zip_is_container (const char *fname) {
314 const char *ext = strrchr (fname, '.');
315 if (ext && !strcasecmp (ext, ".zip")) {
316 return 1;
317 }
318 return 0;
319 }
320 const char *
vfs_zip_get_scheme_for_name(const char * fname)321 vfs_zip_get_scheme_for_name (const char *fname) {
322 return scheme_names[0];
323 }
324
325 static DB_vfs_t plugin = {
326 .plugin.api_vmajor = 1,
327 .plugin.api_vminor = 6,
328 .plugin.version_major = 1,
329 .plugin.version_minor = 0,
330 .plugin.type = DB_PLUGIN_VFS,
331 .plugin.id = "vfs_zip",
332 .plugin.name = "ZIP vfs",
333 .plugin.descr = "play files directly from zip files",
334 .plugin.copyright =
335 "ZIP VFS plugin for DeaDBeeF Player\n"
336 "Copyright (C) 2009-2014 Alexey Yakovenko\n"
337 "\n"
338 "This software is provided 'as-is', without any express or implied\n"
339 "warranty. In no event will the authors be held liable for any damages\n"
340 "arising from the use of this software.\n"
341 "\n"
342 "Permission is granted to anyone to use this software for any purpose,\n"
343 "including commercial applications, and to alter it and redistribute it\n"
344 "freely, subject to the following restrictions:\n"
345 "\n"
346 "1. The origin of this software must not be misrepresented; you must not\n"
347 " claim that you wrote the original software. If you use this software\n"
348 " in a product, an acknowledgment in the product documentation would be\n"
349 " appreciated but is not required.\n"
350 "\n"
351 "2. Altered source versions must be plainly marked as such, and must not be\n"
352 " misrepresented as being the original software.\n"
353 "\n"
354 "3. This notice may not be removed or altered from any source distribution.\n"
355 ,
356 .plugin.website = "http://deadbeef.sf.net",
357 .open = vfs_zip_open,
358 .close = vfs_zip_close,
359 .read = vfs_zip_read,
360 .seek = vfs_zip_seek,
361 .tell = vfs_zip_tell,
362 .rewind = vfs_zip_rewind,
363 .getlength = vfs_zip_getlength,
364 .get_schemes = vfs_zip_get_schemes,
365 .is_streaming = vfs_zip_is_streaming,
366 .is_container = vfs_zip_is_container,
367 .scandir = vfs_zip_scandir,
368 .get_scheme_for_name = vfs_zip_get_scheme_for_name,
369 };
370
371 DB_plugin_t *
vfs_zip_load(DB_functions_t * api)372 vfs_zip_load (DB_functions_t *api) {
373 deadbeef = api;
374 return DB_PLUGIN (&plugin);
375 }
376