[daap/rsp/smartpl] Drop ANTLR parsers
Replacing the antlr parsers solves multiple issues: - Build warnings (issue #307) - Build hacks: "-Xconversiontimeout 30000" and other Makefile magic - Incorrect parsing of daap queries with sql wildcards (like 'tag:*tes%t_ng*') - Infinite recursion/memory/CPU (issue #570 and #1248) - systemd service file workarounds due to memory/CPU issue - ANTLR3 being replaced with ANTLR4 (that doesn't support C file generation) - Runtime dependency on antlr library - Difficult installs of ANTLR3 on some systems (special install script)
This commit is contained in:
parent
a95b226fdb
commit
3a93dc5da8
54
INSTALL.md
54
INSTALL.md
|
@ -19,11 +19,11 @@ libraries:
|
|||
```bash
|
||||
sudo apt-get install \
|
||||
build-essential git autotools-dev autoconf automake libtool gettext gawk \
|
||||
gperf antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
|
||||
gperf libconfuse-dev libunistring-dev libsqlite3-dev libprotobuf-c-dev \
|
||||
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
|
||||
libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
|
||||
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
|
||||
libcurl4-openssl-dev libprotobuf-c-dev
|
||||
libcurl4-openssl-dev
|
||||
```
|
||||
|
||||
Note that OwnTone will also work with other versions and flavours of
|
||||
|
@ -83,7 +83,7 @@ sudo yum install \
|
|||
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
|
||||
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
|
||||
libplist-devel libsodium-devel json-c-devel libwebsockets-devel \
|
||||
libcurl-devel protobuf-c-devel antlr3 antlr3-C-devel
|
||||
libcurl-devel protobuf-c-devel
|
||||
```
|
||||
|
||||
Clone the OwnTone repo:
|
||||
|
@ -184,12 +184,6 @@ git clone https://github.com/owntone/owntone-server.git
|
|||
cd owntone-server
|
||||
```
|
||||
|
||||
Install antlr3 and library using the included script:
|
||||
|
||||
```bash
|
||||
scripts/antlr35_install.sh -p /usr/local
|
||||
```
|
||||
|
||||
Finally, configure, build and install, adding configure arguments for
|
||||
optional features:
|
||||
|
||||
|
@ -229,11 +223,6 @@ dns-sd -B _daap._tcp
|
|||
|
||||
Required tools:
|
||||
|
||||
- ANTLR v3 is required to build OwnTone, along with its C runtime
|
||||
(libantlr3c). Use a version between 3.1.3 and 3.5 of ANTLR v3 and the
|
||||
matching C runtime version. Get it from <http://www.antlr3.org/>
|
||||
- Java runtime: ANTLR is written in Java and as such a JRE is required to
|
||||
run the tool. The JRE is enough, you don't need a full JDK.
|
||||
- autotools: autoconf 2.63+, automake 1.10+, libtool 2.2. Run `autoreconf -i`
|
||||
at the top of the source tree to generate the build system.
|
||||
- gettext: libunistring requires iconv and gettext provides the autotools
|
||||
|
@ -242,8 +231,6 @@ Required tools:
|
|||
|
||||
Libraries:
|
||||
|
||||
- libantlr3c (ANTLR3 C runtime, use the same version as antlr3)
|
||||
from <https://github.com/antlr/website-antlr3/tree/gh-pages/download/C>
|
||||
- Avahi client libraries (avahi-client), 0.6.24 minimum
|
||||
from <http://avahi.org/>
|
||||
- sqlite3 3.5.0+ with unlock notify API enabled (read below)
|
||||
|
@ -297,22 +284,6 @@ documentation, look for `SQLITE_ENABLE_UNLOCK_NOTIFY`.
|
|||
Start by generating the build system by running `autoreconf -i`. This will
|
||||
generate the configure script and `Makefile.in`.
|
||||
|
||||
The configure script will look for a wrapper called antlr3 in the PATH to
|
||||
invoke ANTLR3. If your installation of ANTLR3 does not come with such a
|
||||
wrapper, create one as follows:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
CLASSPATH=...
|
||||
exec /path/to/java -cp $CLASSPATH org.antlr.Tool "$@"
|
||||
```
|
||||
|
||||
Adjust the `CLASSPATH` as needed so that Java will find all the jars needed
|
||||
by ANTLR3.
|
||||
|
||||
The parsers will be generated during the build, no manual intervention is
|
||||
needed.
|
||||
|
||||
To display the configure options `run ./configure --help`.
|
||||
|
||||
Support for Spotify is optional. Use `--disable-spotify` to disable this feature.
|
||||
|
@ -371,25 +342,6 @@ Use `--disable-install-systemd` if you don't want that.
|
|||
Using `--enable-install-user` means that `make install` will also add a system
|
||||
user and group for owntone.
|
||||
|
||||
You may see two kinds of warnings during make.
|
||||
First, `/usr/bin/antlr3` may generate a long series of warnings that
|
||||
begin like this:
|
||||
|
||||
```log
|
||||
warning(24): template error: context ...
|
||||
```
|
||||
|
||||
Second, you may see compiler warnings that look like this:
|
||||
|
||||
```log
|
||||
RSPLexer.c: In function `mESCAPED':
|
||||
RSPLexer.c:2674:16: warning: unused variable `_type' [-Wunused-variable]
|
||||
ANTLR3_UINT32 _type;
|
||||
^~~~~
|
||||
```
|
||||
|
||||
You can safely ignore all of these warnings.
|
||||
|
||||
After installation:
|
||||
|
||||
- edit the configuration file, `/etc/owntone.conf`
|
||||
|
|
24
configure.ac
24
configure.ac
|
@ -27,22 +27,10 @@ AS_IF([[test -z "$GPERF"]],
|
|||
AC_MSG_NOTICE([[
|
||||
|
||||
GNU gperf not found, but it's output appears to be present.
|
||||
If you modify any gperf or ANTLR grammar files (.g), you will need
|
||||
to install it.]])],
|
||||
If you modify any .gperf files, you will need to install it.]])],
|
||||
[AC_MSG_ERROR([[GNU gperf required, please install it.]])])
|
||||
])
|
||||
|
||||
AC_PATH_PROG([ANTLR], [[antlr3]])
|
||||
AS_IF([[test -z "$ANTLR"]],
|
||||
[AS_IF([[test -f "$srcdir/src/SMARTPLLexer.h"]],
|
||||
[AM_MISSING_PROG([ANTLR], [[antlr3]])
|
||||
AC_MSG_NOTICE([[
|
||||
|
||||
antlr3 not found, but it's output appears to be present.
|
||||
If you modify any ANTLR grammar files (.g), you will need to install it.]])],
|
||||
[AC_MSG_ERROR([[antlr3 wrapper required, please install it.]])])
|
||||
])
|
||||
|
||||
dnl Enable all warnings by default.
|
||||
AM_CPPFLAGS="-Wall"
|
||||
AC_SUBST([AM_CPPFLAGS])
|
||||
|
@ -174,16 +162,6 @@ PKG_CHECK_EXISTS([libplist],
|
|||
[OWNTONE_MODULES_CHECK([OWNTONE], [LIBPLIST], [libplist-2.0],
|
||||
[plist_dict_get_item], [plist/plist.h])])
|
||||
|
||||
|
||||
OWNTONE_FUNC_REQUIRE([OWNTONE], [ANTLR3 C runtime], [ANTLR3C], [antlr3c],
|
||||
[antlr3BaseRecognizerNew], [antlr3.h],
|
||||
[AC_CHECK_FUNC([[antlr3NewAsciiStringInPlaceStream]],
|
||||
[AC_DEFINE([ANTLR3C_NEW_INPUT], 0,
|
||||
[define if antlr3 C runtime uses new input routines])],
|
||||
[AC_DEFINE([ANTLR3C_NEW_INPUT], 1,
|
||||
[define if antlr3 C runtime uses new input routines])])
|
||||
])
|
||||
|
||||
AM_PATH_LIBGCRYPT([1:1.2.0])
|
||||
OWNTONE_FUNC_REQUIRE([OWNTONE], [GNU Crypt Library], [LIBGCRYPT], [gcrypt],
|
||||
[gcry_control], [gcrypt.h])
|
||||
|
|
|
@ -21,7 +21,7 @@ Source0: https://github.com/owntone/%{name}/archive/%{version}/%{name}-%{version
|
|||
BuildRequires: gcc, make, systemd, pkgconfig, libunistring-devel
|
||||
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(mxml)
|
||||
BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0
|
||||
BuildRequires: pkgconfig(json-c), antlr3-C-devel, libgcrypt-devel >= 1.2.0
|
||||
BuildRequires: pkgconfig(json-c), libgcrypt-devel >= 1.2.0
|
||||
BuildRequires: libgpg-error-devel >= 1.6
|
||||
BuildRequires: pkgconfig(libavformat), pkgconfig(libavcodec)
|
||||
BuildRequires: pkgconfig(libswscale), pkgconfig(libavutil)
|
||||
|
@ -109,6 +109,9 @@ exit 0
|
|||
%{_mandir}/man?/*
|
||||
|
||||
%changelog
|
||||
* Mon Jan 10 2022 Espen Jürgensen <espen.jurgensen@gmail.com> - 28.3-1
|
||||
- Remove antlr dependency
|
||||
|
||||
* Mon Nov 22 2021 Derek Atkins <derek@ihtfp.com> - 28.2-1
|
||||
- Release tarball is a XZ not GZ file
|
||||
- Configure always needs protobuf-c, not just for chromecast
|
||||
|
|
|
@ -1,255 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
WORKDIR=~/antlr35.tmp
|
||||
|
||||
# programs
|
||||
MAKE=${MAKE-make}
|
||||
DOWNLOAD="wget --no-check-certificate"
|
||||
ALTDOWNLOAD="curl -LO"
|
||||
SUDO=sudo
|
||||
|
||||
# source
|
||||
ANTLR_VERSION=3.5
|
||||
|
||||
ANTLR3_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download"
|
||||
ANTLR3_JAR="antlr-3.5.2-complete.jar"
|
||||
ANTLR3_URL="$ANTLR3_SOURCE/$ANTLR3_JAR"
|
||||
|
||||
LIBANTLR3C="libantlr3c-3.4"
|
||||
LIBANTLR3C_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download/C"
|
||||
LIBANTLR3C_TAR="${LIBANTLR3C}.tar.gz"
|
||||
LIBANTLR3C_URL="$LIBANTLR3C_SOURCE/$LIBANTLR3C_TAR"
|
||||
|
||||
usage() {
|
||||
echo
|
||||
echo "This script will download, build and install antlr $ANTLR_VERSION"
|
||||
echo " (and matching libantlrc) on your computer."
|
||||
echo
|
||||
echo "Usage: ${0##*/} -h | [ -p <prefix> ] [ -y ] [ <build-dir> ]"
|
||||
echo
|
||||
echo "Parameters:"
|
||||
echo " -h Show this help"
|
||||
echo " -p <prefix> Install to prefix (default: choose /usr or /usr/local)"
|
||||
echo " -y Automatic yes to prompts (run non-interactively with -p)"
|
||||
echo " <build-dir> Build directory (default: $WORKDIR)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
GIVEN_PREFIX=
|
||||
ALWAYS_YES=
|
||||
while [ "$1" != "" ]; do
|
||||
case $1 in
|
||||
-p | --prefix )
|
||||
shift
|
||||
GIVEN_PREFIX=$1
|
||||
;;
|
||||
-y | --yes )
|
||||
ALWAYS_YES=1
|
||||
;;
|
||||
-h | --help )
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
* )
|
||||
echo "Unrecognized option $1 (try -h for usage)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# override build directory? (support ~ expansion)
|
||||
[ -n "$1" ] && WORKDIR=$1
|
||||
ORIG_DIR=`pwd`
|
||||
|
||||
err() {
|
||||
echo "$*"
|
||||
if [ -n "$FILES_EXIST" ]; then
|
||||
echo "Files remain in $WORKDIR..."
|
||||
else
|
||||
cd "$ORIG_DIR"
|
||||
rmdir "$WORKDIR"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
is_yes() {
|
||||
case "$1" in
|
||||
[N]*|[n]*) return 1;;
|
||||
*) ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
ask_yn() {
|
||||
if [ "$ALWAYS_YES" = "1" ]; then
|
||||
yn="y"
|
||||
else
|
||||
read -p "$1" yn
|
||||
fi
|
||||
}
|
||||
|
||||
prog_install() {
|
||||
ask_yn "Would you like to install into $PREFIX now? [Y/n] "
|
||||
if ! is_yes "$yn"; then
|
||||
echo "Build left ready to install from $WORKDIR"
|
||||
echo "You can re-run the script (eg. as root) to install into"
|
||||
echo " $PREFIX later."
|
||||
exit
|
||||
fi
|
||||
if [ `id -u` -ne 0 ]; then
|
||||
ask_yn "Would you like to install with sudo? NOTE: You WILL be asked for your password! [Y/n] "
|
||||
if ! is_yes "$yn"; then
|
||||
SUDO=
|
||||
ask_yn "Continue to install as non-root user? [Y/n] "
|
||||
is_yes "$yn" || err "Install cancelled"
|
||||
fi
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
cd $LIBANTLR3C || err "Unable to cd to build libantlr3c build directory!"
|
||||
echo "Installing libantlr3c to $PREFIX"
|
||||
$SUDO $MAKE install || err "Install of libantlr3c to $PREFIX failed!"
|
||||
|
||||
cd "$ORIG_DIR"
|
||||
cd $WORKDIR
|
||||
echo "Installing antlr3 to $PREFIX"
|
||||
$SUDO mkdir -p "$PREFIX_JAVA" || err "Unable to create $PREFIX_JAVA"
|
||||
$SUDO install "$ANTLR3_JAR" "$PREFIX_JAVA" || \
|
||||
err "Failed to install antlr3 jar to $PREFIX_JAVA"
|
||||
$SUDO mkdir -p "$PREFIX/bin" || err "Unable to create $PREFIX/bin"
|
||||
$SUDO install -m 755 antlr3 "$PREFIX/bin" || \
|
||||
err "Failed to install antlr3 to $PREFIX/bin"
|
||||
echo "Install complete (build remains in $WORKDIR)"
|
||||
}
|
||||
|
||||
echo "This script will download, build and install antlr $ANTLR_VERSION"
|
||||
echo " (and matching libantlrc) on your computer."
|
||||
echo
|
||||
|
||||
# check if make works
|
||||
ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
|
||||
if [ -z "$ISGNU" ]; then
|
||||
MAKE=gmake
|
||||
ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
|
||||
fi
|
||||
[ -z "$ISGNU" ] && err "Unable to locate GNU Make, set \$MAKE to it's location and re-run"
|
||||
|
||||
if [ -f "$WORKDIR/install_env" ]; then
|
||||
echo "Existing build found in $WORKDIR"
|
||||
FILES_EXIST=1
|
||||
cd $WORKDIR || err "Unable to cd to '$WORKDIR'"
|
||||
. install_env
|
||||
[ -n "$PREFIX" ] || err "PREFIX is missing in file 'install_env'"
|
||||
if [ -n "$GIVEN_PREFIX" ] && [ "$GIVEN_PREFIX" != "$PREFIX" ]; then
|
||||
echo "You must rebuild to install into $GIVEN_PREFIX (current build for $PREFIX)"
|
||||
ask_yn "Would you like to rebuild for ${GIVEN_PREFIX}? [Y/n] "
|
||||
if is_yes "$yn"; then
|
||||
rm -f install_env
|
||||
PREFIX=
|
||||
else
|
||||
ask_yn "Would you like to install to ${PREFIX}? [Y/n] "
|
||||
! is_yes "$yn" && err "Install cancelled"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$PREFIX" ]; then
|
||||
PREFIX_JAVA=$PREFIX/share/java
|
||||
prog_install
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d "$WORKDIR" ]; then
|
||||
ask_yn "Should the script create $WORKDIR and use it for building? [Y/n] "
|
||||
is_yes "$yn" || exit
|
||||
fi
|
||||
|
||||
if [ -n "$GIVEN_PREFIX" ]; then
|
||||
PREFIX=$GIVEN_PREFIX
|
||||
else
|
||||
read -p "Should the script install with prefix /usr or /usr/local? [U/l] " yn
|
||||
if [ "$yn" = "l" ]; then
|
||||
PREFIX=/usr/local
|
||||
else
|
||||
PREFIX=/usr
|
||||
fi
|
||||
fi
|
||||
PREFIX_JAVA=$PREFIX/share/java
|
||||
|
||||
MACHBITS=`getconf LONG_BIT 2>/dev/null`
|
||||
[ "$MACHBITS" = "64" ] && DEF_AN="[Y/n]" || DEF_AN="[y/N]"
|
||||
ask_yn "Should the script build libantlr3c for 64 bit? $DEF_AN "
|
||||
[ -z "$yn" -a "$MACHBITS" != "64" ] && yn=n
|
||||
is_yes "$yn" && ENABLE64BIT="--enable-64bit"
|
||||
|
||||
mkdir -p "$WORKDIR" || err "Error creating $WORKDIR"
|
||||
# don't quote WORKDIR to catch a WORKDIR that will break the build (eg spaces)
|
||||
cd $WORKDIR || err "Unable to cd to '$WORKDIR' (does it include spaces?)"
|
||||
|
||||
REMOVE_ON_CANCEL=
|
||||
cancel_download() {
|
||||
echo "removing $REMOVE_ON_CANCEL"
|
||||
[ -n "$REMOVE_ON_CANCEL" ] && rm -f "$REMOVE_ON_CANCEL"
|
||||
err "Cancelling download..."
|
||||
}
|
||||
|
||||
antlr_download() {
|
||||
trap cancel_download SIGINT
|
||||
$DOWNLOAD --help >/dev/null 2>&1 || DOWNLOAD=$ALTDOWNLOAD
|
||||
$DOWNLOAD --help >/dev/null 2>&1 || {
|
||||
echo "Unable to find wget or curl commands to download source,"
|
||||
echo " please install either one and re-try."
|
||||
exit 1
|
||||
}
|
||||
[ "x$1" = "xreset" ] && rm "$ANTLR3_JAR" "$LIBANTLR3C_TAR"
|
||||
if [ ! -f "$ANTLR3_JAR" ]; then
|
||||
echo
|
||||
echo "Downloading antlr from $ANTLR3_URL"
|
||||
echo "Ctrl-C to abort..."
|
||||
REMOVE_ON_CANCEL=$ANTLR3_JAR
|
||||
$DOWNLOAD "$ANTLR3_URL" || err "Download of $ANTLR3_JAR failed!"
|
||||
FILES_EXIST=1
|
||||
fi
|
||||
if [ ! -f "$LIBANTLR3C_TAR" ]; then
|
||||
echo
|
||||
echo "Downloading libantlr3c from $LIBANTLR3C_URL"
|
||||
echo "Ctrl-C to abort..."
|
||||
REMOVE_ON_CANCEL=$LIBANTLR3C_TAR
|
||||
$DOWNLOAD "$LIBANTLR3C_URL" || err "Download of $LIBANTLR3C_TAR failed!"
|
||||
FILES_EXIST=1
|
||||
fi
|
||||
trap - SIGINT
|
||||
}
|
||||
|
||||
# retrieve the source
|
||||
if [ -f "$ANTLR3_JAR" -a -f "$LIBANTLR3C_TAR" ]; then
|
||||
FILES_EXIST=1
|
||||
ask_yn "Files appear to already be downloaded, use them? [Y/n] "
|
||||
! is_yes "$yn" && antlr_download reset
|
||||
else
|
||||
ask_yn "Should the script download and build antlr and libantlr3c? [Y/n] "
|
||||
is_yes "$yn" || exit
|
||||
antlr_download
|
||||
fi
|
||||
|
||||
# build/install libantlr3c
|
||||
[ -d "$LIBANTLR3C" ] && rm -rf "$LIBANTLR3C"
|
||||
tar xzf "$LIBANTLR3C_TAR" || err "Uncompress of $LIBANTLR3C_TAR failed!"
|
||||
cd $LIBANTLR3C || err "Unable to cd to build $LIBANTLR3C build directory!"
|
||||
./configure $ENABLE64BIT --prefix=$PREFIX && $MAKE
|
||||
[ $? -ne 0 ] && err "Build of libantlr3c failed!"
|
||||
|
||||
# install antlr3 jar and wrapper
|
||||
cd "$ORIG_DIR"
|
||||
cd $WORKDIR
|
||||
printf "#!/bin/sh
|
||||
export CLASSPATH
|
||||
CLASSPATH=\$CLASSPATH:$PREFIX_JAVA/${ANTLR3_JAR}:$PREFIX_JAVA
|
||||
/usr/bin/java org.antlr.Tool \$*
|
||||
" > antlr3
|
||||
|
||||
# save for later install attempts
|
||||
echo "PREFIX=$PREFIX" > install_env
|
||||
echo
|
||||
|
||||
prog_install
|
|
@ -46,30 +46,6 @@ if [ ! -d $WORKDIR ]; then
|
|||
fi
|
||||
cd $WORKDIR
|
||||
|
||||
read -p "Should the script install antlr and libantlr3c? [y/N] " yn
|
||||
if [ "$yn" = "y" ]; then
|
||||
read -p "Should the script build libantlr3c for 64 bit? [Y/n] " yn
|
||||
if [ "$yn" != "n" ]; then
|
||||
ENABLE64BIT="--enable-64bit"
|
||||
fi
|
||||
|
||||
wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/antlr-3.4-complete.jar
|
||||
wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/C/libantlr3c-3.4.tar.gz
|
||||
|
||||
sudo install antlr-3.4-complete.jar /usr/local/share/java
|
||||
printf "#!/bin/sh
|
||||
export CLASSPATH
|
||||
CLASSPATH=\$CLASSPATH:/usr/local/share/java/antlr-3.4-complete.jar:/usr/local/share/java
|
||||
/usr/local/bin/java org.antlr.Tool \$*
|
||||
" > antlr3
|
||||
sudo install -m 755 antlr3 /usr/local/bin
|
||||
|
||||
tar xzf libantlr3c-3.4.tar.gz
|
||||
cd libantlr3c-3.4
|
||||
./configure $ENABLE64BIT && gmake && sudo gmake install
|
||||
cd $WORKDIR
|
||||
fi
|
||||
|
||||
read -p "Should the script build owntone? [y/N] " yn
|
||||
if [ "$yn" = "y" ]; then
|
||||
git clone https://github.com/owntone/owntone-server.git
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
owntone
|
||||
|
||||
*.tokens
|
||||
*Lexer.[ch]
|
||||
*Parser.[ch]
|
||||
*2SQL.[ch]
|
||||
*.u
|
||||
|
||||
daap_query_hash.h
|
||||
rsp_query_hash.h
|
||||
dacp_prop_hash.h
|
||||
|
|
63
src/DAAP.g
63
src/DAAP.g
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2010 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)"\'")); }
|
||||
)
|
||||
;
|
383
src/DAAP2SQL.g
383
src/DAAP2SQL.g
|
@ -1,383 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2011 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 <string.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "daap_query.h"
|
||||
}
|
||||
|
||||
@members {
|
||||
struct dmap_query_field_map {
|
||||
char *dmap_field;
|
||||
char *db_col;
|
||||
int as_int;
|
||||
};
|
||||
|
||||
/* gperf static hash, daap_query.gperf */
|
||||
#include "daap_query_hash.h"
|
||||
}
|
||||
|
||||
query returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: 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)
|
||||
{
|
||||
$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, ")");
|
||||
}
|
||||
else if ($a.valid)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->appendS($result, $a.result);
|
||||
}
|
||||
else if ($b.valid)
|
||||
{
|
||||
$result = $b.result->factory->newRaw($b.result->factory);
|
||||
$result->appendS($result, $b.result);
|
||||
}
|
||||
else
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
}
|
||||
| ^(OPOR a = expr b = expr)
|
||||
{
|
||||
if ($a.valid && $b.valid)
|
||||
{
|
||||
$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, ")");
|
||||
}
|
||||
else if ($a.valid)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->appendS($result, $a.result);
|
||||
}
|
||||
else if ($b.valid)
|
||||
{
|
||||
$result = $b.result->factory->newRaw($b.result->factory);
|
||||
$result->appendS($result, $b.result);
|
||||
}
|
||||
else
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
}
|
||||
| STR
|
||||
{
|
||||
pANTLR3_STRING str;
|
||||
pANTLR3_UINT8 field;
|
||||
pANTLR3_UINT8 val;
|
||||
pANTLR3_UINT8 escaped;
|
||||
ANTLR3_UINT8 op;
|
||||
int neg_op;
|
||||
const struct dmap_query_field_map *dqfm;
|
||||
char *end;
|
||||
long long llval;
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
/* Make daap.songalbumid:0 a no-op */
|
||||
if (strcmp((char *)str->chars, "daap.songalbumid:0") == 0)
|
||||
{
|
||||
$result->append8($result, "1 = 1");
|
||||
|
||||
goto STR_out;
|
||||
}
|
||||
|
||||
field = str->chars;
|
||||
|
||||
val = field;
|
||||
while ((*val != '\0') && ((*val == '.')
|
||||
|| (*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 (op == '!')
|
||||
{
|
||||
if (*val == '\0')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
neg_op = 1;
|
||||
op = *val;
|
||||
val++;
|
||||
}
|
||||
else
|
||||
neg_op = 0;
|
||||
|
||||
/* Lookup DMAP field in the query field map */
|
||||
dqfm = daap_query_field_lookup((char *)field, strlen((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 */
|
||||
}
|
||||
|
||||
/* Empty values OK for string fields, NOK for integer */
|
||||
if (*val == '\0')
|
||||
{
|
||||
if (dqfm->as_int)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
/* No need to exclude empty artist and album, as the server makes sure there always exists an artist/album. */
|
||||
if (neg_op && (op == ':' || op == '@')
|
||||
&& (strcmp((char *)field, "daap.songalbumartist") == 0
|
||||
|| strcmp((char *)field, "daap.songartist") == 0
|
||||
|| strcmp((char *)field, "daap.songalbum") == 0))
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Ignoring clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0;
|
||||
}
|
||||
|
||||
/* Need to check against NULL too */
|
||||
if (op == ':' || op == '@')
|
||||
$result->append8($result, "(");
|
||||
}
|
||||
|
||||
/* Int field: check integer conversion */
|
||||
if (dqfm->as_int)
|
||||
{
|
||||
errno = 0;
|
||||
llval = strtoll((const char *)val, &end, 10);
|
||||
|
||||
if (((errno == ERANGE) && ((llval == LLONG_MAX) || (llval == LLONG_MIN)))
|
||||
|| ((errno != 0) && (llval == 0)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not convert to an integer type\n",
|
||||
val, field, (neg_op) ? "!" : "", op, val);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
if (end == (char *)val)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not represent an integer value\n",
|
||||
val, field, (neg_op) ? "!" : "", op, val);
|
||||
$valid = 0;
|
||||
goto STR_result_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
*end = '\0'; /* Cut out potential garbage - we're being kind */
|
||||
|
||||
/* The server only has media_kind = 1 for music - so remove media_kind = 32 to imporve select query performance. */
|
||||
if (llval == 32
|
||||
&& (strcmp((char *)field, "com.apple.itunes.mediakind") == 0
|
||||
|| strcmp((char *)field, "com.apple.itunes.extended-media-kind") == 0))
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Ignoring clause '\%s\%s\%c\%s'\n", field, (neg_op) ? "!" : "", op, val);
|
||||
|
||||
if (neg_op)
|
||||
$result->append8($result, "1 = 1");
|
||||
else
|
||||
$result->append8($result, "1 = 0");
|
||||
|
||||
goto STR_out;
|
||||
}
|
||||
}
|
||||
/* String field: escape string, check for '*' */
|
||||
else
|
||||
{
|
||||
/* With Apple Music 1.0.1.37, we observed some ':' queries using '@' instead, resulting in 'Unknown operator' errors */
|
||||
/* Ex: '/databases/1/containers?query=('com.apple.itunes.extended-media-kind@1', ...) */
|
||||
if (op != ':' && 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_escape_string((char *)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[0] && val[1] && val[strlen((char *)val) - 1] == '*')
|
||||
{
|
||||
op = '\%';
|
||||
val[strlen((char *)val) - 1] = '\%';
|
||||
}
|
||||
}
|
||||
|
||||
$result->append8($result, dqfm->db_col);
|
||||
|
||||
switch(op)
|
||||
{
|
||||
case '@':
|
||||
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 '-':
|
||||
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, "'");
|
||||
|
||||
/* For empty string value, we need to check against NULL too */
|
||||
if ((*val == '\0') && (op == ':' || op == '@'))
|
||||
{
|
||||
if (neg_op)
|
||||
$result->append8($result, " AND ");
|
||||
else
|
||||
$result->append8($result, " OR ");
|
||||
|
||||
$result->append8($result, dqfm->db_col);
|
||||
|
||||
if (neg_op)
|
||||
$result->append8($result, " IS NOT NULL");
|
||||
else
|
||||
$result->append8($result, " IS NULL");
|
||||
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
|
||||
STR_result_valid_0: /* bail out label */
|
||||
;
|
||||
|
||||
if (escaped)
|
||||
free(escaped);
|
||||
|
||||
STR_out: /* get out of here */
|
||||
;
|
||||
}
|
||||
;
|
|
@ -62,28 +62,6 @@ GPERF_FILES = \
|
|||
|
||||
GPERF_SRC = $(GPERF_FILES:.gperf=_hash.h)
|
||||
|
||||
ANTLR_GRAMMARS = \
|
||||
RSP.g RSP2SQL.g \
|
||||
DAAP.g DAAP2SQL.g \
|
||||
SMARTPL.g SMARTPL2SQL.g
|
||||
|
||||
ANTLR_TOKENS = $(ANTLR_GRAMMARS:.g=.tokens)
|
||||
|
||||
ANTLR_DEPS = $(ANTLR_GRAMMARS:%.g=$(srcdir)/%.u)
|
||||
|
||||
ANTLR_SRC = \
|
||||
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
|
||||
RSP2SQL.c RSP2SQL.h \
|
||||
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
||||
DAAP2SQL.c DAAP2SQL.h \
|
||||
SMARTPLLexer.c SMARTPLLexer.h SMARTPLParser.c SMARTPLParser.h \
|
||||
SMARTPL2SQL.c SMARTPL2SQL.h
|
||||
|
||||
ANTLR_OBJECTS = \
|
||||
RSPLexer.$(OBJEXT) RSPParser.$(OBJEXT) RSP2SQL.$(OBJEXT) \
|
||||
DAAPLexer.$(OBJEXT) DAAPParser.$(OBJEXT) DAAP2SQL.$(OBJEXT) \
|
||||
SMARTPLLexer.$(OBJEXT) SMARTPLParser.$(OBJEXT) SMARTPL2SQL.$(OBJEXT)
|
||||
|
||||
AM_CPPFLAGS += \
|
||||
$(OWNTONE_CPPFLAGS) \
|
||||
$(OWNTONE_OPTS_CPPFLAGS) \
|
||||
|
@ -152,42 +130,16 @@ owntone_SOURCES = main.c \
|
|||
mxml-compat.h \
|
||||
outputs/plist_wrap.h \
|
||||
$(LIBWEBSOCKETS_SRC) \
|
||||
$(GPERF_SRC) \
|
||||
$(ANTLR_SRC)
|
||||
$(GPERF_SRC)
|
||||
|
||||
# built by maintainers, and distributed. Clean with maintainer-clean
|
||||
BUILT_SOURCES = \
|
||||
$(GPERF_SRC) \
|
||||
$(ANTLR_SRC) \
|
||||
$(ANTLR_TOKENS) \
|
||||
$(ANTLR_DEPS)
|
||||
$(GPERF_SRC)
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(GPERF_FILES) \
|
||||
$(ANTLR_GRAMMARS) \
|
||||
$(ANTLR_TOKENS) \
|
||||
$(ANTLR_DEPS)
|
||||
|
||||
# silence unused warnings from antlr generated files
|
||||
$(ANTLR_OBJECTS): AM_CPPFLAGS += -Wno-unused
|
||||
$(GPERF_FILES)
|
||||
|
||||
# gperf construction rules
|
||||
%_hash.h: %.gperf
|
||||
$(AM_V_GEN)$(GPERF) --output-file=$@ $<
|
||||
|
||||
# silent rules for antlr
|
||||
antlr_verbose = $(antlr_verbose_@AM_V@)
|
||||
antlr_verbose_ = $(antlr_verbose_@AM_DEFAULT_V@)
|
||||
antlr_verbose_0 = @echo " GEN " $< "products";
|
||||
|
||||
# ANTLR grammar products
|
||||
%.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g
|
||||
$(antlr_verbose)$(ANTLR) -Xconversiontimeout 30000 $(ANTLR_OPTIONS) -fo . $<
|
||||
|
||||
# ANTLR dependency files (bypass circular dependency of .g on .tokens)
|
||||
%.u: %.g
|
||||
$(AM_V_GEN)$(ANTLR) -depend -fo . $< > $@
|
||||
$(AM_V_at)$(SED) -n -e '/^.*\.g[ ]*:\(.*\)/ { s//\1/;h;d; }' -e '/\.tokens.*:/ { p;d; }' -e '/:/ { G;s/\n/ /;p; }' $@ > $@-t
|
||||
$(AM_V_at)mv $@-t $@
|
||||
|
||||
-include $(ANTLR_DEPS)
|
||||
|
|
148
src/RSP.g
148
src/RSP.g
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2010 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 RSP;
|
||||
|
||||
options {
|
||||
output = AST;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
query : expr NEWLINE? EOF -> expr
|
||||
;
|
||||
|
||||
expr : aexpr (OR^ aexpr)*
|
||||
;
|
||||
|
||||
aexpr : crit (AND^ crit)*
|
||||
;
|
||||
|
||||
crit : LPAR expr RPAR -> expr
|
||||
| strcrit
|
||||
| intcrit
|
||||
| datecrit
|
||||
;
|
||||
|
||||
strcrit : FIELD strop STR -> ^(strop FIELD STR)
|
||||
| FIELD NOT strop STR -> ^(NOT ^(strop FIELD STR))
|
||||
;
|
||||
|
||||
strop : equal=EQUAL
|
||||
| includes=INCLUDES
|
||||
| startsw=STARTSW
|
||||
| endsw=ENDSW
|
||||
;
|
||||
|
||||
intcrit : FIELD intop INT -> ^(intop FIELD INT)
|
||||
| FIELD NOT intop INT -> ^(NOT ^(intop FIELD INT))
|
||||
;
|
||||
|
||||
intop : equal=EQUAL
|
||||
| less=LESS
|
||||
| greater=GREATER
|
||||
| lte=LTE
|
||||
| gte=GTE
|
||||
;
|
||||
|
||||
datecrit: FIELD dateop datespec -> ^(dateop FIELD datespec)
|
||||
;
|
||||
|
||||
dateop : before=BEFORE
|
||||
| after=AFTER
|
||||
;
|
||||
|
||||
datespec: dateref
|
||||
| INT dateintval dateop dateref -> ^(dateop dateref INT dateintval)
|
||||
;
|
||||
|
||||
dateref : date=DATE
|
||||
| today=TODAY
|
||||
;
|
||||
|
||||
dateintval
|
||||
: day=DAY
|
||||
| week=WEEK
|
||||
| month=MONTH
|
||||
| year=YEAR
|
||||
;
|
||||
|
||||
QUOTE : '"';
|
||||
LPAR : '(';
|
||||
RPAR : ')';
|
||||
|
||||
AND : 'and';
|
||||
OR : 'or';
|
||||
NOT : '!';
|
||||
|
||||
/* Both string & int */
|
||||
EQUAL : '=';
|
||||
|
||||
/* String */
|
||||
INCLUDES: 'includes';
|
||||
STARTSW : 'startswith';
|
||||
ENDSW : 'endswith';
|
||||
|
||||
/* Int */
|
||||
GREATER : '>';
|
||||
LESS : '<';
|
||||
GTE : '>=';
|
||||
LTE : '<=';
|
||||
|
||||
/* Date */
|
||||
BEFORE : 'before';
|
||||
AFTER : 'after';
|
||||
DAY : 'day' | 'days';
|
||||
WEEK : 'week' | 'weeks';
|
||||
MONTH : 'month' | 'months';
|
||||
YEAR : 'year' | 'years';
|
||||
TODAY : 'today';
|
||||
|
||||
NEWLINE : '\r'? '\n';
|
||||
|
||||
WS : (' ' | '\t') { $channel = HIDDEN; };
|
||||
|
||||
FIELD : 'a'..'z' ('a'..'z' | '_')* 'a'..'z';
|
||||
|
||||
INT : DIGIT19 DIGIT09*;
|
||||
|
||||
/* YYYY-MM-DD */
|
||||
DATE : DIGIT19 DIGIT09 DIGIT09 DIGIT09 '-' ('0' DIGIT19 | '1' '0'..'2') '-' ('0' DIGIT19 | '1'..'2' DIGIT09 | '3' '0'..'1');
|
||||
|
||||
/*
|
||||
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)"\"")); }
|
||||
)
|
||||
;
|
||||
|
||||
fragment
|
||||
DIGIT09 : '0'..'9';
|
||||
|
||||
fragment
|
||||
DIGIT19 : '1'..'9';
|
474
src/RSP2SQL.g
474
src/RSP2SQL.g
|
@ -1,474 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2011 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 RSP2SQL;
|
||||
|
||||
options {
|
||||
tokenVocab = RSP;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
@header {
|
||||
/* Needs #define _GNU_SOURCE for strptime() */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "misc.h"
|
||||
#include "rsp_query.h"
|
||||
}
|
||||
|
||||
@members {
|
||||
#define RSP_TYPE_STRING 0
|
||||
#define RSP_TYPE_INT 1
|
||||
#define RSP_TYPE_DATE 2
|
||||
|
||||
struct rsp_query_field_map {
|
||||
char *rsp_field;
|
||||
int field_type;
|
||||
/* RSP fields are named after the DB columns - or vice versa */
|
||||
};
|
||||
|
||||
/* gperf static hash, rsp_query.gperf */
|
||||
#include "rsp_query_hash.h"
|
||||
}
|
||||
|
||||
query returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: 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; }
|
||||
: ^(AND 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, ")");
|
||||
}
|
||||
}
|
||||
| ^(OR 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, ")");
|
||||
}
|
||||
}
|
||||
| c = strcrit
|
||||
{
|
||||
$valid = $c.valid;
|
||||
$result = $c.result;
|
||||
}
|
||||
| ^(NOT c = strcrit)
|
||||
{
|
||||
if (!$c.valid || !$c.result)
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $c.result->factory->newRaw($c.result->factory);
|
||||
$result->append8($result, "(NOT ");
|
||||
$result->appendS($result, $c.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
}
|
||||
| i = intcrit
|
||||
{
|
||||
$valid = $i.valid;
|
||||
$result = $i.result;
|
||||
}
|
||||
| ^(NOT i = intcrit)
|
||||
{
|
||||
if (!$i.valid || !$i.result)
|
||||
{
|
||||
$valid = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $i.result->factory->newRaw($i.result->factory);
|
||||
$result->append8($result, "(NOT ");
|
||||
$result->appendS($result, $i.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
}
|
||||
| d = datecrit
|
||||
{
|
||||
$valid = $d.valid;
|
||||
$result = $d.result;
|
||||
}
|
||||
;
|
||||
|
||||
strcrit returns [ pANTLR3_STRING result, int valid ]
|
||||
@init { $result = NULL; $valid = 1; }
|
||||
: ^(o = strop f = FIELD s = STR)
|
||||
{
|
||||
char *op;
|
||||
const struct rsp_query_field_map *rqfp;
|
||||
pANTLR3_STRING field;
|
||||
char *escaped;
|
||||
ANTLR3_UINT32 optok;
|
||||
|
||||
escaped = NULL;
|
||||
|
||||
op = NULL;
|
||||
optok = $o.op->getType($o.op);
|
||||
switch (optok)
|
||||
{
|
||||
case EQUAL:
|
||||
op = " = ";
|
||||
break;
|
||||
|
||||
case INCLUDES:
|
||||
case STARTSW:
|
||||
case ENDSW:
|
||||
op = " LIKE ";
|
||||
break;
|
||||
}
|
||||
|
||||
field = $f->getText($f);
|
||||
|
||||
/* Field lookup */
|
||||
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
|
||||
if (!rqfp)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
|
||||
$valid = 0;
|
||||
goto strcrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
/* Check field type */
|
||||
if (rqfp->field_type != RSP_TYPE_STRING)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a string field\n", field->chars);
|
||||
$valid = 0;
|
||||
goto strcrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
escaped = db_escape_string((char *)$s->getText($s)->chars);
|
||||
if (!escaped)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Could not escape value\n");
|
||||
$valid = 0;
|
||||
goto strcrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
$result = field->factory->newRaw(field->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, field);
|
||||
$result->append8($result, op);
|
||||
$result->append8($result, "'");
|
||||
if ((optok == INCLUDES) || (optok == STARTSW))
|
||||
$result->append8($result, "\%");
|
||||
|
||||
$result->append8($result, escaped);
|
||||
|
||||
if ((optok == INCLUDES) || (optok == ENDSW))
|
||||
$result->append8($result, "\%");
|
||||
$result->append8($result, "'");
|
||||
|
||||
strcrit_valid_0:
|
||||
;
|
||||
|
||||
if (escaped)
|
||||
free(escaped);
|
||||
}
|
||||
;
|
||||
|
||||
strop returns [ pANTLR3_COMMON_TOKEN op ]
|
||||
@init { $op = NULL; }
|
||||
: n = EQUAL
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = INCLUDES
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = STARTSW
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = ENDSW
|
||||
{ $op = $n->getToken($n); }
|
||||
;
|
||||
|
||||
intcrit returns [ pANTLR3_STRING result, int valid ]
|
||||
@init { $result = NULL; $valid = 1; }
|
||||
: ^(o = intop f = FIELD i = INT)
|
||||
{
|
||||
char *op;
|
||||
const struct rsp_query_field_map *rqfp;
|
||||
pANTLR3_STRING field;
|
||||
|
||||
op = NULL;
|
||||
switch ($o.op->getType($o.op))
|
||||
{
|
||||
case EQUAL:
|
||||
op = " = ";
|
||||
break;
|
||||
|
||||
case LESS:
|
||||
op = " < ";
|
||||
break;
|
||||
|
||||
case GREATER:
|
||||
op = " > ";
|
||||
break;
|
||||
|
||||
case LTE:
|
||||
op = " <= ";
|
||||
break;
|
||||
|
||||
case GTE:
|
||||
op = " >= ";
|
||||
break;
|
||||
}
|
||||
|
||||
field = $f->getText($f);
|
||||
|
||||
/* Field lookup */
|
||||
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
|
||||
if (!rqfp)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
|
||||
$valid = 0;
|
||||
goto intcrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
/* Check field type */
|
||||
if (rqfp->field_type != RSP_TYPE_INT)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not an integer field\n", field->chars);
|
||||
$valid = 0;
|
||||
goto intcrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
$result = field->factory->newRaw(field->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, field);
|
||||
$result->append8($result, op);
|
||||
$result->appendS($result, $i->getText($i));
|
||||
|
||||
intcrit_valid_0:
|
||||
;
|
||||
}
|
||||
;
|
||||
|
||||
intop returns [ pANTLR3_COMMON_TOKEN op ]
|
||||
@init { $op = NULL; }
|
||||
: n = EQUAL
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = LESS
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = GREATER
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = LTE
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = GTE
|
||||
{ $op = $n->getToken($n); }
|
||||
;
|
||||
|
||||
datecrit returns [ pANTLR3_STRING result, int valid ]
|
||||
@init { $result = NULL; $valid = 1; }
|
||||
: ^(o = dateop f = FIELD d = datespec)
|
||||
{
|
||||
char *op;
|
||||
const struct rsp_query_field_map *rqfp;
|
||||
pANTLR3_STRING field;
|
||||
char buf[32];
|
||||
int ret;
|
||||
|
||||
op = NULL;
|
||||
switch ($o.op->getType($o.op))
|
||||
{
|
||||
case BEFORE:
|
||||
op = " < ";
|
||||
break;
|
||||
|
||||
case AFTER:
|
||||
op = " > ";
|
||||
break;
|
||||
}
|
||||
|
||||
field = $f->getText($f);
|
||||
|
||||
/* Field lookup */
|
||||
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
|
||||
if (!rqfp)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
|
||||
$valid = 0;
|
||||
goto datecrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
/* Check field type */
|
||||
if (rqfp->field_type != RSP_TYPE_DATE)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a date field\n", field->chars);
|
||||
$valid = 0;
|
||||
goto datecrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
ret = snprintf(buf, sizeof(buf), "\%ld", $d.date);
|
||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Date \%ld too large for buffer, oops!\n", $d.date);
|
||||
$valid = 0;
|
||||
goto datecrit_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
$result = field->factory->newRaw(field->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, field);
|
||||
$result->append8($result, op);
|
||||
$result->append8($result, buf);
|
||||
|
||||
datecrit_valid_0:
|
||||
;
|
||||
}
|
||||
;
|
||||
|
||||
dateop returns [ pANTLR3_COMMON_TOKEN op ]
|
||||
@init { $op = NULL; }
|
||||
: n = BEFORE
|
||||
{ $op = $n->getToken($n); }
|
||||
| n = AFTER
|
||||
{ $op = $n->getToken($n); }
|
||||
;
|
||||
|
||||
datespec returns [ time_t date, int valid ]
|
||||
@init { $date = 0; $valid = 1; }
|
||||
: r = dateref
|
||||
{
|
||||
if (!$r.valid)
|
||||
$valid = 0;
|
||||
else
|
||||
$date = $r.date;
|
||||
}
|
||||
| ^(o = dateop r = dateref m = INT i = dateintval)
|
||||
{
|
||||
int32_t val;
|
||||
int ret;
|
||||
|
||||
if (!$r.valid || !$i.valid)
|
||||
{
|
||||
$valid = 0;
|
||||
goto datespec_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
ret = safe_atoi32((char *)$m->getText($m)->chars, &val);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Could not convert '\%s' to integer\n", (char *)$m->getText($m));
|
||||
$valid = 0;
|
||||
goto datespec_valid_0; /* ABORT */
|
||||
}
|
||||
|
||||
switch ($o.op->getType($o.op))
|
||||
{
|
||||
case BEFORE:
|
||||
$date = $r.date - (val * $i.period);
|
||||
break;
|
||||
|
||||
case AFTER:
|
||||
$date = $r.date + (val * $i.period);
|
||||
break;
|
||||
}
|
||||
|
||||
datespec_valid_0:
|
||||
;
|
||||
}
|
||||
;
|
||||
|
||||
dateref returns [ time_t date, int valid ]
|
||||
@init { $date = 0; $valid = 1; }
|
||||
: n = DATE
|
||||
{
|
||||
struct tm tm;
|
||||
char *ret;
|
||||
|
||||
ret = strptime((char *)$n->getText($n), "\%Y-\%m-\%d", &tm);
|
||||
if (!ret)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be interpreted\n", (char *)$n->getText($n));
|
||||
$valid = 0;
|
||||
goto dateref_valid_0; /* ABORT */
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*ret != '\0')
|
||||
DPRINTF(E_LOG, L_RSP, "Garbage at end of date '\%s' ?!\n", (char *)$n->getText($n));
|
||||
|
||||
$date = mktime(&tm);
|
||||
if ($date == (time_t) -1)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be converted to an epoch\n", (char *)$n->getText($n));
|
||||
$valid = 0;
|
||||
goto dateref_valid_0; /* ABORT */
|
||||
}
|
||||
}
|
||||
|
||||
dateref_valid_0:
|
||||
;
|
||||
}
|
||||
| TODAY
|
||||
{ $date = time(NULL); }
|
||||
;
|
||||
|
||||
dateintval returns [ time_t period, int valid ]
|
||||
@init { $period = 0; $valid = 1; }
|
||||
: DAY
|
||||
{ $period = 24 * 60 * 60; }
|
||||
| WEEK
|
||||
{ $period = 7 * 24 * 60 * 60; }
|
||||
| MONTH
|
||||
{ $period = 30 * 24 * 60 * 60; }
|
||||
| YEAR
|
||||
{ $period = 365 * 24 * 60 * 60; }
|
||||
;
|
240
src/SMARTPL.g
240
src/SMARTPL.g
|
@ -1,240 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
|
||||
*
|
||||
* 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 SMARTPL;
|
||||
|
||||
options {
|
||||
output = AST;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
playlist : STR '{' expression '}' EOF
|
||||
;
|
||||
|
||||
expression : aexpr (OR^ aexpr)* (HAVING^ aexpr)? (ORDERBY^ orderexpr)? (LIMIT^ limitexpr)?
|
||||
;
|
||||
|
||||
orderexpr : ordertag SORTDIR
|
||||
;
|
||||
|
||||
ordertag : STRTAG
|
||||
| INTTAG
|
||||
| DATETAG
|
||||
| ENUMTAG
|
||||
| RANDOMTAG
|
||||
| (XXX)? NeverUsedRule
|
||||
;
|
||||
|
||||
NeverUsedRule: /* antlr3 seems to have a problem with ordertag, introducing the NeverUsedRule fixes it. See: https://stackoverflow.com/questions/20057063/follow-set-in-is-undefined-in-generated-parser */
|
||||
;
|
||||
|
||||
XXX : 'XXX' /**/
|
||||
;
|
||||
|
||||
limitexpr : INT
|
||||
;
|
||||
|
||||
aexpr : nexpr (AND^ nexpr)*
|
||||
;
|
||||
|
||||
nexpr : NOT^ crit
|
||||
| crit
|
||||
;
|
||||
|
||||
crit : LPAR expression RPAR -> expression
|
||||
| STRTAG (INCLUDES|IS|STARTSWITH) STR
|
||||
| INTTAG INTBOOL INT
|
||||
| DATETAG (AFTER|BEFORE) dateval
|
||||
| ENUMTAG IS ENUMVAL
|
||||
| GROUPTAG INTBOOL INT
|
||||
;
|
||||
|
||||
dateval : DATE
|
||||
| interval BEFORE DATE
|
||||
| interval AFTER DATE
|
||||
| interval AGO
|
||||
;
|
||||
|
||||
interval : INT DATINTERVAL
|
||||
;
|
||||
|
||||
STRTAG : 'artist'
|
||||
| 'album_artist'
|
||||
| 'album'
|
||||
| 'title'
|
||||
| 'genre'
|
||||
| 'composer'
|
||||
| 'path'
|
||||
| 'type'
|
||||
| 'grouping'
|
||||
| 'artist_id'
|
||||
| 'album_id'
|
||||
| 'songartistid'
|
||||
| 'songalbumid'
|
||||
| 'codectype'
|
||||
| 'comment'
|
||||
;
|
||||
|
||||
INTTAG : 'play_count'
|
||||
| 'skip_count'
|
||||
| 'rating'
|
||||
| 'year'
|
||||
| 'compilation'
|
||||
| 'track'
|
||||
| 'disc'
|
||||
| 'bitrate'
|
||||
| 'bits_per_sample'
|
||||
| 'samplerate'
|
||||
| 'song_length'
|
||||
| 'usermark'
|
||||
;
|
||||
|
||||
DATETAG : 'time_added'
|
||||
| 'time_modified'
|
||||
| 'time_played'
|
||||
| 'time_skipped'
|
||||
| 'date_released'
|
||||
;
|
||||
|
||||
ENUMTAG : 'data_kind'
|
||||
| 'media_kind'
|
||||
| 'scan_kind'
|
||||
;
|
||||
|
||||
GROUPTAG : 'track_count'
|
||||
| 'album_count'
|
||||
;
|
||||
|
||||
RANDOMTAG : 'random'
|
||||
;
|
||||
|
||||
INCLUDES : 'includes'
|
||||
;
|
||||
|
||||
IS : 'is'
|
||||
;
|
||||
|
||||
STARTSWITH : 'starts with'
|
||||
;
|
||||
|
||||
INTBOOL : (GREATER|GREATEREQUAL|LESS|LESSEQUAL|EQUAL)
|
||||
;
|
||||
|
||||
fragment
|
||||
GREATER : '>'
|
||||
;
|
||||
|
||||
fragment
|
||||
GREATEREQUAL: '>='
|
||||
;
|
||||
|
||||
fragment
|
||||
LESS : '<'
|
||||
;
|
||||
|
||||
fragment
|
||||
LESSEQUAL : '<='
|
||||
;
|
||||
|
||||
fragment
|
||||
EQUAL : '='
|
||||
;
|
||||
|
||||
AFTER : 'after'
|
||||
;
|
||||
|
||||
BEFORE : 'before'
|
||||
;
|
||||
|
||||
AGO : 'ago'
|
||||
;
|
||||
|
||||
AND : 'AND'
|
||||
| 'and'
|
||||
;
|
||||
|
||||
OR : 'OR'
|
||||
| 'or'
|
||||
;
|
||||
|
||||
NOT : 'NOT'
|
||||
| 'not'
|
||||
;
|
||||
|
||||
LPAR : '('
|
||||
;
|
||||
|
||||
RPAR : ')'
|
||||
;
|
||||
|
||||
DATE : ('0'..'9')('0'..'9')('0'..'9')('0'..'9')'-'('0'..'1')('0'..'9')'-'('0'..'3')('0'..'9')
|
||||
| 'today'
|
||||
| 'yesterday'
|
||||
| 'last week'
|
||||
| 'last month'
|
||||
| 'last year'
|
||||
;
|
||||
|
||||
DATINTERVAL : 'days'
|
||||
| 'weeks'
|
||||
| 'months'
|
||||
| 'years'
|
||||
;
|
||||
|
||||
ENUMVAL : 'music'
|
||||
| 'movie'
|
||||
| 'podcast'
|
||||
| 'audiobook'
|
||||
| 'tvshow'
|
||||
| 'file'
|
||||
| 'url'
|
||||
| 'spotify'
|
||||
| 'pipe'
|
||||
| 'files'
|
||||
| 'rss'
|
||||
;
|
||||
|
||||
ORDERBY : 'order by'
|
||||
| 'ORDER BY'
|
||||
;
|
||||
|
||||
SORTDIR : 'asc'
|
||||
| 'ASC'
|
||||
| 'desc'
|
||||
| 'DESC'
|
||||
;
|
||||
|
||||
LIMIT : 'limit'
|
||||
| 'LIMIT'
|
||||
;
|
||||
|
||||
HAVING : 'having'
|
||||
| 'HAVING'
|
||||
;
|
||||
|
||||
STR : '"' ~('"')+ '"'
|
||||
;
|
||||
|
||||
INT : ('0'..'9')+
|
||||
;
|
||||
|
||||
WHITESPACE : ('\t'|' '|'\r'|'\n'|'\u000C') { $channel = HIDDEN; }
|
||||
;
|
||||
|
||||
|
|
@ -1,427 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
|
||||
*
|
||||
* 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 SMARTPL2SQL;
|
||||
|
||||
options {
|
||||
tokenVocab = SMARTPL;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
@header {
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
}
|
||||
|
||||
@members {
|
||||
static void append_date(pANTLR3_STRING result, const char *datval, char beforeorafter, const char *interval)
|
||||
{
|
||||
if (strcmp((char *)datval, "today") == 0)
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime('now', 'start of day'");
|
||||
}
|
||||
else if (strcmp((char *)datval, "yesterday") == 0)
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime('now', 'start of day', '-1 day'");
|
||||
}
|
||||
else if (strcmp((char *)datval, "last week") == 0)
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime('now', 'start of day', 'weekday 0', '-13 days'");
|
||||
}
|
||||
else if (strcmp((char *)datval, "last month") == 0)
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime('now', 'start of month', '-1 month'");
|
||||
}
|
||||
else if (strcmp((char *)datval, "last year") == 0)
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime('now', 'start of year', '-1 year'");
|
||||
}
|
||||
else
|
||||
{
|
||||
result->append8(result, "strftime('\%s', datetime(\'");
|
||||
result->append8(result, datval);
|
||||
result->append8(result, "\'");
|
||||
}
|
||||
|
||||
if (beforeorafter)
|
||||
{
|
||||
result->append8(result, ", '");
|
||||
result->addc(result, beforeorafter);
|
||||
result->append8(result, interval);
|
||||
result->addc(result, '\'');
|
||||
}
|
||||
result->append8(result, ", 'utc'))");
|
||||
}
|
||||
}
|
||||
|
||||
playlist returns [ pANTLR3_STRING title, pANTLR3_STRING query, pANTLR3_STRING orderby, pANTLR3_STRING having, int limit ]
|
||||
@init { $title = NULL; $query = NULL; $orderby = NULL; $having = NULL; $limit = -1; }
|
||||
: STR '{' e = expression '}'
|
||||
{
|
||||
pANTLR3_UINT8 val;
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
$title = $STR.text->factory->newRaw($STR.text->factory);
|
||||
$title->append8($title, (const char *)val);
|
||||
|
||||
$query = $e.result->factory->newRaw($e.result->factory);
|
||||
$query->append8($query, "(");
|
||||
$query->appendS($query, $e.result);
|
||||
$query->append8($query, ")");
|
||||
|
||||
$limit = $e.limit;
|
||||
|
||||
$orderby = $e.result->factory->newRaw($e.result->factory);
|
||||
$orderby->appendS($orderby, $e.orderby);
|
||||
|
||||
$having = $e.result->factory->newRaw($e.result->factory);
|
||||
$having->appendS($having, $e.having);
|
||||
}
|
||||
;
|
||||
|
||||
expression returns [ pANTLR3_STRING result, pANTLR3_STRING orderby, pANTLR3_STRING having, int limit ]
|
||||
@init { $result = NULL; $orderby = NULL; $having = NULL; $limit = -1; }
|
||||
: ^(LIMIT a = expression INT)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->appendS($result, $a.result);
|
||||
|
||||
$having = $a.result->factory->newRaw($a.result->factory);
|
||||
$having->appendS($having, $a.having);
|
||||
|
||||
$orderby = $a.result->factory->newRaw($a.result->factory);
|
||||
$orderby->appendS($orderby, $a.orderby);
|
||||
|
||||
$limit = atoi((const char *)$INT.text->chars);
|
||||
}
|
||||
| ^(ORDERBY a = expression o = ordertag SORTDIR)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->appendS($result, $a.result);
|
||||
|
||||
$having = $a.result->factory->newRaw($a.result->factory);
|
||||
$having->appendS($having, $a.having);
|
||||
|
||||
$orderby = $o.result->factory->newRaw($o.result->factory);
|
||||
$orderby->appendS($orderby, $o.result);
|
||||
$orderby->append8($orderby, " ");
|
||||
$orderby->appendS($orderby, $SORTDIR.text->toUTF8($SORTDIR.text));
|
||||
}
|
||||
| ^(HAVING a = expression b = expression)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->appendS($result, $a.result);
|
||||
|
||||
$having = $b.result->factory->newRaw($b.result->factory);
|
||||
$having->appendS($having, $b.result);
|
||||
}
|
||||
| ^(NOT a = expression)
|
||||
{
|
||||
$result = $a.result->factory->newRaw($a.result->factory);
|
||||
$result->append8($result, "NOT(");
|
||||
$result->appendS($result, $a.result);
|
||||
$result->append8($result, ")");
|
||||
}
|
||||
| ^(AND a = expression b = expression)
|
||||
{
|
||||
$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, ")");
|
||||
}
|
||||
| ^(OR a = expression b = expression)
|
||||
{
|
||||
$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, ")");
|
||||
}
|
||||
| STRTAG INCLUDES STR
|
||||
{
|
||||
pANTLR3_UINT8 val;
|
||||
char *tmp;
|
||||
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
tmp = sqlite3_mprintf("\%q", (const char *)val);
|
||||
|
||||
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||
$result->append8($result, " LIKE '\%");
|
||||
$result->append8($result, tmp);
|
||||
$result->append8($result, "\%'");
|
||||
|
||||
sqlite3_free(tmp);
|
||||
}
|
||||
| STRTAG IS STR
|
||||
{
|
||||
pANTLR3_UINT8 val;
|
||||
char *tmp;
|
||||
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
tmp = sqlite3_mprintf("\%q", (const char *)val);
|
||||
|
||||
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||
$result->append8($result, " LIKE '");
|
||||
$result->append8($result, tmp);
|
||||
$result->append8($result, "'");
|
||||
|
||||
sqlite3_free(tmp);
|
||||
}
|
||||
| STRTAG STARTSWITH STR
|
||||
{
|
||||
pANTLR3_UINT8 val;
|
||||
char *tmp;
|
||||
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
tmp = sqlite3_mprintf("\%q", (const char *)val);
|
||||
|
||||
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||
$result->append8($result, " LIKE '");
|
||||
$result->append8($result, tmp);
|
||||
$result->append8($result, "\%'");
|
||||
|
||||
sqlite3_free(tmp);
|
||||
}
|
||||
| INTTAG INTBOOL INT
|
||||
{
|
||||
$result = $INTTAG.text->factory->newRaw($INTTAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $INTTAG.text->toUTF8($INTTAG.text));
|
||||
$result->append8($result, " ");
|
||||
$result->appendS($result, $INTBOOL.text->toUTF8($INTBOOL.text));
|
||||
$result->append8($result, " ");
|
||||
$result->appendS($result, $INT.text->toUTF8($INT.text));
|
||||
}
|
||||
| DATETAG AFTER dateval
|
||||
{
|
||||
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
|
||||
$result->append8($result, " > ");
|
||||
$result->append8($result, (const char*)$dateval.result->chars);
|
||||
}
|
||||
| DATETAG BEFORE dateval
|
||||
{
|
||||
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
|
||||
$result->append8($result, " < ");
|
||||
$result->append8($result, (const char*)$dateval.result->chars);
|
||||
}
|
||||
| ENUMTAG IS ENUMVAL
|
||||
{
|
||||
pANTLR3_UINT8 tag;
|
||||
pANTLR3_UINT8 val;
|
||||
char str[20];
|
||||
|
||||
sprintf(str, "1=1");
|
||||
|
||||
tag = $ENUMTAG.text->chars;
|
||||
val = $ENUMVAL.text->chars;
|
||||
if (strcmp((char *)tag, "media_kind") == 0)
|
||||
{
|
||||
if (strcmp((char *)val, "music") == 0)
|
||||
{
|
||||
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MUSIC);
|
||||
}
|
||||
else if (strcmp((char *)val, "movie") == 0)
|
||||
{
|
||||
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MOVIE);
|
||||
}
|
||||
else if (strcmp((char *)val, "podcast") == 0)
|
||||
{
|
||||
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_PODCAST);
|
||||
}
|
||||
else if (strcmp((char *)val, "audiobook") == 0)
|
||||
{
|
||||
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_AUDIOBOOK);
|
||||
}
|
||||
else if (strcmp((char *)val, "tvshow") == 0)
|
||||
{
|
||||
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_TVSHOW);
|
||||
}
|
||||
}
|
||||
else if (strcmp((char *)tag, "data_kind") == 0)
|
||||
{
|
||||
if (strcmp((char *)val, "file") == 0)
|
||||
{
|
||||
sprintf(str, "f.data_kind = \%d", DATA_KIND_FILE);
|
||||
}
|
||||
else if (strcmp((char *)val, "url") == 0)
|
||||
{
|
||||
sprintf(str, "f.data_kind = \%d", DATA_KIND_HTTP);
|
||||
}
|
||||
else if (strcmp((char *)val, "spotify") == 0)
|
||||
{
|
||||
sprintf(str, "f.data_kind = \%d", DATA_KIND_SPOTIFY);
|
||||
}
|
||||
else if (strcmp((char *)val, "pipe") == 0)
|
||||
{
|
||||
sprintf(str, "f.data_kind = \%d", DATA_KIND_PIPE);
|
||||
}
|
||||
}
|
||||
else if (strcmp((char *)tag, "scan_kind") == 0)
|
||||
{
|
||||
if (strcmp((char *)val, "files") == 0)
|
||||
{
|
||||
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_FILES);
|
||||
}
|
||||
else if (strcmp((char *)val, "spotify") == 0)
|
||||
{
|
||||
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_SPOTIFY);
|
||||
}
|
||||
else if (strcmp((char *)val, "rss") == 0)
|
||||
{
|
||||
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_RSS);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
|
||||
$result->append8($result, str);
|
||||
}
|
||||
| GROUPTAG INTBOOL INT
|
||||
{
|
||||
$result = $GROUPTAG.text->factory->newRaw($GROUPTAG.text->factory);
|
||||
$result->appendS($result, $GROUPTAG.text->toUTF8($GROUPTAG.text));
|
||||
$result->append8($result, " ");
|
||||
$result->appendS($result, $INTBOOL.text->toUTF8($INTBOOL.text));
|
||||
$result->append8($result, " ");
|
||||
$result->appendS($result, $INT.text->toUTF8($INT.text));
|
||||
}
|
||||
;
|
||||
|
||||
ordertag returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: STRTAG
|
||||
{
|
||||
$result = $STRTAG.text->factory->newRaw($STRTAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||
}
|
||||
| INTTAG
|
||||
{
|
||||
$result = $INTTAG.text->factory->newRaw($INTTAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $INTTAG.text->toUTF8($INTTAG.text));
|
||||
}
|
||||
| DATETAG
|
||||
{
|
||||
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
|
||||
}
|
||||
| ENUMTAG
|
||||
{
|
||||
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $ENUMTAG.text->toUTF8($ENUMTAG.text));
|
||||
}
|
||||
| RANDOMTAG
|
||||
{
|
||||
$result = $RANDOMTAG.text->factory->newRaw($RANDOMTAG.text->factory);
|
||||
$result->append8($result, "random()");
|
||||
}
|
||||
;
|
||||
|
||||
dateval returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: DATE
|
||||
{
|
||||
pANTLR3_UINT8 datval;
|
||||
|
||||
datval = $DATE.text->chars;
|
||||
$result = $DATE.text->factory->newRaw($DATE.text->factory);
|
||||
|
||||
append_date($result, (const char *)datval, 0, NULL);
|
||||
}
|
||||
| interval BEFORE DATE
|
||||
{
|
||||
$result = $DATE.text->factory->newRaw($DATE.text->factory);
|
||||
append_date($result, (const char *)$DATE.text->chars, '-', (const char *)$interval.result->chars);
|
||||
}
|
||||
| interval AFTER DATE
|
||||
{
|
||||
$result = $DATE.text->factory->newRaw($DATE.text->factory);
|
||||
append_date($result, (const char *)$DATE.text->chars, '+', (const char *)$interval.result->chars);
|
||||
}
|
||||
| interval AGO
|
||||
{
|
||||
$result = $AGO.text->factory->newRaw($AGO.text->factory);
|
||||
append_date($result, "today", '-', (const char *)$interval.result->chars);
|
||||
}
|
||||
;
|
||||
|
||||
interval returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: INT DATINTERVAL
|
||||
{
|
||||
pANTLR3_UINT8 interval;
|
||||
int intval;
|
||||
char buf[25];
|
||||
|
||||
$result = $DATINTERVAL.text->factory->newRaw($DATINTERVAL.text->factory);
|
||||
|
||||
// SQL doesnt have a modifer for 'week' but for day/hr/min/sec/month/yr
|
||||
interval = $DATINTERVAL.text->chars;
|
||||
if (strcmp((char *)interval, "weeks") == 0)
|
||||
{
|
||||
intval = atoi((const char *)$INT.text->chars) * 7;
|
||||
snprintf(buf, sizeof(buf), "\%d days", intval);
|
||||
|
||||
$result->append8($result, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
$result->append8($result, (const char *)$INT.text->chars);
|
||||
$result->append8($result, " ");
|
||||
$result->append8($result, (const char *)$DATINTERVAL.text->chars);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
;
|
||||
|
||||
|
122
src/daap_query.c
122
src/daap_query.c
|
@ -29,129 +29,9 @@
|
|||
#include "misc.h"
|
||||
#include "daap_query.h"
|
||||
|
||||
#include "DAAPLexer.h"
|
||||
#include "DAAPParser.h"
|
||||
#include "DAAP2SQL.h"
|
||||
|
||||
|
||||
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;
|
||||
|
||||
if (!daap_query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP query is null\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Trying DAAP query -%s-\n", daap_query);
|
||||
|
||||
#if ANTLR3C_NEW_INPUT
|
||||
query = antlr3StringStreamNew ((pANTLR3_UINT8)daap_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query");
|
||||
#else
|
||||
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)daap_query, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query");
|
||||
#endif
|
||||
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 -%s-\n", daap_query);
|
||||
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;
|
||||
return NULL;
|
||||
}
|
||||
|
|
116
src/rsp_query.c
116
src/rsp_query.c
|
@ -29,122 +29,8 @@
|
|||
#include "misc.h"
|
||||
#include "rsp_query.h"
|
||||
|
||||
#include "RSPLexer.h"
|
||||
#include "RSPParser.h"
|
||||
#include "RSP2SQL.h"
|
||||
|
||||
|
||||
char *
|
||||
rsp_query_parse_sql(const char *rsp_query)
|
||||
{
|
||||
/* Input RSP query, fed to the lexer */
|
||||
pANTLR3_INPUT_STREAM query;
|
||||
|
||||
/* Lexer and the resulting token stream, fed to the parser */
|
||||
pRSPLexer lxr;
|
||||
pANTLR3_COMMON_TOKEN_STREAM tkstream;
|
||||
|
||||
/* Parser and the resulting AST, fed to the tree parser */
|
||||
pRSPParser psr;
|
||||
RSPParser_query_return qtree;
|
||||
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
|
||||
|
||||
/* Tree parser and the resulting SQL query string */
|
||||
pRSP2SQL sqlconv;
|
||||
pANTLR3_STRING sql;
|
||||
|
||||
char *ret = NULL;
|
||||
|
||||
DPRINTF(E_DBG, L_RSP, "Trying RSP query -%s-\n", rsp_query);
|
||||
|
||||
#if ANTLR3C_NEW_INPUT
|
||||
query = antlr3StringStreamNew ((pANTLR3_UINT8)rsp_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query");
|
||||
#else
|
||||
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)rsp_query, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query");
|
||||
#endif
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create input stream\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lxr = RSPLexerNew(query);
|
||||
if (!lxr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP lexer\n");
|
||||
goto lxr_fail;
|
||||
}
|
||||
|
||||
tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
|
||||
if (!tkstream)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP token stream\n");
|
||||
goto tkstream_fail;
|
||||
}
|
||||
|
||||
psr = RSPParserNew(tkstream);
|
||||
if (!psr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP parser\n");
|
||||
goto psr_fail;
|
||||
}
|
||||
|
||||
qtree = psr->query(psr);
|
||||
|
||||
/* Check for parser errors */
|
||||
if (psr->pParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "RSP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_RSP, "RSP 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_RSP, "Could not create node stream\n");
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
sqlconv = RSP2SQLNew(nodes);
|
||||
if (!sqlconv)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "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_RSP, "RSP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
|
||||
goto sql_error;
|
||||
}
|
||||
|
||||
if (sql)
|
||||
{
|
||||
ret = strdup((char *)sql->chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Invalid RSP 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;
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -35,183 +35,17 @@
|
|||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "SMARTPLLexer.h"
|
||||
#include "SMARTPLParser.h"
|
||||
#include "SMARTPL2SQL.h"
|
||||
|
||||
|
||||
static int
|
||||
parse_input(struct smartpl *smartpl, pANTLR3_INPUT_STREAM input)
|
||||
{
|
||||
pSMARTPLLexer lxr;
|
||||
pANTLR3_COMMON_TOKEN_STREAM tstream;
|
||||
pSMARTPLParser psr;
|
||||
SMARTPLParser_playlist_return qtree;
|
||||
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
|
||||
pSMARTPL2SQL sqlconv;
|
||||
SMARTPL2SQL_playlist_return plreturn;
|
||||
int ret;
|
||||
|
||||
lxr = SMARTPLLexerNew(input);
|
||||
|
||||
// Need to check for errors
|
||||
if (lxr == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL lexer\n");
|
||||
ret = -1;
|
||||
goto lxr_fail;
|
||||
}
|
||||
|
||||
tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
|
||||
|
||||
if (tstream == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL token stream\n");
|
||||
ret = -1;
|
||||
goto tkstream_fail;
|
||||
}
|
||||
|
||||
// Finally, now that we have our lexer constructed, we can create the parser
|
||||
psr = SMARTPLParserNew(tstream); // CParserNew is generated by ANTLR3
|
||||
|
||||
if (psr == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL parser\n");
|
||||
ret = -1;
|
||||
goto psr_fail;
|
||||
}
|
||||
|
||||
qtree = psr->playlist(psr);
|
||||
|
||||
/* Check for parser errors */
|
||||
if (psr->pParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "SMARTPL query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
|
||||
ret = -1;
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "SMARTPL query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
|
||||
|
||||
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
|
||||
if (!nodes)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create node stream\n");
|
||||
ret = -1;
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
sqlconv = SMARTPL2SQLNew(nodes);
|
||||
if (!sqlconv)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SQL converter\n");
|
||||
ret = -1;
|
||||
goto sql_fail;
|
||||
}
|
||||
|
||||
plreturn = sqlconv->playlist(sqlconv);
|
||||
|
||||
/* Check for tree parser errors */
|
||||
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "SMARTPL query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
|
||||
ret = -1;
|
||||
goto sql_error;
|
||||
}
|
||||
|
||||
if (plreturn.title && plreturn.query)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SCAN, "SMARTPL SQL title '%s', query: '%s', having: '%s', order by: '%s', limit: %d \n", plreturn.title->chars, plreturn.query->chars, plreturn.having->chars, plreturn.orderby->chars, plreturn.limit);
|
||||
|
||||
if (smartpl->title)
|
||||
free(smartpl->title);
|
||||
smartpl->title = strdup((char *)plreturn.title->chars);
|
||||
|
||||
if (smartpl->query_where)
|
||||
free(smartpl->query_where);
|
||||
smartpl->query_where = strdup((char *)plreturn.query->chars);
|
||||
|
||||
if (smartpl->having)
|
||||
free(smartpl->having);
|
||||
smartpl->having = safe_strdup((char *)plreturn.having->chars);
|
||||
|
||||
if (smartpl->order)
|
||||
free(smartpl->order);
|
||||
smartpl->order = safe_strdup((char *)plreturn.orderby->chars);
|
||||
|
||||
smartpl->limit = plreturn.limit;
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Invalid SMARTPL query\n");
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
sql_error:
|
||||
sqlconv->free(sqlconv);
|
||||
sql_fail:
|
||||
nodes->free(nodes);
|
||||
psr_error:
|
||||
psr->free(psr);
|
||||
psr_fail:
|
||||
tstream->free(tstream);
|
||||
tkstream_fail:
|
||||
lxr->free(lxr);
|
||||
lxr_fail:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
smartpl_query_parse_file(struct smartpl *smartpl, const char *file)
|
||||
{
|
||||
pANTLR3_INPUT_STREAM input;
|
||||
int ret;
|
||||
|
||||
#if ANTLR3C_NEW_INPUT
|
||||
input = antlr3FileStreamNew((pANTLR3_UINT8) file, ANTLR3_ENC_8BIT);
|
||||
#else
|
||||
input = antlr3AsciiFileStreamNew((pANTLR3_UINT8) file);
|
||||
#endif
|
||||
|
||||
// The input will be created successfully, providing that there is enough memory and the file exists etc
|
||||
if (input == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Unable to open smart playlist file %s\n", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = parse_input(smartpl, input);
|
||||
input->close(input);
|
||||
|
||||
return ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
smartpl_query_parse_string(struct smartpl *smartpl, const char *expression)
|
||||
{
|
||||
pANTLR3_INPUT_STREAM input;
|
||||
int ret;
|
||||
|
||||
#if ANTLR3C_NEW_INPUT
|
||||
input = antlr3StringStreamNew ((pANTLR3_UINT8)expression, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(expression), (pANTLR3_UINT8)"SMARTPL expression");
|
||||
#else
|
||||
input = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)expression, (ANTLR3_UINT64)strlen(expression), (pANTLR3_UINT8)"SMARTPL expression");
|
||||
#endif
|
||||
|
||||
// The input will be created successfully, providing that there is enough memory and the file exists etc
|
||||
if (input == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Unable to pars smart pl expression %s\n", expression);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = parse_input(smartpl, input);
|
||||
input->close(input);
|
||||
|
||||
return ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue