Better thread sync, cleanup and libevent compability in ICY filescanner

This commit is contained in:
ejurgensen 2014-05-26 23:38:19 +02:00
parent 76fa7c1849
commit 02c23b0065

View File

@ -1,9 +1,6 @@
/* /*
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org> * Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
* *
* Rewritten from mt-daapd code:
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
@ -37,13 +34,14 @@
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <pthread.h>
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#include <netinet/in.h> #include <netinet/in.h>
#endif #endif
#include <event.h> #include <event.h>
#ifdef HAVE_LIBEVENT2 #if defined HAVE_LIBEVENT2
# include <evhttp.h> # include <evhttp.h>
#else #else
# include "evhttp/evhttp.h" # include "evhttp/evhttp.h"
@ -71,15 +69,16 @@ struct icy_ctx
char hostname[PATH_MAX]; char hostname[PATH_MAX];
char path[PATH_MAX]; char path[PATH_MAX];
int port; int port;
char *icy_name;
char *icy_description;
char *icy_genre;
pthread_mutex_t lck;
pthread_cond_t cond;
}; };
static void #ifndef HAVE_LIBEVENT2
free_icy(struct icy_ctx *ctx)
{
if (ctx)
free(ctx);
}
static int static int
resolve_address(char *hostname, char *s, size_t maxlen) resolve_address(char *hostname, char *s, size_t maxlen)
{ {
@ -102,19 +101,30 @@ resolve_address(char *hostname, char *s, size_t maxlen)
default: default:
strncpy(s, "Unknown AF", maxlen); strncpy(s, "Unknown AF", maxlen);
freeaddrinfo(result);
return -1; return -1;
} }
freeaddrinfo(result); freeaddrinfo(result);
return 0; return 0;
} }
#endif
static void static void
scan_icy_request_cb(struct evhttp_request *req, void *arg) scan_icy_request_cb(struct evhttp_request *req, void *arg)
{ {
DPRINTF(E_DBG, L_SCAN, "ICY metadata request completed\n"); struct icy_ctx *ctx;
ctx = (struct icy_ctx *)arg;
pthread_mutex_lock(&ctx->lck);
DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Signal callback\n");
status = ICY_DONE; status = ICY_DONE;
pthread_cond_signal(&ctx->cond);
pthread_mutex_unlock(&ctx->lck);
return; return;
} }
@ -122,29 +132,36 @@ scan_icy_request_cb(struct evhttp_request *req, void *arg)
static int static int
scan_icy_header_cb(struct evhttp_request *req, void *arg) scan_icy_header_cb(struct evhttp_request *req, void *arg)
{ {
struct media_file_info *mfi; struct icy_ctx *ctx;
struct evkeyvalq *headers;
const char *ptr; const char *ptr;
mfi = (struct media_file_info *)arg; ctx = (struct icy_ctx *)arg;
if ( (ptr = evhttp_find_header(req->input_headers, "icy-name")) ) DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Headers received\n");
#ifdef HAVE_LIBEVENT2
headers = evhttp_request_get_input_headers(req);
#else
headers = req->input_headers;
#endif
if ( (ptr = evhttp_find_header(headers, "icy-name")) )
{ {
mfi->title = strdup(ptr); ctx->icy_name = strdup(ptr);
mfi->artist = strdup(ptr); DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is %s\n", ctx->icy_name);
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name (title/artist) is %s\n", mfi->title);
} }
if ( (ptr = evhttp_find_header(req->input_headers, "icy-description")) ) if ( (ptr = evhttp_find_header(headers, "icy-description")) )
{ {
mfi->album = strdup(ptr); ctx->icy_description = strdup(ptr);
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description (album) is %s\n", mfi->album); DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is %s\n", ctx->icy_description);
} }
if ( (ptr = evhttp_find_header(req->input_headers, "icy-genre")) ) if ( (ptr = evhttp_find_header(headers, "icy-genre")) )
{ {
mfi->genre = strdup(ptr); ctx->icy_genre = strdup(ptr);
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", mfi->genre); DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", ctx->icy_genre);
} }
status = ICY_DONE;
return -1; return -1;
} }
@ -153,11 +170,14 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
{ {
struct evhttp_connection *evcon; struct evhttp_connection *evcon;
struct evhttp_request *req; struct evhttp_request *req;
struct evkeyvalq *headers;
struct icy_ctx *ctx; struct icy_ctx *ctx;
time_t start;
time_t end;
int ret; int ret;
int i;
status = ICY_INIT; status = ICY_INIT;
start = time(NULL);
/* We can set this straight away */ /* We can set this straight away */
mfi->url = strdup(url); mfi->url = strdup(url);
@ -167,10 +187,13 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
{ {
DPRINTF(E_LOG, L_SCAN, "Out of memory for ICY metadata context\n"); DPRINTF(E_LOG, L_SCAN, "Out of memory for ICY metadata context\n");
goto no_icy; return -1;
} }
memset(ctx, 0, sizeof(struct icy_ctx)); memset(ctx, 0, sizeof(struct icy_ctx));
pthread_mutex_init(&ctx->lck, NULL);
pthread_cond_init(&ctx->cond, NULL);
ctx->url = url; ctx->url = url;
/* TODO https */ /* TODO https */
@ -185,6 +208,15 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
if (ctx->port < 0) if (ctx->port < 0)
ctx->port = 80; ctx->port = 80;
#ifdef HAVE_LIBEVENT2
evcon = evhttp_connection_base_new(evbase_main, NULL, ctx->hostname, (unsigned short)ctx->port);
if (!evcon)
{
DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname);
goto no_icy;
}
#else
/* Resolve IP address */ /* Resolve IP address */
ret = resolve_address(ctx->hostname, ctx->address, sizeof(ctx->address)); ret = resolve_address(ctx->hostname, ctx->address, sizeof(ctx->address));
if (ret < 0) if (ret < 0)
@ -205,37 +237,44 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
goto no_icy; goto no_icy;
} }
evhttp_connection_set_base(evcon, evbase_main); evhttp_connection_set_base(evcon, evbase_main);
#endif
evhttp_connection_set_timeout(evcon, ICY_TIMEOUT); evhttp_connection_set_timeout(evcon, ICY_TIMEOUT);
/* Set up request */ /* Set up request */
req = evhttp_request_new(scan_icy_request_cb, mfi); req = evhttp_request_new(scan_icy_request_cb, ctx);
if (!req) if (!req)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not create request to %s\n", ctx->hostname); DPRINTF(E_LOG, L_SCAN, "Could not create request to %s\n", ctx->hostname);
evhttp_connection_free(evcon);
goto no_icy; goto no_icy;
} }
#ifdef HAVE_LIBEVENT2
evhttp_request_set_header_cb(req, scan_icy_header_cb);
headers = evhttp_request_get_output_headers(req);
#else
req->header_cb = scan_icy_header_cb; req->header_cb = scan_icy_header_cb;
evhttp_add_header(req->output_headers, "Host", ctx->hostname); headers = req->output_headers;
evhttp_add_header(req->output_headers, "Icy-MetaData", "1"); #endif
evhttp_add_header(headers, "Host", ctx->hostname);
evhttp_add_header(headers, "Icy-MetaData", "1");
/* Make request */ /* Make request */
DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", ctx->hostname);
status = ICY_WAITING; status = ICY_WAITING;
ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, ctx->path); ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, ctx->path);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not make request to %s\n", ctx->hostname); DPRINTF(E_LOG, L_SCAN, "Error making request to %s\n", ctx->hostname);
status = ICY_DONE; status = ICY_DONE;
evhttp_connection_free(evcon);
goto no_icy; goto no_icy;
} }
DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", url);
/* Can't count on server support for ICY metadata, so /* Can't count on server support for ICY metadata, so
* while waiting for a reply make a parallel call to scan_metadata_ffmpeg. * while waiting for a reply make a parallel call to scan_metadata_ffmpeg.
* This call will also determine final return value.
*/ */
no_icy: no_icy:
ret = scan_metadata_ffmpeg(url, mfi); ret = scan_metadata_ffmpeg(url, mfi);
@ -247,13 +286,58 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
mfi->description = strdup("MPEG audio file"); mfi->description = strdup("MPEG audio file");
} }
/* Wait till ICY request completes or we reach timeout */ /* Wait for ICY request to complete or timeout */
for (i = 0; (status == ICY_WAITING) && (i <= ICY_TIMEOUT); i++) pthread_mutex_lock(&ctx->lck);
sleep(1);
free_icy(ctx); if (status == ICY_WAITING)
pthread_cond_wait(&ctx->cond, &ctx->lck);
DPRINTF(E_DBG, L_SCAN, "scan_metadata_icy exiting with status %d after waiting %d sec\n", status, i); pthread_mutex_unlock(&ctx->lck);
/* Copy result to mfi */
if (ctx->icy_name)
{
if (mfi->title)
free(mfi->title);
if (mfi->artist)
free(mfi->artist);
if (mfi->album_artist)
free(mfi->album_artist);
mfi->title = strdup(ctx->icy_name);
mfi->artist = strdup(ctx->icy_name);
mfi->album_artist = strdup(ctx->icy_name);
free(ctx->icy_name);
}
if (ctx->icy_description)
{
if (mfi->album)
free(mfi->album);
mfi->album = ctx->icy_description;
}
if (ctx->icy_genre)
{
if (mfi->genre)
free(mfi->genre);
mfi->genre = ctx->icy_genre;
}
/* Clean up */
if (evcon)
evhttp_connection_free(evcon);
pthread_cond_destroy(&ctx->cond);
pthread_mutex_destroy(&ctx->lck);
free(ctx);
end = time(NULL);
DPRINTF(E_DBG, L_SCAN, "ICY metadata scan of %s completed in %.f sec\n", url, difftime(end, start));
return 1; return 1;
} }