mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 22:55:56 -05:00
Add new ANTLR parser for DAAP queries
This commit is contained in:
parent
c4cf7c08a4
commit
68db2ae7c2
21
configure.in
21
configure.in
@ -11,6 +11,23 @@ AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
LT_INIT([disable-static])
|
||||
|
||||
AC_CHECK_PROG(ANTLR, [antlr3], [antlr3])
|
||||
if test "x$ANTLR" = x; then
|
||||
AC_MSG_NOTICE([antlr3 wrapper not found, checking direct java invocation])
|
||||
AC_CHECK_PROG(JAVA, [java], [java])
|
||||
if test "x$JAVA" = x; then
|
||||
AC_MSG_FAILURE([java not found; check your java installation])
|
||||
else
|
||||
if $JAVA org.antlr.Tool > /dev/null 2>&1; then
|
||||
AC_MSG_NOTICE([Direct java invocation working, using java org.antlr.Tool to invoke antlr])
|
||||
ANTLR="$JAVA org.antlr.Tool"
|
||||
else
|
||||
AC_MSG_FAILURE([antlr3 wrapper not found and direct java invocation failed; check your antlr3/java installation])
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
AC_SUBST(ANTLR)
|
||||
|
||||
CFLAGS="$CFLAGS -Wall"
|
||||
|
||||
AC_CHECK_HEADERS([sys/wait.h])
|
||||
@ -68,6 +85,10 @@ AC_CHECK_HEADER(avl.h, , AC_MSG_ERROR([avl.h not found]))
|
||||
AC_CHECK_LIB([avl], [avl_alloc_tree], [LIBAVL_LIBS="-lavl"], AC_MSG_ERROR([libavl not found]))
|
||||
AC_SUBST(LIBAVL_LIBS)
|
||||
|
||||
AC_CHECK_HEADER(antlr3.h, , AC_MSG_ERROR([antlr3.h not found]))
|
||||
AC_CHECK_LIB([antlr3c], [antlr3BaseRecognizerNew], [ANTLR3C_LIBS="-lantlr3c"], AC_MSG_ERROR([ANTLR3 C runtime (libantlr3c) not found]))
|
||||
AC_SUBST(ANTLR3C_LIBS)
|
||||
|
||||
if test x$use_flac = xtrue; then
|
||||
PKG_CHECK_MODULES(FLAC, [ flac ])
|
||||
fi
|
||||
|
6
src/.gitignore
vendored
6
src/.gitignore
vendored
@ -1,2 +1,8 @@
|
||||
mt-daapd
|
||||
|
||||
*.tokens
|
||||
*Lexer.[ch]
|
||||
*Parser.[ch]
|
||||
DAAP2SQL.[ch]
|
||||
|
||||
*.u
|
||||
|
63
src/DAAP.g
Normal file
63
src/DAAP.g
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2009 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
grammar DAAP;
|
||||
|
||||
options {
|
||||
output = AST;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
query : expr NEWLINE? EOF -> expr
|
||||
;
|
||||
|
||||
expr : aexpr (OPOR^ aexpr)*
|
||||
;
|
||||
|
||||
aexpr : crit (OPAND^ crit)*
|
||||
;
|
||||
|
||||
crit : LPAR expr RPAR -> expr
|
||||
| STR
|
||||
;
|
||||
|
||||
QUOTE : '\'';
|
||||
LPAR : '(';
|
||||
RPAR : ')';
|
||||
|
||||
OPAND : '+';
|
||||
OPOR : ',';
|
||||
|
||||
NEWLINE : '\r'? '\n';
|
||||
|
||||
/*
|
||||
Unescaping adapted from (ported to the C runtime)
|
||||
<http://stackoverflow.com/questions/504402/how-to-handle-escape-sequences-in-string-literals-in-antlr-3>
|
||||
*/
|
||||
STR
|
||||
@init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); }
|
||||
: QUOTE ( reg = ~('\\' | '\'') { unesc->addc(unesc, reg); }
|
||||
| esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); };
|
||||
|
||||
fragment
|
||||
ESCAPED : '\\'
|
||||
( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); }
|
||||
| '\'' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\'")); }
|
||||
)
|
||||
;
|
294
src/DAAP2SQL.g
Normal file
294
src/DAAP2SQL.g
Normal file
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright (C) 2009 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
tree grammar DAAP2SQL;
|
||||
|
||||
options {
|
||||
tokenVocab = DAAP;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
@header {
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ff-dbstruct.h"
|
||||
#include "db-generic.h"
|
||||
#include "db-sql.h"
|
||||
#include "daap_query.h"
|
||||
}
|
||||
|
||||
query returns [ pANTLR3_STRING result ]
|
||||
: e = expr
|
||||
{
|
||||
if (!$e.valid)
|
||||
{
|
||||
$result = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $e.result->factory->newRaw($e.result->factory);
|
||||
$result->append8($result, "(");
|
||||
$result->appendS($result, $e.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
expr returns [ pANTLR3_STRING result, int valid ]
|
||||
@init { $result = NULL; $valid = 1; }
|
||||
: ^(OPAND a = expr b = expr)
|
||||
{
|
||||
if (!$a.valid || !$b.valid)
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->append8($result, "(");
|
||||
$result->appendS($result, $a.result);
|
||||
$result->append8($result, " AND ");
|
||||
$result->appendS($result, $b.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
}
|
||||
| ^(OPOR a = expr b = expr)
|
||||
{
|
||||
if (!$a.valid || !$b.valid)
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->append8($result, "(");
|
||||
$result->appendS($result, $a.result);
|
||||
$result->append8($result, " OR ");
|
||||
$result->appendS($result, $b.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
}
|
||||
| STR
|
||||
{
|
||||
pANTLR3_STRING str;
|
||||
pANTLR3_UINT8 field;
|
||||
pANTLR3_UINT8 val;
|
||||
pANTLR3_UINT8 escaped;
|
||||
ANTLR3_UINT8 op;
|
||||
int neg_op;
|
||||
struct dmap_query_field_map *dqfm;
|
||||
char *end;
|
||||
long longval;
|
||||
|
||||
escaped = NULL;
|
||||
|
||||
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||
|
||||
str = $STR.text->toUTF8($STR.text);
|
||||
|
||||
/* NOTE: the lexer delivers the string without quotes
|
||||
which may not be obvious from the grammar due to embedded code
|
||||
*/
|
||||
field = str->chars;
|
||||
|
||||
val = field;
|
||||
while ((*val != '\0') && ((*val == '.')
|
||||
|| ((*val >= 'a') && (*val <= 'z'))
|
||||
|| ((*val >= 'A') && (*val <= 'Z'))
|
||||
|| ((*val >= '0') && (*val <= '9'))))
|
||||
{
|
||||
val++;
|
||||
}
|
||||
|
||||
if (*field == '\0')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No field name found in clause '\%s'\n", field);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
if (*val == '\0')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No operator found in clause '\%s'\n", field);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
op = *val;
|
||||
*val = '\0';
|
||||
val++;
|
||||
|
||||
if (*val == '\0')
|
||||
{
|
||||
if (op == '!')
|
||||
DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op);
|
||||
else
|
||||
DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%c'\n", field, op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
if (op == '!')
|
||||
{
|
||||
neg_op = 1;
|
||||
op = *val;
|
||||
val++;
|
||||
|
||||
if (*val == '\0')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s!\%c'\n", field, op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
}
|
||||
else
|
||||
neg_op = 0;
|
||||
|
||||
/* Lookup DMAP field in the query field map */
|
||||
dqfm = daap_query_field_lookup((char *)field);
|
||||
if (!dqfm)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DMAP field '\%s' is not a valid field in queries\n", field);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
$result->append8($result, dqfm->db_col);
|
||||
|
||||
/* Int field: check integer conversion */
|
||||
if (dqfm->as_int)
|
||||
{
|
||||
errno = 0;
|
||||
longval = strtol((const char *)val, &end, 10);
|
||||
|
||||
if (((errno == ERANGE) && ((longval == LONG_MAX) || (longval == LONG_MIN)))
|
||||
|| ((errno != 0) && (longval == 0)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%c\%s' does not convert to an integer type\n",
|
||||
val, field, op, val);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
if (end == (char *)val)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%c\%s' does not represent an integer value\n",
|
||||
val, field, op, val);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
*end = '\0'; /* Cut out potential garbage - we're being kind */
|
||||
}
|
||||
/* String field: escape string, check for '*' */
|
||||
else
|
||||
{
|
||||
if (neg_op)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Negation not valid for string operations\n");
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
if (op != ':')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Operation '\%c' not valid for string values\n", op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
escaped = (pANTLR3_UINT8)db_sql_escape_dup("\%q", val);
|
||||
if (!escaped)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not escape value\n");
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
val = escaped;
|
||||
|
||||
if (val[0] == '*')
|
||||
{
|
||||
op = '\%';
|
||||
val[0] = '\%';
|
||||
}
|
||||
|
||||
if (val[strlen((char *)val) - 1] == '*')
|
||||
{
|
||||
op = '\%';
|
||||
val[strlen((char *)val) - 1] = '\%';
|
||||
}
|
||||
}
|
||||
|
||||
switch(op)
|
||||
{
|
||||
case ':':
|
||||
if (neg_op) /* Not valid for strings, checked above */
|
||||
$result->append8($result, " <> ");
|
||||
else
|
||||
$result->append8($result, " = ");
|
||||
break;
|
||||
|
||||
case '+':
|
||||
if (neg_op)
|
||||
$result->append8($result, " <= ");
|
||||
else
|
||||
$result->append8($result, " > ");
|
||||
break;
|
||||
|
||||
case '-':
|
||||
if (neg_op)
|
||||
$result->append8($result, " >= ");
|
||||
else
|
||||
$result->append8($result, " < ");
|
||||
break;
|
||||
|
||||
case '\%':
|
||||
$result->append8($result, " LIKE ");
|
||||
break;
|
||||
|
||||
default:
|
||||
if (neg_op)
|
||||
DPRINTF(E_LOG, L_DAAP, "Missing or unknown operator '\%c' in clause '\%s!\%c\%s'\n", op, field, op, val);
|
||||
else
|
||||
DPRINTF(E_LOG, L_DAAP, "Unknown operator '\%c' in clause '\%s\%c\%s'\n", op, field, op, val);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dqfm->as_int)
|
||||
$result->append8($result, "'");
|
||||
|
||||
$result->append8($result, (const char *)val);
|
||||
|
||||
if (!dqfm->as_int)
|
||||
$result->append8($result, "'");
|
||||
|
||||
STR_result_valid_0: /* bail out label */
|
||||
;
|
||||
|
||||
if (escaped)
|
||||
free(escaped);
|
||||
}
|
||||
;
|
@ -9,8 +9,17 @@ if COND_MUSEPACK
|
||||
MUSEPACKSRC=scan-mpc.c
|
||||
endif
|
||||
|
||||
ANTLR_GRAMMARS = \
|
||||
DAAP.g DAAP2SQL.g
|
||||
|
||||
ANTLR_SOURCES = \
|
||||
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
||||
DAAP2SQL.c DAAP2SQL.h
|
||||
|
||||
ANTLR_PRODUCTS =
|
||||
|
||||
mt_daapd_CPPFLAGS = -D_GNU_SOURCE @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @FFMPEG_CFLAGS@ @CONFUSE_CFLAGS@ @TAGLIB_CFLAGS@ @MINIXML_CFLAGS@
|
||||
mt_daapd_LDADD = @AVAHI_LIBS@ @SQLITE3_LIBS@ @FFMPEG_LIBS@ @CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ @LIBAVL_LIBS@ @MINIXML_LIBS@
|
||||
mt_daapd_LDADD = @AVAHI_LIBS@ @SQLITE3_LIBS@ @FFMPEG_LIBS@ @CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ @LIBAVL_LIBS@ @MINIXML_LIBS@ @ANTLR3C_LIBS@
|
||||
mt_daapd_SOURCES = main.c \
|
||||
logger.c logger.h \
|
||||
conffile.c conffile.h \
|
||||
@ -24,6 +33,7 @@ mt_daapd_SOURCES = main.c \
|
||||
httpd_daap.c httpd_daap.h \
|
||||
transcode.c transcode.h \
|
||||
misc.c misc.h \
|
||||
daap_query.c daap_query.h \
|
||||
db-generic.c db-generic.h \
|
||||
scan-wma.c \
|
||||
smart-parser.c smart-parser.h \
|
||||
@ -31,6 +41,34 @@ mt_daapd_SOURCES = main.c \
|
||||
db-sql.c db-sql.h db-sql-sqlite3.c db-sql-sqlite3.h\
|
||||
$(FLACSRC) $(MUSEPACKSRC)
|
||||
|
||||
EXTRA_DIST = scan-mpc.c \
|
||||
nodist_mt_daapd_SOURCES = \
|
||||
$(ANTLR_SOURCES)
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(ANTLR_GRAMMARS) \
|
||||
scan-mpc.c \
|
||||
scan-flac.c \
|
||||
ff-dbstruct.h
|
||||
|
||||
|
||||
# Let's help the dependencies a little.
|
||||
daap_query.c: DAAPLexer.h DAAPParser.h DAAP2SQL.h
|
||||
|
||||
|
||||
SUFFIXES = .g .u
|
||||
|
||||
%.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g
|
||||
@ANTLR@ $(ANTLR_OPTIONS) $<
|
||||
|
||||
%.u: %.g
|
||||
@ANTLR@ -depend $< > $@
|
||||
@echo -n "ANTLR_PRODUCTS += " > $@.tmp
|
||||
@grep : $@ | cut -d : -f 1 | tr -d ' ' | { while read f; do test "$$f" != "$<" && echo -n "$$f "; done } >> $@.tmp
|
||||
@cat $@.tmp >> $@
|
||||
rm $@.tmp
|
||||
|
||||
clean-local:
|
||||
-rm $(ANTLR_PRODUCTS)
|
||||
rm $(ANTLR_GRAMMARS:.g=.u)
|
||||
|
||||
-include $(ANTLR_GRAMMARS:.g=.u)
|
||||
|
262
src/daap_query.c
Normal file
262
src/daap_query.c
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (C) 2009 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <avl.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
#include "daap_query.h"
|
||||
|
||||
#include "DAAPLexer.h"
|
||||
#include "DAAPParser.h"
|
||||
#include "DAAP2SQL.h"
|
||||
|
||||
|
||||
static struct dmap_query_field_map dmap_query_fields[] =
|
||||
{
|
||||
{ 0, 0, "dmap.itemname", "title" },
|
||||
{ 0, 1, "dmap.itemid", "id" },
|
||||
{ 0, 0, "daap.songalbum", "album" },
|
||||
{ 0, 0, "daap.songartist", "artist" },
|
||||
{ 0, 1, "daap.songbitrate", "bitrate" },
|
||||
{ 0, 0, "daap.songcomment", "comment" },
|
||||
{ 0, 1, "daap.songcompilation", "compilation" },
|
||||
{ 0, 0, "daap.songcomposer", "composer" },
|
||||
{ 0, 1, "daap.songdatakind", "data_kind" },
|
||||
{ 0, 0, "daap.songdataurl", "url" },
|
||||
{ 0, 1, "daap.songdateadded", "time_added" },
|
||||
{ 0, 1, "daap.songdatemodified", "time_modified" },
|
||||
{ 0, 0, "daap.songdescription", "description" },
|
||||
{ 0, 1, "daap.songdisccount", "total_discs" },
|
||||
{ 0, 1, "daap.songdiscnumber", "disc" },
|
||||
{ 0, 0, "daap.songformat", "type" },
|
||||
{ 0, 0, "daap.songgenre", "genre" },
|
||||
{ 0, 1, "daap.songsamplerate", "samplerate" },
|
||||
{ 0, 1, "daap.songsize", "file_size" },
|
||||
{ 0, 1, "daap.songstoptime", "song_length" },
|
||||
{ 0, 1, "daap.songtime", "song_length" },
|
||||
{ 0, 1, "daap.songtrackcount", "total_tracks" },
|
||||
{ 0, 1, "daap.songtracknumber", "track" },
|
||||
{ 0, 1, "daap.songyear", "year" },
|
||||
|
||||
{ -1, -1, NULL, NULL }
|
||||
};
|
||||
|
||||
static avl_tree_t *dmap_query_fields_hash;
|
||||
|
||||
|
||||
static int
|
||||
dmap_query_field_map_compare(const void *aa, const void *bb)
|
||||
{
|
||||
struct dmap_query_field_map *a = (struct dmap_query_field_map *)aa;
|
||||
struct dmap_query_field_map *b = (struct dmap_query_field_map *)bb;
|
||||
|
||||
if (a->hash < b->hash)
|
||||
return -1;
|
||||
|
||||
if (a->hash > b->hash)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct dmap_query_field_map *
|
||||
daap_query_field_lookup(char *field)
|
||||
{
|
||||
struct dmap_query_field_map dqfm;
|
||||
avl_node_t *node;
|
||||
|
||||
dqfm.hash = djb_hash(field, strlen(field));
|
||||
|
||||
node = avl_search(dmap_query_fields_hash, &dqfm);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
return (struct dmap_query_field_map *)node->item;
|
||||
}
|
||||
|
||||
char *
|
||||
daap_query_parse_sql(const char *daap_query)
|
||||
{
|
||||
/* Input DAAP query, fed to the lexer */
|
||||
pANTLR3_INPUT_STREAM query;
|
||||
|
||||
/* Lexer and the resulting token stream, fed to the parser */
|
||||
pDAAPLexer lxr;
|
||||
pANTLR3_COMMON_TOKEN_STREAM tkstream;
|
||||
|
||||
/* Parser and the resulting AST, fed to the tree parser */
|
||||
pDAAPParser psr;
|
||||
DAAPParser_query_return qtree;
|
||||
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
|
||||
|
||||
/* Tree parser and the resulting SQL query string */
|
||||
pDAAP2SQL sqlconv;
|
||||
pANTLR3_STRING sql;
|
||||
|
||||
char *ret = NULL;
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Trying DAAP query -%s-\n", daap_query);
|
||||
|
||||
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)daap_query, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query");
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create input stream\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lxr = DAAPLexerNew(query);
|
||||
if (!lxr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP lexer\n");
|
||||
goto lxr_fail;
|
||||
}
|
||||
|
||||
tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
|
||||
if (!tkstream)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP token stream\n");
|
||||
goto tkstream_fail;
|
||||
}
|
||||
|
||||
psr = DAAPParserNew(tkstream);
|
||||
if (!psr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP parser\n");
|
||||
goto psr_fail;
|
||||
}
|
||||
|
||||
qtree = psr->query(psr);
|
||||
|
||||
/* Check for parser errors */
|
||||
if (psr->pParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_DAAP, "DAAP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
|
||||
|
||||
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
|
||||
if (!nodes)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create node stream\n");
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
sqlconv = DAAP2SQLNew(nodes);
|
||||
if (!sqlconv)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Could not create SQL converter\n");
|
||||
goto sql_fail;
|
||||
}
|
||||
|
||||
sql = sqlconv->query(sqlconv);
|
||||
|
||||
/* Check for tree parser errors */
|
||||
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
|
||||
goto sql_error;
|
||||
}
|
||||
|
||||
if (sql)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "DAAP SQL query: -%s-\n", sql->chars);
|
||||
ret = strdup((char *)sql->chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP query\n");
|
||||
ret = NULL;
|
||||
}
|
||||
|
||||
sql_error:
|
||||
sqlconv->free(sqlconv);
|
||||
sql_fail:
|
||||
nodes->free(nodes);
|
||||
psr_error:
|
||||
psr->free(psr);
|
||||
psr_fail:
|
||||
tkstream->free(tkstream);
|
||||
tkstream_fail:
|
||||
lxr->free(lxr);
|
||||
lxr_fail:
|
||||
query->close(query);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
daap_query_init(void)
|
||||
{
|
||||
avl_node_t *node;
|
||||
struct dmap_query_field_map *dqfm;
|
||||
int i;
|
||||
|
||||
dmap_query_fields_hash = avl_alloc_tree(dmap_query_field_map_compare, NULL);
|
||||
if (!dmap_query_fields_hash)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_DAAP, "DAAP query init could not allocate AVL tree\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; dmap_query_fields[i].hash == 0; i++)
|
||||
{
|
||||
dmap_query_fields[i].hash = djb_hash(dmap_query_fields[i].dmap_field, strlen(dmap_query_fields[i].dmap_field));
|
||||
|
||||
node = avl_insert(dmap_query_fields_hash, &dmap_query_fields[i]);
|
||||
if (!node)
|
||||
{
|
||||
if (errno != EEXIST)
|
||||
DPRINTF(E_FATAL, L_DAAP, "DAAP query init failed; AVL insert error: %s\n", strerror(errno));
|
||||
else
|
||||
{
|
||||
node = avl_search(dmap_query_fields_hash, &dmap_query_fields[i]);
|
||||
dqfm = node->item;
|
||||
|
||||
DPRINTF(E_FATAL, L_DAAP, "DAAP query init failed; WARNING: duplicate hash key\n");
|
||||
DPRINTF(E_FATAL, L_DAAP, "Hash %x, string %s\n", dmap_query_fields[i].hash, dmap_query_fields[i].dmap_field);
|
||||
|
||||
DPRINTF(E_FATAL, L_DAAP, "Hash %x, string %s\n", dqfm->hash, dqfm->dmap_field);
|
||||
}
|
||||
|
||||
goto avl_insert_fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
avl_insert_fail:
|
||||
avl_free_tree(dmap_query_fields_hash);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
daap_query_deinit(void)
|
||||
{
|
||||
avl_free_tree(dmap_query_fields_hash);
|
||||
}
|
27
src/daap_query.h
Normal file
27
src/daap_query.h
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
#ifndef __DAAP_QUERY_H__
|
||||
#define __DAAP_QUERY_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct dmap_query_field_map {
|
||||
uint32_t hash;
|
||||
int as_int;
|
||||
char *dmap_field;
|
||||
char *db_col;
|
||||
};
|
||||
|
||||
|
||||
struct dmap_query_field_map *
|
||||
daap_query_field_lookup(char *field);
|
||||
|
||||
char *
|
||||
daap_query_parse_sql(const char *daap_query);
|
||||
|
||||
int
|
||||
daap_query_init(void);
|
||||
|
||||
void
|
||||
daap_query_deinit(void);
|
||||
|
||||
#endif /* !__DAAP_QUERY_H__ */
|
17
src/db-sql.c
17
src/db-sql.c
@ -150,6 +150,23 @@ int db_sql_escape(char *buffer, int *size, char *fmt, ...) {
|
||||
return DB_E_SUCCESS;
|
||||
}
|
||||
|
||||
char *
|
||||
db_sql_escape_dup(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *escaped;
|
||||
char *ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
escaped = db_sql_vmquery_fn(fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = strdup(escaped);
|
||||
|
||||
db_sql_vmfree_fn(escaped);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch a single row, using the underlying database enum
|
||||
|
@ -31,6 +31,7 @@ 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 char * db_sql_escape_dup(char *fmt, ...);
|
||||
extern int db_sql_add(char **pe, MP3FILE *pmp3, int *id);
|
||||
extern int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo);
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "httpd.h"
|
||||
#include "transcode.h"
|
||||
#include "httpd_daap.h"
|
||||
#include "daap_query.h"
|
||||
|
||||
|
||||
struct uri_map {
|
||||
@ -1949,6 +1950,10 @@ daap_init(void)
|
||||
|
||||
session_id = 100; /* gotta start somewhere, right? */
|
||||
|
||||
ret = daap_query_init();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; daap_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regcomp(&daap_handlers[i].preg, daap_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
|
||||
@ -1957,7 +1962,7 @@ daap_init(void)
|
||||
regerror(ret, &daap_handlers[i].preg, buf, sizeof(buf));
|
||||
|
||||
DPRINTF(E_FATAL, L_DAAP, "DAAP init failed; regexp error: %s\n", buf);
|
||||
return -1;
|
||||
goto regexp_fail;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2000,6 +2005,8 @@ daap_init(void)
|
||||
avl_alloc_fail:
|
||||
for (i = 0; daap_handlers[i].handler; i++)
|
||||
regfree(&daap_handlers[i].preg);
|
||||
regexp_fail:
|
||||
daap_query_deinit();
|
||||
|
||||
return -1;
|
||||
}
|
||||
@ -2009,6 +2016,8 @@ daap_deinit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
daap_query_deinit();
|
||||
|
||||
for (i = 0; daap_handlers[i].handler; i++)
|
||||
regfree(&daap_handlers[i].preg);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user