08533abc051e64b1c3ed8b220e86ac8389c69b70
[lms] / lightmediascanner / 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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
274 struct plugin {
275     struct lms_plugin plugin;
276     lms_db_playlist_t *playlist_db;
277 };
278
279 static void *
280 _match(struct plugin *p, const char *path, int len, int base)
281 {
282     int i;
283
284     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
285     if (i < 0)
286       return NULL;
287     else
288       return (void*)(i + 1);
289 }
290
291 static int
292 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
293 {
294     struct lms_playlist_info info = {0};
295     int fd, r, ext_idx;
296
297     fd = open(finfo->path, O_RDONLY);
298     if (fd < 0) {
299         perror("open");
300         return -1;
301     }
302
303     if (_pls_parse(fd, finfo, &info) != 0) {
304         fprintf(stderr,
305                 "WARNING: could not parse playlist '%s'.\n", finfo->path);
306         return -1;
307     }
308
309     ext_idx = ((int)match) - 1;
310     info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
311     info.title.str = malloc((info.title.len + 1) * sizeof(char));
312     memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
313     info.title.str[info.title.len] = '\0';
314     lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
315
316     info.id = finfo->id;
317     r = lms_db_playlist_add(plugin->playlist_db, &info);
318
319     if (info.title.str)
320         free(info.title.str);
321     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
322     close(fd);
323
324     return r;
325 }
326
327 static int
328 _setup(struct plugin *plugin, struct lms_context *ctxt)
329 {
330     plugin->playlist_db = lms_db_playlist_new(ctxt->db);
331     if (!plugin->playlist_db)
332         return -1;
333
334     return 0;
335 }
336
337 static int
338 _start(struct plugin *plugin, struct lms_context *ctxt)
339 {
340     return lms_db_playlist_start(plugin->playlist_db);
341 }
342
343 static int
344 _finish(struct plugin *plugin, struct lms_context *ctxt)
345 {
346     if (plugin->playlist_db)
347         return lms_db_playlist_free(plugin->playlist_db);
348
349     return 0;
350 }
351
352
353 static int
354 _close(struct plugin *plugin)
355 {
356     free(plugin);
357     return 0;
358 }
359
360 API struct lms_plugin *
361 lms_plugin_open(void)
362 {
363     struct plugin *plugin;
364
365     plugin = malloc(sizeof(*plugin));
366     plugin->plugin.name = _name;
367     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
368     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
369     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
370     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
371     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
372     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
373
374     return (struct lms_plugin *)plugin;
375 }