view src/mpg123/http.c @ 220:1e2d575fd2e7 trunk

[svn] - allow seeking in http streams that define a content length.
author nenolod
date Sun, 05 Nov 2006 02:21:22 -0800
parents a6f6309ab0a0
children f6bdf10fe48c
line wrap: on
line source

/*  BMP - Cross-platform multimedia player
 *  Copyright (C) 2003-2004  BMP development team.
 *
 *  Based on XMMS:
 *  Copyright (C) 1998-2003  XMMS development team.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <audacious/util.h>

#include "mpg123.h"


#define min(x,y) ((x)<(y)?(x):(y))
#define min3(x,y,z) (min(x,y)<(z)?min(x,y):(z))
#define min4(x,y,z,w) (min3(x,y,z)<(w)?min3(x,y,z):(w))

static gchar *icy_name = NULL;
static gint icy_metaint = 0;

#undef DEBUG_UDP

/* Static udp channel functions */
static gint udp_establish_listener(gint * sock);
static gint udp_check_for_data(gint sock);

extern gint mpgdec_bitrate, mpgdec_frequency, mpgdec_stereo;
extern gboolean mpgdec_stereo;

static gboolean prebuffering, going, eof = FALSE;
static gint sock;
static gsize rd_index, wr_index, buffer_length, prebuffer_length;
static guint64 buffer_read = 0;
static gchar *buffer;
static GThread *thread;
static GtkWidget *error_dialog = NULL;
static unsigned long range;

static VFSFile *output_file = NULL;

#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))

/* Encode the string S of length LENGTH to base64 format and place it
   to STORE.  STORE will be 0-terminated, and must point to a writable
   buffer of at least 1+BASE64_LENGTH(length) bytes.  */
static void
base64_encode(const gchar * s, gchar * store, gint length)
{
    /* Conversion table.  */
    static gchar tbl[64] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
        'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3',
        '4', '5', '6', '7', '8', '9', '+', '/'
    };
    gint i;
    guchar *p = (guchar *) store;

    /* Transform the 3x8 bits to 4x6 bits, as required by base64.  */
    for (i = 0; i < length; i += 3) {
        *p++ = tbl[s[0] >> 2];
        *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
        *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
        *p++ = tbl[s[2] & 0x3f];
        s += 3;
    }
    /* Pad the result if necessary...  */
    if (i == length + 1)
        *(p - 1) = '=';
    else if (i == length + 2)
        *(p - 1) = *(p - 2) = '=';
    /* ...and zero-terminate it.  */
    *p = '\0';
}

/* Create the authentication header contents for the `Basic' scheme.
   This is done by encoding the string `USER:PASS' in base64 and
   prepending `HEADER: Basic ' to it.  */
static gchar *
basic_authentication_encode(const gchar * user,
                            const gchar * passwd, const gchar * header)
{
    gchar *t1, *t2, *res;
    gint len1 = strlen(user) + 1 + strlen(passwd);
    gint len2 = BASE64_LENGTH(len1);

    t1 = g_strdup_printf("%s:%s", user, passwd);
    t2 = g_malloc0(len2 + 1);
    base64_encode(t1, t2, len1);
    res = g_strdup_printf("%s: Basic %s\r\n", header, t2);
    g_free(t2);
    g_free(t1);

    return res;
}

void
parse_url(const gchar * url, gchar ** user, gchar ** pass,
          gchar ** host, gint * port, gchar ** filename)
{
    gchar *h, *p, *pt, *f, *temp, *ptr;

    temp = g_strdup(url);
    ptr = temp;

    if (!strncasecmp("http://", ptr, 7))
        ptr += 7;
    h = strchr(ptr, '@');
    f = strchr(ptr, '/');
    if (h != NULL && (!f || h < f)) {
        *h = '\0';
        p = strchr(ptr, ':');
        if (p != NULL && p < h) {
            *p = '\0';
            p++;
            *pass = g_strdup(p);
        }
        else
            *pass = NULL;
        *user = g_strdup(ptr);
        h++;
        ptr = h;
    }
    else {
        *user = NULL;
        *pass = NULL;
        h = ptr;
    }
    pt = strchr(ptr, ':');
    if (pt != NULL && (f == NULL || pt < f)) {
        *pt = '\0';
        *port = atoi(pt + 1);
    }
    else {
        if (f)
            *f = '\0';
        *port = 80;
    }
    *host = g_strdup(h);

    if (f)
        *filename = g_strdup(f + 1);
    else
        *filename = NULL;
    g_free(temp);
}

