Add rsp files

This commit is contained in:
Ron Pedde 2006-04-30 23:02:35 +00:00
parent a2ea8c3de0
commit 553c57ccc1
6 changed files with 1480 additions and 0 deletions

516
src/plugins/compat.c Normal file
View File

@ -0,0 +1,516 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#if !HAVE_STRCASESTR
/* case-independent string matching, similar to strstr but
* matching */
char * strcasestr(char* haystack, char* needle) {
int i;
int nlength = (int) strlen (needle);
int hlength = (int) strlen (haystack);
if (nlength > hlength) return NULL;
if (hlength <= 0) return NULL;
if (nlength <= 0) return haystack;
/* hlength and nlength > 0, nlength <= hlength */
for (i = 0; i <= (hlength - nlength); i++) {
if (strncasecmp (haystack + i, needle, nlength) == 0) {
return haystack + i;
}
}
/* substring not found */
return NULL;
}
#endif /* !HAVE_STRCASESTR */
/*
* Copyright (c) 1994 Powerdog Industries. All rights reserved.
*
* Redistribution and use in source and binary forms, without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgement:
* This product includes software developed by Powerdog Industries.
* 4. The name of Powerdog Industries may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef HAVE_STRPTIME
#define asizeof(a) (sizeof (a) / sizeof ((a)[0]))
#ifndef sun
struct dtconv {
char *abbrev_month_names[12];
char *month_names[12];
char *abbrev_weekday_names[7];
char *weekday_names[7];
char *time_format;
char *sdate_format;
char *dtime_format;
char *am_string;
char *pm_string;
char *ldate_format;
};
#endif
static struct dtconv En_US = {
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" },
{ "January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December" },
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" },
{ "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" },
"%H:%M:%S",
"%m/%d/%y",
"%a %b %e %T %Z %Y",
"AM",
"PM",
"%A, %B, %e, %Y"
};
#ifdef SUNOS4
extern int strncasecmp();
#endif
void lowercase_string(char *buffer) {
while(*buffer) {
*buffer = tolower(*buffer);
buffer++;
}
}
char *strptime(char *buf, char *fmt, struct tm *tm) {
char c,
*ptr;
int i, j,
len;
ptr = fmt;
while (*ptr != 0) {
if (*buf == 0)
break;
c = *ptr++;
if (c != '%') {
if (isspace(c))
while (*buf != 0 && isspace(*buf))
buf++;
else if (c != *buf++)
return 0;
continue;
}
c = *ptr++;
switch (c) {
case 0:
case '%':
if (*buf++ != '%')
return 0;
break;
case 'C':
buf = strptime(buf, En_US.ldate_format, tm);
if (buf == 0)
return 0;
break;
case 'c':
buf = strptime(buf, "%x %X", tm);
if (buf == 0)
return 0;
break;
case 'D':
buf = strptime(buf, "%m/%d/%y", tm);
if (buf == 0)
return 0;
break;
case 'R':
buf = strptime(buf, "%H:%M", tm);
if (buf == 0)
return 0;
break;
case 'r':
buf = strptime(buf, "%I:%M:%S %p", tm);
if (buf == 0)
return 0;
break;
case 'T':
buf = strptime(buf, "%H:%M:%S", tm);
if (buf == 0)
return 0;
break;
case 'X':
buf = strptime(buf, En_US.time_format, tm);
if (buf == 0)
return 0;
break;
case 'x':
buf = strptime(buf, En_US.sdate_format, tm);
if (buf == 0)
return 0;
break;
case 'j':
if (!isdigit(*buf))
return 0;
for (i = 0; *buf != 0 && isdigit(*buf); buf++) {
i *= 10;
i += *buf - '0';
}
if (i > 365)
return 0;
tm->tm_yday = i;
break;
case 'M':
case 'S':
if (*buf == 0 || isspace(*buf))
break;
if (!isdigit(*buf))
return 0;
for (j=0,i = 0; *buf != 0 && isdigit(*buf) && j<2; j++,buf++) {
i *= 10;
i += *buf - '0';
}
if (i > 59)
return 0;
if (c == 'M')
tm->tm_min = i;
else
tm->tm_sec = i;
if (*buf != 0 && isspace(*buf))
while (*ptr != 0 && !isspace(*ptr))
ptr++;
break;
case 'H':
case 'I':
case 'k':
case 'l':
if (!isdigit(*buf))
return 0;
for (j=0,i = 0; *buf != 0 && isdigit(*buf) && j<2; j++,buf++) {
i *= 10;
i += *buf - '0';
}
if (c == 'H' || c == 'k') {
if (i > 23)
return 0;
} else if (i > 11)
return 0;
tm->tm_hour = i;
if (*buf != 0 && isspace(*buf))
while (*ptr != 0 && !isspace(*ptr))
ptr++;
break;
case 'p':
len = (int) strlen(En_US.am_string);
lowercase_string( buf );
if (strncmp(buf, En_US.am_string, len) == 0) {
if (tm->tm_hour > 12)
return 0;
if (tm->tm_hour == 12)
tm->tm_hour = 0;
buf += len;
break;
}
len = (int) strlen(En_US.pm_string);
if (strncmp(buf, En_US.pm_string, len) == 0) {
if (tm->tm_hour > 12)
return 0;
if (tm->tm_hour != 12)
tm->tm_hour += 12;
buf += len;
break;
}
return 0;
case 'A':
case 'a':
for (i = 0; i < asizeof(En_US.weekday_names); i++) {
len = (int) strlen(En_US.weekday_names[i]);
lowercase_string( buf );
if (strncmp(buf,
En_US.weekday_names[i],
len) == 0)
break;
len = (int) strlen(En_US.abbrev_weekday_names[i]);
if (strncmp(buf,
En_US.abbrev_weekday_names[i],
len) == 0)
break;
}
if (i == asizeof(En_US.weekday_names))
return 0;
tm->tm_wday = i;
buf += len;
break;
case 'd':
case 'e':
if (!isdigit(*buf))
return 0;
for (j=0,i = 0; *buf != 0 && isdigit(*buf) && j<2; j++,buf++) {
i *= 10;
i += *buf - '0';
}
if (i > 31)
return 0;
tm->tm_mday = i;
if (*buf != 0 && isspace(*buf))
while (*ptr != 0 && !isspace(*ptr))
ptr++;
break;
case 'B':
case 'b':
case 'h':
for (i = 0; i < asizeof(En_US.month_names); i++) {
len = (int) strlen(En_US.month_names[i]);
lowercase_string( buf );
if (strncmp(buf, En_US.month_names[i],len) == 0)
break;
len = (int) strlen(En_US.abbrev_month_names[i]);
if (strncmp(buf,
En_US.abbrev_month_names[i],
len) == 0)
break;
}
if (i == asizeof(En_US.month_names))
return 0;
tm->tm_mon = i;
buf += len;
break;
case 'm':
if (!isdigit(*buf))
return 0;
for (j=0,i = 0; *buf != 0 && isdigit(*buf) && j<2; j++,buf++) {
i *= 10;
i += *buf - '0';
}
if (i < 1 || i > 12)
return 0;
tm->tm_mon = i - 1;
if (*buf != 0 && isspace(*buf))
while (*ptr != 0 && !isspace(*ptr))
ptr++;
break;
case 'Y':
case 'y':
if (*buf == 0 || isspace(*buf))
break;
if (!isdigit(*buf))
return 0;
for (j=0,i = 0; *buf != 0 && isdigit(*buf) && j<((c=='Y')?4:2); j++,buf++) {
i *= 10;
i += *buf - '0';
}
if (c == 'Y')
i -= 1900;
else if (i < 69) /*c=='y', 00-68 is for 20xx, the rest is for 19xx*/
i += 100;
if (i < 0)
return 0;
tm->tm_year = i;
if (*buf != 0 && isspace(*buf))
while (*ptr != 0 && !isspace(*ptr))
ptr++;
break;
}
}
return buf;
}
#endif /* ndef HAVE_STRPTIME */
/* Compliments of Jay Freeman <saurik@saurik.com> */
#if !HAVE_STRSEP
char *strsep(char **stringp, const char *delim) {
char *ret = *stringp;
if (ret == NULL) return(NULL); /* grrr */
if ((*stringp = strpbrk(*stringp, delim)) != NULL) {
*((*stringp)++) = '\0';
}
return(ret);
}
#endif /* !HAVE_STRSEP */
/* Reentrant string tokenizer. Generic version.
Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc.
This file is part of the GNU C Library.
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, 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. */
#ifndef HAVE_STRTOK_R
#include <stdio.h>
/* Parse S into tokens separated by characters in DELIM.
If S is NULL, the saved pointer in SAVE_PTR is used as
the next starting point. For example:
char s[] = "-abc-=-def";
char *sp;
x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
x = strtok_r(NULL, "=", &sp); // x = NULL
// s = "abc\0-def\0"
*/
char *strtok_r(char *s, const char *delim, char **last)
{
char *spanp;
int c, sc;
char *tok;
if (s == NULL && (s = *last) == NULL) {
return NULL;
}
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0; ) {
if (c == sc) {
goto cont;
}
}
if (c == 0) { /* no non-delimiter characters */
*last = NULL;
return NULL;
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0) {
s = NULL;
}
else {
char *w = s - 1;
*w = '\0';
}
*last = s;
return tok;
}
}
while (sc != 0);
}
/* NOTREACHED */
}
#endif
#ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm) {
time_t ret;
char *tz;
char buffer[255];
tz = getenv("TZ");
_putenv("TZ=UTC0");
_tzset();
ret = mktime(tm);
if(tz)
sprintf(buffer,"TZ=%s",tz);
else
strcpy(buffer,"TZ=");
_putenv(buffer);
_tzset();
return ret;
}
#endif

