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