void
mpgdec_http_close(void)
{
    going = FALSE;

    g_thread_join(thread);
    g_free(icy_name);
    icy_name = NULL;
}


static gsize
http_used(void)
{
    if (wr_index >= rd_index)
        return wr_index - rd_index;
    return buffer_length - (rd_index - wr_index);
}

static gsize
http_free(void)
{
    if (rd_index > wr_index)
        return (rd_index - wr_index) - 1;
    return (buffer_length - (wr_index - rd_index)) - 1;
}

static void
http_wait_for_data(gsize bytes)
{
    while ((prebuffering || http_used() < bytes) && !eof && going
           && mpgdec_info->going)
        g_usleep(10000);
}

static void
show_error_message(gchar * error)
{
    if (!error_dialog) {
        GDK_THREADS_ENTER();
        error_dialog = xmms_show_message(_("Error"), error, _("Ok"), FALSE,
                                         NULL, NULL);
        g_signal_connect(G_OBJECT(error_dialog),
                         "destroy",
                         G_CALLBACK(gtk_widget_destroyed), &error_dialog);
        GDK_THREADS_LEAVE();
    }
}

int
mpgdec_http_read(gpointer data, gsize length)
{
    gsize len, cnt, off = 0, meta_len, meta_off = 0, i;
    gchar *meta_data, **tags;

    http_wait_for_data(length);

    if (!going && !mpgdec_info->going)
        return 0;
    len = min(http_used(), length);

    while (len && http_used()) {
        if ((icy_metaint > 0) && (buffer_read % icy_metaint) == 0 && 
            (buffer_read > 0)) {
            meta_len = *((guchar *) buffer + rd_index) * 16;
            rd_index = (rd_index + 1) % buffer_length;
            if (meta_len > 0) {
                http_wait_for_data(meta_len);
                meta_data = g_malloc0(meta_len);
                if (http_used() >= meta_len) {
                    while (meta_len) {
                        cnt = min(meta_len, buffer_length - rd_index);
                        memcpy(meta_data + meta_off, buffer + rd_index, cnt);
                        rd_index = (rd_index + cnt) % buffer_length;
                        meta_len -= cnt;
                        meta_off += cnt;
                    }
                    tags = g_strsplit(meta_data, "';", 0);

                    for (i = 0; tags[i]; i++) {
                        if (!strncasecmp(tags[i], "StreamTitle=", 12)) {
                            gchar *temp = tags[i] + 13;
                            gchar *title =
                                g_strdup_printf("%s (%s)", temp, icy_name);
                            mpgdec_ip.set_info(title, -1,
                                               mpgdec_bitrate * 1000,
                                               mpgdec_frequency,
                                               mpgdec_stereo);
                            g_free(title);
                        }

                    }
                    g_strfreev(tags);

                }
                g_free(meta_data);
            }
            if (!http_used())
                http_wait_for_data(length - off);
            cnt = min3(len, buffer_length - rd_index, http_used());
        }
        else if (icy_metaint > 0)
            cnt =
                min4(len, buffer_length - rd_index, http_used(),
                     icy_metaint - (buffer_read % icy_metaint));
        else
            cnt = min3(len, buffer_length - rd_index, http_used());
        if (output_file)
            vfs_fwrite(buffer + rd_index, 1, cnt, output_file);

        memcpy((gchar *) data + off, buffer + rd_index, cnt);
        rd_index = (rd_index + cnt) % buffer_length;
        buffer_read += cnt;
        len -= cnt;
        off += cnt;
    }
    return off;
}

