mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 07:35:57 -05:00
[smartpl] Add support for smart playlists
This commit is contained in:
parent
a174a1d18c
commit
4cac01ed88
1
src/.gitignore
vendored
1
src/.gitignore
vendored
@ -5,6 +5,7 @@ forked-daapd
|
||||
*Parser.[ch]
|
||||
DAAP2SQL.[ch]
|
||||
RSP2SQL.[ch]
|
||||
SMARTPL2SQL.[ch]
|
||||
|
||||
*.u
|
||||
|
||||
|
@ -62,13 +62,16 @@ GPERF_PRODUCTS = \
|
||||
|
||||
ANTLR_GRAMMARS = \
|
||||
RSP.g RSP2SQL.g \
|
||||
DAAP.g DAAP2SQL.g
|
||||
DAAP.g DAAP2SQL.g \
|
||||
SMARTPL.g SMARTPL2SQL.g
|
||||
|
||||
ANTLR_SOURCES = \
|
||||
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
|
||||
RSP2SQL.c RSP2SQL.h \
|
||||
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
||||
DAAP2SQL.c DAAP2SQL.h
|
||||
DAAP2SQL.c DAAP2SQL.h \
|
||||
SMARTPLLexer.c SMARTPLLexer.h SMARTPLParser.c SMARTPLParser.h \
|
||||
SMARTPL2SQL.c SMARTPL2SQL.h
|
||||
|
||||
ANTLR_PRODUCTS =
|
||||
|
||||
@ -95,7 +98,8 @@ forked_daapd_SOURCES = main.c \
|
||||
conffile.c conffile.h \
|
||||
cache.c cache.h \
|
||||
filescanner.c filescanner.h \
|
||||
filescanner_ffmpeg.c filescanner_playlist.c $(ITUNES_SRC) \
|
||||
filescanner_ffmpeg.c filescanner_playlist.c \
|
||||
filescanner_smartpl.c $(ITUNES_SRC) \
|
||||
mdns_avahi.c mdns.h \
|
||||
remote_pairing.c remote_pairing.h \
|
||||
$(EVHTTP_SRC) \
|
||||
|
156
src/SMARTPL.g
Normal file
156
src/SMARTPL.g
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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)*
|
||||
;
|
||||
|
||||
aexpr : nexpr (AND^ nexpr)*
|
||||
;
|
||||
|
||||
nexpr : NOT^ crit
|
||||
| crit
|
||||
;
|
||||
|
||||
crit : LPAR expression RPAR -> expression
|
||||
| STRTAG (INCLUDES|IS) STR
|
||||
| INTTAG INTBOOL INT
|
||||
| DATETAG (AFTER|BEFORE) dateval
|
||||
;
|
||||
|
||||
dateval : DATE
|
||||
| interval BEFORE DATE
|
||||
| interval AFTER DATE
|
||||
| interval AGO
|
||||
;
|
||||
|
||||
interval : INT DATINTERVAL
|
||||
;
|
||||
|
||||
STRTAG : 'artist'
|
||||
| 'album_artist'
|
||||
| 'album'
|
||||
| 'title'
|
||||
| 'genre'
|
||||
| 'composer'
|
||||
| 'path'
|
||||
| 'type'
|
||||
;
|
||||
|
||||
INCLUDES : 'includes'
|
||||
;
|
||||
|
||||
IS : 'is'
|
||||
;
|
||||
|
||||
INTTAG : 'data_kind'
|
||||
| 'media_kind'
|
||||
| 'play_count'
|
||||
| 'rating'
|
||||
| 'year'
|
||||
| 'compilation'
|
||||
;
|
||||
|
||||
INTBOOL : (GREATER|GREATEREQUAL|LESS|LESSEQUAL|EQUAL)
|
||||
;
|
||||
|
||||
fragment
|
||||
GREATER : '>'
|
||||
;
|
||||
|
||||
fragment
|
||||
GREATEREQUAL: '>='
|
||||
;
|
||||
|
||||
fragment
|
||||
LESS : '<'
|
||||
;
|
||||
|
||||
fragment
|
||||
LESSEQUAL : '<='
|
||||
;
|
||||
|
||||
fragment
|
||||
EQUAL : '='
|
||||
;
|
||||
|
||||
DATETAG : 'time_added'
|
||||
| 'time_played'
|
||||
;
|
||||
|
||||
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'
|
||||
;
|
||||
|
||||
STR : '"' ~('"')+ '"'
|
||||
;
|
||||
|
||||
INT : ('0'..'9')+
|
||||
;
|
||||
|
||||
WHITESPACE : ('\t'|' '|'\r'|'\n'|'\u000C') { $channel = HIDDEN; }
|
||||
;
|
||||
|
||||
|
295
src/SMARTPL2SQL.g
Normal file
295
src/SMARTPL2SQL.g
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* 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"
|
||||
}
|
||||
|
||||
@members {
|
||||
}
|
||||
|
||||
playlist returns [ pANTLR3_STRING title, pANTLR3_STRING query ]
|
||||
@init { $title = NULL; $query = NULL; }
|
||||
: 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, ")");
|
||||
}
|
||||
;
|
||||
|
||||
expression returns [ pANTLR3_STRING result ]
|
||||
@init { $result = NULL; }
|
||||
: ^(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;
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
$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, sqlite3_mprintf("\%q", (const char *)val));
|
||||
$result->append8($result, "\%'");
|
||||
}
|
||||
| STRTAG IS STR
|
||||
{
|
||||
pANTLR3_UINT8 val;
|
||||
val = $STR.text->toUTF8($STR.text)->chars;
|
||||
val++;
|
||||
val[strlen((const char *)val) - 1] = '\0';
|
||||
|
||||
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||
$result->append8($result, "f.");
|
||||
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||
$result->append8($result, " = '");
|
||||
$result->append8($result, sqlite3_mprintf("\%q", (const char *)val));
|
||||
$result->append8($result, "'");
|
||||
}
|
||||
| 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
|
||||
{
|
||||
char str[15];
|
||||
sprintf(str, "\%d", $dateval.result);
|
||||
|
||||
$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, str);
|
||||
}
|
||||
| DATETAG BEFORE dateval
|
||||
{
|
||||
char str[15];
|
||||
sprintf(str, "\%d", $dateval.result);
|
||||
|
||||
$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, str);
|
||||
}
|
||||
;
|
||||
|
||||
dateval returns [ int result ]
|
||||
@init { $result = 0; }
|
||||
: DATE
|
||||
{
|
||||
pANTLR3_UINT8 datval;
|
||||
|
||||
datval = $DATE.text->chars;
|
||||
|
||||
if (strcmp((char *)datval, "today") == 0)
|
||||
{
|
||||
$result = time(NULL);
|
||||
}
|
||||
else if (strcmp((char *)datval, "yesterday") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last week") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 7;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last month") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 30;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last year") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 365;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct tm tm;
|
||||
char year[5];
|
||||
char month[3];
|
||||
char day[3];
|
||||
|
||||
memset((void*)&tm,0,sizeof(tm));
|
||||
memset(year, 0, sizeof(year));
|
||||
memset(month, 0, sizeof(month));
|
||||
memset(day, 0, sizeof(day));
|
||||
|
||||
strncpy(year, (const char *)datval, 4);
|
||||
strncpy(month, (const char *)datval + 5, 2);
|
||||
strncpy(day, (const char *)datval + 8, 2);
|
||||
|
||||
tm.tm_year = atoi(year) - 1900;
|
||||
tm.tm_mon = atoi(month) - 1;
|
||||
tm.tm_mday = atoi(day);
|
||||
|
||||
$result = mktime(&tm);
|
||||
}
|
||||
}
|
||||
| interval BEFORE DATE
|
||||
{
|
||||
pANTLR3_UINT8 datval;
|
||||
|
||||
datval = $DATE.text->chars;
|
||||
|
||||
if (strcmp((char *)datval, "yesterday") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last week") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 7;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last month") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 30;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last year") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 365;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = time(NULL);
|
||||
}
|
||||
|
||||
$result = $result - $interval.result;
|
||||
}
|
||||
| interval AFTER DATE
|
||||
{
|
||||
pANTLR3_UINT8 datval;
|
||||
|
||||
datval = $DATE.text->chars;
|
||||
|
||||
if (strcmp((char *)datval, "yesterday") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last week") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 7;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last month") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 30;
|
||||
}
|
||||
else if (strcmp((char *)datval, "last year") == 0)
|
||||
{
|
||||
$result = time(NULL) - 24 * 3600 * 365;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = time(NULL);
|
||||
}
|
||||
|
||||
$result = $result + $interval.result;
|
||||
}
|
||||
| interval AGO
|
||||
{
|
||||
$result = time(NULL) - $interval.result;
|
||||
}
|
||||
;
|
||||
|
||||
interval returns [ int result ]
|
||||
@init { $result = 0; }
|
||||
: INT DATINTERVAL
|
||||
{
|
||||
pANTLR3_UINT8 interval;
|
||||
|
||||
$result = atoi((const char *)$INT.text->chars);
|
||||
interval = $DATINTERVAL.text->chars;
|
||||
|
||||
if (strcmp((char *)interval, "days") == 0)
|
||||
{
|
||||
$result = $result * 24 * 3600;
|
||||
}
|
||||
else if (strcmp((char *)interval, "weeks") == 0)
|
||||
{
|
||||
$result = $result * 24 * 3600 * 7;
|
||||
}
|
||||
else if (strcmp((char *)interval, "months") == 0)
|
||||
{
|
||||
$result = $result * 24 * 3600 * 30;
|
||||
}
|
||||
else if (strcmp((char *)interval, "weeks") == 0)
|
||||
{
|
||||
$result = $result * 24 * 3600 * 365;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = 0;
|
||||
}
|
||||
}
|
||||
;
|
10
src/db.c
10
src/db.c
@ -3159,7 +3159,7 @@ db_pl_add(struct playlist_info *pli, int *id)
|
||||
{
|
||||
#define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = TRIM(%Q) AND p.path = '%q';"
|
||||
#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, parent_id, virtual_path)" \
|
||||
" VALUES (TRIM(%Q), %d, NULL, %" PRIi64 ", %d, '%q', %d, %d, %d, '%q');"
|
||||
" VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q');"
|
||||
char *query;
|
||||
char *errmsg;
|
||||
int ret;
|
||||
@ -3184,7 +3184,7 @@ db_pl_add(struct playlist_info *pli, int *id)
|
||||
|
||||
/* Add */
|
||||
query = sqlite3_mprintf(QADD_TMPL,
|
||||
pli->title, pli->type, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||
pli->index, pli->special_id, pli->parent_id, pli->virtual_path);
|
||||
|
||||
if (!query)
|
||||
@ -3249,14 +3249,14 @@ db_pl_add_item_byid(int plid, int fileid)
|
||||
int
|
||||
db_pl_update(struct playlist_info *pli)
|
||||
{
|
||||
#define Q_TMPL "UPDATE playlists SET title = TRIM(%Q), type = %d, db_timestamp = %" PRIi64 ", disabled = %d, path = '%q', " \
|
||||
" idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q' " \
|
||||
#define Q_TMPL "UPDATE playlists SET title = TRIM(%Q), type = %d, query = '%q', db_timestamp = %" PRIi64 ", disabled = %d, " \
|
||||
" path = '%q', idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q' " \
|
||||
" WHERE id = %d;"
|
||||
char *query;
|
||||
int ret;
|
||||
|
||||
query = sqlite3_mprintf(Q_TMPL,
|
||||
pli->title, pli->type, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->id);
|
||||
|
||||
ret = db_query_run(query, 1, 0);
|
||||
|
@ -103,6 +103,7 @@ enum file_type {
|
||||
FILE_IGNORE,
|
||||
FILE_REGULAR,
|
||||
FILE_PLAYLIST,
|
||||
FILE_SMARTPL,
|
||||
FILE_ITUNES,
|
||||
FILE_ARTWORK,
|
||||
FILE_CTRL_REMOTE,
|
||||
@ -317,6 +318,9 @@ file_type_get(const char *path) {
|
||||
if ((strcasecmp(ext, ".m3u") == 0) || (strcasecmp(ext, ".pls") == 0))
|
||||
return FILE_PLAYLIST;
|
||||
|
||||
if (strcasecmp(ext, ".smartpl") == 0)
|
||||
return FILE_SMARTPL;
|
||||
|
||||
if (artwork_file_is_artwork(filename))
|
||||
return FILE_ARTWORK;
|
||||
|
||||
@ -867,6 +871,11 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
|
||||
process_playlist(file, mtime);
|
||||
break;
|
||||
|
||||
case FILE_SMARTPL:
|
||||
DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file);
|
||||
scan_smartpl(file, mtime);
|
||||
break;
|
||||
|
||||
case FILE_ARTWORK:
|
||||
DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
|
||||
cache_artwork_ping(file, mtime);
|
||||
|
@ -31,6 +31,9 @@ scan_metadata_icy(char *url, struct media_file_info *mfi);
|
||||
void
|
||||
scan_playlist(char *file, time_t mtime);
|
||||
|
||||
void
|
||||
scan_smartpl(char *file, time_t mtime);
|
||||
|
||||
#ifdef ITUNES
|
||||
void
|
||||
scan_itunes_itml(char *file);
|
||||
|
216
src/filescanner_smartpl.c
Normal file
216
src/filescanner_smartpl.c
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "filescanner.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "SMARTPLLexer.h"
|
||||
#include "SMARTPLParser.h"
|
||||
#include "SMARTPL2SQL.h"
|
||||
|
||||
static int
|
||||
smartpl_parse_file(const char *file, struct playlist_info *pli)
|
||||
{
|
||||
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;
|
||||
|
||||
input = antlr3AsciiFileStreamNew((pANTLR3_UINT8) file);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
lxr = SMARTPLLexerNew(input);
|
||||
|
||||
// Need to check for errors
|
||||
if (lxr == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL lexer\n");
|
||||
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");
|
||||
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 (tstream == NULL)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL parser\n");
|
||||
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);
|
||||
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");
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
sqlconv = SMARTPL2SQLNew(nodes);
|
||||
if (!sqlconv)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create SQL converter\n");
|
||||
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);
|
||||
goto sql_error;
|
||||
}
|
||||
|
||||
if (plreturn.title && plreturn.query)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SCAN, "SMARTPL SQL title '%s' query: -%s-\n", plreturn.title->chars, plreturn.query->chars);
|
||||
|
||||
if (pli->title)
|
||||
free(pli->title);
|
||||
pli->title = strdup((char *)plreturn.title->chars);
|
||||
|
||||
if (pli->query)
|
||||
free(pli->query);
|
||||
pli->query = strdup((char *)plreturn.query->chars);
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
DPRINTF(E_LOG, L_SCAN, "Invalid SMARTPL query\n");
|
||||
|
||||
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:
|
||||
input->close(input);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
scan_smartpl(char *file, time_t mtime)
|
||||
{
|
||||
struct playlist_info *pli;
|
||||
int pl_id;
|
||||
char virtual_path[PATH_MAX];
|
||||
int ret;
|
||||
|
||||
/* Fetch or create playlist */
|
||||
pli = db_pl_fetch_bypath(file);
|
||||
if (!pli)
|
||||
{
|
||||
pli = (struct playlist_info *) malloc(sizeof(struct playlist_info));
|
||||
if (!pli)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Out of memory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(pli, 0, sizeof(struct playlist_info));
|
||||
|
||||
pli->path = strdup(file);
|
||||
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
||||
pli->virtual_path = strdup(virtual_path);
|
||||
pli->type = PL_SMART;
|
||||
}
|
||||
else
|
||||
pl_id = pli->id;
|
||||
|
||||
ret = smartpl_parse_file(file, pli);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error parsing smart playlist '%s'\n", file);
|
||||
|
||||
free_pli(pli, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pli->id)
|
||||
{
|
||||
ret = db_pl_update(pli);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = db_pl_add(pli, &pl_id);
|
||||
}
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error adding smart playlist '%s'\n", file);
|
||||
|
||||
free_pli(pli, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF(E_INFO, L_SCAN, "Added smart playlist as id %d\n", pl_id);
|
||||
|
||||
free_pli(pli, 0);
|
||||
|
||||
DPRINTF(E_INFO, L_SCAN, "Done processing smart playlist\n");
|
||||
}
|
Loading…
Reference in New Issue
Block a user