mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-10 05:08:11 -05:00
565 lines
16 KiB
C
565 lines
16 KiB
C
/*
|
|
* $Id: $
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "compat.h"
|
|
#include "ff-plugins.h"
|
|
#include "rsp.h"
|
|
#include "xml-rpc.h"
|
|
|
|
typedef struct tag_rsp_privinfo {
|
|
DB_QUERY dq;
|
|
int uri_count;
|
|
char *uri_sections[10];
|
|
} PRIVINFO;
|
|
|
|
/* Forwards */
|
|
PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *);
|
|
void plugin_handler(WS_CONNINFO *pwsc);
|
|
int plugin_auth(WS_CONNINFO *pwsc, char *username, char *password);
|
|
void rsp_info(WS_CONNINFO *pwsc, PRIVINFO *ppi);
|
|
void rsp_db(WS_CONNINFO *pwsc, PRIVINFO *ppi);
|
|
void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi);
|
|
void rsp_browse(WS_CONNINFO *pwsc, PRIVINFO *ppi);
|
|
void rsp_stream(WS_CONNINFO *pwsc, PRIVINFO *ppi);
|
|
void rsp_error(WS_CONNINFO *pwsc, PRIVINFO *ppi, int eno, char *estr);
|
|
|
|
/* Globals */
|
|
PLUGIN_OUTPUT_FN _pofn = { plugin_handler, plugin_auth };
|
|
PLUGIN_REND_INFO _pri[] = {
|
|
{ "_rsp._tcp", NULL },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
PLUGIN_INPUT_FN *_ppi;
|
|
|
|
PLUGIN_INFO _pi = {
|
|
PLUGIN_VERSION, /* version */
|
|
PLUGIN_OUTPUT, /* type */
|
|
"rsp/" VERSION, /* server */
|
|
"/rsp/.*", /* url */
|
|
&_pofn, /* output fns */
|
|
NULL, /* event fns */
|
|
NULL, /* transcode fns */
|
|
_pri, /* rend info */
|
|
NULL /* transcode info */
|
|
};
|
|
|
|
typedef struct tag_response {
|
|
char *uri[10];
|
|
void (*dispatch)(WS_CONNINFO *, PRIVINFO *);
|
|
} PLUGIN_RESPONSE;
|
|
|
|
|
|
PLUGIN_RESPONSE rsp_uri_map[] = {
|
|
{{"rsp", "info",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL }, rsp_info },
|
|
{{"rsp", "db" ,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL }, rsp_db },
|
|
{{"rsp", "db" , "*",NULL,NULL,NULL,NULL,NULL,NULL,NULL }, rsp_playlist },
|
|
{{"rsp", "db" , "*", "*",NULL,NULL,NULL,NULL,NULL,NULL }, rsp_browse },
|
|
{{"rsp","stream", "*",NULL,NULL,NULL,NULL,NULL,NULL,NULL }, rsp_stream }
|
|
};
|
|
|
|
#define E_RSP 0x0000
|
|
#define E_DB 0x1000
|
|
|
|
|
|
#define T_STRING 0
|
|
#define T_INT 1
|
|
#define T_DATE 2
|
|
|
|
#define F_FULL 1
|
|
#define F_BROWSE 2
|
|
#define F_ID 4
|
|
#define F_DETAILED 8
|
|
|
|
typedef struct tag_fieldspec {
|
|
char *name;
|
|
int flags; /* 1: full, 2: browse, 4: id */
|
|
int type; /* 0: string, 1: int, 2: date */
|
|
} FIELDSPEC;
|
|
|
|
FIELDSPEC rsp_playlist_fields[] = {
|
|
{ "id" , 15, T_INT },
|
|
{ "title" , 11, T_STRING },
|
|
{ "type" , 8, T_INT },
|
|
{ "items" , 11, T_INT },
|
|
{ "query" , 8, T_STRING },
|
|
{ "db_timestamp" , 8, T_DATE },
|
|
{ "path" , 8, T_STRING },
|
|
{ "index" , 8, T_INT },
|
|
{ NULL , 0, 0 }
|
|
};
|
|
|
|
FIELDSPEC rsp_fields[] = {
|
|
{ "id" , 15, T_INT },
|
|
{ "path" , 8, T_STRING },
|
|
{ "fname" , 8, T_STRING },
|
|
{ "title" , 15, T_STRING },
|
|
{ "artist" , 11, T_STRING },
|
|
{ "album" , 11, T_STRING },
|
|
{ "genre" , 9, T_STRING },
|
|
{ "comment" , 9, T_STRING },
|
|
{ "type" , 9, T_STRING },
|
|
{ "composer" , 9, T_STRING },
|
|
{ "orchestra" , 9, T_STRING },
|
|
{ "conductor" , 9, T_STRING },
|
|
{ "grouping" , 0, T_STRING },
|
|
{ "url" , 9, T_STRING },
|
|
{ "bitrate" , 9, T_INT },
|
|
{ "samplerate" , 9, T_INT },
|
|
{ "song_length" , 9, T_INT },
|
|
{ "file_size" , 9, T_INT },
|
|
{ "year" , 9, T_INT },
|
|
{ "track" , 11, T_INT },
|
|
{ "total_tracks" , 9, T_INT },
|
|
{ "disc" , 11, T_INT },
|
|
{ "total_discs" , 9, T_INT },
|
|
{ "bpm" , 9, T_INT },
|
|
{ "compilation" , 9, T_INT },
|
|
{ "rating" , 9, T_INT },
|
|
{ "play_count" , 9, T_INT },
|
|
{ "data_kind" , 8, T_INT },
|
|
{ "item_kind" , 8, T_INT },
|
|
{ "description" , 9, T_STRING },
|
|
{ "time_added" , 9, T_DATE },
|
|
{ "time_modified", 9, T_DATE },
|
|
{ "time_played" , 9, T_DATE },
|
|
{ "db_timestamp" , 8, T_DATE },
|
|
{ "disabled" , 9, T_INT },
|
|
{ "sample_count" , 8, T_INT },
|
|
{ "force_update" , 8, T_INT },
|
|
{ "codectype" , 15, T_INT },
|
|
{ "idx" , 8, T_INT },
|
|
{ "has_video" , 8, T_INT },
|
|
{ "contentrating", 8, T_INT },
|
|
{ NULL , 0 }
|
|
};
|
|
|
|
/**
|
|
* return info about this plugin module
|
|
*/
|
|
PLUGIN_INFO *plugin_info(PLUGIN_INPUT_FN *ppi) {
|
|
_ppi = ppi;
|
|
return &_pi;
|
|
}
|
|
|
|
|
|
/**
|
|
* check for auth. Kind of a ham-handed implementation, but
|
|
* works.
|
|
*/
|
|
int plugin_auth(WS_CONNINFO *pwsc, char *username, char *password) {
|
|
char *readpassword;
|
|
|
|
readpassword = _ppi->conf_alloc_string("general","password",NULL);
|
|
if(password == NULL) { /* testing to see if we need a pw */
|
|
if((readpassword == NULL) || (strlen(readpassword)==0)) {
|
|
if(readpassword) _ppi->conf_dispose_string(readpassword);
|
|
return TRUE;
|
|
} else {
|
|
_ppi->conf_dispose_string(readpassword);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
if(strcasecmp(password,readpassword)) {
|
|
_ppi->conf_dispose_string(readpassword);
|
|
return FALSE;
|
|
} else {
|
|
_ppi->conf_dispose_string(readpassword);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return TRUE; /* ?? */
|
|
}
|
|
|
|
/**
|
|
* dispatch handler for web stuff
|
|
*/
|
|
void plugin_handler(WS_CONNINFO *pwsc) {
|
|
char *string, *save, *token;
|
|
PRIVINFO *ppi;
|
|
int elements;
|
|
int index, part;
|
|
int found;
|
|
|
|
_ppi->log(E_DBG,"Getting uri...\n");
|
|
|
|
string = _ppi->ws_uri(pwsc);
|
|
string++;
|
|
|
|
_ppi->log(E_DBG,"Mallocing privinfo...\n");
|
|
ppi = (PRIVINFO *)malloc(sizeof(PRIVINFO));
|
|
if(ppi) {
|
|
memset(ppi,0,sizeof(PRIVINFO));
|
|
}
|
|
|
|
if(!ppi) {
|
|
_ppi->ws_returnerror(pwsc,500,"Malloc error in plugin_handler");
|
|
return;
|
|
}
|
|
|
|
memset((void*)&ppi->dq,0,sizeof(DB_QUERY));
|
|
|
|
_ppi->log(E_DBG,"Tokenizing url\n");
|
|
while((ppi->uri_count < 10) && (token=strtok_r(string,"/",&save))) {
|
|
string=NULL;
|
|
ppi->uri_sections[ppi->uri_count++] = token;
|
|
}
|
|
|
|
elements = sizeof(rsp_uri_map) / sizeof(PLUGIN_RESPONSE);
|
|
_ppi->log(E_DBG,"Found %d elements\n",elements);
|
|
|
|
index = 0;
|
|
found = 0;
|
|
|
|
while((!found) && (index < elements)) {
|
|
/* test this set */
|
|
_ppi->log(E_DBG,"Checking reponse %d\n",index);
|
|
part=0;
|
|
while(part < 10) {
|
|
if((rsp_uri_map[index].uri[part]) && (!ppi->uri_sections[part]))
|
|
break;
|
|
if((ppi->uri_sections[part]) && (!rsp_uri_map[index].uri[part]))
|
|
break;
|
|
|
|
if((rsp_uri_map[index].uri[part]) &&
|
|
(strcmp(rsp_uri_map[index].uri[part],"*") != 0)) {
|
|
if(strcmp(rsp_uri_map[index].uri[part],
|
|
ppi->uri_sections[part])!= 0)
|
|
break;
|
|
}
|
|
part++;
|
|
}
|
|
|
|
if(part == 10) {
|
|
found = 1;
|
|
_ppi->log(E_DBG,"Found it! Index: %d\n",index);
|
|
} else {
|
|
index++;
|
|
}
|
|
}
|
|
|
|
if(found) {
|
|
rsp_uri_map[index].dispatch(pwsc, ppi);
|
|
_ppi->ws_close(pwsc);
|
|
free(ppi);
|
|
return;
|
|
}
|
|
|
|
rsp_error(pwsc, ppi, 1, "Bad path");
|
|
_ppi->ws_close(pwsc);
|
|
free(ppi);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* get server info
|
|
*/
|
|
void rsp_info(WS_CONNINFO *pwsc, PRIVINFO *ppi) {
|
|
XMLSTRUCT *pxml;
|
|
char servername[256];
|
|
int size;
|
|
|
|
_ppi->log(E_DBG,"Starting rsp_info\n");
|
|
|
|
pxml = xml_init(pwsc,1);
|
|
|
|
xml_push(pxml,"response");
|
|
xml_push(pxml,"status");
|
|
xml_output(pxml,"errorcode","0");
|
|
xml_output(pxml,"errorstring","");
|
|
xml_output(pxml,"records","0");
|
|
xml_output(pxml,"totalrecords","0");
|
|
xml_pop(pxml); /* status */
|
|
|
|
/* info block */
|
|
xml_push(pxml,"info");
|
|
xml_output(pxml,"count","%d",_ppi->db_count());
|
|
xml_output(pxml,"rsp-version","%s",RSP_VERSION);
|
|
|
|
xml_output(pxml,"server-version","%s",_ppi->server_ver());
|
|
|
|
size = sizeof(servername);
|
|
_ppi->server_name(servername,&size);
|
|
xml_output(pxml,"name","%s",servername);
|
|
xml_pop(pxml); /* info */
|
|
|
|
xml_pop(pxml); /* response */
|
|
xml_deinit(pxml);
|
|
}
|
|
|
|
/**
|
|
* /rsp/db
|
|
*
|
|
* dump details about all playlists
|
|
*/
|
|
void rsp_db(WS_CONNINFO *pwsc, PRIVINFO *ppi) {
|
|
XMLSTRUCT *pxml;
|
|
char *pe;
|
|
int err;
|
|
char **row;
|
|
int rowindex;
|
|
|
|
ppi->dq.query_type = QUERY_TYPE_PLAYLISTS;
|
|
|
|
if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) {
|
|
rsp_error(pwsc, ppi, err | E_DB, pe);
|
|
_ppi->db_enum_dispose(NULL,&ppi->dq);
|
|
return;
|
|
}
|
|
|
|
pxml = xml_init(pwsc,1);
|
|
|
|
xml_push(pxml,"response");
|
|
xml_push(pxml,"status");
|
|
xml_output(pxml,"errorcode","0");
|
|
xml_output(pxml,"errorstring","");
|
|
xml_output(pxml,"records","%d",ppi->dq.totalcount);
|
|
xml_output(pxml,"totalrecords","%d",ppi->dq.totalcount);
|
|
xml_pop(pxml); /* status */
|
|
|
|
xml_push(pxml,"playlists");
|
|
|
|
while((_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) {
|
|
xml_push(pxml,"playlist");
|
|
rowindex=0;
|
|
while(rsp_playlist_fields[rowindex].name) {
|
|
if(rsp_playlist_fields[rowindex].flags & F_FULL) {
|
|
xml_output(pxml,rsp_playlist_fields[rowindex].name,"%s",
|
|
row[rowindex]);
|
|
}
|
|
rowindex++;
|
|
}
|
|
xml_pop(pxml); /* playlist */
|
|
}
|
|
|
|
_ppi->db_enum_end(NULL);
|
|
_ppi->db_enum_dispose(NULL,&ppi->dq);
|
|
|
|
xml_pop(pxml); /* playlists */
|
|
xml_pop(pxml); /* response */
|
|
xml_deinit(pxml);
|
|
|
|
}
|
|
|
|
/**
|
|
* get all items under the playlist
|
|
*/
|
|
void rsp_playlist(WS_CONNINFO *pwsc, PRIVINFO *ppi) {
|
|
XMLSTRUCT *pxml;
|
|
char *pe;
|
|
int err;
|
|
char **row;
|
|
int rowindex;
|
|
int returned;
|
|
char *browse_type;
|
|
int type;
|
|
int transcode;
|
|
unsigned int samplerate;
|
|
int done = 0;
|
|
|
|
ppi->dq.filter = _ppi->ws_getvar(pwsc,"query");
|
|
ppi->dq.filter_type = FILTER_TYPE_FIREFLY;
|
|
|
|
if(_ppi->ws_getvar(pwsc,"offset")) {
|
|
ppi->dq.offset = atoi(_ppi->ws_getvar(pwsc,"offset"));
|
|
}
|
|
if(_ppi->ws_getvar(pwsc,"limit")) {
|
|
ppi->dq.limit = atoi(_ppi->ws_getvar(pwsc,"limit"));
|
|
}
|
|
|
|
browse_type = _ppi->ws_getvar(pwsc,"type");
|
|
type = F_FULL;
|
|
|
|
if(browse_type) {
|
|
if(strcasecmp(browse_type,"browse") == 0) {
|
|
type = F_BROWSE;
|
|
} else if(strcasecmp(browse_type,"id") == 0) {
|
|
type = F_ID;
|
|
} else if(strcasecmp(browse_type,"detailed") ==0) {
|
|
type = F_DETAILED;
|
|
}
|
|
}
|
|
|
|
ppi->dq.query_type = QUERY_TYPE_ITEMS;
|
|
ppi->dq.playlist_id = atoi(ppi->uri_sections[2]);
|
|
|
|
if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) {
|
|
rsp_error(pwsc, ppi, err | E_DB, pe);
|
|
_ppi->db_enum_dispose(NULL,&ppi->dq);
|
|
free(pe);
|
|
return;
|
|
}
|
|
|
|
pxml = xml_init(pwsc,1);
|
|
|
|
if(ppi->dq.offset > ppi->dq.totalcount) {
|
|
returned = 0;
|
|
} else {
|
|
returned = ppi->dq.limit;
|
|
if(returned > (ppi->dq.totalcount - ppi->dq.offset))
|
|
returned = ppi->dq.totalcount - ppi->dq.offset;
|
|
}
|
|
|
|
xml_push(pxml,"response");
|
|
xml_push(pxml,"status");
|
|
xml_output(pxml,"errorcode","0");
|
|
xml_output(pxml,"errorstring","");
|
|
xml_output(pxml,"records","%d",returned);
|
|
xml_output(pxml,"totalrecords","%d",ppi->dq.totalcount);
|
|
xml_pop(pxml); /* status */
|
|
|
|
xml_push(pxml,"items");
|
|
|
|
while((!done) && (_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) &&
|
|
(row)) {
|
|
xml_push(pxml,"item");
|
|
rowindex=0;
|
|
transcode = 0;
|
|
|
|
transcode = _ppi->should_transcode(pwsc,row[37]);
|
|
|
|
_ppi->log(E_DBG,"Transcode: %d, %s: %s\n",transcode,row[37],row[2]);
|
|
|
|
while(rsp_fields[rowindex].name) {
|
|
if((rsp_fields[rowindex].flags & type) &&
|
|
(row[rowindex] && strlen(row[rowindex]))) {
|
|
if(transcode) {
|
|
switch(rowindex) {
|
|
case 8:
|
|
xml_output(pxml,rsp_fields[rowindex].name,"%s","wav");
|
|
break;
|
|
case 29:
|
|
xml_output(pxml,rsp_fields[rowindex].name,"%s",
|
|
"wav audio file");
|
|
break;
|
|
case 14: /* bitrate */
|
|
samplerate = atoi(row[15]);
|
|
if(samplerate) {
|
|
samplerate = (samplerate * 8) / 250;
|
|
} else {
|
|
samplerate = 1411;
|
|
}
|
|
xml_output(pxml,rsp_fields[rowindex].name,"%d",
|
|
samplerate);
|
|
break;
|
|
case 37:
|
|
xml_output(pxml,rsp_fields[rowindex].name,"%s","wav");
|
|
xml_output(pxml,"original_codec","%s",row[37]);
|
|
break;
|
|
default:
|
|
xml_output(pxml,rsp_fields[rowindex].name,"%s",
|
|
row[rowindex]);
|
|
break;
|
|
}
|
|
} else {
|
|
/* check for pushing against closed socket */
|
|
if(xml_output(pxml,rsp_fields[rowindex].name,"%s",
|
|
row[rowindex]) == -1)
|
|
done=1;
|
|
}
|
|
|
|
}
|
|
rowindex++;
|
|
}
|
|
xml_pop(pxml); /* item */
|
|
}
|
|
|
|
_ppi->db_enum_end(NULL);
|
|
|
|
xml_pop(pxml); /* items */
|
|
xml_pop(pxml); /* response */
|
|
xml_deinit(pxml);
|
|
}
|
|
|
|
void rsp_browse(WS_CONNINFO *pwsc, PRIVINFO *ppi) {
|
|
XMLSTRUCT *pxml;
|
|
char *pe;
|
|
int err;
|
|
char **row;
|
|
int returned;
|
|
|
|
/* this might fail if an unsupported browse type */
|
|
ppi->dq.query_type = QUERY_TYPE_DISTINCT;
|
|
ppi->dq.distinct_field = ppi->uri_sections[3];
|
|
ppi->dq.filter = _ppi->ws_getvar(pwsc,"query");
|
|
ppi->dq.filter_type = FILTER_TYPE_FIREFLY;
|
|
|
|
if(_ppi->ws_getvar(pwsc,"offset")) {
|
|
ppi->dq.offset = atoi(_ppi->ws_getvar(pwsc,"offset"));
|
|
}
|
|
|
|
if(_ppi->ws_getvar(pwsc,"limit")) {
|
|
ppi->dq.limit = atoi(_ppi->ws_getvar(pwsc,"limit"));
|
|
}
|
|
|
|
ppi->dq.playlist_id = atoi(ppi->uri_sections[2]);
|
|
|
|
if((err=_ppi->db_enum_start(&pe,&ppi->dq)) != 0) {
|
|
rsp_error(pwsc, ppi, err | E_DB, pe);
|
|
_ppi->db_enum_dispose(NULL,&ppi->dq);
|
|
return;
|
|
}
|
|
|
|
pxml = xml_init(pwsc,1);
|
|
|
|
if(ppi->dq.offset > ppi->dq.totalcount) {
|
|
returned = 0;
|
|
} else {
|
|
returned = ppi->dq.limit;
|
|
if(returned > (ppi->dq.totalcount - ppi->dq.offset))
|
|
returned = ppi->dq.totalcount - ppi->dq.offset;
|
|
}
|
|
|
|
xml_push(pxml,"response");
|
|
xml_push(pxml,"status");
|
|
xml_output(pxml,"errorcode","0");
|
|
xml_output(pxml,"errorstring","");
|
|
xml_output(pxml,"records","%d",returned);
|
|
xml_output(pxml,"totalrecords","%d",ppi->dq.totalcount);
|
|
xml_pop(pxml); /* status */
|
|
|
|
xml_push(pxml,"items");
|
|
|
|
while((_ppi->db_enum_fetch_row(NULL,&row,&ppi->dq) == 0) && (row)) {
|
|
xml_output(pxml,"item","%s",row[0]);
|
|
}
|
|
|
|
_ppi->db_enum_end(NULL);
|
|
_ppi->db_enum_dispose(NULL,&ppi->dq);
|
|
|
|
xml_pop(pxml); /* items */
|
|
xml_pop(pxml); /* response */
|
|
xml_deinit(pxml);
|
|
}
|
|
|
|
void rsp_stream(WS_CONNINFO *pwsc, PRIVINFO *ppi) {
|
|
_ppi->stream(pwsc, ppi->uri_sections[2]);
|
|
return;
|
|
}
|
|
|
|
void rsp_error(WS_CONNINFO *pwsc, PRIVINFO *ppi, int eno, char *estr) {
|
|
XMLSTRUCT *pxml;
|
|
|
|
pxml = xml_init(pwsc, 1);
|
|
xml_push(pxml,"response");
|
|
xml_push(pxml,"status");
|
|
xml_output(pxml,"errorcode","%d",eno);
|
|
xml_output(pxml,"errorstring","%s",estr);
|
|
xml_output(pxml,"records","0");
|
|
xml_output(pxml,"totalrecords","0");
|
|
xml_pop(pxml); /* status */
|
|
xml_pop(pxml); /* response */
|
|
xml_deinit(pxml);
|
|
_ppi->ws_close(pwsc);
|
|
}
|
|
|