25
src/plugins/compat.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _COMPAT_H_
#define _COMPAT_H_
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifndef HAVE_STRCASESTR
extern char *strcasestr(char* haystack, char* needle);
#endif
#ifndef HAVE_STRPTIME
extern char *strptime(char *buf, char *fmt, struct tm *tm );
#endif
#ifndef HAVE_STRTOK_R
#undef strtok_r /* defend against win32 pthreads */
extern char *strtok_r(char *s, char *delim, char **last);
#endif
#ifndef HAVE_TIMEGM
extern time_t timegm(struct tm *tm);
#endif
#endif /* _COMPAT_H_ */

521
src/plugins/rsp.c Normal file
View File

@ -0,0 +1,521 @@
/*
* $Id: $
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mtd-plugins.h"
#include "rsp.h"
#include "xml-rpc.h"
/* Forwards */
PLUGIN_INFO *plugin_info(void);
void plugin_handler(WS_CONNINFO *pwsc);
int plugin_auth(WS_CONNINFO *pwsc, char *username, char *pw);
void rsp_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
void rsp_db(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
void rsp_playlist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
void rsp_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
void rsp_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
void rsp_error(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, int eno, char *estr);
/* Globals */
PLUGIN_OUTPUT_FN _pofn = { plugin_handler, plugin_auth };
PLUGIN_INFO _pi = {
PLUGIN_VERSION,
PLUGIN_OUTPUT,
"rsp/" RSP_VERSION,
"/rsp/.*",
&_pofn,
NULL
};
typedef struct tag_response {
char *uri[10];
void (*dispatch)(WS_CONNINFO *, DBQUERYINFO *);
} 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
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" , 7, T_INT },
{ "title" , 3, T_STRING },
{ "type" , 0, T_INT },
{ "items" , 3, T_INT },
{ "query" , 0, T_STRING },
{ "db_timestamp" , 0, T_DATE },
{ "path" , 0, T_STRING },
{ "index" , 0, T_INT },
{ NULL , 0, 0 }
};
FIELDSPEC rsp_fields[] = {
{ "id" , 7, T_INT },
{ "path" , 0, T_STRING },
{ "fname" , 0, T_STRING },
{ "title" , 7, T_STRING },
{ "artist" , 3, T_STRING },
{ "album" , 3, T_STRING },
{ "genre" , 1, T_STRING },
{ "comment" , 0, T_STRING },
{ "type" , 1, T_STRING },
{ "composer" , 1, T_STRING },
{ "orchestra" , 1, T_STRING },
{ "conductor" , 1, T_STRING },
{ "grouping" , 0, T_STRING },
{ "url" , 1, T_STRING },
{ "bitrate" , 1, T_INT },
{ "samplerate" , 1, T_INT },
{ "song_length" , 1, T_INT },
{ "file_size" , 1, T_INT },
{ "year" , 1, T_INT },
{ "track" , 3, T_INT },
{ "total_tracks" , 1, T_INT },
{ "disc" , 3, T_INT },
{ "total_discs" , 1, T_INT },
{ "bpm" , 1, T_INT },
{ "compilation" , 1, T_INT },
{ "rating" , 1, T_INT },
{ "play_count" , 1, T_INT },
{ "data_kind" , 0, T_INT },
{ "item_kind" , 0, T_INT },
{ "description" , 1, T_STRING },
{ "time_added" , 1, T_DATE },
{ "time_modified", 1, T_DATE },
{ "time_played" , 1, T_DATE },
{ "db_timestamp" , 0, T_DATE },
{ "disabled" , 1, T_INT },
{ "sample_count" , 0, T_INT },
{ "force_update" , 0, T_INT },
{ "codectype" , 7, T_INT },
{ "idx" , 0, T_INT },
{ "has_video" , 0, T_INT },
{ "contentrating", 0, T_INT },
{ NULL , 0 }
};
/**
* return info about this plugin module
*/
PLUGIN_INFO *plugin_info(void) {
return &_pi;
}
/**
* check for auth
*/
int plugin_auth(WS_CONNINFO *pwsc, char *username, char *pw) {
/* disable passwords for now */
return 1;
}
/**
* dispatch handler for web stuff
*/
void plugin_handler(WS_CONNINFO *pwsc) {
char *string, *save, *token;
DBQUERYINFO *pqi;
int elements;
int index, part;
int found;
string = infn->ws_uri(pwsc);
string++;
pqi = (DBQUERYINFO *)malloc(sizeof(DBQUERYINFO));
if(!pqi) {
infn->ws_returnerror(pwsc,500,"Malloc error in plugin_handler");
return;
}
memset(pqi,0,sizeof(DBQUERYINFO));
infn->log(E_DBG,"Tokenizing url\n");
while((pqi->uri_count < 10) && (token=strtok_r(string,"/",&save))) {
string=NULL;
pqi->uri_sections[pqi->uri_count++] = token;
}
elements = sizeof(rsp_uri_map) / sizeof(PLUGIN_RESPONSE);
infn->log(E_DBG,"Found %d elements\n",elements);
index = 0;
found = 0;
while((!found) && (index < elements)) {
/* test this set */
infn->log(E_DBG,"Checking reponse %d\n",index);
part=0;
while(part < 10) {
if((rsp_uri_map[index].uri[part]) && (!pqi->uri_sections[part]))
break;
if((pqi->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],
pqi->uri_sections[part])!= 0)
break;
}
part++;
}
if(part == 10) {
found = 1;
infn->log(E_DBG,"Found it! Index: %d\n",index);
} else {
index++;
}
}
if(found) {
rsp_uri_map[index].dispatch(pwsc, pqi);
infn->ws_close(pwsc);
return;
}
rsp_error(pwsc, pqi, 1, "Bad path");
infn->ws_close(pwsc);
return;
}
/**
* get server info
*/
void rsp_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
XMLSTRUCT *pxml;
char servername[256];
int size;
infn->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",infn->db_count());
xml_output(pxml,"rsp-version",RSP_VERSION);
xml_output(pxml,"server-version",infn->server_ver());
size = sizeof(servername);
infn->server_name(servername,&size);
xml_output(pxml,"name",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, DBQUERYINFO *pqi) {
XMLSTRUCT *pxml;
char *pe;
int err;
char **row;
int rowindex;
pqi->want_count = 1;
pqi->query_type = queryTypePlaylists;
if((err=infn->db_enum_start(&pe,pqi)) != 0) {
rsp_error(pwsc, pqi, err | E_DB, pe);
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",pqi->specifiedtotalcount);
xml_output(pxml,"totalrecords","%d",pqi->specifiedtotalcount);
xml_pop(pxml); /* status */
xml_push(pxml,"playlists");
while((infn->db_enum_fetch_row(NULL,&row,pqi) == 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 */
}
infn->db_enum_end(NULL);
xml_pop(pxml); /* playlists */
xml_pop(pxml); /* response */
xml_deinit(pxml);
}
/**
* get all items under the playlist
*/
void rsp_playlist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
XMLSTRUCT *pxml;
char *pe;
int err;
char **row;
int rowindex;
int returned;
char *browse_type;
int type;
char *query;
char *estr = NULL;
query = infn->ws_getvar(pwsc,"query");
if(query) {
pqi->pt = infn->sp_init();
if(!infn->sp_parse(pqi->pt,query)) {
estr = strdup(infn->sp_get_error(pqi->pt));
infn->log(E_LOG,"Ignoring bad query (%s): %s\n",query,estr);
infn->sp_dispose(pqi->pt);
pqi->pt = NULL;
}
}
pqi->index_type = indexTypeNone;
if(infn->ws_getvar(pwsc,"offset")) {
pqi->index_low = atoi(infn->ws_getvar(pwsc,"offset"));
}
if(infn->ws_getvar(pwsc,"limit")) {
pqi->index_high = pqi->index_low + atoi(infn->ws_getvar(pwsc,"limit")) -1;
if(pqi->index_high < pqi->index_low) {
pqi->index_high = 999999;
}
} else {
pqi->index_high = 999999; /* FIXME */
}
pqi->index_type = indexTypeSub;
browse_type = infn->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;
}
}
pqi->want_count = 1;
pqi->query_type = queryTypePlaylistItems;
pqi->playlist_id = atoi(pqi->uri_sections[2]);
if((err=infn->db_enum_start(&pe,pqi)) != 0) {
if(pqi->pt) infn->sp_dispose(pqi->pt);
if(estr) free(estr);
rsp_error(pwsc, pqi, err | E_DB, pe);
return;
}
pxml = xml_init(pwsc,1);
if(pqi->index_low > pqi->specifiedtotalcount) {
returned = 0;
} else {
returned = pqi->index_high - pqi->index_low + 1;
if(returned > (pqi->specifiedtotalcount - pqi->index_low))
returned = pqi->specifiedtotalcount - pqi->index_low;
}
xml_push(pxml,"response");
xml_push(pxml,"status");
xml_output(pxml,"errorcode","0");
xml_output(pxml,"errorstring",estr ? estr : "");
xml_output(pxml,"records","%d",returned);
xml_output(pxml,"totalrecords","%d",pqi->specifiedtotalcount);
xml_pop(pxml); /* status */
xml_push(pxml,"items");
while((infn->db_enum_fetch_row(NULL,&row,pqi) == 0) && (row)) {
xml_push(pxml,"item");
rowindex=0;
while(rsp_fields[rowindex].name) {
if((rsp_fields[rowindex].flags & type) &&
(row[rowindex] && strlen(row[rowindex]))) {
xml_output(pxml,rsp_fields[rowindex].name,"%s",
row[rowindex]);
}
rowindex++;
}
xml_pop(pxml); /* item */
}
infn->db_enum_end(NULL);
xml_pop(pxml); /* items */
xml_pop(pxml); /* response */
xml_deinit(pxml);
if(pqi->pt) infn->sp_dispose(pqi->pt);
if(estr) free(estr);
}
void rsp_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
XMLSTRUCT *pxml;
char *pe;
int err;
char **row;
int returned;
char *query;
char *estr = NULL;
/* FIXME */
if(!strcmp(pqi->uri_sections[3],"artist")) {
pqi->query_type = queryTypeBrowseArtists;
} else if(!strcmp(pqi->uri_sections[3],"genre")) {
pqi->query_type = queryTypeBrowseGenres;
} else if(!strcmp(pqi->uri_sections[3],"album")) {
pqi->query_type = queryTypeBrowseAlbums;
} else if(!strcmp(pqi->uri_sections[3],"composer")) {
pqi->query_type = queryTypeBrowseComposers;
} else {
rsp_error(pwsc, pqi, 0 | E_RSP, "Unsupported browse type");
return;
}
query = infn->ws_getvar(pwsc,"query");
if(query) {
pqi->pt = infn->sp_init();
if(!infn->sp_parse(pqi->pt,query)) {
estr = strdup(infn->sp_get_error(pqi->pt));
infn->log(E_LOG,"Ignoring bad query (%s): %s\n",query,estr);
infn->sp_dispose(pqi->pt);
pqi->pt = NULL;
}
}
pqi->index_type = indexTypeNone;
if(infn->ws_getvar(pwsc,"offset")) {
pqi->index_low = atoi(infn->ws_getvar(pwsc,"offset"));
}
if(infn->ws_getvar(pwsc,"limit")) {
pqi->index_high = pqi->index_low + atoi(infn->ws_getvar(pwsc,"limit")) -1;
if(pqi->index_high < pqi->index_low) {
pqi->index_high = 999999;
}
} else {
pqi->index_high = 999999; /* FIXME */
}
pqi->index_type = indexTypeSub;
pqi->want_count = 1;
pqi->playlist_id = atoi(pqi->uri_sections[2]);
if((err=infn->db_enum_start(&pe,pqi)) != 0) {
if(pqi->pt) infn->sp_dispose(pqi->pt);
if(estr) free(estr);
rsp_error(pwsc, pqi, err | E_DB, pe);
return;
}
pxml = xml_init(pwsc,1);
if(pqi->index_low > pqi->specifiedtotalcount) {
returned = 0;
} else {
returned = pqi->index_high - pqi->index_low + 1;
if(returned > (pqi->specifiedtotalcount - pqi->index_low))
returned = pqi->specifiedtotalcount - pqi->index_low;
}
xml_push(pxml,"response");
xml_push(pxml,"status");
xml_output(pxml,"errorcode","0");
xml_output(pxml,"errorstring",estr ? estr : "");
xml_output(pxml,"records","%d",returned);
xml_output(pxml,"totalrecords","%d",pqi->specifiedtotalcount);
xml_pop(pxml); /* status */
xml_push(pxml,"items");
while((infn->db_enum_fetch_row(NULL,&row,pqi) == 0) && (row)) {
xml_output(pxml,"item",row[0]);
}
infn->db_enum_end(NULL);
xml_pop(pxml); /* items */
xml_pop(pxml); /* response */
xml_deinit(pxml);
if(pqi->pt) infn->sp_dispose(pqi->pt);
if(estr) free(estr);
}
void rsp_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
infn->stream(pwsc, pqi, pqi->uri_sections[2]);
return;
}
void rsp_error(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, 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);
infn->ws_close(pwsc);
}

