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