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.
36 #include <lightmediascanner_plugin.h>
37 #include <lightmediascanner_db.h>
38 #include <sys/types.h>
47 #define PLS_MAX_N_ENTRIES_BYTES_LOOKUP 64
50 _pls_find_header(int fd)
52 const char header[] = "[playlist]";
53 char buf[sizeof(header) - 1];
56 /* skip out white spaces */
63 fprintf(stderr, "ERROR: premature end of file.\n");
71 if (buf[0] != header[0])
74 /* try to read rest (from the second on) of the header */
75 r = read(fd, buf + 1, sizeof(buf) - 1);
79 } else if (r != sizeof(buf) - 1) {
80 fprintf(stderr, "ERROR: premature end of file: read %zd of %zd"
81 "bytes.\n", r, sizeof(buf) - 1);
85 if (memcmp(buf + 1, header + 1, sizeof(buf) - 1) != 0) {
86 fprintf(stderr, "ERROR: invalid pls header '%.*s'\n",
87 (int)sizeof(buf) - 1, buf);
108 _pls_find_n_entries_start(int fd, struct lms_playlist_info *info)
110 char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
111 const char n_entries[] = "NumberOfEntries=";
116 off = lseek(fd, 0, SEEK_CUR);
122 r = read(fd, buf, sizeof(buf));
129 for (i = 0; i < r; i++) {
133 if (c == n_entries[0]) {
136 if (memcmp(buf + i, n_entries + 1, sizeof(n_entries) - 2) != 0) {
137 off += i + sizeof(n_entries) - 2;
141 i += sizeof(n_entries) - 2;
148 fprintf(stderr, "WARNING: missing end of line\n");
152 info->n_entries = atoi(p);
154 } else if (c == 'V') {
155 /* skip possible 'Version=XX' */
156 for (i++; i < r; i++)
159 } else if (isspace(c))
168 /* not at the file beginning, reset offset */
169 if (lseek(fd, off, SEEK_SET) < 0) {
178 _pls_parse_entries_line(int fd, struct lms_playlist_info *info, char *buf, int len)
180 const char n_entries[] = "NumberOfEntries=";
183 for (i = 0; i < len; i++, buf++)
191 if (memcmp(buf, n_entries, sizeof(n_entries) - 1) != 0)
194 buf += sizeof(n_entries) - 1;
195 len -= sizeof(n_entries) - 1;
198 info->n_entries = atoi(buf);
203 _pls_find_n_entries_end(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
205 char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
209 if (finfo->size > sizeof(buf))
210 if (lseek(fd, finfo->size - sizeof(buf), SEEK_SET) < 0) {
215 r = read(fd, buf, sizeof(buf));
223 for (i = r - 1; i >= 0; i--) {
224 if (buf[i] == '\n') {
228 len = last_nl - i - 1;
232 ret = _pls_parse_entries_line(fd, info, buf + i + 1, len);
245 _pls_parse(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
249 r = _pls_find_header(fd);
251 fprintf(stderr, "ERROR: could not find pls header. code=%d\n", r);
255 r = _pls_find_n_entries_start(fd, info);
259 r = _pls_find_n_entries_end(fd, finfo, info);
261 fprintf(stderr, "ERROR: could not find pls NumberOfEntries=\n");
266 static const char _name[] = "pls";
267 static const struct lms_string_size _exts[] = {
268 LMS_STATIC_STRING_SIZE(".pls")
270 static const char *_cats[] = {
276 static const char *_authors[] = {
277 "Gustavo Sverzut Barbieri",
282 struct lms_plugin plugin;
283 lms_db_playlist_t *playlist_db;
287 _match(struct plugin *p, const char *path, int len, int base)
291 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
295 return (void*)(i + 1);
299 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
301 struct lms_playlist_info info = { };
305 fd = open(finfo->path, O_RDONLY);
311 if (_pls_parse(fd, finfo, &info) != 0) {
313 "WARNING: could not parse playlist '%s'.\n", finfo->path);
317 ext_idx = ((long)match) - 1;
318 lms_string_size_strndup(&info.title, finfo->path + finfo->base,
319 finfo->path_len - finfo->base - _exts[ext_idx].len);
320 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
323 r = lms_db_playlist_add(plugin->playlist_db, &info);
325 free(info.title.str);
326 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
333 _setup(struct plugin *plugin, struct lms_context *ctxt)
335 plugin->playlist_db = lms_db_playlist_new(ctxt->db);
336 if (!plugin->playlist_db)
343 _start(struct plugin *plugin, struct lms_context *ctxt)
345 return lms_db_playlist_start(plugin->playlist_db);
349 _finish(struct plugin *plugin, struct lms_context *ctxt)
351 if (plugin->playlist_db)
352 return lms_db_playlist_free(plugin->playlist_db);
359 _close(struct plugin *plugin)
365 API struct lms_plugin *
366 lms_plugin_open(void)
368 struct plugin *plugin;
370 plugin = malloc(sizeof(*plugin));
371 plugin->plugin.name = _name;
372 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
373 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
374 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
375 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
376 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
377 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
379 return (struct lms_plugin *)plugin;
382 API const struct lms_plugin_info *
383 lms_plugin_info(void)
385 static struct lms_plugin_info info = {
388 "Playlists (INI-style)",
391 "http://lms.garage.maemo.org"