18
src/plugins/rsp.h Normal file
View File

@ -0,0 +1,18 @@
/*
* $Id: $
*/
#ifndef _RSP_H_
#define _RSP_H_
#define RSP_VERSION "1.0"
extern PLUGIN_INFO _pi;
#define infn ((PLUGIN_INPUT_FN *)(_pi.fn))
#ifndef TRUE
# define TRUE 1
# define FALSE 0
#endif
#endif /* _RSP_H_ */

380
src/plugins/xml-rpc.c Normal file
View File

@ -0,0 +1,380 @@
/*
* $Id: xml-rpc.c 970 2006-04-20 06:52:21Z rpedde $
*
* This really isn't xmlrpc. It's xmlrpc-ish. Emphasis on -ish.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#include "mtd-plugins.h"
#include "rsp.h"
#include "xml-rpc.h"
/* typedefs */
typedef struct tag_xmlstack {
char *tag;
struct tag_xmlstack *next;
} XMLSTACK;
#define XML_STREAM_BLOCK 4096
typedef struct tag_xml_streambuffer {
int fd;
z_stream strm;
unsigned char *in_buffer;
unsigned char *out_buffer;
} XML_STREAMBUFFER;
struct tag_xmlstruct {
WS_CONNINFO *pwsc;
int stack_level;
XMLSTACK stack;
XML_STREAMBUFFER *psb;
};
/* Forwards */
void xml_get_stats(WS_CONNINFO *pwsc);
void xml_set_config(WS_CONNINFO *pwsc);
void xml_return_error(WS_CONNINFO *pwsc, int errno, char *errstr);
char *xml_entity_encode(char *original);
XML_STREAMBUFFER *xml_stream_open(int fd);
int xml_stream_write(XML_STREAMBUFFER *psb, char *out);
int xml_stream_close(XML_STREAMBUFFER *psb);
void xml_write(XMLSTRUCT *pxml, char *fmt, ...) {
char buffer[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, 1024, fmt, ap);
va_end(ap);
if(pxml->psb) {
infn->log(E_DBG,"Writing %d bytes to output\n",strlen(buffer));
xml_stream_write(pxml->psb, buffer);
} else {
infn->ws_writefd(pxml->pwsc,"%s",buffer);
}
}
void xml_return_error(WS_CONNINFO *pwsc, int errno, char *errstr) {
XMLSTRUCT *pxml;
pxml=xml_init(pwsc,TRUE);
xml_push(pxml,"results");
xml_output(pxml,"status","%d",errno);
xml_output(pxml,"statusstring","%s",errstr);
xml_pop(pxml); /* results */
xml_deinit(pxml);
return;
}
/**
* open a gzip stream
*/
XML_STREAMBUFFER *xml_stream_open(int fd) {
XML_STREAMBUFFER *psb;
psb = (XML_STREAMBUFFER*) malloc(sizeof(XML_STREAMBUFFER));
if(!psb) {
infn->log(E_FATAL,"xml_stream_open: malloc\n");
}
psb->fd = fd;
psb->out_buffer = (unsigned char*) malloc(XML_STREAM_BLOCK);
psb->in_buffer = (unsigned char*) malloc(XML_STREAM_BLOCK);
if((!psb->out_buffer) || (!psb->in_buffer)) {
infn->log(E_FATAL,"xml_stream_open: malloc\n");
}
psb->strm.zalloc = Z_NULL;
psb->strm.zfree = Z_NULL;
psb->strm.opaque = Z_NULL;
psb->strm.next_in = psb->in_buffer;
psb->strm.next_out = psb->out_buffer;
deflateInit2(&psb->strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
24, 8, Z_DEFAULT_STRATEGY);
return psb;
}
/**
* write a block to the stream
*/
int xml_stream_write(XML_STREAMBUFFER *psb, char *out) {
int bytes = strlen(out);
int done = 0;
int result;
if((!out)||(!strlen(out)))
return TRUE;
if(strlen(out) > 1024)
return TRUE;
memcpy(psb->in_buffer,out,strlen(out));
psb->strm.avail_in = strlen(out);
psb->strm.next_in = psb->in_buffer;
psb->strm.next_out = psb->out_buffer;
psb->strm.avail_out = XML_STREAM_BLOCK;
while(!done) {
result = deflate(&psb->strm, Z_NO_FLUSH);
if(result != Z_OK) {
infn->log(E_FATAL,"Error in zlib: %d\n",result);
}
/* FIXME: error handling */
infn->log(E_DBG,"Writing %d bytes compressed info (avail: %d)\n",
XML_STREAM_BLOCK-psb->strm.avail_out,
psb->strm.avail_out);
write(psb->fd,psb->out_buffer,XML_STREAM_BLOCK-psb->strm.avail_out);
if(psb->strm.avail_out != 0) {
done=1;
} else {
psb->strm.avail_out = XML_STREAM_BLOCK;
psb->strm.next_out = psb->out_buffer;
}
}
return TRUE;
}
/**
* close the stream
*/
int xml_stream_close(XML_STREAMBUFFER *psb) {
int done = 0;
/* flush what's left */
while(!done) {
psb->strm.avail_out = XML_STREAM_BLOCK;
psb->strm.next_out = psb->out_buffer;
psb->strm.avail_in = 0;
psb->strm.next_in = psb->in_buffer;
deflate(&psb->strm,Z_FINISH);
write(psb->fd,psb->out_buffer,XML_STREAM_BLOCK - psb->strm.avail_out);
if(psb->strm.avail_out != 0)
done=1;
}
infn->log(E_INF,"Done sending xml stream\n");
deflateEnd(&psb->strm);
if(psb->out_buffer != NULL)
free(psb->out_buffer);
if(psb->in_buffer != NULL)
free(psb->in_buffer);
free(psb);
}
/**
* create an xml response structure, a helper struct for
* building xml responses.
*
* @param pwsc the pwsc we are emitting to
* @param emit_header whether or not to throw out html headers and xml header
* @returns XMLSTRUCT on success, or NULL if failure
*/
XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) {
XMLSTRUCT *pxml;
char *accept;
char *nogzip;
pxml=(XMLSTRUCT*)malloc(sizeof(XMLSTRUCT));
if(!pxml) {
infn->log(E_FATAL,"Malloc error\n");
}
memset(pxml,0,sizeof(XMLSTRUCT));
pxml->pwsc = pwsc;
/* should we compress output? */
nogzip = infn->ws_getvar(pwsc,"nogzip");
accept = infn->ws_getrequestheader(pwsc,"accept-encoding");
if((!nogzip) && (accept) && (strcasestr(accept,"gzip"))) {
infn->log(E_LOG,"Gzipping output\n");
pxml->psb = xml_stream_open(infn->ws_fd(pwsc));
if(pxml->psb) {
infn->ws_addresponseheader(pwsc,"Content-Encoding","gzip");
infn->ws_addresponseheader(pwsc,"Vary","Accept-Encoding");
infn->ws_addresponseheader(pwsc,"Connection","Close");
}
}
/* the world would be a wonderful place without ie */
infn->ws_addresponseheader(pwsc,"Cache-Control","no-cache");
infn->ws_addresponseheader(pwsc,"Expires","-1");
if(emit_header) {
infn->ws_addresponseheader(pwsc,"Content-Type","text/xml; charset=utf-8");
infn->ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n");
infn->ws_emitheaders(pwsc);
xml_write(pxml,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
}
return pxml;
}
/**
* push a new term on the stack
*
* @param pxml xml struct obtained from xml_init
* @param term next xlm section to start
*/
void xml_push(XMLSTRUCT *pxml, char *term) {
XMLSTACK *pstack;
pstack = (XMLSTACK *)malloc(sizeof(XMLSTACK));
pstack->next=pxml->stack.next;
pstack->tag=strdup(term);
pxml->stack.next=pstack;
pxml->stack_level++;
xml_write(pxml,"<%s>",term);
}
/**
* end an xml section
*
* @param pxml xml struct we are working with
*/
void xml_pop(XMLSTRUCT *pxml) {
XMLSTACK *pstack;
pstack=pxml->stack.next;
if(!pstack) {
infn->log(E_LOG,"xml_pop: tried to pop an empty stack\n");
return;
}
pxml->stack.next = pstack->next;
xml_write(pxml,"</%s>",pstack->tag);
free(pstack->tag);
free(pstack);
pxml->stack_level--;
}
/* FIXME: Fixed at 256? And can't I get an expandable sprintf/cat? */
/**
* output a string
*/
void xml_output(XMLSTRUCT *pxml, char *section, char *fmt, ...) {
va_list ap;
char buf[256];
char *output;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
output = xml_entity_encode(buf);
if(section) {
xml_push(pxml,section);
}
xml_write(pxml,"%s",output);
free(output);
if(section) {
xml_pop(pxml);
}
}
/**
* clean up an xml struct
*
* @param pxml xml struct to clean up
*/
void xml_deinit(XMLSTRUCT *pxml) {
XMLSTACK *pstack;
if(pxml->stack.next) {
infn->log(E_LOG,"xml_deinit: entries still on stack (%s)\n",
pxml->stack.next->tag);
}
while((pstack=pxml->stack.next)) {
pxml->stack.next=pstack->next;
free(pstack->tag);
free(pstack);
}
if(pxml->psb) {
xml_stream_close(pxml->psb);
}
free(pxml);
}
/**
* xml entity encoding, stupid style
*/
char *xml_entity_encode(char *original) {
char *new;
char *s, *d;
int destsize;
destsize = 6*(int)strlen(original)+1;
new=(char *)malloc(destsize);
if(!new) return NULL;
memset(new,0x00,destsize);
s=original;
d=new;
while(*s) {
switch(*s) {
case '>':
strcat(d,"&gt;");
d += 4;
s++;
break;
case '<':
strcat(d,"&lt;");
d += 4;
s++;
break;
case '"':
strcat(d,"&quot;");
d += 6;
s++;
break;
case '\'':
strcat(d,"&apos;");
d += 6;
s++;
break;
case '&':
strcat(d,"&amp;");
d += 5;
s++;
break;
default:
*d++ = *s++;
}
}
return new;
}

20
src/plugins/xml-rpc.h Normal file
View File

@ -0,0 +1,20 @@
/*
* $Id: xml-rpc.h 917 2006-04-04 06:31:19Z rpedde $
*/
#ifndef _XMLRPC_H_
#define _XMLRPC_H_
#include "mtd-plugins.h"
struct tag_xmlstruct;
typedef struct tag_xmlstruct XMLSTRUCT;
extern XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header);
extern void xml_push(XMLSTRUCT *pxml, char *term);
extern void xml_pop(XMLSTRUCT *pxml);
extern void xml_output(XMLSTRUCT *pxml, char *section, char *fmt, ...);
extern void xml_deinit(XMLSTRUCT *pxml);
#endif /* _XMLRPC_H_ */