2 * Copyright (C) 2008-2011 by ProFUSION embedded systems
3 * Copyright (C) 2007 by INdT
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.
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.
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
20 * @author Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
26 * pls playlist parser.
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.
40 #define _XOPEN_SOURCE 600
41 #include <lightmediascanner_plugin.h>
42 #include <lightmediascanner_db.h>
43 #include <sys/types.h>
52 #define PLS_MAX_N_ENTRIES_BYTES_LOOKUP 64
55 _pls_find_header(int fd)
57 const char header[] = "[playlist]";
58 char buf[sizeof(header) - 1];
61 /* skip out white spaces */
68 fprintf(stderr, "ERROR: premature end of file.\n");
76 if (buf[0] != header[0])
79 /* try to read rest (from the second on) of the header */
80 r = read(fd, buf + 1, sizeof(buf) - 1);
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);
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);
113 _pls_find_n_entries_start(int fd, struct lms_playlist_info *info)
115 char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
116 const char n_entries[] = "NumberOfEntries=";
121 off = lseek(fd, 0, SEEK_CUR);
127 r = read(fd, buf, sizeof(buf));
134 for (i = 0; i < r; i++) {
138 if (c == n_entries[0]) {
141 if (memcmp(buf + i, n_entries + 1, sizeof(n_entries) - 2) != 0) {
142 off += i + sizeof(n_entries) - 2;
146 i += sizeof(n_entries) - 2;
153 fprintf(stderr, "WARNING: missing end of line\n");
157 info->n_entries = atoi(p);
159 } else if (c == 'V') {
160 /* skip possible 'Version=XX' */
161 for (i++; i < r; i++)
164 } else if (isspace(c))
173 /* not at the file beginning, reset offset */
174 if (lseek(fd, off, SEEK_SET) < 0) {
183 _pls_parse_entries_line(int fd, struct lms_playlist_info *info, char *buf, int len)
185 const char n_entries[] = "NumberOfEntries=";
188 for (i = 0; i < len; i++, buf++)
196 if (memcmp(buf, n_entries, sizeof(n_entries) - 1) != 0)
199 buf += sizeof(n_entries) - 1;
200 len -= sizeof(n_entries) - 1;
203 info->n_entries = atoi(buf);
208 _pls_find_n_entries_end(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
210 char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
214 if (finfo->size > sizeof(buf))
215 if (lseek(fd, finfo->size - sizeof(buf), SEEK_SET) < 0) {
220 r = read(fd, buf, sizeof(buf));
228 for (i = r - 1; i >= 0; i--) {
229 if (buf[i] == '\n') {
233 len = last_nl - i - 1;
237 ret = _pls_parse_entries_line(fd, info, buf + i + 1, len);
250 _pls_parse(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
254 r = _pls_find_header(fd);
256 fprintf(stderr, "ERROR: could not find pls header. code=%d\n", r);
260 r = _pls_find_n_entries_start(fd, info);
264 r = _pls_find_n_entries_end(fd, finfo, info);
266 fprintf(stderr, "ERROR: could not find pls NumberOfEntries=\n");
271 static const char _name[] = "pls";
272 static const struct lms_string_size _exts[] = {
273 LMS_STATIC_STRING_SIZE(".pls")
275 static const char *_cats[] = {
281 static const char *_authors[] = {
282 "Gustavo Sverzut Barbieri",
287 struct lms_plugin plugin;
288 lms_db_playlist_t *playlist_db;
292 _match(struct plugin *p, const char *path, int len, int base)
296 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
300 return (void*)(i + 1);
304 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
306 struct lms_playlist_info info = { };
310 fd = open(finfo->path, O_RDONLY);
316 if (_pls_parse(fd, finfo, &info) != 0) {
318 "WARNING: could not parse playlist '%s'.\n", finfo->path);
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);
330 r = lms_db_playlist_add(plugin->playlist_db, &info);
332 free(info.title.str);
333 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
340 _setup(struct plugin *plugin, struct lms_context *ctxt)
342 plugin->playlist_db = lms_db_playlist_new(ctxt->db);
343 if (!plugin->playlist_db)
350 _start(struct plugin *plugin, struct lms_context *ctxt)
352 return lms_db_playlist_start(plugin->playlist_db);
356 _finish(struct plugin *plugin, struct lms_context *ctxt)
358 if (plugin->playlist_db)
359 return lms_db_playlist_free(plugin->playlist_db);
366 _close(struct plugin *plugin)
372 API struct lms_plugin *
373 lms_plugin_open(void)
375 struct plugin *plugin;
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;
386 return (struct lms_plugin *)plugin;
389 API const struct lms_plugin_info *
390 lms_plugin_info(void)
392 static struct lms_plugin_info info = {
395 "Playlists (INI-style)",
398 "http://lms.garage.maemo.org"