help_view: one in all patch
[cmus.git] / input.c
blob2d83c2f0487f501fc5b85d7b1c88debb3d33beb7
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU 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
17 * 02111-1307, USA.
20 #include "input.h"
21 #include "ip.h"
22 #include "pcm.h"
23 #include "http.h"
24 #include "xmalloc.h"
25 #include "file.h"
26 #include "utils.h"
27 #include "cmus.h"
28 #include "list.h"
29 #include "prog.h"
30 #include "misc.h"
31 #include "debug.h"
32 #include "config/libdir.h"
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <stdarg.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <dirent.h>
42 #include <dlfcn.h>
44 struct input_plugin {
45 const struct input_plugin_ops *ops;
46 struct input_plugin_data data;
47 unsigned int open : 1;
48 unsigned int eof : 1;
49 int http_code;
50 char *http_reason;
53 * pcm is converted to 16-bit signed little-endian stereo
54 * NOTE: no conversion is done if channels > 2 or bits > 16
56 void (*pcm_convert)(char *, const char *, int);
57 void (*pcm_convert_in_place)(char *, int);
59 * 4 if 8-bit mono
60 * 2 if 8-bit stereo or 16-bit mono
61 * 1 otherwise
63 int pcm_convert_scale;
66 struct ip {
67 struct list_head node;
68 char *name;
69 void *handle;
71 const char * const *extensions;
72 const char * const *mime_types;
73 const struct input_plugin_ops *ops;
76 static const char * const plugin_dir = LIBDIR "/cmus/ip";
77 static LIST_HEAD(ip_head);
79 /* timeouts (ms) */
80 static int http_connection_timeout = 5e3;
81 static int http_read_timeout = 5e3;
83 static const char *get_extension(const char *filename)
85 const char *ext;
87 ext = filename + strlen(filename) - 1;
88 while (ext >= filename && *ext != '/') {
89 if (*ext == '.') {
90 ext++;
91 return ext;
93 ext--;
95 return NULL;
98 static const struct input_plugin_ops *get_ops_by_filename(const char *filename)
100 struct ip *ip;
101 const char *ext;
103 ext = get_extension(filename);
104 if (ext == NULL)
105 return NULL;
106 list_for_each_entry(ip, &ip_head, node) {
107 const char * const *exts = ip->extensions;
108 int i;
110 for (i = 0; exts[i]; i++) {
111 if (strcasecmp(ext, exts[i]) == 0)
112 return ip->ops;
115 return NULL;
118 static const struct input_plugin_ops *get_ops_by_mime_type(const char *mime_type)
120 struct ip *ip;
122 list_for_each_entry(ip, &ip_head, node) {
123 const char * const *types = ip->mime_types;
124 int i;
126 for (i = 0; types[i]; i++) {
127 if (strcasecmp(mime_type, types[i]) == 0)
128 return ip->ops;
131 return NULL;
134 static int do_http_get(const char *uri, struct http_header **headersp, int *codep, char **reasonp)
136 struct http_uri u;
137 struct http_header *h;
138 int sock, i, rc, code;
139 char *reason;
141 *headersp = NULL;
142 *codep = -1;
143 *reasonp = NULL;
145 if (http_parse_uri(uri, &u))
146 return -IP_ERROR_INVALID_URI;
148 /* d_print("%s -> '%s':'%s'@'%s':%d'%s'\n", uri, user, pass, host, port, path); */
150 sock = http_open(u.host, u.port, http_connection_timeout);
151 if (sock == -1) {
152 http_free_uri(&u);
153 return -IP_ERROR_ERRNO;
156 h = xnew(struct http_header, 5);
157 i = 0;
158 h[i].key = xstrdup("Host");
159 h[i].val = xstrdup(u.host);
160 i++;
161 h[i].key = xstrdup("User-Agent");
162 h[i].val = xstrdup("cmus/" VERSION);
163 i++;
164 h[i].key = xstrdup("Icy-MetaData");
165 h[i].val = xstrdup("1");
166 i++;
167 if (u.user && u.pass) {
168 char buf[256];
169 char *encoded;
171 snprintf(buf, sizeof(buf), "%s:%s", u.user, u.pass);
172 encoded = base64_encode(buf);
173 if (encoded == NULL) {
174 d_print("couldn't base64 encode '%s'\n", buf);
175 } else {
176 snprintf(buf, sizeof(buf), "Basic %s", encoded);
177 free(encoded);
178 h[i].key = xstrdup("Authorization");
179 h[i].val = xstrdup(buf);
180 i++;
183 h[i].key = NULL;
184 h[i].val = NULL;
185 i++;
187 rc = http_get(sock, u.path, h, &code, &reason, headersp, http_read_timeout);
188 http_headers_free(h);
189 http_free_uri(&u);
190 switch (rc) {
191 case -1:
192 d_print("error: %s\n", strerror(errno));
193 close(sock);
194 return -IP_ERROR_ERRNO;
195 case -2:
196 d_print("error parsing HTTP response\n");
197 close(sock);
198 return -IP_ERROR_HTTP_RESPONSE;
200 d_print("HTTP response: %d %s\n", code, reason);
201 if (code != 200) {
202 *codep = code;
203 *reasonp = reason;
204 close(sock);
205 return -IP_ERROR_HTTP_STATUS;
207 free(reason);
208 return sock;
211 static int setup_remote(struct input_plugin *ip, const struct http_header *headers, int sock)
213 const char *val;
215 val = http_headers_get_value(headers, "Content-Type");
216 if (val) {
217 ip->ops = get_ops_by_mime_type(val);
218 if (ip->ops == NULL) {
219 d_print("unsupported content type: %s\n", val);
220 close(sock);
221 return -IP_ERROR_FILE_FORMAT;
223 } else {
224 const char *type = "audio/mpeg";
226 d_print("assuming %s content type\n", type);
227 ip->ops = get_ops_by_mime_type(type);
228 if (ip->ops == NULL) {
229 d_print("unsupported content type: %s\n", type);
230 close(sock);
231 return -IP_ERROR_FILE_FORMAT;
235 ip->data.fd = sock;
236 ip->data.metadata = (char *)xmalloc(16 * 255 + 1);
238 val = http_headers_get_value(headers, "icy-metaint");
239 if (val) {
240 long int lint;
242 if (str_to_int(val, &lint) == 0 && lint >= 0) {
243 ip->data.metaint = lint;
244 d_print("metaint: %d\n", ip->data.metaint);
247 return 0;
250 static int handle_line(void *data, const char *line)
252 char **firstp = data;
254 *firstp = xstrdup(line);
255 /* ignore other lines */
256 return 1;
259 static int read_playlist(struct input_plugin *ip, int sock)
261 struct http_header *headers;
262 char *body, *reason, *first;
263 int rc, code;
265 rc = http_read_body(sock, &body, http_read_timeout);
266 close(sock);
267 if (rc)
268 return -IP_ERROR_ERRNO;
270 /* get only first URL from the playlist */
271 first = NULL;
272 cmus_playlist_for_each(body, strlen(body), 0, handle_line, &first);
273 free(body);
275 if (first == NULL) {
276 d_print("empty playlist\n");
277 return -IP_ERROR_HTTP_RESPONSE;
280 sock = do_http_get(first, &headers, &code, &reason);
281 free(first);
282 if (sock < 0) {
283 ip->http_code = code;
284 ip->http_reason = reason;
285 /* URI in the _playlist_ is invalid, not our fault */
286 if (sock == -IP_ERROR_INVALID_URI)
287 sock = -IP_ERROR_HTTP_RESPONSE;
288 return sock;
291 rc = setup_remote(ip, headers, sock);
292 http_headers_free(headers);
293 return rc;
296 static int open_remote(struct input_plugin *ip)
298 struct input_plugin_data *d = &ip->data;
299 char *reason;
300 int sock, rc, code;
301 struct http_header *headers;
302 const char *val;
304 sock = do_http_get(d->filename, &headers, &code, &reason);
305 if (sock < 0) {
306 ip->http_code = code;
307 ip->http_reason = reason;
308 return sock;
311 val = http_headers_get_value(headers, "Content-Type");
312 if (val) {
313 d_print("Content-Type: %s\n", val);
314 if (!strcasecmp(val, "audio/x-scpls") || !strcasecmp(val, "audio/m3u")) {
315 http_headers_free(headers);
316 return read_playlist(ip, sock);
320 rc = setup_remote(ip, headers, sock);
321 http_headers_free(headers);
322 return rc;
325 static int open_file(struct input_plugin *ip)
327 ip->ops = get_ops_by_filename(ip->data.filename);
328 if (ip->ops == NULL)
329 return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
330 ip->data.fd = open(ip->data.filename, O_RDONLY);
331 if (ip->data.fd == -1) {
332 ip->ops = NULL;
333 return -IP_ERROR_ERRNO;
335 return 0;
338 void ip_load_plugins(void)
340 DIR *dir;
341 struct dirent *d;
343 dir = opendir(plugin_dir);
344 if (dir == NULL) {
345 warn_errno("couldn't open directory `%s'", plugin_dir);
346 return;
348 while ((d = readdir(dir)) != NULL) {
349 char filename[256];
350 struct ip *ip;
351 void *so;
352 char *ext;
353 const char *sym;
355 if (d->d_name[0] == '.')
356 continue;
357 ext = strrchr(d->d_name, '.');
358 if (ext == NULL)
359 continue;
360 if (strcmp(ext, ".so"))
361 continue;
363 snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
365 so = dlopen(filename, RTLD_NOW);
366 if (so == NULL) {
367 warn("%s\n", dlerror());
368 continue;
371 ip = xnew(struct ip, 1);
373 sym = "ip_extensions";
374 if (!(ip->extensions = dlsym(so, sym)))
375 goto sym_err;
377 sym = "ip_mime_types";
378 if (!(ip->mime_types = dlsym(so, sym)))
379 goto sym_err;
381 sym = "ip_ops";
382 if (!(ip->ops = dlsym(so, sym)))
383 goto sym_err;
385 ip->name = xstrndup(d->d_name, ext - d->d_name);
386 ip->handle = so;
388 list_add_tail(&ip->node, &ip_head);
389 continue;
390 sym_err:
391 warn("%s: symbol %s not found\n", filename, sym);
392 free(ip);
393 dlclose(so);
395 closedir(dir);
398 static void ip_init(struct input_plugin *ip, char *filename)
400 memset(ip, 0, sizeof(*ip));
401 ip->http_code = -1;
402 ip->pcm_convert_scale = -1;
403 ip->data.fd = -1;
404 ip->data.filename = filename;
405 ip->data.remote = is_url(filename);
408 struct input_plugin *ip_new(const char *filename)
410 struct input_plugin *ip = xnew(struct input_plugin, 1);
412 ip_init(ip, xstrdup(filename));
413 return ip;
416 void ip_delete(struct input_plugin *ip)
418 if (ip->open)
419 ip_close(ip);
420 free(ip->data.filename);
421 free(ip);
424 int ip_open(struct input_plugin *ip)
426 int rc;
428 BUG_ON(ip->open);
430 /* set fd and ops */
431 if (ip->data.remote) {
432 rc = open_remote(ip);
433 } else {
434 rc = open_file(ip);
437 if (rc) {
438 d_print("opening `%s' failed: %d %s\n", ip->data.filename, rc,
439 rc == -1 ? strerror(errno) : "");
440 return rc;
443 rc = ip->ops->open(&ip->data);
444 if (rc) {
445 d_print("opening file `%s' failed: %d %s\n", ip->data.filename, rc,
446 rc == -1 ? strerror(errno) : "");
447 if (ip->data.fd != -1)
448 close(ip->data.fd);
449 ip->data.fd = -1;
450 ip->ops = NULL;
451 free(ip->data.metadata);
452 ip->data.metadata = NULL;
453 return rc;
455 ip->open = 1;
456 return 0;
459 void ip_setup(struct input_plugin *ip)
461 unsigned int bits, is_signed, channels;
462 sample_format_t sf = ip->data.sf;
464 bits = sf_get_bits(sf);
465 is_signed = sf_get_signed(sf);
466 channels = sf_get_channels(sf);
468 ip->pcm_convert_scale = 1;
469 ip->pcm_convert = NULL;
470 ip->pcm_convert_in_place = NULL;
472 if (bits <= 16 && channels <= 2) {
473 unsigned int mask = ((bits >> 2) & 4) | (is_signed << 1);
475 ip->pcm_convert = pcm_conv[mask | (channels - 1)];
476 ip->pcm_convert_in_place = pcm_conv_in_place[mask | sf_get_bigendian(sf)];
478 ip->pcm_convert_scale = (3 - channels) * (3 - bits / 8);
481 d_print("pcm convert: scale=%d convert=%d convert_in_place=%d\n",
482 ip->pcm_convert_scale,
483 ip->pcm_convert != NULL,
484 ip->pcm_convert_in_place != NULL);
487 int ip_close(struct input_plugin *ip)
489 int rc;
491 rc = ip->ops->close(&ip->data);
492 BUG_ON(ip->data.private);
493 if (ip->data.fd != -1)
494 close(ip->data.fd);
495 free(ip->data.metadata);
496 free(ip->http_reason);
498 ip_init(ip, ip->data.filename);
499 return rc;
502 int ip_read(struct input_plugin *ip, char *buffer, int count)
504 struct timeval tv;
505 fd_set readfds;
506 /* 4608 seems to be optimal for mp3s, 4096 for oggs */
507 char tmp[8 * 1024];
508 char *buf;
509 int rc;
511 BUG_ON(count <= 0);
513 FD_ZERO(&readfds);
514 FD_SET(ip->data.fd, &readfds);
515 /* zero timeout -> return immediately */
516 tv.tv_sec = 0;
517 tv.tv_usec = 50e3;
518 rc = select(ip->data.fd + 1, &readfds, NULL, NULL, &tv);
519 if (rc == -1) {
520 d_print("select: error: %s\n", strerror(errno));
521 if (errno == EINTR)
522 errno = EAGAIN;
523 return -1;
525 if (rc == 0) {
526 errno = EAGAIN;
527 return -1;
530 buf = buffer;
531 if (ip->pcm_convert_scale > 1) {
532 /* use tmp buffer for 16-bit mono and 8-bit */
533 buf = tmp;
534 count /= ip->pcm_convert_scale;
535 if (count > sizeof(tmp))
536 count = sizeof(tmp);
539 rc = ip->ops->read(&ip->data, buf, count);
540 if (rc == 0)
541 ip->eof = 1;
542 if (rc == -1)
543 d_print("error: %s\n", strerror(errno));
545 if (rc > 0) {
546 int sample_size = sf_get_sample_size(ip->data.sf);
548 if (ip->pcm_convert_in_place != NULL)
549 ip->pcm_convert_in_place(buf, rc / sample_size);
550 if (ip->pcm_convert != NULL)
551 ip->pcm_convert(buffer, tmp, rc / sample_size);
552 rc *= ip->pcm_convert_scale;
554 return rc;
557 int ip_seek(struct input_plugin *ip, double offset)
559 int rc;
561 if (ip->data.remote)
562 return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
563 rc = ip->ops->seek(&ip->data, offset);
564 if (rc == 0)
565 ip->eof = 0;
566 return rc;
569 int ip_read_comments(struct input_plugin *ip, struct keyval **comments)
571 return ip->ops->read_comments(&ip->data, comments);
574 int ip_duration(struct input_plugin *ip)
576 return ip->ops->duration(&ip->data);
579 sample_format_t ip_get_sf(struct input_plugin *ip)
581 BUG_ON(!ip->open);
582 return ip->data.sf;
585 const char *ip_get_filename(struct input_plugin *ip)
587 return ip->data.filename;
590 const char *ip_get_metadata(struct input_plugin *ip)
592 BUG_ON(!ip->open);
593 return ip->data.metadata;
596 int ip_is_remote(struct input_plugin *ip)
598 return ip->data.remote;
601 int ip_metadata_changed(struct input_plugin *ip)
603 int ret = ip->data.metadata_changed;
605 BUG_ON(!ip->open);
606 ip->data.metadata_changed = 0;
607 return ret;
610 int ip_eof(struct input_plugin *ip)
612 BUG_ON(!ip->open);
613 return ip->eof;
616 char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg)
618 char buffer[1024];
620 switch (-rc) {
621 case IP_ERROR_ERRNO:
622 snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
623 break;
624 case IP_ERROR_UNRECOGNIZED_FILE_TYPE:
625 snprintf(buffer, sizeof(buffer),
626 "%s: unrecognized filename extension", arg);
627 break;
628 case IP_ERROR_FUNCTION_NOT_SUPPORTED:
629 snprintf(buffer, sizeof(buffer),
630 "%s: function not supported", arg);
631 break;
632 case IP_ERROR_FILE_FORMAT:
633 snprintf(buffer, sizeof(buffer),
634 "%s: file format not supported or corrupted file",
635 arg);
636 break;
637 case IP_ERROR_INVALID_URI:
638 snprintf(buffer, sizeof(buffer), "%s: invalid URI", arg);
639 break;
640 case IP_ERROR_SAMPLE_FORMAT:
641 snprintf(buffer, sizeof(buffer),
642 "%s: input plugin doesn't support the sample format",
643 arg);
644 break;
645 case IP_ERROR_HTTP_RESPONSE:
646 snprintf(buffer, sizeof(buffer), "%s: invalid HTTP response", arg);
647 break;
648 case IP_ERROR_HTTP_STATUS:
649 snprintf(buffer, sizeof(buffer), "%s: %d %s", arg, ip->http_code, ip->http_reason);
650 free(ip->http_reason);
651 ip->http_reason = NULL;
652 ip->http_code = -1;
653 break;
654 case IP_ERROR_INTERNAL:
655 snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
656 break;
657 case IP_ERROR_SUCCESS:
658 default:
659 snprintf(buffer, sizeof(buffer),
660 "%s: this is not an error (%d), this is a bug",
661 arg, rc);
662 break;
664 return xstrdup(buffer);
667 char **ip_get_supported_extensions(void)
669 struct ip *ip;
670 char **exts;
671 int i, size;
672 int count = 0;
674 size = 8;
675 exts = xnew(char *, size);
676 list_for_each_entry(ip, &ip_head, node) {
677 const char * const *e = ip->extensions;
679 for (i = 0; e[i]; i++) {
680 if (count == size - 1) {
681 size *= 2;
682 exts = xrenew(char *, exts, size);
684 exts[count++] = xstrdup(e[i]);
687 exts[count] = NULL;
688 qsort(exts, count, sizeof(char *), strptrcmp);
689 return exts;
692 void ip_dump_plugins(void)
694 struct ip *ip;
695 int i;
697 printf("Input Plugins: %s\n", plugin_dir);
698 list_for_each_entry(ip, &ip_head, node) {
699 printf(" %s:\n File Types:", ip->name);
700 for (i = 0; ip->extensions[i]; i++)
701 printf(" %s", ip->extensions[i]);
702 printf("\n MIME Types:");
703 for (i = 0; ip->mime_types[i]; i++)
704 printf(" %s", ip->mime_types[i]);
705 printf("\n");