Complete unifying smart playlists and query/filters
This commit is contained in:
parent
ae22cba1ae
commit
90dc66110a
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
38
src/db-sql.c
38
src/db-sql.c
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue