Implement DACP getproperty and DACP properties core code
This commit is contained in:
parent
b73631bb5c
commit
297c86c453
325
src/httpd_dacp.c
325
src/httpd_dacp.c
|
@ -39,6 +39,7 @@
|
|||
|
||||
#include <event.h>
|
||||
#include "evhttp/evhttp.h"
|
||||
#include <avl.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
|
@ -73,6 +74,50 @@ struct dacp_update_request {
|
|||
struct dacp_update_request *next;
|
||||
};
|
||||
|
||||
typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
|
||||
struct dacp_prop_map {
|
||||
uint32_t hash;
|
||||
char *desc;
|
||||
dacp_propget propget;
|
||||
};
|
||||
|
||||
|
||||
/* Forward - properties getters */
|
||||
static void
|
||||
dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
static void
|
||||
dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
|
||||
|
||||
static struct dacp_prop_map dacp_props[] =
|
||||
{
|
||||
{ 0, "dmcp.volume", dacp_propget_volume },
|
||||
{ 0, "dacp.playerstate", dacp_propget_playerstate },
|
||||
{ 0, "dacp.nowplaying", dacp_propget_nowplaying },
|
||||
{ 0, "dacp.playingtime", dacp_propget_playingtime },
|
||||
{ 0, "dacp.volumecontrollable", dacp_propget_volumecontrollable },
|
||||
{ 0, "dacp.availableshufflestates", dacp_propget_availableshufflestates },
|
||||
{ 0, "dacp.availablerepeatstates", dacp_propget_availablerepeatstates },
|
||||
{ 0, "dacp.shufflestate", dacp_propget_shufflestate },
|
||||
{ 0, "dacp.repeatstate", dacp_propget_repeatstate },
|
||||
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
/* Play status update */
|
||||
#ifdef USE_EVENTFD
|
||||
|
@ -86,6 +131,9 @@ static int current_rev;
|
|||
/* Play status update requests */
|
||||
static struct dacp_update_request *update_requests;
|
||||
|
||||
/* Properties */
|
||||
static avl_tree_t *dacp_props_hash;
|
||||
|
||||
|
||||
/* DACP helpers */
|
||||
static void
|
||||
|
@ -306,6 +354,151 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg)
|
|||
}
|
||||
|
||||
|
||||
/* Properties helpers */
|
||||
static int
|
||||
dacp_prop_map_compare(const void *aa, const void *bb)
|
||||
{
|
||||
struct dacp_prop_map *a = (struct dacp_prop_map *)aa;
|
||||
struct dacp_prop_map *b = (struct dacp_prop_map *)bb;
|
||||
|
||||
if (a->hash < b->hash)
|
||||
return -1;
|
||||
|
||||
if (a->hash > b->hash)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dacp_prop_map *
|
||||
dacp_find_prop(uint32_t hash)
|
||||
{
|
||||
struct dacp_prop_map dpm;
|
||||
avl_node_t *node;
|
||||
|
||||
dpm.hash = hash;
|
||||
|
||||
node = avl_search(dacp_props_hash, &dpm);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
return (struct dacp_prop_map *)node->item;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_properties(struct evhttp_request *req, char *tag, const char *param, uint32_t **out_prop, int *out_nprop)
|
||||
{
|
||||
char *ptr;
|
||||
char *prop;
|
||||
char *propstr;
|
||||
uint32_t *hashes;
|
||||
int nprop;
|
||||
int i;
|
||||
|
||||
*out_nprop = -1;
|
||||
|
||||
propstr = strdup(param);
|
||||
if (!propstr)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not duplicate properties parameter; out of memory\n");
|
||||
|
||||
dmap_send_error(req, tag, "Out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
nprop = 1;
|
||||
ptr = propstr;
|
||||
while ((ptr = strchr(ptr + 1, ',')))
|
||||
nprop++;
|
||||
|
||||
DPRINTF(E_DBG, L_DACP, "Asking for %d properties\n", nprop);
|
||||
|
||||
hashes = (uint32_t *)malloc((nprop + 1) * sizeof(uint32_t));
|
||||
if (!hashes)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not allocate properties array; out of memory\n");
|
||||
|
||||
dmap_send_error(req, tag, "Out of memory");
|
||||
|
||||
free(propstr);
|
||||
return;
|
||||
}
|
||||
memset(hashes, 0, (nprop + 1) * sizeof(uint32_t));
|
||||
|
||||
prop = strtok_r(propstr, ",", &ptr);
|
||||
for (i = 0; i < nprop; i++)
|
||||
{
|
||||
hashes[i] = djb_hash(prop, strlen(prop));
|
||||
|
||||
prop = strtok_r(NULL, ",", &ptr);
|
||||
if (!prop)
|
||||
break;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DACP, "Found %d properties\n", nprop);
|
||||
|
||||
*out_nprop = nprop;
|
||||
*out_prop = hashes;
|
||||
|
||||
free(propstr);
|
||||
}
|
||||
|
||||
/* Properties getters */
|
||||
static void
|
||||
dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_int(evbuf, "cmvo", status->volume);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_char(evbuf, "cavc", 1);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_char(evbuf, "caps", status->status);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_char(evbuf, "cash", status->shuffle);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_int(evbuf, "caas", 2);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_char(evbuf, "carp", status->repeat);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dmap_add_int(evbuf, "caar", 6);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dacp_nowplaying(evbuf, status, mfi);
|
||||
}
|
||||
|
||||
static void
|
||||
dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
|
||||
{
|
||||
dacp_playingtime(evbuf, status, mfi);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
||||
{
|
||||
|
@ -723,8 +916,16 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||
static void
|
||||
dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
||||
{
|
||||
struct player_status status;
|
||||
struct daap_session *s;
|
||||
struct dacp_prop_map *dpm;
|
||||
struct media_file_info *mfi;
|
||||
struct evbuffer *proplist;
|
||||
const char *param;
|
||||
uint32_t *prop;
|
||||
int nprop;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
s = daap_session_find(req, query, evbuf);
|
||||
if (!s)
|
||||
|
@ -739,20 +940,83 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
|
|||
return;
|
||||
}
|
||||
|
||||
/* The client can request multiple properties at once (hence the parameter
|
||||
* name), similar to DAAP meta tags. Should be treated likewise.
|
||||
*/
|
||||
|
||||
if (strcmp(param, "dmcp.volume") == 0)
|
||||
parse_properties(req, "cmgt", param, &prop, &nprop);
|
||||
if (nprop < 0)
|
||||
{
|
||||
dmap_add_container(evbuf, "cmgt", 24); /* 8 + len */
|
||||
dmap_add_int(evbuf, "mstt", 200); /* 12 */
|
||||
dmap_add_int(evbuf, "cmvo", 42); /* 12 */ /* Volume, 0-100 */
|
||||
DPRINTF(E_LOG, L_DACP, "Failed to parse properties parameter in getproperty call\n");
|
||||
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
proplist = evbuffer_new();
|
||||
if (!proplist)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for properties list\n");
|
||||
|
||||
dmap_send_error(req, "cmgt", "Out of memory");
|
||||
goto out_free_prop;
|
||||
}
|
||||
|
||||
player_get_status(&status);
|
||||
|
||||
if (status.status != PLAY_STOPPED)
|
||||
{
|
||||
mfi = db_file_fetch_byid(status.id);
|
||||
if (!mfi)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", status.id);
|
||||
|
||||
dmap_send_error(req, "cmgt", "Server error");
|
||||
goto out_free_proplist;
|
||||
}
|
||||
}
|
||||
else
|
||||
dmap_send_error(req, "cmgt", "Not implemented");
|
||||
mfi = NULL;
|
||||
|
||||
for (i = 0; i < nprop; i++)
|
||||
{
|
||||
dpm = dacp_find_prop(prop[i]);
|
||||
if (!dpm)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not find requested property (%d)\n", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dpm->propget)
|
||||
dpm->propget(proplist, &status, mfi);
|
||||
else
|
||||
DPRINTF(E_WARN, L_DACP, "No getter method for DACP property %s\n", dpm->desc);
|
||||
}
|
||||
|
||||
if (mfi)
|
||||
free_mfi(mfi, 0);
|
||||
|
||||
if (nprop > 0)
|
||||
free(prop);
|
||||
|
||||
dmap_add_container(evbuf, "cmgt", 12 + EVBUFFER_LENGTH(proplist)); /* 8 + len */
|
||||
dmap_add_int(evbuf, "mstt", 200); /* 12 */
|
||||
|
||||
ret = evbuffer_add_buffer(evbuf, proplist);
|
||||
evbuffer_free(proplist);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DACP, "Could not add properties to getproperty reply\n");
|
||||
|
||||
dmap_send_error(req, "cmgt", "Out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||
|
||||
return;
|
||||
|
||||
out_free_proplist:
|
||||
evbuffer_free(proplist);
|
||||
|
||||
out_free_prop:
|
||||
if (nprop > 0)
|
||||
free(prop);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1117,6 +1381,8 @@ int
|
|||
dacp_init(void)
|
||||
{
|
||||
char buf[64];
|
||||
avl_node_t *node;
|
||||
struct dacp_prop_map *dpm;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
|
@ -1157,6 +1423,38 @@ dacp_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
dacp_props_hash = avl_alloc_tree(dacp_prop_map_compare, NULL);
|
||||
if (!dacp_props_hash)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_DACP, "DACP init could not allocate DACP props AVL tree\n");
|
||||
|
||||
goto dacp_avl_alloc_fail;
|
||||
}
|
||||
|
||||
for (i = 0; dacp_props[i].desc; i++)
|
||||
{
|
||||
dacp_props[i].hash = djb_hash(dacp_props[i].desc, strlen(dacp_props[i].desc));
|
||||
|
||||
node = avl_insert(dacp_props_hash, &dacp_props[i]);
|
||||
if (!node)
|
||||
{
|
||||
if (errno != EEXIST)
|
||||
DPRINTF(E_FATAL, L_DACP, "DACP init failed; AVL insert error: %s\n", strerror(errno));
|
||||
else
|
||||
{
|
||||
node = avl_search(dacp_props_hash, &dacp_props[i]);
|
||||
dpm = node->item;
|
||||
|
||||
DPRINTF(E_FATAL, L_DACP, "DACP init failed; WARNING: duplicate hash key\n");
|
||||
DPRINTF(E_FATAL, L_DACP, "Hash %x, string %s\n", dacp_props[i].hash, dacp_props[i].desc);
|
||||
|
||||
DPRINTF(E_FATAL, L_DACP, "Hash %x, string %s\n", dpm->hash, dpm->desc);
|
||||
}
|
||||
|
||||
goto dacp_avl_insert_fail;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EVENTFD
|
||||
event_set(&updateev, update_efd, EV_READ, playstatusupdate_cb, NULL);
|
||||
#else
|
||||
|
@ -1173,6 +1471,11 @@ dacp_init(void)
|
|||
|
||||
return 0;
|
||||
|
||||
dacp_avl_insert_fail:
|
||||
avl_free_tree(dacp_props_hash);
|
||||
dacp_avl_alloc_fail:
|
||||
for (i = 0; dacp_handlers[i].handler; i++)
|
||||
regfree(&dacp_handlers[i].preg);
|
||||
regexp_fail:
|
||||
#ifdef USE_EVENTFD
|
||||
close(update_efd);
|
||||
|
@ -1202,6 +1505,8 @@ dacp_deinit(void)
|
|||
|
||||
player_set_updatefd(-1);
|
||||
|
||||
avl_free_tree(dacp_props_hash);
|
||||
|
||||
#ifdef USE_EVENTFD
|
||||
close(update_efd);
|
||||
#else
|
||||
|
|
Loading…
Reference in New Issue