static gboolean
http_check_for_data(void)
{
    fd_set set;
    struct timeval tv;
    gint ret;

    tv.tv_sec = 0;
    tv.tv_usec = 20000;
    FD_ZERO(&set);
    FD_SET(sock, &set);
    ret = select(sock + 1, &set, NULL, NULL, &tv);
    if (ret > 0)
        return TRUE;
    return FALSE;
}

gint
mpgdec_http_read_line(gchar * buf, gint size)
{
    gint i = 0;

    while (going && i < size - 1) {
        if (http_check_for_data()) {
            if (read(sock, buf + i, 1) <= 0)
                return -1;
            if (buf[i] == '\n')
                break;
            if (buf[i] != '\r')
                i++;
        }
    }
    if (!going)
        return -1;
    buf[i] = '\0';
    return i;
}

static gpointer
http_buffer_loop(gpointer arg)
{
    gchar line[1024], *user, *pass, *host, *filename,
        *status, *url, *temp, *temp2, *file;
    gchar *chost;
    gint cnt, written, error, port, cport;
    guint err_len;
    gboolean redirect;
    gint udp_sock = 0;
    fd_set set;
#ifdef USE_IPV6
    struct addrinfo hints, *res, *res0;
    char service[6];
#else
    struct hostent *hp;
    struct sockaddr_in address;
#endif
    struct timeval tv;

    url = (gchar *) arg;
    do {
        redirect = FALSE;

        g_strstrip(url);

        parse_url(url, &user, &pass, &host, &port, &filename);

        if ((!filename || !*filename) && url[strlen(url) - 1] != '/')
            temp = g_strconcat(url, "/", NULL);
        else
            temp = g_strdup(url);
        g_free(url);
        url = temp;

        chost = mpgdec_cfg.use_proxy ? mpgdec_cfg.proxy_host : host;
        cport = mpgdec_cfg.use_proxy ? mpgdec_cfg.proxy_port : port;

#ifdef USE_IPV6
        snprintf(service, 6, "%d", cport);
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
        if (! getaddrinfo(chost, service, &hints, &res0)) {
            eof = TRUE;
            for (res = res0; res; res = res->ai_next) {
                if ((sock = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) < 0)
                    continue;
                fcntl(sock, F_SETFL, O_NONBLOCK);
                status = g_strdup_printf(_("CONNECTING TO %s:%d"), chost, cport);
                mpgdec_ip.set_info_text(status);
                g_free(status);
                ((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(cport);
                if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
                    if (errno != EINPROGRESS) {
                        close(sock);
                        continue;
                    }
                }
                eof = FALSE;
                break;
            }
            freeaddrinfo(res0);
            if (eof) {
                status = g_strdup_printf(_("Couldn't connect to host %s:%d"), chost, cport);
                show_error_message(status);
                g_free(status);
                mpgdec_ip.set_info_text(NULL);
            }
        } else {
            status = g_strdup_printf(_("Couldn't look up host %s"), chost);
            show_error_message(status);
            g_free(status);

            mpgdec_ip.set_info_text(NULL);
            eof = TRUE;
        }
#else
        sock = socket(AF_INET, SOCK_STREAM, 0);
        fcntl(sock, F_SETFL, O_NONBLOCK);
        address.sin_family = AF_INET;

        status = g_strdup_printf(_("LOOKING UP %s"), chost);
        mpgdec_ip.set_info_text(status);
        g_free(status);

        if (!(hp = gethostbyname(chost))) {
            status = g_strdup_printf(_("Couldn't look up host %s"), chost);
            show_error_message(status);
            g_free(status);

            mpgdec_ip.set_info_text(NULL);
            eof = TRUE;
        }
#endif

        if (!eof) {
#ifndef USE_IPV6
            memcpy(&address.sin_addr.s_addr, *(hp->h_addr_list),
                   sizeof(address.sin_addr.s_addr));
            address.sin_port = g_htons(cport);

            status = g_strdup_printf(_("CONNECTING TO %s:%d"), chost, cport);
            mpgdec_ip.set_info_text(status);
            g_free(status);
            if (connect
                (sock, (struct sockaddr *) &address,
                 sizeof(struct sockaddr_in)) == -1) {
                if (errno != EINPROGRESS) {
                    status =
                        g_strdup_printf(_("Couldn't connect to host %s"),
                                        chost);
                    show_error_message(status);
                    g_free(status);

                    mpgdec_ip.set_info_text(NULL);
                    eof = TRUE;
                }
            }
#endif
            while (going) {
                tv.tv_sec = 0;
                tv.tv_usec = 10000;
                FD_ZERO(&set);
                FD_SET(sock, &set);
                if (select(sock + 1, NULL, &set, NULL, &tv) > 0) {
                    err_len = sizeof(error);
                    getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &err_len);
                    if (error) {
                        status =
                            g_strdup_printf(_
                                            ("Couldn't connect to host %s"),
                                            chost);
                        show_error_message(status);
                        g_free(status);

                        mpgdec_ip.set_info_text(NULL);
                        eof = TRUE;

                    }
                    break;
                }
            }
            if (!eof) {
                gchar *auth = NULL, *proxy_auth = NULL;
                gchar udpspace[30];
                gint udp_port;

                if (mpgdec_cfg.use_udp_channel) {
                    udp_port = udp_establish_listener(&udp_sock);
                    if (udp_port > 0)
                        sprintf(udpspace, "x-audiocast-udpport: %d\r\n",
                                udp_port);
                    else
                        udp_sock = 0;
                }

                if (user && pass)
                    auth =
                        basic_authentication_encode(user, pass,
                                                    "Authorization");

                if (mpgdec_cfg.use_proxy) {
                    file = g_strdup(url);
                    if (mpgdec_cfg.proxy_use_auth && mpgdec_cfg.proxy_user
                        && mpgdec_cfg.proxy_pass) {
                        proxy_auth =
                            basic_authentication_encode(mpgdec_cfg.
                                                        proxy_user,
                                                        mpgdec_cfg.
                                                        proxy_pass,
                                                        "Proxy-Authorization");
                    }
                }
                else
                    file = g_strconcat("/", filename, NULL);
                if (range)
                {
                    temp2 = g_strdup_printf("Range: bytes=%lu-\r\n", range);
                } else
                    temp2 = NULL;
                temp = g_strdup_printf("GET %s HTTP/1.1\r\n"
                                       "Host: %s\r\n"
                                       "User-Agent: %s/%s\r\n"
                                       "%s%s%s%s%s\r\n",
                                       file, host, PACKAGE_NAME, PACKAGE_VERSION,
                                       proxy_auth ? proxy_auth : "",
                                       auth ? auth : "",
                                       "Icy-MetaData:1\r\n",
                                       mpgdec_cfg.
                                       use_udp_channel ? udpspace : "",
				       temp2 != NULL ? temp2 : "");

                g_free(file);
                if (proxy_auth)
                    g_free(proxy_auth);
                if (auth)
                    g_free(auth);
                write(sock, temp, strlen(temp));
                g_free(temp);
                mpgdec_ip.set_info_text(_("CONNECTED: WAITING FOR REPLY"));
                while (going && !eof) {
                    if (http_check_for_data()) {
                        if (mpgdec_http_read_line(line, 1024)) {
                            status = strchr(line, ' ');
                            if (status) {
                                if (status[1] == '2')
                                    break;
                                else if (status[1] == '3'
                                         && status[2] == '0'
                                         && status[3] == '2') {
                                    while (going) {
                                        if (http_check_for_data()) {
                                            if ((cnt =
                                                 mpgdec_http_read_line
                                                 (line, 1024)) != -1) {
                                                if (!cnt)
                                                    break;
                                                if (!strncmp
                                                    (line, "Location:", 9)) {
                                                    g_free(url);
                                                    url = g_strdup(line + 10);
                                                }
                                            }
                                            else {
                                                eof = TRUE;
                                                mpgdec_ip.set_info_text(NULL);
                                                break;
                                            }
                                        }
                                    }
                                    redirect = TRUE;
                                    break;
                                }
                                else {
                                    status =
                                        g_strdup_printf(_
                                                        ("Couldn't connect to host %s\nServer reported: %s"),
                                                        chost, status);
                                    show_error_message(status);
                                    g_free(status);
                                    break;
                                }
                            }
                        }
                        else {
                            eof = TRUE;
                            mpgdec_ip.set_info_text(NULL);
                        }
                    }
                }

                while (going && !redirect) {
                    if (http_check_for_data()) {
                        if ((cnt = mpgdec_http_read_line(line, 1024)) != -1) {
                            if (!cnt)
                                break;
                            if (!strncmp(line, "icy-name:", 9))
                                icy_name = g_strdup(line + 9);
                            else if (!strncmp(line, "x-audiocast-name:", 17))
                                icy_name = g_strdup(line + 17);
                            if (!strncmp(line, "icy-metaint:", 12))
                                icy_metaint = atoi(line + 12);
                            if (!strncmp(line, "x-audiocast-udpport:", 20)) {
#ifdef DEBUG_UDP
                                fprintf(stderr,
                                        "Server wants udp messages on port %d\n",
                                        atoi(line + 20));
#endif
/*  								udp_serverport = atoi (line + 20); */

                            }

                            if (!strncasecmp(line, "content-length:", 15)) {
                                mpgdec_info->filesize = atoi(line + 15);
                            }
                        }
                        else {
                            eof = TRUE;
                            mpgdec_ip.set_info_text(NULL);
                            break;
                        }
                    }
                }
            }
        }

        if (redirect) {
            if (output_file) {
                vfs_fclose(output_file);
                output_file = NULL;
            }
            close(sock);
            g_free(user);
            g_free(pass);
            g_free(host);
            g_free(filename);
        }
    } while (redirect);

    if (mpgdec_cfg.save_http_stream) {
        gchar *output_name_seed, *output_name;
        gint i = 1;

        file = mpgdec_http_get_title(url);
        output_name_seed = file;
        if (!strncasecmp(output_name_seed, "http://", 7))
            output_name_seed += 7;
        temp = strrchr(output_name_seed, '.');
        if (temp && !strcasecmp(temp, ".mp3"))
            *temp = '\0';

        while ((temp = strchr(output_name_seed, '/')))
            *temp = '_';

        output_name = g_strdup_printf("%s/%s.mp3",
                                      mpgdec_cfg.save_http_path, output_name_seed);
        while (!access(output_name, F_OK) && i < 100000) {
            g_free(output_name);
            output_name = g_strdup_printf("%s/%s-%d.mp3",
                                          mpgdec_cfg.save_http_path,
                                          output_name_seed, i++);
        }

        g_free(file);

        output_file = vfs_fopen(output_name, "wb");
        g_free(output_name);
    }

    while (going) {

        if (!http_used() && !mpgdec_ip.output->buffer_playing()) {
            prebuffering = TRUE;
            mpgdec_ip.set_status_buffering(TRUE);
        }
        if (http_free() > 0 && !eof) {
            if (http_check_for_data()) {
                cnt = min(http_free(), buffer_length - wr_index);
                if (cnt > 1024)
                    cnt = 1024;
                written = read(sock, buffer + wr_index, cnt);
                if (written <= 0) {
                    eof = TRUE;
                    if (prebuffering) {
                        prebuffering = FALSE;
                        mpgdec_ip.set_status_buffering(FALSE);

                        mpgdec_ip.set_info_text(NULL);
                    }

                }
                else
                    wr_index = (wr_index + written) % buffer_length;
            }

            if (prebuffering) {
                if (http_used() > prebuffer_length) {
                    prebuffering = FALSE;
                    mpgdec_ip.set_status_buffering(FALSE);
                    mpgdec_ip.set_info_text(NULL);
                }
                else {
                    status =
                        g_strdup_printf(_("PRE-BUFFERING: %zuKB/%zuKB"),
                                        http_used() / 1024,
                                        prebuffer_length / 1024);
                    mpgdec_ip.set_info_text(status);
                    g_free(status);
                }

            }
        }
        else
            g_usleep(10000);

        if (mpgdec_cfg.use_udp_channel && udp_sock != 0)
            if (udp_check_for_data(udp_sock) < 0) {
                close(udp_sock);
                udp_sock = 0;
            }
    }
    if (output_file) {
        vfs_fclose(output_file);
        output_file = NULL;
    }
    close(sock);
    if (udp_sock != 0)
        close(udp_sock);

    g_free(user);
    g_free(pass);
    g_free(host);
    g_free(filename);
    g_free(buffer);
    g_free(url);

    return NULL;
}

