Add xterm-white.theme
[cmus.git] / input.c
blob649276eb0ee12af435479279ce0cb5ab8a1ce92e
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"
33 #include "config/version.h"
35 #include <unistd.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <stdarg.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42 #include <dirent.h>
43 #include <dlfcn.h>
45 struct input_plugin {
46 const struct input_plugin_ops *ops;
47 struct input_plugin_data data;
48 unsigned int open : 1;
49 unsigned int eof : 1;
50 int http_code;
51 char *http_reason;
54 * pcm is converted to 16-bit signed little-endian stereo
55 * NOTE: no conversion is done if channels > 2 or bits > 16
57 void (*pcm_convert)(char *, const char *, int);
58 void (*pcm_convert_in_place)(char *, int);
60 * 4 if 8-bit mono
61 * 2 if 8-bit stereo or 16-bit mono
62 * 1 otherwise
64 int pcm_convert_scale;
67 struct ip {
68 struct list_head node;
69 char *name;
70 void *handle;
72 const char * const *extensions;
73 const char * const *mime_types;
74 const struct input_plugin_ops *ops;
77 static const char * const plugin_dir = LIBDIR "/cmus/ip";
78 static LIST_HEAD(ip_head);
80 /* timeouts (ms) */
81 static int http_connection_timeout = 5e3;
82 static int http_read_timeout = 5e3;
84 static const char *get_extension(const char *filename)
86 const char *ext;
88 ext = filename + strlen(filename) - 1;
89 while (ext >= filename && *ext != '/') {
90 if (*ext == '.') {
91 ext++;
92 return ext;
94 ext--;
96 return NULL;
99 static const struct input_plugin_ops *get_ops_by_filename(const char *filename)
101 struct ip *ip;
102 const char *ext;
104 ext = get_extension(filename);
105 if (ext == NULL)
106 return NULL;
107 list_for_each_entry(ip, &ip_head, node) {
108 const char * const *exts = ip->extensions;
109 int i;
111 for (i = 0; exts[i]; i++) {
112 if (strcasecmp(ext, exts[i]) == 0)
113 return ip->ops;
116 return NULL;
119 static const struct input_plugin_ops *get_ops_by_mime_type(const char *mime_type)
121 struct ip *ip;
123 list_for_each_entry(ip, &ip_head, node) {
124 const char * const *types = ip->mime_types;
125 int i;
127 for (i = 0; types[i]; i++) {
128 if (strcasecmp(mime_type, types[i]) == 0)
129 return ip->ops;
132 return NULL;
135 static int do_http_get(const char *uri, struct http_header **headersp, int *codep, char **reasonp)
137 struct http_uri u;
138 struct http_header *h;
139 int sock, i, rc, code;
140 char *reason;
142 *headersp = NULL;
143 *codep = -1;
144 *reasonp = NULL;
146 if (http_parse_uri(uri, &u))
147 return -IP_ERROR_INVALID_URI;
149 /* d_print("%s -> '%s':'%s'@'%s':%d'%s'\n", uri, user, pass, host, port, path); */
151 sock = http_open(u.host, u.port, http_connection_timeout);
152 if (sock == -1) {
153 http_free_uri(&u);
154 return -IP_ERROR_ERRNO;
157 h = xnew(struct http_header, 5);
158 i = 0;
159 h[i].key = xstrdup("Host");
160 h[i].val = xstrdup(u.host);
161 i++;
162 h[i].key = xstrdup("User-Agent");
163 h[i].val = xstrdup("cmus/" VERSION);
164 i++;
165 h[i].key = xstrdup("Icy-MetaData");
166 h[i].val = xstrdup("1");
167 i++;
168 if (u.user && u.pass) {
169 char buf[256];
170 char *encoded;
172 snprintf(buf, sizeof(buf), "%s:%s", u.user, u.pass);
173 encoded = base64_encode(buf);
174 if (encoded == NULL) {
175 d_print("couldn't base64 encode '%s'\n", buf);
176 } else {
177 snprintf(buf, sizeof(buf), "Basic %s", encoded);
178 free(encoded);
179 h[i].key = xstrdup("Authorization");
180 h[i].val = xstrdup(buf);
181 i++;
184 h[i].key = NULL;
185 h[i].val = NULL;
186 i++;
188 rc = http_get(sock, u.path, h, &code, &reason, headersp, http_read_timeout);
189 http_headers_free(h);
190 http_free_uri(&u);
191 switch (rc) {
192 case -1:
193 d_print("error: %s\n", strerror(errno));
194 close(sock);
195 return -IP_ERROR_ERRNO;
196 case -2:
197 d_print("error parsing HTTP response\n");
198 close(sock);
199 return -IP_ERROR_HTTP_RESPONSE;
201 d_print("HTTP response: %d %s\n", code, reason);
202 if (code != 200) {
203 *codep = code;
204 *reasonp = reason;
205 close(sock);
206 return -IP_ERROR_HTTP_STATUS;
208 free(reason);
209 return sock;
212 static int setup_remote(struct input_plugin *ip, const struct http_header *headers, int sock)
214 const char *val;
216 val = http_headers_get_value(headers, "Content-Type");
217 if (val) {
218 ip->ops = get_ops_by_mime_type(val);
219 if (ip->ops == NULL) {
220 d_print("unsupported content type: %s\n", val);
221 close(sock);
222 return -IP_ERROR_FILE_FORMAT;
224 } else {
225 const char *type = "audio/mpeg";
227 d_print("assuming %s content type\n", type);
228 ip->ops = get_ops_by_mime_type(type);
229 if (ip->ops == NULL) {
230 d_print("unsupported content type: %s\n", type);
231 close(sock);
232 return -IP_ERROR_FILE_FORMAT;
236 ip->data.fd = sock;
237 ip->data.metadata = (char *)xmalloc(16 * 255 + 1);
239 val = http_headers_get_value(headers, "icy-metaint");
240 if (val) {
241 long int lint;
243 if (str_to_int(val, &lint) == 0 && lint >= 0) {
244 ip->data.metaint = lint;
245 d_print("metaint: %d\n", ip->data.metaint);
248 return 0;
251 static int handle_line(void *data, const char *line)
253 char **firstp = data;
255 *firstp = xstrdup(line);
256 /* ignore other lines */
257 return 1;
260 static int read_playlist(struct input_plugin *ip, int sock)
262 struct http_header *headers;
263 char *body, *reason, *first;
264 int rc, code;
266 rc = http_read_body(sock, &body, http_read_timeout);
267 close(sock);
268 if (rc)
269 return -IP_ERROR_ERRNO;
271 /* get only first URL from the playlist */
272 first = NULL;
273 cmus_playlist_for_each(body, strlen(body), 0, handle_line, &first);
274 free(body);
276 if (first == NULL) {
277 d_print("empty playlist\n");
278 return -IP_ERROR_HTTP_RESPONSE;
281 sock = do_http_get(first, &headers, &code, &reason);
282 free(first);
283 if (sock < 0) {
284 ip->http_code = code;
285 ip->http_reason = reason;
286 /* URI in the _playlist_ is invalid, not our fault */
287 if (sock == -IP_ERROR_INVALID_URI)
288 sock = -IP_ERROR_HTTP_RESPONSE;
289 return sock;
292 rc = setup_remote(ip, headers, sock);
293 http_headers_free(headers);
294 return rc;
297 static int open_remote(struct input_plugin *ip)
299 struct input_plugin_data *d = &ip->data;
300 char *reason;
301 int sock, rc, code;
302 struct http_header *headers;
303 const char *val;
305 sock = do_http_get(d->filename, &headers, &code, &reason);
306 if (sock < 0) {
307 ip->http_code = code;
308 ip->http_reason = reason;
309 return sock;
312 val = http_headers_get_value(headers, "Content-Type");
313 if (val) {
314 d_print("Content-Type: %s\n", val);
315 if (!strcasecmp(val, "audio/x-scpls") || !strcasecmp(val, "audio/m3u")) {
316 http_headers_free(headers);
317 return read_playlist(ip, sock);
321 rc = setup_remote(ip, headers, sock);
322 http_headers_free(headers);
323 return rc;
326 static int open_file(struct input_plugin *ip)
328 ip->ops = get_ops_by_filename(ip->data.filename);
329 if (ip->ops == NULL)
330 return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
331 ip->data.fd = open(ip->data.filename, O_RDONLY);
332 if (ip->data.fd == -1) {
333 ip->ops = NULL;
334 return -IP_ERROR_ERRNO;
336 return 0;
339 void ip_load_plugins(void)
341 DIR *dir;
342 struct dirent *d;
344 dir = opendir(plugin_dir);
345 if (dir == NULL) {
346 warn_errno("couldn't open directory `%s'", plugin_dir);
347 return;
349 while ((d = readdir(dir)) != NULL) {
350 char filename[256];
351 struct ip *ip;
352 void *so;
353 char *ext;
354 const char *sym;
356 if (d->d_name[0] == '.')
357 continue;
358 ext = strrchr(d->d_name, '.');
359 if (ext == NULL)
360 continue;
361 if (strcmp(ext, ".so"))
362 continue;
364 snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
366 so = dlopen(filename, RTLD_NOW);
367 if (so == NULL) {
368 warn("%s\n", dlerror());
369 continue;
372 ip = xnew(struct ip, 1);
374 sym = "ip_extensions";
375 if (!(ip->extensions = dlsym(so, sym)))
376 goto sym_err;
378 sym = "ip_mime_types";
379 if (!(ip->mime_types = dlsym(so, sym)))
380 goto sym_err;
382 sym = "ip_ops";
383 if (!(ip->ops = dlsym(so, sym)))
384 goto sym_err;
386 ip->name = xstrndup(d->d_name, ext - d->d_name);
387 ip->handle = so;
389 list_add_tail(&ip->node, &ip_head);
390 continue;
391 sym_err:
392 warn("%s: symbol %s not found\n", filename, sym);
393 free(ip);
394 dlclose(so);
396 closedir(dir);
399 static void ip_init(struct input_plugin *ip, char *filename)
401 memset(ip, 0, sizeof(*ip));
402 ip->http_code = -1;
403 ip->pcm_convert_scale = -1;
404 ip->data.fd = -1;
405 ip->data.filename = filename;
406 ip->data.remote = is_url(filename);
409 struct input_plugin *ip_new(const char *filename)
411 struct input_plugin *ip = xnew(struct input_plugin, 1);
413 ip_init(ip, xstrdup(filename));
414 return ip;
417 void ip_delete(struct input_plugin *ip)
419 if (ip->open)
420 ip_close(ip);
421 free(ip->data.filename);
422 free(ip);
425 int ip_open(struct input_plugin *ip)
427 unsigned int bits, is_signed, channels;
428 int rc;
429 sample_format_t sf;
431 BUG_ON(ip->open);
433 /* set fd and ops */
434 if (ip->data.remote) {
435 rc = open_remote(ip);
436 } else {
437 rc = open_file(ip);
440 if (rc) {
441 d_print("opening `%s' failed: %d %s\n", ip->data.filename, rc,
442 rc == -1 ? strerror(errno) : "");
443 return rc;
446 rc = ip->ops->open(&ip->data);
447 if (rc) {
448 d_print("opening file `%s' failed: %d %s\n", ip->data.filename, rc,
449 rc == -1 ? strerror(errno) : "");
450 if (ip->data.fd != -1)
451 close(ip->data.fd);
452 ip->data.fd = -1;
453 ip->ops = NULL;
454 free(ip->data.metadata);
455 ip->data.metadata = NULL;
456 return rc;
459 sf = ip->data.sf;
461 bits = sf_get_bits(sf);
462 is_signed = sf_get_signed(sf);
463 channels = sf_get_channels(sf);
465 ip->pcm_convert_scale = 1;
466 ip->pcm_convert = NULL;
467 ip->pcm_convert_in_place = NULL;
469 if (bits <= 16 && channels <= 2) {
470 unsigned int mask = ((bits >> 2) & 4) | (is_signed << 1);
472 ip->pcm_convert = pcm_conv[mask | (channels - 1)];
473 ip->pcm_convert_in_place = pcm_conv_in_place[mask | sf_get_bigendian(sf)];
475 ip->pcm_convert_scale = (3 - channels) * (3 - bits / 8);
478 d_print("pcm convert: scale=%d convert=%d convert_in_place=%d\n",
479 ip->pcm_convert_scale,
480 ip->pcm_convert != NULL,
481 ip->pcm_convert_in_place != NULL);
483 ip->open = 1;
484 return 0;
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");