Begin to add support for a central metadata bundle or respository. Lots more to do.

This commit is contained in:
Mike Brady
2017-12-26 20:28:14 +00:00
parent 231e7419cc
commit 6af24f7c1f
6 changed files with 398 additions and 133 deletions
+1 -1
View File
@@ -88,7 +88,7 @@ dbus-interface.c: org.gnome.ShairportSync.xml
endif
if USE_DBUS_CORE_AND_DACP
shairport_sync_SOURCES += dacp.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
shairport_sync_SOURCES += dacp.c metadata.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
endif
if USE_MPRIS
+294 -130
View File
@@ -10,7 +10,6 @@
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
@@ -24,6 +23,9 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
// Information about the four-character codes is from many sources, with thanks, including
// https://github.com/melloware/dacp-net/blob/master/Melloware.DACP/
#include "dacp.h"
#include "common.h"
#include "config.h"
@@ -39,6 +41,7 @@
#include <time.h>
#include <unistd.h>
#include "metadata.h"
#include "tinyhttp/http.h"
typedef struct {
@@ -67,7 +70,8 @@ static void response_body(void *opaque, const char *data, int size) {
ssize_t space_available = response->malloced_size - response->size;
if (space_available < size) {
// debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n", size,
// debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n",
// size,
// size - space_available);
ssize_t size_requested = size - space_available + response->malloced_size + 16384;
void *t = realloc(response->body, size_requested);
@@ -75,7 +79,7 @@ static void response_body(void *opaque, const char *data, int size) {
if (t)
response->body = t;
else {
debug(1,"Can't allocate any more space for parser.\n");
debug(1, "Can't allocate any more space for parser.\n");
exit(-1);
}
}
@@ -103,7 +107,7 @@ static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
int dacp_send_command(const char *command, char **body, size_t *bodysize) {
// will malloc space for the body or set it to NULL -- the caller should free it.
// Using some custom HTTP-like return codes
// 498 Bad Address information for the DACP server
// 497 Can't establish a socket to the DACP server
@@ -111,7 +115,7 @@ int dacp_send_command(const char *command, char **body, size_t *bodysize) {
// 495 Error receiving response
// 494 This client is already busy
// 493 Client failed to send a message
// try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
// to do it.
struct timespec mutex_wait_time;
@@ -142,98 +146,99 @@ int dacp_send_command(const char *command, char **body, size_t *bodysize) {
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
debug(1, "DHCP port string is \"%s:%s\".", server, portstring);
// debug(1, "DHCP port string is \"%s:%s\".", server, portstring);
int ires = getaddrinfo(server, portstring, &hints, &res);
if (ires) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
// only do this one at a time -- not sure it is necessary, but better safe than sorry
// only do this one at a time -- not sure it is necessary, but better safe than sorry
int mutex_reply = pthread_mutex_timedlock(&dacp_conversation_lock, &mutex_wait_time);
if (mutex_reply == 0) {
int mutex_reply = pthread_mutex_timedlock(&dacp_conversation_lock, &mutex_wait_time);
if (mutex_reply == 0) {
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
// debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
response.code = 497; // Can't establish a socket to the DACP server
} else {
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
// debug(1, "DACP connect failed.");
response.code = 496; // Can't connect to the DACP server
if (sockfd == -1) {
// debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
response.code = 497; // Can't establish a socket to the DACP server
} else {
// debug(1,"DACP connect succeeded.");
sprintf(message, "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
// Send command
// debug(1,"DACP connect message: \"%s\".",message);
if (send(sockfd, message, strlen(message), 0) != strlen(message)) {
// debug(1, "Send failed");
response.code = 493; // Client failed to send a message
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
// debug(1, "DACP connect failed.");
response.code = 496; // Can't connect to the DACP server
} else {
// debug(1,"DACP connect succeeded.");
response.body = malloc(2048); // it can resize this if necessary
response.malloced_size = 2048;
sprintf(message,
"GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
struct http_roundtripper rt;
http_init(&rt, responseFuncs, &response);
// Send command
// debug(1,"DACP connect message: \"%s\".",message);
if (send(sockfd, message, strlen(message), 0) != strlen(message)) {
// debug(1, "Send failed");
response.code = 493; // Client failed to send a message
int needmore = 1;
int looperror = 0;
char buffer[1024];
memset(buffer,0,sizeof(buffer));
while (needmore && !looperror) {
const char *data = buffer;
int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
// debug(1,"Received %d bytes: \"%s\".",ndata,buffer);
if (ndata <= 0) {
debug(1, "Error receiving data.");
} else {
response.body = malloc(2048); // it can resize this if necessary
response.malloced_size = 2048;
struct http_roundtripper rt;
http_init(&rt, responseFuncs, &response);
int needmore = 1;
int looperror = 0;
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
while (needmore && !looperror) {
const char *data = buffer;
int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
// debug(1,"Received %d bytes: \"%s\".",ndata,buffer);
if (ndata <= 0) {
debug(1, "Error receiving data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
response.code = 495; // Error receiving response
looperror = 1;
}
while (needmore && ndata && !looperror) {
int read;
needmore = http_data(&rt, data, ndata, &read);
ndata -= read;
data += read;
}
}
if (http_iserror(&rt)) {
debug(1, "Error parsing data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
response.code = 495; // Error receiving response
looperror = 1;
}
while (needmore && ndata && !looperror) {
int read;
needmore = http_data(&rt, data, ndata, &read);
ndata -= read;
data += read;
}
// debug(1,"Size of response body is %d",response.size);
http_free(&rt);
}
if (http_iserror(&rt)) {
debug(1, "Error parsing data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
}
// debug(1,"Size of response body is %d",response.size);
http_free(&rt);
}
}
close(sockfd);
// debug(1,"DACP socket closed.");
}
close(sockfd);
// debug(1,"DACP socket closed.");
pthread_mutex_unlock(&dacp_conversation_lock);
} else {
// debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible
// timeout?");
response.code = 494; // This client is already busy
}
pthread_mutex_unlock(&dacp_conversation_lock);
} else {
// debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
response.code = 494; // This client is already busy
}
}
*body = response.body;
*bodysize = response.size;
@@ -281,8 +286,9 @@ void *dacp_monitor_thread_code(void *na) {
pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
}
pthread_mutex_unlock(&dacp_server_information_lock);
debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
dacp_server.ip_string, dacp_server.port, scan_index++);
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
// dacp_server.ip_string, dacp_server.port, scan_index);
scan_index++;
ssize_t le;
char *response = NULL;
int32_t item_size;
@@ -292,18 +298,21 @@ void *dacp_monitor_thread_code(void *na) {
int result = dacp_send_command(command, &response, &le);
if (result == 200) {
char *sp = response;
if (le >= 0) {
if (le >= 8) {
// here, we know that we are receiving playerstatusupdates, so set a flag
metadata.playerstatusupdates_are_received = 1;
// here start looking for the contents of the status update
if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
sp -= item_size; // drop down into the array -- don't skip over it
le -= 8;
char typestring[5];
// we need to acquire the metadata data structure and possibly update it
while (le >= 8) {
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
*(uint32_t *)typestring = htonl(type);
typestring[4] = 0;
printf("\"%s\" %4d", typestring, item_size);
//*(uint32_t *)typestring = htonl(type);
// typestring[4] = 0;
// printf("\"%s\" %4d", typestring, item_size);
le -= item_size + 8;
char *t;
@@ -312,67 +321,221 @@ void *dacp_monitor_thread_code(void *na) {
int32_t r;
uint64_t s, v;
int i;
switch (type) {
case 'mstt':
case 'cant':
case 'cast':
case 'cmmk':
case 'caas':
case 'caar':
case 'astm':
t = sp - item_size;
r = ntohl(*(int32_t *)(t));
printf(" %d", r);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t&0xff);
t++;
}
printf(")");
break;
case 'cmsr':
switch (type) {
case 'cmsr': // revision number
t = sp - item_size;
revision_number = ntohl(*(int32_t *)(t));
printf(" Serial Number: %d", revision_number);
// debug(1," Serial Number: %d", revision_number);
break;
case 'cann':
case 'cana':
case 'canl':
case 'cang':
case 'caps': // play status
t = sp - item_size;
st = strndup(t, item_size);
printf(" \"%s\"", st);
free(st);
break;
case 'asai':
t = sp - item_size;
s = ntohl(*(uint32_t *)(t));
s = s << 32;
t += 4;
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
s += v;
printf(" %lu", s);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t&0xff);
t++;
r = *(unsigned char *)(t);
switch (r) {
case 2:
if (metadata.play_status != PS_STOPPED) {
metadata.play_status = PS_STOPPED;
metadata.play_status_changed = 1;
debug(1, "Play status set to \"stopped\".");
metadata.changed = 1;
}
break;
case 3:
if (metadata.play_status != PS_PAUSED) {
metadata.play_status = PS_PAUSED;
metadata.play_status_changed = 1;
debug(1, "Play status set to \"paused\".");
metadata.changed = 1;
}
break;
case 4:
if (metadata.play_status != PS_PLAYING) {
metadata.play_status = PS_PLAYING;
metadata.play_status_changed = 1;
debug(1, "Play status set to \"playing\".");
metadata.changed = 1;
}
break;
default:
debug(1, "Unrecognised play status %d received.", r);
break;
}
printf(")");
break;
case 'cash': // shuffle status
t = sp - item_size;
r = *(unsigned char *)(t);
switch (r) {
case 0:
if (metadata.shuffle_status != SS_OFF) {
metadata.shuffle_status = SS_OFF;
metadata.shuffle_status_changed = 1;
debug(1, "Shuffle status set to \"off\".");
metadata.changed = 1;
}
break;
case 1:
if (metadata.shuffle_status != SS_ON) {
metadata.shuffle_status = SS_ON;
metadata.shuffle_status_changed = 1;
debug(1, "Shuffle status set to \"on\".");
metadata.changed = 1;
}
break;
default:
debug(1, "Unrecognised shuffle status %d received.", r);
break;
}
break;
case 'carp': // repeat status
t = sp - item_size;
r = *(unsigned char *)(t);
switch (r) {
case 0:
if (metadata.repeat_status != RS_NONE) {
metadata.repeat_status = RS_NONE;
metadata.repeat_status_changed = 1;
debug(1, "Repeat status set to \"none\".");
metadata.changed = 1;
}
break;
case 1:
if (metadata.repeat_status != RS_SINGLE) {
metadata.repeat_status = RS_SINGLE;
metadata.repeat_status_changed = 1;
debug(1, "Repeat status set to \"single\".");
metadata.changed = 1;
}
break;
case 2:
if (metadata.repeat_status != RS_ALL) {
metadata.repeat_status = RS_ALL;
metadata.repeat_status_changed = 1;
debug(1, "Repeat status set to \"all\".");
metadata.changed = 1;
}
break;
default:
debug(1, "Unrecognised repeat status %d received.", r);
break;
}
break;
case 'cann': // track name
t = sp - item_size;
if ((metadata.track_name == NULL) ||
(strncmp(metadata.track_name, t, item_size) != 0)) {
if (metadata.track_name)
free(metadata.track_name);
metadata.track_name = strndup(t, item_size);
debug(1, "Track name set to: \"%s\"", metadata.track_name);
metadata.track_name_changed = 1;
metadata.changed = 1;
}
break;
case 'cana': // artist name
t = sp - item_size;
if ((metadata.artist_name == NULL) ||
(strncmp(metadata.artist_name, t, item_size) != 0)) {
if (metadata.artist_name)
free(metadata.artist_name);
metadata.artist_name = strndup(t, item_size);
debug(1, "Artist name set to: \"%s\"", metadata.artist_name);
metadata.artist_name_changed = 1;
metadata.changed = 1;
}
break;
case 'canl': // album name
t = sp - item_size;
if ((metadata.album_name == NULL) ||
(strncmp(metadata.album_name, t, item_size) != 0)) {
if (metadata.album_name)
free(metadata.album_name);
metadata.album_name = strndup(t, item_size);
debug(1, "Album name set to: \"%s\"", metadata.album_name);
metadata.album_name_changed = 1;
metadata.changed = 1;
}
break;
case 'cang': // genre
t = sp - item_size;
if ((metadata.genre == NULL) || (strncmp(metadata.genre, t, item_size) != 0)) {
if (metadata.genre)
free(metadata.genre);
metadata.genre = strndup(t, item_size);
debug(1, "Genre set to: \"%s\"", metadata.genre);
metadata.genre_changed = 1;
metadata.changed = 1;
}
break;
case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware --
// see reference above)
t = sp - item_size;
if (memcmp(metadata.item_composite_id, t, sizeof(metadata.item_composite_id)) != 0) {
memcpy(metadata.item_composite_id, t, sizeof(metadata.item_composite_id));
char st[33];
char *pt = st;
int it;
for (it = 0; it < 16; it++) {
sprintf(pt, "%02X", metadata.item_composite_id[it]);
pt += 2;
}
*pt = 0;
debug(1, "Item composite ID set to 0x%s.", st);
metadata.item_id_changed = 1;
metadata.changed = 1;
}
break;
/*
case 'mstt':
case 'cant':
case 'cast':
case 'cmmk':
case 'caas':
case 'caar':
case 'astm':
t = sp - item_size;
r = ntohl(*(int32_t *)(t));
printf(" %d", r);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t & 0xff);
t++;
}
printf(")");
break;
case 'asai':
t = sp - item_size;
s = ntohl(*(uint32_t *)(t));
s = s << 32;
t += 4;
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
s += v;
printf(" %lu", s);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t & 0xff);
t++;
}
printf(")");
break;
*/
default:
printf(" 0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t&0xff);
t++;
}
/*
printf(" 0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t & 0xff);
t++;
}
*/
break;
}
printf("\n");
// printf("\n");
}
// now, if the metadata is changed, send a signal
} else {
printf("Status Update not found.\n");
}
@@ -381,6 +544,7 @@ void *dacp_monitor_thread_code(void *na) {
debug(1, "Can't find any content in playerstatusupdate request");
}
} else {
if (result != 403)
debug(1, "Unexpected response %d to playerstatusupdate request", result);
}
if (response) {
+36
View File
@@ -0,0 +1,36 @@
/*
* Metadata store and access methods.
* This file is part of Shairport Sync.
* Copyright (c) Mike Brady 2017
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string.h>
#include "dacp.h"
#include "metadata.h"
void metadata_bundle_init(void) {
debug(1, "Metadata bundle initialisation.");
memset(&metadata, 0, sizeof(metadata));
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "common.h"
#include "config.h"
#include <pthread.h>
enum play_status_type {
PS_PLAYING = 0,
PS_PAUSED,
PS_STOPPED,
} play_status_type;
enum shuffle_status_type {
SS_OFF = 0,
SS_ON,
} shuffle_status_type;
enum repeat_status_type {
RS_NONE = 0,
RS_SINGLE,
RS_ALL,
} repeat_status_type;
typedef struct metadata_bundle {
int changed; // normally 0, nonzero if a field has been changed
int playerstatusupdates_are_received; // false if it's "traditional" metadata
enum play_status_type play_status;
int play_status_changed;
enum shuffle_status_type shuffle_status;
int shuffle_status_changed;
enum repeat_status_type repeat_status;
int repeat_status_changed;
char *track_name; // a malloced string -- if non-zero, free it before replacing it
int track_name_changed;
char *artist_name; // a malloced string -- if non-zero, free it before replacing it
int artist_name_changed;
char *album_name; // a malloced string -- if non-zero, free it before replacing it
int album_name_changed;
char *genre; // a malloced string -- if non-zero, free it before replacing it
int genre_changed;
uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c
int item_id_changed;
unsigned char
item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid
} metadata_bundle;
struct metadata_bundle metadata;
void metadata_bundle_init(void);
+3 -2
View File
@@ -231,8 +231,9 @@ void *rtp_control_receiver(void *arg) {
sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
if (la != config.latency) {
config.latency = la;
//debug(1,
// "Using negotiated latency of %lld frames and a static latency correction of %lld",
// debug(1,
// "Using negotiated latency of %lld frames and a static latency correction of
// %lld",
// sync_rtp_timestamp - rtp_timestamp_less_latency, conn->staticLatencyCorrection);
}
}
+6
View File
@@ -60,6 +60,7 @@
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
#include "dacp.h"
#include "metadata.h"
#endif
#ifdef HAVE_DBUS
@@ -1540,6 +1541,11 @@ int main(int argc, char **argv) {
metadata_init(); // create the metadata pipe if necessary
#endif
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
debug(1, "Initialising metadata bundle");
metadata_bundle_init();
#endif
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
debug(1, "Requesting DACP Monitor");
dacp_monitor_start();