Complete unifying smart playlists and query/filters

This commit is contained in:
Ron Pedde 2006-03-12 11:30:58 +00:00
parent ae22cba1ae
commit 90dc66110a
10 changed files with 155 additions and 28 deletions

View File

@ -341,7 +341,8 @@ char *db_error_list[] = {
"Invalid song id: %d",
"Parse error: %s",
"No backend database support for type: %s",
"Could not initialize thread pool"
"Could not initialize thread pool",
"Passed buffer too small for result"
};
/* Globals */

View File

@ -209,6 +209,8 @@ extern void db_dispose_playlist(M3UFILE *pm3u);
#define DB_E_PARSE 0x08 /**< could not parse result */
#define DB_E_BADPROVIDER 0x09 /**< requested db backend not there */
#define DB_E_PROC 0x0A /**< could not start threadpool */
#define DB_E_SIZE 0x0B /**< passed buffer too small */
/* describes the individual database handlers */
typedef struct tag_dbinfo {
char *handler_name;

View File

@ -45,7 +45,6 @@
#ifdef HAVE_LIBSQLITE
#include "db-sql-sqlite2.h"
#endif
#ifdef HAVE_LIBSQLITE3
#include "db-sql-sqlite3.h"
#endif
@ -122,6 +121,38 @@ int db_sql_open_sqlite3(char **pe, char *parameters) {
}
#endif
/**
* escape a sql string, returning it the supplied buffer.
* note that this uses the sqlite escape format -- use %q for quoted
* sql.
*
* @param buffer buffer to throw the escaped sql into
* @param size size of buffer (or size required, if DB_E_SIZE)
* @param fmt printf style format
* @returns DB_E_SUCCESS with buffer filled, or DB_E_SIZE, with size
* set to the required size
*/
int db_sql_escape(char *buffer, int *size, char *fmt, ...) {
va_list ap;
char *escaped;
va_start(ap,fmt);
escaped = db_sql_vmquery_fn(fmt,ap);
va_end(ap);
if(*size < (int)strlen(escaped)) {
*size = strlen(escaped) + 1;
db_sql_vmfree_fn(escaped);
return DB_E_SIZE;
}
strcpy(buffer,escaped);
*size = strlen(escaped);
db_sql_vmfree_fn(escaped);
return DB_E_SUCCESS;
}
/**
* fetch a single row, using the underlying database enum
@ -1050,8 +1081,8 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) {
/* Apply the query/filter */
if(pinfo->pt) {
DPRINTF(E_DBG,L_DB,"Got query/filter\n");
filter = sp_sql_clause(pinfo->pt);
if(filter) {
if(have_clause) {
strcat(query_rest," and ");
@ -1059,13 +1090,14 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) {
strcpy(query_rest," where ");
}
strcat(query_rest,"(");
strcat(query_rest,filter);
strcat(query_rest,")");
free(filter);
} else {
DPRINTF(E_LOG,L_DB,"Error getting sql for parse tree\n");
}
} else {
DPRINTF(E_DBG,L_DB,"No query/filter\n");
}
if(pinfo->index_type == indexTypeLast) {

View File

@ -34,6 +34,7 @@ extern int db_sql_open_sqlite3(char **pe, char *parameters);
extern int db_sql_open(char **pe, char *parameters);
extern int db_sql_init(int reload);
extern int db_sql_deinit(void);
extern int db_sql_escape(char *buffer, int *size, char *fmt, ...);
extern int db_sql_add(char **pe, MP3FILE *pmp3, int *id);
extern int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo);
extern int db_sql_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size);

View File