int
mpgdec_http_open(gchar * _url, unsigned long rng)
{
    gchar *url;

    url = g_strdup(_url);

    rd_index = 0;
    wr_index = 0;
    buffer_length = mpgdec_cfg.http_buffer_size * 1024;
    prebuffer_length = (buffer_length * mpgdec_cfg.http_prebuffer) / 100;
    buffer_read = 0;
    icy_metaint = 0;
    prebuffering = TRUE;
    mpgdec_ip.set_status_buffering(TRUE);
    going = TRUE;
    eof = FALSE;
    buffer = g_malloc(buffer_length);
    range = rng;

    thread = g_thread_create(http_buffer_loop, url, TRUE, NULL);

    return 0;
}

char *
mpgdec_http_get_title(gchar * url)
{
    if (icy_name)
        return g_strdup(icy_name);
    if (g_basename(url) && strlen(g_basename(url)) > 0)
        return g_strdup(g_basename(url));
    return g_strdup(url);
}

/* Start UDP Channel specific stuff */

/* Find a good local udp port and bind udp_sock to it, return the port */
static gint
udp_establish_listener(gint * sock)
{
#ifdef USE_IPV6
    struct sockaddr_in6 sin;
    socklen_t sinlen = sizeof(struct sockaddr_in6);
#else
    struct sockaddr_in sin;
    socklen_t sinlen = sizeof(struct sockaddr_in);
#endif

#ifdef DEBUG_UDP
    fprintf(stderr, "Establishing udp listener\n");
#endif

#ifdef USE_IPV6
    if ((*sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
#else
    if ((*sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
#endif
        g_log(NULL, G_LOG_LEVEL_CRITICAL,
              "udp_establish_listener(): unable to create socket");
        return -1;
    }

    memset(&sin, 0, sinlen);
#ifdef USE_IPV6
    sin.sin6_family = AF_INET6;
#else
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = g_htonl(INADDR_ANY);
#endif

    if (bind(*sock, (struct sockaddr *) &sin, sinlen) < 0) {
        g_log(NULL, G_LOG_LEVEL_CRITICAL,
              "udp_establish_listener():  Failed to bind socket to localhost: %s",
              strerror(errno));
        close(*sock);
        return -1;
    }
    if (fcntl(*sock, F_SETFL, O_NONBLOCK) < 0) {
        g_log(NULL, G_LOG_LEVEL_CRITICAL,
              "udp_establish_listener():  Failed to set flags: %s",
              strerror(errno));
        close(*sock);
        return -1;
    }

    memset(&sin, 0, sinlen);
    if (getsockname(*sock, (struct sockaddr *) &sin, &sinlen) < 0) {
        g_log(NULL, G_LOG_LEVEL_CRITICAL,
              "udp_establish_listener():  Failed to retrieve socket info: %s",
              strerror(errno));
        close(*sock);
        return -1;
    }
#ifdef DEBUG_UDP
    fprintf(stderr, "Listening on local %s:%d\n", inet_ntoa(sin.sin_addr),
            g_ntohs(sin.sin_port));
#endif

#ifdef USE_IPV6
    return g_ntohs(sin.sin6_port);
#else
    return g_ntohs(sin.sin_port);
#endif
}

static int
udp_check_for_data(int sock)
{
    char buf[1025], **lines;
    char *valptr;
    gchar *title;
    gint len, i;
#ifdef USE_IPV6
    struct sockaddr_in6 from;
#else
    struct sockaddr_in from;
#endif
    socklen_t fromlen;

    fromlen = sizeof from;

    if ((len =
         recvfrom(sock, buf, 1024, 0, (struct sockaddr *) &from,
                  &fromlen)) < 0) {
        if (errno != EAGAIN) {
            g_log(NULL, G_LOG_LEVEL_CRITICAL,
                  "udp_read_data(): Error reading from socket: %s",
                  strerror(errno));
            return -1;
        }
        return 0;
    }
    buf[len] = '\0';
#ifdef DEBUG_UDP
    fprintf(stderr, "Received: [%s]\n", buf);
#endif
    lines = g_strsplit(buf, "\n", 0);
    if (!lines)
        return 0;

    for (i = 0; lines[i]; i++) {
        while ((lines[i][strlen(lines[i]) - 1] == '\n') ||
               (lines[i][strlen(lines[i]) - 1] == '\r'))
            lines[i][strlen(lines[i]) - 1] = '\0';

        valptr = strchr(lines[i], ':');

        if (!valptr)
            continue;
        else
            valptr++;

        g_strstrip(valptr);
        if (!strlen(valptr))
            continue;

        if (strstr(lines[i], "x-audiocast-streamtitle") != NULL) {
            title = g_strdup_printf("%s (%s)", valptr, icy_name);
            if (going)
                mpgdec_ip.set_info(title, -1, mpgdec_bitrate * 1000,
                                   mpgdec_frequency, mpgdec_stereo);
            g_free(title);
        }
#if 0
        else if (strstr(lines[i], "x-audiocast-streamlength") != NULL) {
            if (atoi(valptr) != -1)
                mpgdec_ip.set_info(NULL, atoi(valptr),
                                   mpgdec_bitrate * 1000, mpgdec_frequency,
                                   mpgdec_stereo);
        }
#endif

        else if (strstr(lines[i], "x-audiocast-streammsg") != NULL) {
            /*  mpgdec_ip.set_info(title, -1, mpgdec_bitrate * 1000, mpgdec_frequency, mpgdec_stereo); */
/*  			xmms_show_message(_("Message"), valptr, _("Ok"), */
/*  					  FALSE, NULL, NULL); */
            g_message("Stream_message: %s", valptr);
        }
#if 0
        /* Use this to direct your webbrowser.. yeah right.. */
        else if (strstr(lines[i], "x-audiocast-streamurl") != NULL) {
            if (lasturl && g_strcmp(valptr, lasturl)) {
                c_message(stderr, "Song URL: %s\n", valptr);
                g_free(lasturl);
                lasturl = g_strdup(valptr);
            }
        }
#endif
        else if (strstr(lines[i], "x-audiocast-udpseqnr:") != NULL) {
            gchar obuf[60];
            sprintf(obuf, "x-audiocast-ack: %ld \r\n", atol(valptr));
            if (sendto
                (sock, obuf, strlen(obuf), 0, (struct sockaddr *) &from,
                 fromlen) < 0) {
                g_log(NULL, G_LOG_LEVEL_WARNING,
                      "udp_check_for_data(): Unable to send ack to server: %s",
                      strerror(errno));
            }
#ifdef DEBUG_UDP
            else
                fprintf(stderr, "Sent ack: %s", obuf);
#ifdef USE_IPV6
            {
                char adr[INET6_ADDRSTRLEN];
                inet_ntop(AF_INET6, &from.sin6_addr, adr, INET6_ADDRSTRLEN);
                fprintf(stderr, "Remote: [%s]:%d\n", adr,
                        g_ntohs(from.sin6_port));
            }
#else
            fprintf(stderr, "Remote: %s:%d\n", inet_ntoa(from.sin_addr),
                    g_ntohs(from.sin_port));
#endif
#endif
        }
    }
    g_strfreev(lines);
    return 0;
}