introduce and use helpers lms_string_size_strndup() and lms_string_size_dup()
[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 #include <lightmediascanner_plugin.h>
37 #include <lightmediascanner_db.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <ctype.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <string.h>
46
47 #define PLS_MAX_N_ENTRIES_BYTES_LOOKUP 64
48
49 static int
50 _pls_find_header(int fd)
51 {
52     const char header[] = "[playlist]";
53     char buf[sizeof(header) - 1];
54     ssize_t r;
55
56     /* skip out white spaces */
57     do {
58         r = read(fd, buf, 1);
59         if (r < 0) {
60             perror("read");
61             return -1;
62         } else if (r == 0) {
63             fprintf(stderr, "ERROR: premature end of file.\n");
64             return -2;
65         }
66
67         if (!isspace(buf[0]))
68             break;
69     } while (1);
70
71     if (buf[0] != header[0])
72         return -3;
73
74     /* try to read rest (from the second on) of the header */
75     r = read(fd, buf + 1, sizeof(buf) - 1);
76     if (r < 0) {
77         perror("read");
78         return -4;
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);
82         return -5;
83     }
84
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);
88         return -6;
89     }
90
91     /* find '\n' */
92     do {
93         r = read(fd, buf, 1);
94         if (r < 0) {
95             perror("read");
96             return -7;
97         } else if (r == 0)
98             return -8;
99
100         if (buf[0] == '\n')
101             return 0;
102     } while (1);
103
104     return -1;
105 }
106
107 static int
108 _pls_find_n_entries_start(int fd, struct lms_playlist_info *info)
109 {
110     char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
111     const char n_entries[] = "NumberOfEntries=";
112     ssize_t r;
113     int i;
114     off_t off;
115
116     off = lseek(fd, 0, SEEK_CUR);
117     if (off < 0) {
118         perror("lseek");
119         return -1;
120     }
121
122     r = read(fd, buf, sizeof(buf));
123     if (r < 0) {
124         perror("read");
125         return -2;
126     } else if (r == 0)
127         return -3;
128
129     for (i = 0; i < r; i++) {
130         char c;
131
132         c = buf[i];
133         if (c == n_entries[0]) {
134             const char *p;
135             i++;
136             if (memcmp(buf + i, n_entries + 1, sizeof(n_entries) - 2) != 0) {
137                 off += i + sizeof(n_entries) - 2;
138                 goto done;
139             }
140
141             i += sizeof(n_entries) - 2;
142             p = buf + i;
143             for (; i < r; i++)
144                 if (buf[i] == '\n')
145                     break;
146
147             if (i == r) {
148                 fprintf(stderr, "WARNING: missing end of line\n");
149                 i = r - 1;
150             }
151             buf[i] = '\0';
152             info->n_entries = atoi(p);
153             return 0;
154         } else if (c == 'V') {
155             /* skip possible 'Version=XX' */
156             for (i++; i < r; i++)
157                 if (buf[i] == '\n')
158                     break;
159         } else if (isspace(c))
160             continue;
161         else {
162             off += i;
163             goto done;
164         }
165     }
166
167   done:
168     /* not at the file beginning, reset offset */
169     if (lseek(fd, off, SEEK_SET) < 0) {
170         perror("lseek");
171         return -1;
172     }
173
174     return 1;
175 }
176
177 static int
178 _pls_parse_entries_line(int fd, struct lms_playlist_info *info, char *buf, int len)
179 {
180     const char n_entries[] = "NumberOfEntries=";
181     int i;
182
183     for (i = 0; i < len; i++, buf++)
184         if (!isspace(*buf))
185             break;
186
187     if (i == len)
188         return 1;
189     len -= i;
190
191     if (memcmp(buf, n_entries, sizeof(n_entries) - 1) != 0)
192         return 1;
193
194     buf += sizeof(n_entries) - 1;
195     len -= sizeof(n_entries) - 1;
196     buf[len] = '\0';
197
198     info->n_entries = atoi(buf);
199     return 0;
200 }
201
202 static int
203 _pls_find_n_entries_end(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
204 {
205     char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
206     ssize_t r;
207     int i, last_nl;
208
209     if (finfo->size > sizeof(buf))
210         if (lseek(fd, finfo->size - sizeof(buf), SEEK_SET) < 0) {
211             perror("lseek");
212             return -1;
213         }
214
215     r = read(fd, buf, sizeof(buf));
216     if (r < 0) {
217         perror("read");
218         return -1;
219     } else if (r == 0)
220         return -2;
221
222     last_nl = -1;
223     for (i = r - 1; i >= 0; i--) {
224         if (buf[i] == '\n') {
225             if (last_nl >= 0) {
226                 int len;
227
228                 len = last_nl - i - 1;
229                 if (len > 0) {
230                     int ret;
231
232                     ret = _pls_parse_entries_line(fd, info, buf + i + 1, len);
233                     if (ret <= 0)
234                         return ret;
235                 }
236             }
237             last_nl = i;
238         }
239     }
240
241     return 1;
242 }
243
244 static int
245 _pls_parse(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
246 {
247     int r;
248
249     r = _pls_find_header(fd);
250     if (r != 0) {
251         fprintf(stderr, "ERROR: could not find pls header. code=%d\n", r);
252         return -1;
253     }
254
255     r = _pls_find_n_entries_start(fd, info);
256     if (r <= 0)
257         return r;
258
259     r = _pls_find_n_entries_end(fd, finfo, info);
260     if (r != 0)
261         fprintf(stderr, "ERROR: could not find pls NumberOfEntries=\n");
262
263     return r;
264 }
265
266 static const char _name[] = "pls";
267 static const struct lms_string_size _exts[] = {
268     LMS_STATIC_STRING_SIZE(".pls")
269 };
270 static const char *_cats[] = {
271     "multimedia",
272     "audio",
273     "playlist",
274     NULL
275 };
276 static const char *_authors[] = {
277     "Gustavo Sverzut Barbieri",
278     NULL
279 };
280
281 struct plugin {
282     struct lms_plugin plugin;
283     lms_db_playlist_t *playlist_db;
284 };
285
286 static void *
287 _match(struct plugin *p, const char *path, int len, int base)
288 {
289     long i;
290
291     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
292     if (i < 0)
293       return NULL;
294     else
295       return (void*)(i + 1);
296 }
297
298 static int
299 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
300 {
301     struct lms_playlist_info info = { };
302     int fd, r;
303     long ext_idx;
304
305     fd = open(finfo->path, O_RDONLY);
306     if (fd < 0) {
307         perror("open");
308         return -1;
309     }
310
311     if (_pls_parse(fd, finfo, &info) != 0) {
312         fprintf(stderr,
313                 "WARNING: could not parse playlist '%s'.\n", finfo->path);
314         return -1;
315     }
316
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);
321
322     info.id = finfo->id;
323     r = lms_db_playlist_add(plugin->playlist_db, &info);
324
325     free(info.title.str);
326     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
327     close(fd);
328
329     return r;
330 }
331
332 static int
333 _setup(struct plugin *plugin, struct lms_context *ctxt)
334 {
335     plugin->playlist_db = lms_db_playlist_new(ctxt->db);
336     if (!plugin->playlist_db)
337         return -1;
338
339     return 0;
340 }
341
342 static int
343 _start(struct plugin *plugin, struct lms_context *ctxt)
344 {
345     return lms_db_playlist_start(plugin->playlist_db);
346 }
347
348 static int
349 _finish(struct plugin *plugin, struct lms_context *ctxt)
350 {
351     if (plugin->playlist_db)
352         return lms_db_playlist_free(plugin->playlist_db);
353
354     return 0;
355 }
356
357
358 static int
359 _close(struct plugin *plugin)
360 {
361     free(plugin);
362     return 0;
363 }
364
365 API struct lms_plugin *
366 lms_plugin_open(void)
367 {
368     struct plugin *plugin;
369
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;
378
379     return (struct lms_plugin *)plugin;
380 }
381
382 API const struct lms_plugin_info *
383 lms_plugin_info(void)
384 {
385     static struct lms_plugin_info info = {
386         _name,
387         _cats,
388         "Playlists (INI-style)",
389         PACKAGE_VERSION,
390         _authors,
391         "http://lms.garage.maemo.org"
392     };
393
394     return &info;
395 }