Do not use {0} struct initializers
[platform/upstream/lightmediascanner.git] / src / plugins / pls / pls.c
1 /**
2  * Copyright (C) 2008-2011 by ProFUSION embedded systems
3  * Copyright (C) 2007 by INdT
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public License
7  * as published by the Free Software Foundation; either version 2.1 of
8  * the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA
19  *
20  * @author Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
21  */
22
23 /**
24  * @brief
25  *
26  * pls playlist parser.
27  *
28  * This parser doesn't actually parse the whole file, instead it just checks
29  * for the header [playlist], then search the beginning and ending of the file
30  * (at most PLS_MAX_N_ENTRIES_BYTES_LOOKUP bytes) in order to find out
31  * NumberOfEntries=XXX line. If there are too many bogus (ie: empty) lines or
32  * this line is inside the data declaration, then it will fail the parse.
33  * In theory this should not happen, so let's wait for bug reports.
34  */
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #define _XOPEN_SOURCE 600
41 #include <lightmediascanner_plugin.h>
42 #include <lightmediascanner_db.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <fcntl.h>
46 #include <unistd.h>
47 #include <ctype.h>
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <string.h>
51
52 #define PLS_MAX_N_ENTRIES_BYTES_LOOKUP 64
53
54 static int
55 _pls_find_header(int fd)
56 {
57     const char header[] = "[playlist]";
58     char buf[sizeof(header) - 1];
59     ssize_t r;
60
61     /* skip out white spaces */
62     do {
63         r = read(fd, buf, 1);
64         if (r < 0) {
65             perror("read");
66             return -1;
67         } else if (r == 0) {
68             fprintf(stderr, "ERROR: premature end of file.\n");
69             return -2;
70         }
71
72         if (!isspace(buf[0]))
73             break;
74     } while (1);
75
76     if (buf[0] != header[0])
77         return -3;
78
79     /* try to read rest (from the second on) of the header */
80     r = read(fd, buf + 1, sizeof(buf) - 1);
81     if (r < 0) {
82         perror("read");
83         return -4;
84     } else if (r != sizeof(buf) - 1) {
85         fprintf(stderr, "ERROR: premature end of file: read %zd of %zd"
86                 "bytes.\n", r, sizeof(buf) - 1);
87         return -5;
88     }
89
90     if (memcmp(buf + 1, header + 1, sizeof(buf) - 1) != 0) {
91         fprintf(stderr, "ERROR: invalid pls header '%.*s'\n",
92                 (int)sizeof(buf) - 1, buf);
93         return -6;
94     }
95
96     /* find '\n' */
97     do {
98         r = read(fd, buf, 1);
99         if (r < 0) {
100             perror("read");
101             return -7;
102         } else if (r == 0)
103             return -8;
104
105         if (buf[0] == '\n')
106             return 0;
107     } while (1);
108
109     return -1;
110 }
111
112 static int
113 _pls_find_n_entries_start(int fd, struct lms_playlist_info *info)
114 {
115     char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
116     const char n_entries[] = "NumberOfEntries=";
117     ssize_t r;
118     int i;
119     off_t off;
120
121     off = lseek(fd, 0, SEEK_CUR);
122     if (off < 0) {
123         perror("lseek");
124         return -1;
125     }
126
127     r = read(fd, buf, sizeof(buf));
128     if (r < 0) {
129         perror("read");
130         return -2;
131     } else if (r == 0)
132         return -3;
133
134     for (i = 0; i < r; i++) {
135         char c;
136
137         c = buf[i];
138         if (c == n_entries[0]) {
139             const char *p;
140             i++;
141             if (memcmp(buf + i, n_entries + 1, sizeof(n_entries) - 2) != 0) {
142                 off += i + sizeof(n_entries) - 2;
143                 goto done;
144             }
145
146             i += sizeof(n_entries) - 2;
147             p = buf + i;
148             for (; i < r; i++)
149                 if (buf[i] == '\n')
150                     break;
151
152             if (i == r) {
153                 fprintf(stderr, "WARNING: missing end of line\n");
154                 i = r - 1;
155             }
156             buf[i] = '\0';
157             info->n_entries = atoi(p);
158             return 0;
159         } else if (c == 'V') {
160             /* skip possible 'Version=XX' */
161             for (i++; i < r; i++)
162                 if (buf[i] == '\n')
163                     break;
164         } else if (isspace(c))
165             continue;
166         else {
167             off += i;
168             goto done;
169         }
170     }
171
172   done:
173     /* not at the file beginning, reset offset */
174     if (lseek(fd, off, SEEK_SET) < 0) {
175         perror("lseek");
176         return -1;
177     }
178
179     return 1;
180 }
181
182 static int
183 _pls_parse_entries_line(int fd, struct lms_playlist_info *info, char *buf, int len)
184 {
185     const char n_entries[] = "NumberOfEntries=";
186     int i;
187
188     for (i = 0; i < len; i++, buf++)
189         if (!isspace(*buf))
190             break;
191
192     if (i == len)
193         return 1;
194     len -= i;
195
196     if (memcmp(buf, n_entries, sizeof(n_entries) - 1) != 0)
197         return 1;
198
199     buf += sizeof(n_entries) - 1;
200     len -= sizeof(n_entries) - 1;
201     buf[len] = '\0';
202
203     info->n_entries = atoi(buf);
204     return 0;
205 }
206
207 static int
208 _pls_find_n_entries_end(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
209 {
210     char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
211     ssize_t r;
212     int i, last_nl;
213
214     if (finfo->size > sizeof(buf))
215         if (lseek(fd, finfo->size - sizeof(buf), SEEK_SET) < 0) {
216             perror("lseek");
217             return -1;
218         }
219
220     r = read(fd, buf, sizeof(buf));
221     if (r < 0) {
222         perror("read");
223         return -1;
224     } else if (r == 0)
225         return -2;
226
227     last_nl = -1;
228     for (i = r - 1; i >= 0; i--) {
229         if (buf[i] == '\n') {
230             if (last_nl >= 0) {
231                 int len;
232
233                 len = last_nl - i - 1;
234                 if (len > 0) {
235                     int ret;
236
237                     ret = _pls_parse_entries_line(fd, info, buf + i + 1, len);
238                     if (ret <= 0)
239                         return ret;
240                 }
241             }
242             last_nl = i;
243         }
244     }
245
246     return 1;
247 }
248
249 static int
250 _pls_parse(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
251 {
252     int r;
253
254     r = _pls_find_header(fd);
255     if (r != 0) {
256         fprintf(stderr, "ERROR: could not find pls header. code=%d\n", r);
257         return -1;
258     }
259
260     r = _pls_find_n_entries_start(fd, info);
261     if (r <= 0)
262         return r;
263
264     r = _pls_find_n_entries_end(fd, finfo, info);
265     if (r != 0)
266         fprintf(stderr, "ERROR: could not find pls NumberOfEntries=\n");
267
268     return r;
269 }
270
271 static const char _name[] = "pls";
272 static const struct lms_string_size _exts[] = {
273     LMS_STATIC_STRING_SIZE(".pls")
274 };
275 static const char *_cats[] = {
276     "multimedia",
277     "audio",
278     "playlist",
279     NULL
280 };
281 static const char *_authors[] = {
282     "Gustavo Sverzut Barbieri",
283     NULL
284 };
285
286 struct plugin {
287     struct lms_plugin plugin;
288     lms_db_playlist_t *playlist_db;
289 };
290
291 static void *
292 _match(struct plugin *p, const char *path, int len, int base)
293 {
294     long i;
295
296     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
297     if (i < 0)
298       return NULL;
299     else
300       return (void*)(i + 1);
301 }
302
303 static int
304 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
305 {
306     struct lms_playlist_info info = { };
307     int fd, r;
308     long ext_idx;
309
310     fd = open(finfo->path, O_RDONLY);
311     if (fd < 0) {
312         perror("open");
313         return -1;
314     }
315
316     if (_pls_parse(fd, finfo, &info) != 0) {
317         fprintf(stderr,
318                 "WARNING: could not parse playlist '%s'.\n", finfo->path);
319         return -1;
320     }
321
322     ext_idx = ((long)match) - 1;
323     info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
324     info.title.str = malloc((info.title.len + 1) * sizeof(char));
325     memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
326     info.title.str[info.title.len] = '\0';
327     lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
328
329     info.id = finfo->id;
330     r = lms_db_playlist_add(plugin->playlist_db, &info);
331
332     free(info.title.str);
333     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
334     close(fd);
335
336     return r;
337 }
338
339 static int
340 _setup(struct plugin *plugin, struct lms_context *ctxt)
341 {
342     plugin->playlist_db = lms_db_playlist_new(ctxt->db);
343     if (!plugin->playlist_db)
344         return -1;
345
346     return 0;
347 }
348
349 static int
350 _start(struct plugin *plugin, struct lms_context *ctxt)
351 {
352     return lms_db_playlist_start(plugin->playlist_db);
353 }
354
355 static int
356 _finish(struct plugin *plugin, struct lms_context *ctxt)
357 {
358     if (plugin->playlist_db)
359         return lms_db_playlist_free(plugin->playlist_db);
360
361     return 0;
362 }
363
364
365 static int
366 _close(struct plugin *plugin)
367 {
368     free(plugin);
369     return 0;
370 }
371
372 API struct lms_plugin *
373 lms_plugin_open(void)
374 {
375     struct plugin *plugin;
376
377     plugin = malloc(sizeof(*plugin));
378     plugin->plugin.name = _name;
379     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
380     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
381     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
382     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
383     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
384     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
385
386     return (struct lms_plugin *)plugin;
387 }
388
389 API const struct lms_plugin_info *
390 lms_plugin_info(void)
391 {
392     static struct lms_plugin_info info = {
393         _name,
394         _cats,
395         "Playlists (INI-style)",
396         PACKAGE_VERSION,
397         _authors,
398         "http://lms.garage.maemo.org"
399     };
400
401     return &info;
402 }