@ -77,6 +77,7 @@ static DAAP_ITEMS *dispatch_xml_lookup_tag(char *tag);
static char *dispatch_xml_encode(char *original, int len);
static int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char *block, int len);
static void dispatch_cleanup(DBQUERYINFO *pqi);
/**
* Hold the inf for the output serializer
@ -96,6 +97,17 @@ typedef struct tag_output_info {
} OUTPUT_INFO;
/**
* do cleanup on the pqi structure... free any allocated memory, etc
*/
void dispatch_cleanup(DBQUERYINFO *pqi) {
if(pqi->pt) {
sp_dispose(pqi->pt);
}
free(pqi);
}
/**
* Handles authentication for the daap server. This isn't the
* authenticator for the web admin page, but rather the iTunes
@ -157,12 +169,13 @@ void daap_handler(WS_CONNINFO *pwsc) {
if(!query) query=ws_getvar(pwsc,"filter");
if(query) {
pqi->pt = sp_init();
if(!sp_parse(&pqi->pt,query,SP_TYPE_QUERY)) {
if(!sp_parse(pqi->pt,query,SP_TYPE_QUERY)) {
DPRINTF(E_LOG,L_DAAP,"Ignoring bad query/filter (%s): %s\n",
query,sp_get_error(pqi->pt));
sp_dispose(pqi->pt);
pqi->pt = NULL;
}
DPRINTF(E_DBG,L_DAAP,"Parsed query successfully\n");
}
@ -173,15 +186,6 @@ void daap_handler(WS_CONNINFO *pwsc) {
ws_addresponseheader(pwsc,"Cache-Control","no-cache"); /* anti-ie defense */
ws_addresponseheader(pwsc,"Expires","-1");
/* This we should put in a quirks file or something, but here might
* be a decent workaround for various failures on different clients */
/* nm... backing this out. Really do need a "quirks" mode
pwsc->close=0;
if(ws_testrequestheader(pwsc,"Connection","Close")) {
pwsc->close = 1;
}
*/
if(ws_getvar(pwsc,"session-id"))
pqi->session_id = atoi(ws_getvar(pwsc,"session-id"));
@ -195,26 +199,31 @@ void daap_handler(WS_CONNINFO *pwsc) {
/* Start dispatching */
if(!strcasecmp(pqi->uri_sections[0],"server-info")) {
dispatch_server_info(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[0],"content-codes")) {
dispatch_content_codes(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[0],"login")) {
dispatch_login(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[0],"update")) {
dispatch_update(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[0],"logout")) {
dispatch_logout(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
@ -228,6 +237,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
if(!strcasecmp(pqi->uri_sections[0],"databases")) {
if(pqi->uri_count == 1) {
dispatch_dbinfo(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
pqi->db_id=atoi(pqi->uri_sections[1]);
@ -235,16 +245,18 @@ void daap_handler(WS_CONNINFO *pwsc) {
if(!strcasecmp(pqi->uri_sections[2],"items")) {
/* /databases/id/items */
dispatch_items(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[2],"containers")) {
/* /databases/id/containers */
dispatch_playlists(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
pwsc->close=1;
free(pqi);
dispatch_cleanup(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}
@ -252,34 +264,39 @@ void daap_handler(WS_CONNINFO *pwsc) {
if(!strcasecmp(pqi->uri_sections[2],"browse")) {
/* /databases/id/browse/something */
dispatch_browse(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if(!strcasecmp(pqi->uri_sections[2],"items")) {
/* /databases/id/items/id.mp3 */
dispatch_stream(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if((!strcasecmp(pqi->uri_sections[2],"containers")) &&
(!strcasecmp(pqi->uri_sections[3],"add"))) {
/* /databases/id/containers/add */
dispatch_addplaylist(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if((!strcasecmp(pqi->uri_sections[2],"containers")) &&
(!strcasecmp(pqi->uri_sections[3],"del"))) {
/* /databases/id/containers/del */
dispatch_deleteplaylist(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if((!strcasecmp(pqi->uri_sections[2],"containers")) &&
(!strcasecmp(pqi->uri_sections[3],"edit"))) {
/* /databases/id/contaienrs/edit */
dispatch_editplaylist(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
pwsc->close=1;
free(pqi);
dispatch_cleanup(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}
@ -288,6 +305,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
(!strcasecmp(pqi->uri_sections[4],"items"))) {
pqi->playlist_id=atoi(pqi->uri_sections[3]);
dispatch_playlistitems(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
if((!strcasecmp(pqi->uri_sections[2],"containers")) &&
@ -295,6 +313,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
/* /databases/id/containers/id/del */
pqi->playlist_id=atoi(pqi->uri_sections[3]);
dispatch_deleteplaylistitems(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
}
@ -304,13 +323,14 @@ void daap_handler(WS_CONNINFO *pwsc) {
(!strcasecmp(pqi->uri_sections[5],"add"))) {
pqi->playlist_id=atoi(pqi->uri_sections[3]);
dispatch_addplaylistitems(pwsc,pqi);
dispatch_cleanup(pqi);
return;
}
}
}
pwsc->close=1;
free(pqi);
dispatch_cleanup(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}

View File

@ -203,7 +203,9 @@ void scan_process_playlistlist(void) {
}
if(strcasecmp(ext,".xml") == 0) {
scan_xml_playlist(pnext->path);
if(conf_get_int("scanning","process xml",1)) {
scan_xml_playlist(pnext->path);
}
} else if(strcasecmp(ext,".m3u") == 0) {
scan_static_playlist(pnext->path);
} else {

View File

@ -2,11 +2,14 @@
* Test harness for the parser
*/
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "conf.h"
#include "db-generic.h"
#include "err.h"
#include "smart-parser.h"
@ -19,9 +22,18 @@ int main(int argc, char *argv[]) {
int option;
int type=0;
PARSETREE pt;
while((option = getopt(argc, argv, "d:t:")) != -1) {
char *configfile = "/etc/mt-daapd.conf";
char db_type[40];
char db_parms[PATH_MAX];
int size;
int err;
char *perr;
while((option = getopt(argc, argv, "d:t:c:")) != -1) {
switch(option) {
case 'c':
configfile = optarg;
break;
case 'd':
err_setlevel(atoi(optarg));
break;
@ -34,6 +46,22 @@ int main(int argc, char *argv[]) {
}
}
if(conf_read(configfile) != CONF_E_SUCCESS) {
fprintf(stderr,"could not read config file: %s\n",configfile);
exit(1);
}
size = sizeof(db_type);
conf_get_string("general","db_type","sqlite",db_type,&size);
size = sizeof(db_parms);
conf_get_string("general","db_parms","/var/cache/mt-daapd",db_parms,&size);
err=db_open(&perr,db_type,db_parms);
if(err != DB_E_SUCCESS) {
fprintf(stderr,"Error opening db: %s\n",perr);
free(perr);
exit(1);
}
printf("Parsing %s\n",argv[optind]);
@ -45,6 +73,7 @@ int main(int argc, char *argv[]) {
}
sp_dispose(pt);
conf_close();
printf("Done!\n");
return 0;

View File

@ -1,7 +1,8 @@
CC=gcc
CFLAGS := $(CFLAGS) -g -I/sw/include -DHAVE_CONFIG_H -I. -I.. -Wall -DERR_LEAN
CFLAGS := $(CFLAGS) -g -I/opt/local/include -DHAVE_CONFIG_H -I. -I.. -Wall -DERR_LEAN -DHAVE_SQL
LDFLAGS := $(LDFLAGS) -L/opt/local/lib -lsqlite -lsqlite3
TARGET=parser
OBJECTS=parser-driver.o smart-parser.o err.o
OBJECTS=parser-driver.o smart-parser.o err.o db-sql.o db-generic.o ssc.o db-sql-sqlite3.o db-sql-sqlite2.o conf.o ll.o
$(TARGET): $(OBJECTS)
$(CC) -o $(TARGET) $(LDFLAGS) $(OBJECTS)

View File

@ -23,6 +23,11 @@
#include "err.h"
#ifdef HAVE_SQL
extern int db_sql_escape(char *buffer, int *size, char *fmt, ...);
#endif
typedef struct tag_token {
int token_id;
union {
@ -197,7 +202,7 @@ typedef struct tag_fieldlookup {
/* normal terminators, in-string terminators, escapes, quotes */
char *sp_terminators[2][4] = {
{ " \t\n\r\"<>=()|&!", "\"","\"","\"" },
{ "()'+: -,", "')", "\\*'","" }
{ "()'+: -,", "'", "\\*'","" }
};
FIELDLOOKUP sp_symbols_0[] = {
@ -465,6 +470,7 @@ int sp_scan(PARSETREE tree, int hint) {
time_t tval;
char *qstr;
char *token_string;
char *dst, *src;
if(tree->token.token_id & 0x2000) {
if(tree->token.data.cvalue)
@ -544,8 +550,10 @@ int sp_scan(PARSETREE tree, int hint) {
/* walk to a terminator */
tail = tree->current;
/* FIXME: escaped characters */
while((*tail) && (!strchr(terminator,*tail))) {
/* skip escaped characters -- will be unescaped later */
if((*tail == '\\')&&(*(tail+1) != '\0'))
tail++;
tail++;
}
@ -634,6 +642,19 @@ int sp_scan(PARSETREE tree, int hint) {
}
}
/* escape string */
if(tree->token.token_id == T_STRING) {
src = dst = tree->token.data.cvalue;
while(*src) {
if(*src != '\\') {
*dst++ = *src++;
} else {
src++;
}
}
*dst = '\0';
}
DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ",
tree->token.token_id);
if(tree->token.token_id & 0x2000)
@ -1304,6 +1325,7 @@ int sp_dispose(PARSETREE tree) {
return 1;
}
#ifdef HAVE_SQL
/**
* calculate the size required to render the tree as a
* sql query.
@ -1313,6 +1335,7 @@ int sp_dispose(PARSETREE tree) {
*/
int sp_node_size(SP_NODE *node) {
int size;
int string_size;
if(node->op_type == SP_OPTYPE_ANDOR) {
size = sp_node_size(node->left.node);
@ -1329,7 +1352,9 @@ int sp_node_size(SP_NODE *node) {
}
if(node->op_type == SP_OPTYPE_STRING) {
size += (2 + (int) strlen(node->right.cvalue));
string_size = 0;
db_sql_escape(NULL,&string_size,"%q",node->right.cvalue);
size += (2 + string_size);
if(node->op == T_INCLUDES) {
size += 2; /* extra %'s */
}
@ -1358,6 +1383,7 @@ int sp_node_size(SP_NODE *node) {
*/
void sp_serialize_sql(SP_NODE *node, char *string) {
char buffer[40];
int size;
if(node->op_type == SP_OPTYPE_ANDOR) {
strcat(string,"(");
@ -1379,7 +1405,15 @@ void sp_serialize_sql(SP_NODE *node, char *string) {
strcat(string,"'");
if((node->op == T_INCLUDES) || (node->op == T_ENDSWITH))
strcat(string,"%");
strcat(string,node->right.cvalue);
size = 0;
db_sql_escape(NULL,&size,"%q",node->right.cvalue);
/* we don't have a way to verify we have that much
* room, but we must... we allocated it earlier.
*/
db_sql_escape(&string[strlen(string)],&size,"%q",node->right.cvalue);
// strcat(string,node->right.cvalue);
if((node->op == T_INCLUDES) || (node->op == T_STARTSWITH))
strcat(string,"%");
strcat(string,"'");
@ -1411,15 +1445,20 @@ char *sp_sql_clause(PARSETREE tree) {
int size;
char *sql;
DPRINTF(E_DBG,L_PARSE,"Fetching sql statement size\n");
size = sp_node_size(tree->tree);
DPRINTF(E_DBG,L_PARSE,"Size: %d\n",size);
sql = (char*)malloc(size+1);
memset(sql,0x00,size+1);
sp_serialize_sql(tree->tree,sql);
DPRINTF(E_DBG,L_PARSE,"Serialized to : %s\n",sql);
return sql;
}
#endif /* HAVE_SQL */
/**

View File

@ -8,7 +8,7 @@
typedef void* PARSETREE;
extern PARSETREE sp_init(void);
extern int sp_parse(PARSETREE *tree, char *term, int type);
extern int sp_parse(PARSETREE tree, char *term, int type);
extern int sp_dispose(PARSETREE tree);
extern char *sp_get_error(PARSETREE tree);
extern char *sp_sql_clause(PARSETREE tree);