mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 22:55:56 -05:00
1. Implement browse and query. These are both still somewhat experimental
pending further verification against iTunes. --enable-browse and --enable-query must be specified to configure to enable both options. browse support requires query support. 2. Database iteration is now sorted and the database is not kept locked as long during iteration.
This commit is contained in:
parent
d63435d230
commit
9492a64846
16
configure.in
16
configure.in
@ -33,7 +33,6 @@ AC_ARG_ENABLE(new-howl,[ --enable-new-howl Use howl 0.9.2 or later],
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);;
|
||||
esac ],[rend_howl=false])
|
||||
|
||||
|
||||
AC_ARG_ENABLE(howl,[ --enable-howl Use the howl mDNS library],
|
||||
[ case "${enableval}" in
|
||||
yes) rend_howl=true; LDFLAGS="${LDFLAGS} -lrendezvous -lcorby -lsalt";
|
||||
@ -41,6 +40,21 @@ AC_ARG_ENABLE(howl,[ --enable-howl Use the howl mDNS library],
|
||||
no) rend_howl=false;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);;
|
||||
esac ],[rend_howl=false])
|
||||
AC_ARG_ENABLE(browse,[ --enable-browse enable experimenal browse support],
|
||||
[ case "${enableval}" in
|
||||
yes) opt_browse=true; CPPFLAGS="${CPPFLAGS} -DOPT_BROWSE";;
|
||||
no) opt_browse=false;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-browse);;
|
||||
esac ],[opt_browse=false])
|
||||
AC_ARG_ENABLE(query,[ --enable-query enable experimenal query support],
|
||||
[ case "${enableval}" in
|
||||
yes) opt_query=true; CPPFLAGS="${CPPFLAGS} -DOPT_QUERY";;
|
||||
no) opt_query=false;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-query);;
|
||||
esac ],[opt_query=false])
|
||||
|
||||
AM_CONDITIONAL(OPT_BROWSE, test x$opt_browse = xtrue)
|
||||
AM_CONDITIONAL(OPT_QUERY, test x$opt_query = xtrue)
|
||||
|
||||
AM_CONDITIONAL(COND_REND_HOWL, test x$rend_howl = xtrue)
|
||||
AM_CONDITIONAL(COND_REND_POSIX, test x$rend_howl = xfalse)
|
||||
|
@ -18,19 +18,19 @@ if COND_REND_OSX
|
||||
ORENDSRC=rend-osx.c
|
||||
endif
|
||||
|
||||
if OPT_QUERY
|
||||
QUERYSRC = query.c query.h
|
||||
endif
|
||||
|
||||
mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \
|
||||
webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \
|
||||
daap-proto.c daap-proto.h daap.c daap.h db-gdbm.c db-memory.h \
|
||||
mp3-scanner.h mp3-scanner.c playlist.c playlist.h rend-unix.c \
|
||||
rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \
|
||||
redblack.c redblack.h dynamic-art.c dynamic-art.h \
|
||||
$(PRENDSRC) $(ORENDSRC) $(HRENDSRC)
|
||||
$(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(QUERYSRC)
|
||||
|
||||
EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \
|
||||
mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \
|
||||
rend-howl.c rend-posix.c rend-osx.c strcasestr.c strsep.c db-memory.c \
|
||||
db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -167,9 +167,12 @@ int config_read(char *file) {
|
||||
config.runas=NULL;
|
||||
config.artfilename=NULL;
|
||||
config.logfile=NULL;
|
||||
config.extensions=".mp3";
|
||||
|
||||
config.servername="mt-daapd " VERSION;
|
||||
/* DWB: use alloced space so it can be freed without errors */
|
||||
config.extensions=strdup(".mp3");
|
||||
|
||||
/* DWB: use alloced space so it can be freed without errors */
|
||||
config.servername=strdup("mt-daapd " VERSION);
|
||||
|
||||
while(fgets(buffer,MAX_LINE,fin)) {
|
||||
if(*buffer != '#') {
|
||||
@ -195,6 +198,9 @@ int config_read(char *file) {
|
||||
|
||||
switch(pce->type) {
|
||||
case CONFIG_TYPE_STRING:
|
||||
/* DWB: free space to prevent small leak */
|
||||
if(*((char **)(pce->var)))
|
||||
free(*((char **)(pce->var)));
|
||||
*((char **)(pce->var)) = (void*)strdup(value);
|
||||
break;
|
||||
case CONFIG_TYPE_INT:
|
||||
|
@ -336,3 +336,20 @@ void daap_free(DAAP_BLOCK *root) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// search a block's children and change an integer value
|
||||
int daap_set_int(DAAP_BLOCK* parent, char* tag, int value)
|
||||
{
|
||||
DAAP_BLOCK* child = daap_find(parent, tag);
|
||||
|
||||
if(0 == child || child->size != sizeof(int))
|
||||
return 0;
|
||||
|
||||
child->svalue[0]=(value >> 24) & 0xFF;
|
||||
child->svalue[1]=(value >> 16) & 0xFF;
|
||||
child->svalue[2]=(value >> 8) & 0xFF;
|
||||
child->svalue[3]=value & 0xFF;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -51,5 +51,8 @@ void daap_remove(DAAP_BLOCK* root);
|
||||
// search a block's direct children for a block with a given tag
|
||||
DAAP_BLOCK *daap_find(DAAP_BLOCK *parent, char* tag);
|
||||
|
||||
// search a block's children and change an integer value
|
||||
int daap_set_int(DAAP_BLOCK* parent, char* tag, int value);
|
||||
|
||||
#endif
|
||||
|
||||
|
382
src/daap.c
382
src/daap.c
@ -40,6 +40,10 @@
|
||||
#include "err.h"
|
||||
#include "daapd.h"
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
#include "query.h"
|
||||
#endif
|
||||
|
||||
typedef struct tag_daap_items {
|
||||
int type;
|
||||
char *tag;
|
||||
@ -134,6 +138,39 @@ DAAP_ITEMS taglist[] = {
|
||||
{ 0x00, NULL, NULL }
|
||||
};
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
#define OFFSET_OF(__type, __field) ((size_t) (&((__type*) 0)->__field))
|
||||
|
||||
static query_field_t song_fields[] = {
|
||||
{ qft_string, "dmap.itemname", OFFSET_OF(MP3FILE, title) },
|
||||
{ qft_i32, "dmap.itemid", OFFSET_OF(MP3FILE, id) },
|
||||
{ qft_string, "daap.songalbum", OFFSET_OF(MP3FILE, album) },
|
||||
{ qft_string, "daap.songartist", OFFSET_OF(MP3FILE, artist) },
|
||||
{ qft_i32, "daap.songbitrate", OFFSET_OF(MP3FILE, bitrate) },
|
||||
{ qft_string, "daap.songcomment", OFFSET_OF(MP3FILE, comment) },
|
||||
// { qft_i32_const, "daap.songcompilation", 0 },
|
||||
{ qft_string, "daap.songcomposer", OFFSET_OF(MP3FILE, composer) },
|
||||
// { qft_i32_const, "daap.songdatakind", 0 },
|
||||
// { qft_string, "daap.songdataurl", OFFSET_OF(MP3FILE, url) },
|
||||
{ qft_i32, "daap.songdateadded", OFFSET_OF(MP3FILE, time_added) },
|
||||
{ qft_i32, "daap.songdatemodified",OFFSET_OF(MP3FILE, time_modified) },
|
||||
{ qft_string, "daap.songdescription", OFFSET_OF(MP3FILE, description) },
|
||||
{ qft_i32, "daap.songdisccount", OFFSET_OF(MP3FILE, total_discs) },
|
||||
{ qft_i32, "daap.songdiscnumber", OFFSET_OF(MP3FILE, disc) },
|
||||
{ qft_string, "daap.songformat", OFFSET_OF(MP3FILE, type) },
|
||||
{ qft_string, "daap.songgenre", OFFSET_OF(MP3FILE, genre) },
|
||||
{ qft_i32, "daap.songsamplerate", OFFSET_OF(MP3FILE, samplerate) },
|
||||
{ qft_i32, "daap.songsize", OFFSET_OF(MP3FILE, file_size) },
|
||||
// { qft_i32_const, "daap.songstarttime", 0 },
|
||||
{ qft_i32, "daap.songstoptime", OFFSET_OF(MP3FILE, song_length) },
|
||||
{ qft_i32, "daap.songtime", OFFSET_OF(MP3FILE, song_length) },
|
||||
{ qft_i32, "daap.songtrackcount", OFFSET_OF(MP3FILE, total_tracks) },
|
||||
{ qft_i32, "daap.songtracknumber", OFFSET_OF(MP3FILE, track) },
|
||||
{ qft_i32, "daap.songyear", OFFSET_OF(MP3FILE, year) },
|
||||
{ 0 }
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Forwards */
|
||||
|
||||
int daap_add_mdcl(DAAP_BLOCK *root, char *tag, char *name, short int number) {
|
||||
@ -350,7 +387,7 @@ MetaField_t encodeMetaRequest(char* meta, MetaDataMap* map)
|
||||
break;
|
||||
|
||||
if(m->tag)
|
||||
bits |= (1 << m->bit);
|
||||
bits |= (((MetaField_t) 1) << m->bit);
|
||||
else
|
||||
DPRINTF(ERR_WARN, "Unknown meta code: %*s\n", len, start);
|
||||
}
|
||||
@ -362,10 +399,10 @@ MetaField_t encodeMetaRequest(char* meta, MetaDataMap* map)
|
||||
|
||||
int wantsMeta(MetaField_t meta, MetaFieldName_t fieldNo)
|
||||
{
|
||||
return 0 != (meta & (1ll << fieldNo));
|
||||
return 0 != (meta & (((MetaField_t) 1) << fieldNo));
|
||||
}
|
||||
|
||||
DAAP_BLOCK *daap_response_songlist(char* metaStr) {
|
||||
DAAP_BLOCK *daap_response_songlist(char* metaStr, char* query) {
|
||||
DAAP_BLOCK *root;
|
||||
int g=1;
|
||||
DAAP_BLOCK *mlcl;
|
||||
@ -373,6 +410,10 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
|
||||
ENUMHANDLE henum;
|
||||
MP3FILE *current;
|
||||
MetaField_t meta;
|
||||
#ifdef OPT_QUERY
|
||||
query_node_t* filter = 0;
|
||||
#endif
|
||||
int songs = 0;
|
||||
|
||||
// if the meta tag is specified, encode it, if it's not specified
|
||||
// we're given the latitude to select our own subset, for
|
||||
@ -382,6 +423,18 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
|
||||
else
|
||||
meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != query)
|
||||
{
|
||||
filter = query_build(query, song_fields);
|
||||
if(err_debuglevel >= ERR_INFO)
|
||||
{
|
||||
fprintf(stderr, "query: %s\n", query);
|
||||
query_dump(stderr, filter, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Preparing to send db items\n");
|
||||
|
||||
henum=db_enum_begin();
|
||||
@ -394,22 +447,33 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
|
||||
if(root) {
|
||||
g = (int)daap_add_int(root,"mstt",200);
|
||||
g = g && daap_add_char(root,"muty",0);
|
||||
g = g && daap_add_int(root,"mtco",db_get_song_count());
|
||||
g = g && daap_add_int(root,"mrco",db_get_song_count());
|
||||
g = g && daap_add_int(root,"mtco",0);
|
||||
g = g && daap_add_int(root,"mrco",0);
|
||||
|
||||
mlcl=daap_add_empty(root,"mlcl");
|
||||
|
||||
if(mlcl) {
|
||||
while(g && (current=db_enum(&henum))) {
|
||||
#ifdef OPT_QUERY
|
||||
if(filter == 0 || query_test(filter, current))
|
||||
#endif
|
||||
{
|
||||
DPRINTF(ERR_DEBUG,"Got entry for %s\n",current->fname);
|
||||
// song entry generation extracted for usage with
|
||||
// playlists as well
|
||||
g = 0 != daap_add_song_entry(mlcl, current, meta);
|
||||
songs++;
|
||||
}
|
||||
}
|
||||
} else g=0;
|
||||
}
|
||||
|
||||
db_enum_end();
|
||||
db_enum_end(henum);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(filter != 0)
|
||||
query_free(filter);
|
||||
#endif
|
||||
|
||||
if(!g) {
|
||||
DPRINTF(ERR_DEBUG,"Error enumerating database\n");
|
||||
@ -419,6 +483,9 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Successfully enumerated database\n");
|
||||
|
||||
daap_set_int(root, "mtco", songs);
|
||||
daap_set_int(root, "mrco", songs);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -435,50 +502,14 @@ DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, MetaField_t met
|
||||
mlit=daap_add_empty(mlcl,"mlit");
|
||||
if(mlit) {
|
||||
if(wantsMeta(meta, metaItemKind))
|
||||
g = g && daap_add_char(mlit,"mikd",2); /* audio */
|
||||
g = g && daap_add_char(mlit,"mikd",song->item_kind); /* audio */
|
||||
if(wantsMeta(meta, metaSongDataKind))
|
||||
g = g && daap_add_char(mlit,"asdk",0); /* local file */
|
||||
if(song->album && (wantsMeta(meta, metaSongAlbum)))
|
||||
g = g && daap_add_string(mlit,"asal",song->album);
|
||||
|
||||
if(wantsMeta(meta, metaSongArtist))
|
||||
{
|
||||
char *artist;
|
||||
int artist_len;
|
||||
|
||||
artist=NULL;
|
||||
artist_len=0;
|
||||
if(song->orchestra || song->conductor) {
|
||||
if(song->orchestra)
|
||||
artist_len += strlen(song->orchestra);
|
||||
if(song->conductor)
|
||||
artist_len += strlen(song->conductor);
|
||||
|
||||
artist_len += 3;
|
||||
|
||||
artist=(char*)malloc(artist_len);
|
||||
if(artist) {
|
||||
memset(artist,0x0,artist_len);
|
||||
|
||||
if(song->orchestra)
|
||||
strcat(artist,song->orchestra);
|
||||
|
||||
if(song->orchestra && song->conductor)
|
||||
strcat(artist," - ");
|
||||
|
||||
if(song->conductor)
|
||||
strcat(artist,song->conductor);
|
||||
|
||||
g = g && daap_add_string(mlit,"asar",artist);
|
||||
|
||||
free(artist);
|
||||
artist=NULL;
|
||||
} else
|
||||
g=1;
|
||||
} else if(song->artist) {
|
||||
if(song->artist && wantsMeta(meta, metaSongArtist))
|
||||
g = g && daap_add_string(mlit,"asar",song->artist);
|
||||
}
|
||||
}
|
||||
|
||||
// g = g && daap_add_short(mlit,"asbt",0); /* bpm */
|
||||
if(song->bitrate && (wantsMeta(meta, metaSongBitRate)))
|
||||
@ -521,25 +552,12 @@ DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, MetaField_t met
|
||||
|
||||
/* these quite go hand in hand */
|
||||
if(wantsMeta(meta, metaSongFormat))
|
||||
g = g && daap_add_string(mlit,"asfm",(char*)&song->type[1]); /* song format */
|
||||
g = g && daap_add_string(mlit,"asfm",song->type); /* song format */
|
||||
if(wantsMeta(meta, metaSongDescription))
|
||||
{
|
||||
char fdescr[50];
|
||||
if(!strcasecmp(song->type,".ogg")) {
|
||||
sprintf(fdescr,"QuickTime movie file");
|
||||
} else {
|
||||
sprintf(fdescr,"%s audio file",song->type);
|
||||
}
|
||||
g = g && daap_add_string(mlit,"asdt",fdescr); /* descr */
|
||||
}
|
||||
g = g && daap_add_string(mlit, "asdt",song->description);
|
||||
|
||||
if(wantsMeta(meta, metaItemName))
|
||||
{
|
||||
if(song->title)
|
||||
g = g && daap_add_string(mlit,"minm",song->title); /* descr */
|
||||
else
|
||||
g = g && daap_add_string(mlit,"minm",song->fname);
|
||||
}
|
||||
|
||||
// mper (long)
|
||||
// g = g && daap_add_char(mlit,"asdb",0); /* disabled */
|
||||
@ -553,10 +571,10 @@ DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, MetaField_t met
|
||||
if(wantsMeta(meta, metaSongStartTime))
|
||||
g = g && daap_add_int(mlit,"asst",0); /* song start time? */
|
||||
if(wantsMeta(meta, metaSongStopTime))
|
||||
g = g && daap_add_int(mlit,"assp",0); /* songstoptime */
|
||||
g = g && daap_add_int(mlit,"assp",0); /* song stop time */
|
||||
|
||||
if(song->song_length && (wantsMeta(meta, metaSongTime)))
|
||||
g = g && daap_add_int(mlit,"astm",song->song_length*1000); /* song time */
|
||||
g = g && daap_add_int(mlit,"astm",song->song_length); /* song time */
|
||||
|
||||
if(song->total_tracks && (wantsMeta(meta, metaSongTrackCount)))
|
||||
g = g && daap_add_short(mlit,"astc",song->total_tracks); /* track count */
|
||||
@ -680,7 +698,7 @@ DAAP_BLOCK *daap_response_playlists(char *name) {
|
||||
}
|
||||
g = g && mlit;
|
||||
}
|
||||
db_playlist_enum_end();
|
||||
db_playlist_enum_end(henum);
|
||||
}
|
||||
|
||||
}
|
||||
@ -770,8 +788,12 @@ DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
|
||||
|
||||
g = g && daap_add_string(root,"minm",name); /* server name */
|
||||
|
||||
#if 0
|
||||
/* DWB: login isn't actually required since the session id
|
||||
isn't recorded, and isn't actually used for anything */
|
||||
/* logon is always required, even if a password isn't */
|
||||
g = g && daap_add_char(root,"mslr",1);
|
||||
#endif
|
||||
|
||||
/* authentication method is 0 for nothing, 1 for name and
|
||||
password, 2 for password only */
|
||||
@ -786,12 +808,17 @@ DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
|
||||
supported */
|
||||
g = g && daap_add_char(root,"msex",0); /* extensions */
|
||||
g = g && daap_add_char(root,"msix",0); /* indexing? */
|
||||
|
||||
#ifdef OPT_BROWSE
|
||||
g = g && daap_add_char(root,"msbr",0); /* browsing */
|
||||
#endif
|
||||
#ifdef OPT_QUERY
|
||||
g = g && daap_add_char(root,"msqy",0); /* queries */
|
||||
#endif
|
||||
#if 0
|
||||
g = g && daap_add_char(root,"msal",0); /* autologout */
|
||||
g = g && daap_add_char(root,"msup",0); /* update */
|
||||
g = g && daap_add_char(root,"mspi",0); /* persistant ids */
|
||||
g = g && daap_add_char(root,"msbr",0); /* browsing */
|
||||
g = g && daap_add_char(root,"msqy",0); /* queries */
|
||||
g = g && daap_add_char(root,"msrs",0); /* resolve? req. persist id */
|
||||
#endif
|
||||
g = g && daap_add_int(root,"msdc",1); /* database count */
|
||||
@ -811,7 +838,7 @@ DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
|
||||
*
|
||||
* given a playlist number, return the items on the playlist
|
||||
*/
|
||||
DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
|
||||
DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr, char* query) {
|
||||
DAAP_BLOCK *root;
|
||||
DAAP_BLOCK *mlcl;
|
||||
DAAP_BLOCK *mlit;
|
||||
@ -820,6 +847,10 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
|
||||
int itemid;
|
||||
int g=1;
|
||||
unsigned long long meta;
|
||||
#ifdef OPT_QUERY
|
||||
query_node_t* filter = 0;
|
||||
#endif
|
||||
int songs = 0;
|
||||
|
||||
// if no meta information is specifically requested, return only
|
||||
// the base play list information. iTunes only requests the base
|
||||
@ -834,6 +865,18 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
|
||||
else
|
||||
meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != query)
|
||||
{
|
||||
filter = query_build(query, song_fields);
|
||||
if(err_debuglevel >= ERR_INFO)
|
||||
{
|
||||
fprintf(stderr, "query: %s\n", query);
|
||||
query_dump(stderr, filter, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Preparing to send playlist items for pl #%d\n",playlist);
|
||||
|
||||
if(playlist == 1) {
|
||||
@ -857,22 +900,34 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
|
||||
if(mlcl) {
|
||||
if(playlist == 1) {
|
||||
while((current=db_enum(&henum))) {
|
||||
#ifdef OPT_QUERY
|
||||
if(0 == filter || query_test(filter, current))
|
||||
#endif
|
||||
{
|
||||
songs++;
|
||||
mlit=daap_add_song_entry(mlcl, current, meta);
|
||||
if(0 != mlit) {
|
||||
if(wantsMeta(meta, metaContainerItemId))
|
||||
g = g && daap_add_int(mlit,"mcti",playlist);
|
||||
} else g=0;
|
||||
}
|
||||
}
|
||||
} else { /* other playlist */
|
||||
while((itemid=db_playlist_items_enum(&henum)) != -1) {
|
||||
current = db_find(itemid);
|
||||
if(0 != current) {
|
||||
#ifdef OPT_QUERY
|
||||
if(0 == filter || query_test(filter, current))
|
||||
{
|
||||
#endif
|
||||
songs++;
|
||||
DPRINTF(ERR_DEBUG,"Adding itemid %d\n",itemid);
|
||||
mlit=daap_add_song_entry(mlcl,current,meta);
|
||||
if(0 != mlit) {
|
||||
if(wantsMeta(meta, metaContainerItemId))
|
||||
g = g && daap_add_int(mlit,"mcti",playlist);
|
||||
} else g = 0;
|
||||
}
|
||||
} else g = 0;
|
||||
}
|
||||
}
|
||||
@ -880,15 +935,23 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
|
||||
}
|
||||
|
||||
if(playlist == 1)
|
||||
db_enum_end();
|
||||
db_enum_end(henum);
|
||||
else
|
||||
db_playlist_items_enum_end();
|
||||
db_playlist_items_enum_end(henum);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != filter)
|
||||
query_free(filter);
|
||||
#endif
|
||||
|
||||
if(!g) {
|
||||
daap_free(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
daap_set_int(root, "mtco", songs);
|
||||
daap_set_int(root, "mrco", songs);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -988,12 +1051,7 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
|
||||
|
||||
// update the returned record count entry, it's required, so
|
||||
// should have already be created
|
||||
assert(0 != (item = daap_find(block, "mrco")));
|
||||
|
||||
item->svalue[0] = count >> 24;
|
||||
item->svalue[1] = count >> 16;
|
||||
item->svalue[2] = count >> 8;
|
||||
item->svalue[3] = count;
|
||||
daap_set_int(block, "mrco", count);
|
||||
|
||||
DPRINTF(ERR_INFO, "index:%s first:%d count:%d\n", index, first, count);
|
||||
|
||||
@ -1001,7 +1059,7 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
|
||||
for(back = &list->children ; *back && first ; )
|
||||
if(!strncmp((**back).tag, "mlit", 4))
|
||||
{
|
||||
DPRINTF(ERR_INFO, "first:%d removing\n", first);
|
||||
DPRINTF(ERR_DEBUG, "first:%d removing\n", first);
|
||||
daap_remove(*back);
|
||||
first--;
|
||||
}
|
||||
@ -1012,7 +1070,7 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
|
||||
for( ; *back && count ; back = &(**back).next)
|
||||
if(!strncmp((**back).tag, "mlit", 4))
|
||||
{
|
||||
DPRINTF(ERR_INFO, "count:%d keeping\n", count);
|
||||
DPRINTF(ERR_DEBUG, "count:%d keeping\n", count);
|
||||
count--;
|
||||
}
|
||||
|
||||
@ -1021,10 +1079,182 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
|
||||
{
|
||||
if(!strncmp((**back).tag, "mlit", 4))
|
||||
{
|
||||
DPRINTF(ERR_INFO, "removing spare\n");
|
||||
DPRINTF(ERR_DEBUG, "removing spare\n");
|
||||
daap_remove(*back);
|
||||
}
|
||||
else
|
||||
back = &(**back).next;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef OPT_BROWSE
|
||||
typedef struct _browse_item browse_item;
|
||||
struct _browse_item
|
||||
{
|
||||
char* name;
|
||||
browse_item* next;
|
||||
};
|
||||
|
||||
static void add_browse_item(browse_item** root, char* name)
|
||||
{
|
||||
browse_item* item;
|
||||
|
||||
while(0 != (item = *root) && strcmp(item->name, name) < 0)
|
||||
root = &item->next;
|
||||
|
||||
if(item && strcmp(item->name, name) == 0)
|
||||
return;
|
||||
|
||||
item = calloc(1, sizeof(browse_item));
|
||||
item->name = strdup(name);
|
||||
item->next = *root;
|
||||
*root = item;
|
||||
}
|
||||
|
||||
static void free_browse_items(browse_item* root)
|
||||
{
|
||||
while(0 != root)
|
||||
{
|
||||
browse_item* next = root->next;
|
||||
|
||||
free(root->name);
|
||||
free(root);
|
||||
|
||||
root = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int count_browse_items(browse_item* root)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while(0 != root)
|
||||
{
|
||||
root = root->next;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
/* in theory each type of browse has a separate subset of these fields
|
||||
which can be used for filtering, but it's just not worth the effort
|
||||
and doesn't save anything */
|
||||
static query_field_t browse_fields[] = {
|
||||
{ qft_string, "daap.songartist", OFFSET_OF(MP3FILE, artist) },
|
||||
{ qft_string, "daap.songalbum", OFFSET_OF(MP3FILE, album) },
|
||||
{ qft_string, "daap.songgenre", OFFSET_OF(MP3FILE, genre) },
|
||||
{ qft_string, "daap.songcomposer", OFFSET_OF(MP3FILE, composer) },
|
||||
{ 0 }
|
||||
};
|
||||
#endif
|
||||
|
||||
DAAP_BLOCK* daap_response_browse(const char* name, const char* filter)
|
||||
{
|
||||
MP3FILE* current;
|
||||
ENUMHANDLE henum;
|
||||
size_t field;
|
||||
char* l_type;
|
||||
browse_item* items = 0;
|
||||
browse_item* item;
|
||||
DAAP_BLOCK* root = 0;
|
||||
query_node_t* query = 0;
|
||||
|
||||
if(!strcmp(name, "artists"))
|
||||
{
|
||||
field = OFFSET_OF(MP3FILE, artist);
|
||||
l_type = "abar";
|
||||
}
|
||||
else if(!strcmp(name, "genres"))
|
||||
{
|
||||
field = OFFSET_OF(MP3FILE, genre);
|
||||
l_type = "abgn";
|
||||
}
|
||||
else if(!strcmp(name, "albums"))
|
||||
{
|
||||
field = OFFSET_OF(MP3FILE, album);
|
||||
l_type = "abal";
|
||||
}
|
||||
else if(!strcmp(name, "composers"))
|
||||
{
|
||||
field = OFFSET_OF(MP3FILE, composer);
|
||||
l_type = "abcp";
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(ERR_WARN,"Invalid browse request: %s\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != filter &&
|
||||
0 == (query = query_build(filter, browse_fields)))
|
||||
return NULL;
|
||||
|
||||
if(query && err_debuglevel >= ERR_INFO)
|
||||
{
|
||||
fprintf(stderr, "query: %s\n", query);
|
||||
query_dump(stderr, query, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(0 == (henum = db_enum_begin()))
|
||||
return NULL;
|
||||
|
||||
while((current = db_enum(&henum)))
|
||||
{
|
||||
#ifdef OPT_QUERY
|
||||
if(0 == query || query_test(query, current))
|
||||
#endif
|
||||
{
|
||||
char* name = * (char**) ((size_t) current + field);
|
||||
|
||||
if(0 != name)
|
||||
add_browse_item(&items, name);
|
||||
}
|
||||
}
|
||||
|
||||
db_enum_end(henum);
|
||||
|
||||
if(0 != (root = daap_add_empty(0, "abro")))
|
||||
{
|
||||
int count = count_browse_items(items);
|
||||
DAAP_BLOCK* mlcl;
|
||||
|
||||
if(!daap_add_int(root, "mstt", 200) ||
|
||||
!daap_add_int(root, "mtco", count) ||
|
||||
!daap_add_int(root, "mrco", count) ||
|
||||
0 == (mlcl = daap_add_empty(root, l_type)))
|
||||
goto error;
|
||||
|
||||
for(item = items ; item ; item = item->next)
|
||||
{
|
||||
if(!daap_add_string(mlcl, "mlit", item->name))
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
free_browse_items(items);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != query)
|
||||
query_free(query);
|
||||
#endif
|
||||
|
||||
return root;
|
||||
|
||||
error:
|
||||
free_browse_items(items);
|
||||
|
||||
#ifdef OPT_QUERY
|
||||
if(0 != query)
|
||||
query_free(query);
|
||||
#endif
|
||||
|
||||
if(root != 0)
|
||||
daap_free(root);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
@ -27,11 +27,14 @@ DAAP_BLOCK *daap_response_server_info(char *name, char *client_version);
|
||||
DAAP_BLOCK *daap_response_content_codes(void);
|
||||
DAAP_BLOCK *daap_response_login(char *hostname);
|
||||
DAAP_BLOCK *daap_response_update(int fd, int clientver);
|
||||
DAAP_BLOCK *daap_response_songlist(char* metaInfo);
|
||||
DAAP_BLOCK *daap_response_songlist(char* metaInfo, char* query);
|
||||
DAAP_BLOCK *daap_response_playlists(char *name);
|
||||
DAAP_BLOCK *daap_response_dbinfo(char *name);
|
||||
DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr);
|
||||
DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr, char* query);
|
||||
void daap_handle_index(DAAP_BLOCK* block, const char* index);
|
||||
DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, unsigned long long meta);
|
||||
#ifdef OPT_BROWSE
|
||||
DAAP_BLOCK* daap_response_browse(const char* name, const char* filter);
|
||||
#endif
|
||||
|
||||
#endif /* _DAAP_H_ */
|
||||
|
180
src/db-gdbm.c
180
src/db-gdbm.c
@ -40,6 +40,7 @@
|
||||
#define RB_FREE
|
||||
#include "redblack.h"
|
||||
|
||||
#include "db-memory.h"
|
||||
|
||||
/*
|
||||
* Defines
|
||||
@ -61,10 +62,11 @@
|
||||
/*
|
||||
* Typedefs
|
||||
*/
|
||||
typedef struct tag_mp3record {
|
||||
typedef struct tag_mp3record MP3RECORD;
|
||||
struct tag_mp3record {
|
||||
MP3FILE mp3file;
|
||||
datum key;
|
||||
} MP3RECORD;
|
||||
MP3RECORD* next;
|
||||
};
|
||||
|
||||
typedef struct tag_playlistentry {
|
||||
unsigned int id;
|
||||
@ -116,6 +118,10 @@ typedef struct tag_mp3packed {
|
||||
char data[1];
|
||||
} MP3PACKED;
|
||||
|
||||
typedef struct {
|
||||
MP3RECORD* root;
|
||||
MP3RECORD* next;
|
||||
} MP3HELPER;;
|
||||
|
||||
/*
|
||||
* Globals
|
||||
@ -127,7 +133,6 @@ int db_playlist_count=0;
|
||||
DB_PLAYLIST db_playlists;
|
||||
pthread_rwlock_t db_rwlock; /* OSX doesn't have PTHREAD_RWLOCK_INITIALIZER */
|
||||
pthread_once_t db_initlock=PTHREAD_ONCE_INIT;
|
||||
MP3RECORD db_enum_helper;
|
||||
GDBM_FILE db_songs;
|
||||
struct rbtree *db_removed;
|
||||
|
||||
@ -138,7 +143,7 @@ struct rbtree *db_removed;
|
||||
int db_start_initial_update(void);
|
||||
int db_end_initial_update(void);
|
||||
int db_is_empty(void);
|
||||
int db_open(char *parameters);
|
||||
int db_open(char *parameters, int reload);
|
||||
int db_init();
|
||||
int db_deinit(void);
|
||||
int db_version(void);
|
||||
@ -149,18 +154,6 @@ int db_add_playlist_song(unsigned int playlistid, unsigned int itemid);
|
||||
int db_unpackrecord(datum *pdatum, MP3FILE *pmp3);
|
||||
datum *db_packrecord(MP3FILE *pmp3);
|
||||
|
||||
MP3RECORD *db_enum_begin(void);
|
||||
MP3FILE *db_enum(MP3RECORD **current);
|
||||
int db_enum_end(void);
|
||||
|
||||
DB_PLAYLIST *db_playlist_enum_begin(void);
|
||||
int db_playlist_enum(DB_PLAYLIST **current);
|
||||
int db_playlist_enum_end(void);
|
||||
|
||||
DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid);
|
||||
int db_playlist_items_enum(DB_PLAYLISTENTRY **current);
|
||||
int db_playlist_items_enum_end(void);
|
||||
|
||||
int db_get_song_count(void);
|
||||
int db_get_playlist_count(void);
|
||||
int db_get_playlist_is_smart(int playlistid);
|
||||
@ -199,14 +192,16 @@ void db_init_once(void) {
|
||||
*
|
||||
* Open the database, so we can drop privs
|
||||
*/
|
||||
int db_open(char *parameters) {
|
||||
int db_open(char *parameters, int reload) {
|
||||
char db_path[PATH_MAX + 1];
|
||||
|
||||
if(pthread_once(&db_initlock,db_init_once))
|
||||
return -1;
|
||||
|
||||
snprintf(db_path,sizeof(db_path),"%s/%s",parameters,"songs.gdb");
|
||||
db_songs=gdbm_open(db_path,0,GDBM_WRCREAT | GDBM_SYNC | GDBM_NOLOCK,
|
||||
|
||||
reload = reload ? GDBM_NEWDB : GDBM_WRCREAT;
|
||||
db_songs=gdbm_open(db_path, 0, reload | GDBM_SYNC | GDBM_NOLOCK,
|
||||
0600,NULL);
|
||||
if(!db_songs) {
|
||||
DPRINTF(ERR_FATAL,"Could not open songs database (%s)\n",
|
||||
@ -686,6 +681,8 @@ int db_unpackrecord(datum *pdatum, MP3FILE *pmp3) {
|
||||
pmp3->grouping=strdup(&ppacked->data[offset]);
|
||||
offset += ppacked->grouping_len;
|
||||
|
||||
make_composite_tags(pmp3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -771,19 +768,45 @@ void db_freefile(MP3FILE *pmp3) {
|
||||
MAYBEFREE(pmp3->orchestra);
|
||||
MAYBEFREE(pmp3->conductor);
|
||||
MAYBEFREE(pmp3->grouping);
|
||||
MAYBEFREE(pmp3->description);
|
||||
}
|
||||
|
||||
/*
|
||||
* db_enum_begin
|
||||
*
|
||||
* Begin to walk through an enum of
|
||||
* the database.
|
||||
* Begin to walk through an enum of the database. In order to retain
|
||||
* some sanity in song ordering we scan all the songs and perform an
|
||||
* insertion sort based on album, track, record id.
|
||||
*
|
||||
* Since the list is built entirely within this routine, it's also
|
||||
* safe to release the lock once the list is built.
|
||||
*
|
||||
* Also we eliminate the static db_enum_helper which allows (the
|
||||
* admittedly unnecessary) possibility of reentrance.
|
||||
*
|
||||
* this should be done quickly, as we'll be holding
|
||||
* a reader lock on the db
|
||||
*/
|
||||
MP3RECORD *db_enum_begin(void) {
|
||||
int compare(MP3RECORD* a, MP3RECORD* b)
|
||||
{
|
||||
int comp;
|
||||
|
||||
if((comp = strcmp(a->mp3file.album, b->mp3file.album)) != 0)
|
||||
return comp;
|
||||
|
||||
if((comp = (a->mp3file.disc - b->mp3file.disc)) != 0)
|
||||
return comp;
|
||||
|
||||
if((comp = (a->mp3file.track - b->mp3file.track)) != 0)
|
||||
return comp;
|
||||
|
||||
return a->mp3file.id - b->mp3file.id;
|
||||
}
|
||||
|
||||
ENUMHANDLE db_enum_begin(void) {
|
||||
int err;
|
||||
MP3HELPER* helper;
|
||||
datum key, next;
|
||||
|
||||
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
|
||||
DPRINTF(ERR_FATAL,"Cannot lock rwlock\n");
|
||||
@ -791,13 +814,39 @@ MP3RECORD *db_enum_begin(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset((void*)&db_enum_helper,0x00,sizeof(db_enum_helper));
|
||||
db_enum_helper.key=gdbm_firstkey(db_songs);
|
||||
MEMNOTIFY(db_enum_helper.key.dptr);
|
||||
if(!db_enum_helper.key.dptr)
|
||||
return NULL;
|
||||
helper = calloc(1, sizeof(MP3HELPER));
|
||||
|
||||
return &db_enum_helper;
|
||||
for(key = gdbm_firstkey(db_songs) ; key.dptr ; key = next)
|
||||
{
|
||||
MP3RECORD* entry = calloc(1, sizeof(MP3RECORD));
|
||||
MP3RECORD** root;
|
||||
datum data;
|
||||
|
||||
data = gdbm_fetch(db_songs, key);
|
||||
if(!data.dptr)
|
||||
DPRINTF(ERR_FATAL, "Cannot find item... corrupt database?\n");
|
||||
|
||||
if(db_unpackrecord(&data, &entry->mp3file))
|
||||
DPRINTF(ERR_FATAL, "Cannot unpack item... corrupt database?\n");
|
||||
|
||||
for(root = &helper->root ; *root ; root = &(**root).next)
|
||||
{
|
||||
if(compare(*root, entry) > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
entry->next = *root;
|
||||
*root = entry;
|
||||
|
||||
next = gdbm_nextkey(db_songs, key);
|
||||
free(key.dptr);
|
||||
}
|
||||
|
||||
helper->next = helper->root;
|
||||
|
||||
pthread_rwlock_unlock(&db_rwlock);
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -805,7 +854,7 @@ MP3RECORD *db_enum_begin(void) {
|
||||
*
|
||||
* Start enumerating playlists
|
||||
*/
|
||||
DB_PLAYLIST *db_playlist_enum_begin(void) {
|
||||
ENUMHANDLE db_playlist_enum_begin(void) {
|
||||
int err;
|
||||
DB_PLAYLIST *current;
|
||||
|
||||
@ -828,7 +877,7 @@ DB_PLAYLIST *db_playlist_enum_begin(void) {
|
||||
*
|
||||
* Start enumerating playlist items
|
||||
*/
|
||||
DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid) {
|
||||
ENUMHANDLE db_playlist_items_enum_begin(int playlistid) {
|
||||
DB_PLAYLIST *current;
|
||||
int err;
|
||||
|
||||
@ -854,40 +903,16 @@ DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid) {
|
||||
*
|
||||
* Walk to the next entry
|
||||
*/
|
||||
MP3FILE *db_enum(MP3RECORD **current) {
|
||||
datum nextkey;
|
||||
datum data;
|
||||
MP3FILE *db_enum(ENUMHANDLE *current) {
|
||||
MP3HELPER* helper = *(MP3HELPER**) current;
|
||||
MP3RECORD* record = helper->next;
|
||||
|
||||
if(db_enum_helper.key.dptr) {
|
||||
db_freefile(&db_enum_helper.mp3file);
|
||||
if(helper->next == 0)
|
||||
return 0;
|
||||
|
||||
/* Got the key, let's fetch it */
|
||||
data=gdbm_fetch(db_songs,db_enum_helper.key);
|
||||
MEMNOTIFY(data.dptr);
|
||||
if(!data.dptr) {
|
||||
DPRINTF(ERR_FATAL,"Inconsistant database.\n");
|
||||
}
|
||||
helper->next = helper->next->next;
|
||||
|
||||
if(db_unpackrecord(&data,&db_enum_helper.mp3file)) {
|
||||
DPRINTF(ERR_FATAL,"Cannot unpack item.. Corrupt database?\n");
|
||||
}
|
||||
|
||||
if(data.dptr)
|
||||
free(data.dptr);
|
||||
|
||||
nextkey=gdbm_nextkey(db_songs,db_enum_helper.key);
|
||||
MEMNOTIFY(nextkey.dptr);
|
||||
|
||||
if(db_enum_helper.key.dptr) {
|
||||
free(db_enum_helper.key.dptr);
|
||||
db_enum_helper.key.dptr=NULL;
|
||||
}
|
||||
|
||||
db_enum_helper.key=nextkey;
|
||||
return &db_enum_helper.mp3file;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return &record->mp3file;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -895,7 +920,8 @@ MP3FILE *db_enum(MP3RECORD **current) {
|
||||
*
|
||||
* walk to the next entry
|
||||
*/
|
||||
int db_playlist_enum(DB_PLAYLIST **current) {
|
||||
int db_playlist_enum(ENUMHANDLE* handle) {
|
||||
DB_PLAYLIST** current = (DB_PLAYLIST**) handle;
|
||||
int retval;
|
||||
DB_PLAYLIST *p;
|
||||
|
||||
@ -917,7 +943,8 @@ int db_playlist_enum(DB_PLAYLIST **current) {
|
||||
*
|
||||
* walk to the next entry
|
||||
*/
|
||||
int db_playlist_items_enum(DB_PLAYLISTENTRY **current) {
|
||||
int db_playlist_items_enum(ENUMHANDLE* handle) {
|
||||
DB_PLAYLISTENTRY **current = (DB_PLAYLISTENTRY**) handle;
|
||||
int retval;
|
||||
|
||||
if(*current) {
|
||||
@ -932,14 +959,25 @@ int db_playlist_items_enum(DB_PLAYLISTENTRY **current) {
|
||||
/*
|
||||
* db_enum_end
|
||||
*
|
||||
* quit walking the database (and give up reader lock)
|
||||
* dispose of the list we built up in db_enum_begin, the lock's
|
||||
* already been released.
|
||||
*/
|
||||
int db_enum_end(void) {
|
||||
db_freefile(&db_enum_helper.mp3file);
|
||||
if(db_enum_helper.key.dptr)
|
||||
free(db_enum_helper.key.dptr);
|
||||
int db_enum_end(ENUMHANDLE handle) {
|
||||
MP3HELPER* helper = (MP3HELPER*) handle;
|
||||
MP3RECORD* record;
|
||||
|
||||
return pthread_rwlock_unlock(&db_rwlock);
|
||||
while(helper->root)
|
||||
{
|
||||
MP3RECORD* record = helper->root;
|
||||
|
||||
helper->root = record->next;
|
||||
|
||||
db_freefile(&record->mp3file);
|
||||
}
|
||||
|
||||
free(helper);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -947,7 +985,7 @@ int db_enum_end(void) {
|
||||
*
|
||||
* quit walking the database
|
||||
*/
|
||||
int db_playlist_enum_end(void) {
|
||||
int db_playlist_enum_end(ENUMHANDLE handle) {
|
||||
return pthread_rwlock_unlock(&db_rwlock);
|
||||
}
|
||||
|
||||
@ -956,7 +994,7 @@ int db_playlist_enum_end(void) {
|
||||
*
|
||||
* Quit walking the database
|
||||
*/
|
||||
int db_playlist_items_enum_end(void) {
|
||||
int db_playlist_items_enum_end(ENUMHANDLE handle) {
|
||||
return pthread_rwlock_unlock(&db_rwlock);
|
||||
}
|
||||
|
||||
|
@ -389,6 +389,8 @@ int db_add(MP3FILE *mp3file) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
make_composite_tags(&pnew->mp3file);
|
||||
|
||||
pnew->next=db_root.next;
|
||||
db_root.next=pnew;
|
||||
|
||||
@ -421,6 +423,7 @@ void db_freerecord(MP3RECORD *mp3record) {
|
||||
MAYBEFREE(mp3record->mp3file.orchestra);
|
||||
MAYBEFREE(mp3record->mp3file.conductor);
|
||||
MAYBEFREE(mp3record->mp3file.grouping);
|
||||
MAYBEFREE(mp3record->mp3file.description);
|
||||
free(mp3record);
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ typedef void* ENUMHANDLE;
|
||||
extern int db_start_initial_update(void);
|
||||
extern int db_end_initial_update(void);
|
||||
extern int db_is_empty(void);
|
||||
extern int db_open(char *parameters);
|
||||
extern int db_open(char *parameters, int reload);
|
||||
extern int db_init(void);
|
||||
extern int db_deinit(void);
|
||||
extern int db_version(void);
|
||||
@ -39,8 +39,8 @@ extern int db_add_playlist(unsigned int playlistid, char *name, int is_smart);
|
||||
extern int db_add_playlist_song(unsigned int playlistid, unsigned int itemid);
|
||||
|
||||
extern ENUMHANDLE db_enum_begin(void);
|
||||
extern MP3FILE *db_enum(ENUMHANDLE *current);
|
||||
extern int db_enum_end(void);
|
||||
extern MP3FILE *db_enum(ENUMHANDLE *handle);
|
||||
extern int db_enum_end(ENUMHANDLE handle);
|
||||
extern MP3FILE *db_find(int id);
|
||||
|
||||
extern int db_get_song_count(void);
|
||||
@ -50,11 +50,11 @@ extern int db_get_playlist_is_smart(int playlistid);
|
||||
|
||||
extern ENUMHANDLE db_playlist_enum_begin(void);
|
||||
extern int db_playlist_enum(ENUMHANDLE *current);
|
||||
extern int db_playlist_enum_end(void);
|
||||
extern int db_playlist_enum_end(ENUMHANDLE handle);
|
||||
|
||||
extern ENUMHANDLE db_playlist_items_enum_begin(int playlistid);
|
||||
extern int db_playlist_items_enum(ENUMHANDLE *current);
|
||||
extern int db_playlist_items_enum_end(void);
|
||||
extern int db_playlist_items_enum_end(ENUMHANDLE handle);
|
||||
|
||||
extern char *db_get_playlist_name(int playlistid);
|
||||
|
||||
|
69
src/main.c
69
src/main.c
@ -51,7 +51,13 @@
|
||||
#include "playlist.h"
|
||||
#include "dynamic-art.h"
|
||||
|
||||
#ifndef DEFAULT_CONFIGFILE
|
||||
#define DEFAULT_CONFIGFILE "/etc/mt-daapd.conf"
|
||||
#endif
|
||||
|
||||
#ifndef PIDFILE
|
||||
#define PIDFILE "/var/run/mt-daapd.pid"
|
||||
#endif
|
||||
|
||||
#ifndef SIGCLD
|
||||
# define SIGCLD SIGCHLD
|
||||
@ -67,6 +73,7 @@ CONFIG config;
|
||||
*/
|
||||
RETSIGTYPE sig_child(int signal);
|
||||
int daemon_start(int reap_children);
|
||||
void write_pid_file(void);
|
||||
|
||||
/*
|
||||
* daap_auth
|
||||
@ -167,6 +174,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
* /databases/id/containers, which returns a container
|
||||
* /databases/id/containers/id/items, which returns playlist elements
|
||||
* /databases/id/items/id.mp3, to spool an mp3
|
||||
* /databases/id/browse/category
|
||||
*/
|
||||
|
||||
uri = strdup(pwsc->uri);
|
||||
@ -198,7 +206,9 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
/* songlist */
|
||||
free(uri);
|
||||
// pass the meta field request for processing
|
||||
root=daap_response_songlist(ws_getvar(pwsc,"meta"));
|
||||
// pass the query request for processing
|
||||
root=daap_response_songlist(ws_getvar(pwsc,"meta"),
|
||||
ws_getvar(pwsc,"query"));
|
||||
config_set_status(pwsc,session_id,"Sending songlist");
|
||||
} else if (strncasecmp(last,"containers/",11)==0) {
|
||||
/* playlist elements */
|
||||
@ -213,7 +223,8 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
playlist_index=atoi(first);
|
||||
// pass the meta list info for processing
|
||||
root=daap_response_playlist_items(playlist_index,
|
||||
ws_getvar(pwsc,"meta"));
|
||||
ws_getvar(pwsc,"meta"),
|
||||
ws_getvar(pwsc,"query"));
|
||||
}
|
||||
free(uri);
|
||||
config_set_status(pwsc,session_id,"Sending playlist info");
|
||||
@ -222,6 +233,14 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
free(uri);
|
||||
root=daap_response_playlists(config.servername);
|
||||
config_set_status(pwsc,session_id,"Sending playlist info");
|
||||
#ifdef OPT_BROWSE
|
||||
} else if (strncasecmp(last,"browse/",7)==0) {
|
||||
config_set_status(pwsc,session_id,"Compiling browse info");
|
||||
root = daap_response_browse(last + 7,
|
||||
ws_getvar(pwsc, "filter"));
|
||||
config_set_status(pwsc,session_id,"Sending browse info");
|
||||
free(uri);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +310,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
// content type (dmap tagged) should only be used on
|
||||
// dmap protocol requests, not the actually song data
|
||||
if(pmp3->type)
|
||||
ws_addresponseheader(pwsc,"Content-Type","audio/%s",(pmp3->type)+1);
|
||||
ws_addresponseheader(pwsc,"Content-Type","audio/%s",pmp3->type);
|
||||
|
||||
ws_addresponseheader(pwsc,"Content-Length","%ld",(long)file_len);
|
||||
ws_addresponseheader(pwsc,"Connection","Close");
|
||||
@ -539,13 +558,14 @@ int main(int argc, char *argv[]) {
|
||||
WSHANDLE server;
|
||||
int parseonly=0;
|
||||
int foreground=0;
|
||||
int reload=0;
|
||||
int start_time;
|
||||
int end_time;
|
||||
|
||||
config.use_mdns=1;
|
||||
err_debuglevel=1;
|
||||
|
||||
while((option=getopt(argc,argv,"d:c:mpf")) != -1) {
|
||||
while((option=getopt(argc,argv,"d:c:mpfr")) != -1) {
|
||||
switch(option) {
|
||||
case 'd':
|
||||
err_debuglevel=atoi(optarg);
|
||||
@ -566,6 +586,10 @@ int main(int argc, char *argv[]) {
|
||||
parseonly=1;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
reload=1;
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
@ -599,7 +623,19 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if(db_open(config.dbdir)) {
|
||||
/* DWB: we want to detach before we drop privs so the pid file can
|
||||
be created with the original permissions. This has the
|
||||
drawback that there's a bit less error checking done while
|
||||
we're attached, but if is much better when being automatically
|
||||
started as a system service. */
|
||||
if(!foreground)
|
||||
{
|
||||
daemon_start(1);
|
||||
write_pid_file();
|
||||
}
|
||||
|
||||
/* DWB: shouldn't this be done after dropping privs? */
|
||||
if(db_open(config.dbdir, reload)) {
|
||||
DPRINTF(ERR_FATAL,"Error in db_open: %s\n",strerror(errno));
|
||||
}
|
||||
|
||||
@ -633,10 +669,6 @@ int main(int argc, char *argv[]) {
|
||||
DPRINTF(ERR_FATAL,"Error in db_init: %s\n",strerror(errno));
|
||||
}
|
||||
|
||||
/* will want to detach before we start scanning mp3 files */
|
||||
if(!foreground)
|
||||
daemon_start(1);
|
||||
|
||||
DPRINTF(ERR_LOG,"Starting mp3 scan\n");
|
||||
if(scan_init(config.mp3dir)) {
|
||||
DPRINTF(ERR_FATAL,"Error scanning MP3 files: %s\n",strerror(errno));
|
||||
@ -724,3 +756,22 @@ int main(int argc, char *argv[]) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void write_pid_file(void)
|
||||
{
|
||||
FILE* fp;
|
||||
int fd;
|
||||
|
||||
/* use open/fdopen instead of fopen for more control over the file
|
||||
permissions */
|
||||
if(-1 == (fd = open(PIDFILE, O_CREAT | O_WRONLY | O_TRUNC, 0644)))
|
||||
return;
|
||||
|
||||
if(0 == (fp = fdopen(fd, "w")))
|
||||
{
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "%d\n", getpid());
|
||||
fclose(fp);
|
||||
}
|
||||
|
@ -261,6 +261,8 @@ int scan_freetags(MP3FILE *pmp3);
|
||||
void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb);
|
||||
void scan_music_file(char *path, struct dirent *pde, struct stat *psb);
|
||||
|
||||
void make_composite_tags(MP3FILE *pmp3);
|
||||
|
||||
/*
|
||||
* scan_init
|
||||
*
|
||||
@ -452,14 +454,17 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) {
|
||||
mp3file.path=mp3_path;
|
||||
mp3file.fname=pde->d_name;
|
||||
if(strlen(pde->d_name) > 4)
|
||||
mp3file.type=strdup((char*)&pde->d_name[strlen(pde->d_name) - 4]);
|
||||
mp3file.type=strdup(strrchr(pde->d_name, '.') + 1);
|
||||
|
||||
/* FIXME; assumes that st_ino is a u_int_32 */
|
||||
/* FIXME; assumes that st_ino is a u_int_32
|
||||
DWB: also assumes that the library is contained entirely within
|
||||
one file system */
|
||||
mp3file.id=psb->st_ino;
|
||||
|
||||
/* Do the tag lookup here */
|
||||
if(!scan_gettags(mp3file.path,&mp3file) &&
|
||||
!scan_get_fileinfo(mp3file.path,&mp3file)) {
|
||||
make_composite_tags(&mp3file);
|
||||
db_add(&mp3file);
|
||||
pl_eval(&mp3file); /* FIXME: move to db_add? */
|
||||
} else {
|
||||
@ -630,11 +635,11 @@ int scan_gettags(char *file, MP3FILE *pmp3) {
|
||||
* in turn, just in case the extensions are wrong/lying
|
||||
*/
|
||||
|
||||
if(strcasestr(".aac.m4a.m4p.mp4",pmp3->type))
|
||||
if(strcasestr("aacm4a4pmp4",pmp3->type))
|
||||
return scan_get_aactags(file,pmp3);
|
||||
|
||||
/* should handle mp3 in the same way */
|
||||
if(!strcasecmp(pmp3->type,".mp3"))
|
||||
if(!strcasecmp(pmp3->type,"mp3"))
|
||||
return scan_get_mp3tags(file,pmp3);
|
||||
|
||||
/* maybe this is an extension that we've manually
|
||||
@ -661,7 +666,7 @@ int scan_get_mp3tags(char *file, MP3FILE *pmp3) {
|
||||
char *tmp;
|
||||
int got_numeric_genre;
|
||||
|
||||
if(strcasecmp(pmp3->type,".mp3")) /* can't get tags for non-mp3 */
|
||||
if(strcasecmp(pmp3->type,"mp3")) /* can't get tags for non-mp3 */
|
||||
return 0;
|
||||
|
||||
pid3file=id3_file_open(file,ID3_FILE_MODE_READONLY);
|
||||
@ -775,6 +780,9 @@ int scan_get_mp3tags(char *file, MP3FILE *pmp3) {
|
||||
} else if(!strcmp(pid3frame->id,"TDRC")) {
|
||||
pmp3->year = atoi(utf8_text);
|
||||
DPRINTF(ERR_DEBUG," Year: %d\n",pmp3->year);
|
||||
} else if(!strcmp(pid3frame->id,"TLEN")) {
|
||||
pmp3->song_length = atoi(utf8_text) / 1000;
|
||||
DPRINTF(ERR_DEBUG, " Length: %d\n", pmp3->song_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -807,6 +815,7 @@ int scan_freetags(MP3FILE *pmp3) {
|
||||
MAYBEFREE(pmp3->orchestra);
|
||||
MAYBEFREE(pmp3->conductor);
|
||||
MAYBEFREE(pmp3->grouping);
|
||||
MAYBEFREE(pmp3->description);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -821,10 +830,10 @@ int scan_get_fileinfo(char *file, MP3FILE *pmp3) {
|
||||
FILE *infile;
|
||||
off_t file_size;
|
||||
|
||||
if(strcasestr(".aac.m4a.m4p.mp4",pmp3->type))
|
||||
if(strcasestr("aacm4am4pmp4",pmp3->type))
|
||||
return scan_get_aacfileinfo(file,pmp3);
|
||||
|
||||
if(!strcasecmp(pmp3->type,".mp3"))
|
||||
if(!strcasecmp(pmp3->type,"mp3"))
|
||||
return scan_get_mp3fileinfo(file,pmp3);
|
||||
|
||||
/* a file we don't know anything about... ogg or aiff maybe */
|
||||
@ -878,11 +887,13 @@ int scan_get_aacfileinfo(char *file, MP3FILE *pmp3) {
|
||||
fseek(infile,16,SEEK_CUR);
|
||||
fread((void*)&temp_int,1,sizeof(int),infile);
|
||||
temp_int=ntohl(temp_int);
|
||||
/* DWB: use ms time instead of sec */
|
||||
if(temp_int > 720000)
|
||||
pmp3->song_length=temp_int / 90000; /* PlayFair? */
|
||||
pmp3->song_length=temp_int / 90; /* PlayFair? */
|
||||
else
|
||||
pmp3->song_length=temp_int/600;
|
||||
DPRINTF(ERR_DEBUG,"Song length: %d seconds\n",temp_int/600);
|
||||
pmp3->song_length=temp_int * 1000 / 600;
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Song length: %d seconds\n", pmp3->song_length / 1000);
|
||||
}
|
||||
}
|
||||
fclose(infile);
|
||||
@ -903,7 +914,6 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
|
||||
off_t fp_size=0;
|
||||
off_t file_size;
|
||||
unsigned char buffer[1024];
|
||||
int time_seconds=0;
|
||||
int index;
|
||||
int layer_index;
|
||||
int sample_index;
|
||||
@ -1019,11 +1029,17 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
|
||||
DPRINTF(ERR_DEBUG," Bit Rate: %d\n",bitrate);
|
||||
|
||||
/* guesstimate the file length */
|
||||
if(bitrate)
|
||||
time_seconds = ((int)(file_size * 8)) / (bitrate * 1024);
|
||||
|
||||
if(!pmp3->song_length) /* could have gotten it from the tag */
|
||||
pmp3->song_length=time_seconds;
|
||||
{
|
||||
/* DWB: use ms time instead of seconds, use doubles to
|
||||
avoid overflow */
|
||||
if(bitrate)
|
||||
{
|
||||
pmp3->song_length = (int) ((double) file_size * 8000. /
|
||||
(double) bitrate /
|
||||
1024.);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* FIXME: should really scan forward to next sync frame */
|
||||
fclose(infile);
|
||||
@ -1035,3 +1051,48 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
|
||||
fclose(infile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void make_composite_tags(MP3FILE *song)
|
||||
{
|
||||
int len;
|
||||
|
||||
len=0;
|
||||
|
||||
if(!song->artist && (song->orchestra || song->conductor)) {
|
||||
if(song->orchestra)
|
||||
len += strlen(song->orchestra);
|
||||
if(song->conductor)
|
||||
len += strlen(song->conductor);
|
||||
|
||||
len += 3;
|
||||
|
||||
song->artist=(char*)calloc(len, 1);
|
||||
if(song->artist) {
|
||||
if(song->orchestra)
|
||||
strcat(song->artist,song->orchestra);
|
||||
|
||||
if(song->orchestra && song->conductor)
|
||||
strcat(song->artist," - ");
|
||||
|
||||
if(song->conductor)
|
||||
strcat(song->artist,song->conductor);
|
||||
}
|
||||
}
|
||||
|
||||
if(!strcasecmp(song->type,"ogg")) {
|
||||
song->description = strdup("QuickTime movie file");
|
||||
} else {
|
||||
char fdescr[50];
|
||||
|
||||
sprintf(fdescr,"%s audio file",song->type);
|
||||
song->description = strdup(fdescr);
|
||||
}
|
||||
|
||||
if(!song->title)
|
||||
song->title = strdup(song->fname);
|
||||
|
||||
if(!strcmp(song->type, "ogg"))
|
||||
song->item_kind = 4;
|
||||
else
|
||||
song->item_kind = 2;
|
||||
}
|
||||
|
@ -55,8 +55,14 @@ typedef struct tag_mp3file {
|
||||
|
||||
int got_id3;
|
||||
unsigned int id;
|
||||
|
||||
/* generated fields */
|
||||
char* description; /* long file type */
|
||||
int item_kind; /* song or movie */
|
||||
} MP3FILE;
|
||||
|
||||
extern int scan_init(char *path);
|
||||
|
||||
extern void make_composite_tags(MP3FILE *song);
|
||||
|
||||
#endif /* _MP3_SCANNER_H_ */
|
||||
|
700
src/query.c
Normal file
700
src/query.c
Normal file
@ -0,0 +1,700 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define DEBUG
|
||||
|
||||
#include "err.h"
|
||||
#include "query.h"
|
||||
|
||||
static const query_field_t* find_field(const char* name,
|
||||
const query_field_t* fields);
|
||||
static int arith_query(query_node_t* query, void* target);
|
||||
static int string_query(query_node_t* query, void* target);
|
||||
|
||||
static query_node_t* match_specifier(const char* query,
|
||||
const char** cursor,
|
||||
const query_field_t* fields);
|
||||
static query_node_t* group_match(const char* query,
|
||||
const char** cursor,
|
||||
const query_field_t* fields);
|
||||
static query_node_t* single_match(const char* query,
|
||||
const char** cursor,
|
||||
const query_field_t* fields);
|
||||
static int get_field_name(const char** pcursor,
|
||||
const char* query,
|
||||
char* name,
|
||||
int len);
|
||||
static int get_opcode(const char** pcursor,
|
||||
const char* query,
|
||||
char* name,
|
||||
int len);
|
||||
static query_node_t* match_number(const query_field_t* field,
|
||||
char not, char opcode,
|
||||
const char** pcursor,
|
||||
const char* query);
|
||||
static query_node_t* match_string(const query_field_t* field,
|
||||
char not, char opcode,
|
||||
const char** pcursor,
|
||||
const char* query);
|
||||
char* query_unescape(const char* query);
|
||||
|
||||
query_node_t* query_build(const char* query, const query_field_t* fields)
|
||||
{
|
||||
query_node_t* left = 0;
|
||||
char* raw = query_unescape(query);
|
||||
const char* cursor = raw;
|
||||
query_node_t* right = 0;
|
||||
query_type_t join;
|
||||
|
||||
if(0 == (left = match_specifier(query, &cursor, fields)))
|
||||
goto error;
|
||||
|
||||
while(*cursor)
|
||||
{
|
||||
query_node_t* con;
|
||||
|
||||
switch(*cursor)
|
||||
{
|
||||
case '+':
|
||||
case ' ': join = qot_and; break;
|
||||
case ',': join = qot_or; break;
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Illegal character '%c' (0%o) at index %d: %s\n",
|
||||
*cursor, *cursor, cursor - raw, raw);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cursor++;
|
||||
|
||||
if(0 == (right = match_specifier(raw, &cursor, fields)))
|
||||
goto error;
|
||||
|
||||
con = (query_node_t*) calloc(1, sizeof(*con));
|
||||
con->type = join;
|
||||
con->left.node = left;
|
||||
con->right.node = right;
|
||||
|
||||
left = con;
|
||||
}
|
||||
|
||||
if(query != raw)
|
||||
free(raw);
|
||||
|
||||
return left;
|
||||
|
||||
error:
|
||||
if(left != 0)
|
||||
query_free(left);
|
||||
if(raw != query)
|
||||
free(raw);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static query_node_t* match_specifier(const char* query,
|
||||
const char** cursor,
|
||||
const query_field_t* fields)
|
||||
{
|
||||
switch(**cursor)
|
||||
{
|
||||
case '\'': return single_match(query, cursor, fields);
|
||||
case '(': return group_match(query, cursor, fields);
|
||||
}
|
||||
|
||||
DPRINTF(ERR_LOG, "Illegal character '%c' (0%o) at index %d: %s\n",
|
||||
**cursor, **cursor, *cursor - query, query);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static query_node_t* group_match(const char* query,
|
||||
const char** pcursor,
|
||||
const query_field_t* fields)
|
||||
{
|
||||
query_node_t* left = 0;
|
||||
query_node_t* right = 0;
|
||||
query_node_t* join = 0;
|
||||
query_type_t opcode;
|
||||
const char* cursor = *pcursor;
|
||||
|
||||
/* skip the opening ')' */
|
||||
++cursor;
|
||||
|
||||
if(0 == (left = single_match(query, &cursor, fields)))
|
||||
return NULL;
|
||||
|
||||
switch(*cursor)
|
||||
{
|
||||
case '+':
|
||||
case ' ':
|
||||
opcode = qot_and;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
opcode = qot_or;
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Illegal character '%c' (0%o) at index %d: %s\n",
|
||||
*cursor, *cursor, cursor - query, query);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(0 == (right = single_match(query, &cursor, fields)))
|
||||
goto error;
|
||||
|
||||
if(*cursor != ')')
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal character '%c' (0%o) at index %d: %s\n",
|
||||
*cursor, *cursor, cursor - query, query);
|
||||
goto error;
|
||||
}
|
||||
|
||||
*pcursor = cursor + 1;
|
||||
|
||||
join = (query_node_t*) calloc(1, sizeof(*join));
|
||||
join->type = opcode;
|
||||
join->left.node = left;
|
||||
join->right.node = right;
|
||||
|
||||
return join;
|
||||
|
||||
error:
|
||||
if(0 != left)
|
||||
query_free(left);
|
||||
if(0 != right)
|
||||
query_free(right);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static query_node_t* single_match(const char* query,
|
||||
const char** pcursor,
|
||||
const query_field_t* fields)
|
||||
{
|
||||
char fname[64];
|
||||
const query_field_t* field;
|
||||
char not = 0;
|
||||
char op = 0;
|
||||
query_node_t* node = 0;
|
||||
|
||||
/* skip opening ' */
|
||||
(*pcursor)++;
|
||||
|
||||
/* collect the field name */
|
||||
if(!get_field_name(pcursor, query, fname, sizeof(fname)))
|
||||
return NULL;
|
||||
|
||||
if(**pcursor == '!')
|
||||
{
|
||||
not = '!';
|
||||
++(*pcursor);
|
||||
}
|
||||
|
||||
if(strchr(":+-", **pcursor))
|
||||
{
|
||||
op = **pcursor;
|
||||
++(*pcursor);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal Operator: %c (0%o) at index %d: %s\n",
|
||||
**pcursor, **pcursor, *pcursor - query, query);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(0 == (field = find_field(fname, fields)))
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Unknown field: %s\n", fname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch(field->type)
|
||||
{
|
||||
case qft_i32:
|
||||
case qft_i64:
|
||||
node = match_number(field, not, op, pcursor, query);
|
||||
break;
|
||||
|
||||
case qft_string:
|
||||
node = match_string(field, not, op, pcursor, query);
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Invalid field type: %d\n", field->type);
|
||||
break;
|
||||
}
|
||||
|
||||
if(**pcursor != '\'')
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal Character: %c (0%o) index %d: %s\n",
|
||||
**pcursor, **pcursor, *pcursor - query, query);
|
||||
query_free(node);
|
||||
node = 0;
|
||||
}
|
||||
else
|
||||
++(*pcursor);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static int get_field_name(const char** pcursor,
|
||||
const char* query,
|
||||
char* name,
|
||||
int len)
|
||||
{
|
||||
const char* cursor = *pcursor;
|
||||
|
||||
if(!isalpha(*cursor))
|
||||
return 0;
|
||||
|
||||
while(isalpha(*cursor) || *cursor == '.')
|
||||
{
|
||||
if(--len <= 0)
|
||||
{
|
||||
DPRINTF(ERR_LOG, "token length exceeded at offset %d: %s\n",
|
||||
cursor - query, query);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*name++ = *cursor++;
|
||||
}
|
||||
|
||||
*pcursor = cursor;
|
||||
|
||||
*name = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static query_node_t* match_number(const query_field_t* field,
|
||||
char not, char opcode,
|
||||
const char** pcursor,
|
||||
const char* query)
|
||||
{
|
||||
query_node_t* node = (query_node_t*) calloc(1, sizeof(node));
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
case ':':
|
||||
node->type = not ? qot_ne : qot_eq;
|
||||
break;
|
||||
case '+':
|
||||
case ' ':
|
||||
node->type = not ? qot_le : qot_gt;
|
||||
break;
|
||||
case '-':
|
||||
node->type = not ? qot_ge : qot_lt;
|
||||
break;
|
||||
}
|
||||
|
||||
node->left.field = field;
|
||||
|
||||
switch(field->type)
|
||||
{
|
||||
case qft_i32:
|
||||
node->right.i32 = strtol(*pcursor, (char**) pcursor, 10);
|
||||
break;
|
||||
case qft_i64:
|
||||
node->right.i64 = strtoll(*pcursor, (char**) pcursor, 10);
|
||||
break;
|
||||
}
|
||||
|
||||
if(**pcursor != '\'')
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal char in number '%c' (0%o) at index %d: %s\n",
|
||||
**pcursor, **pcursor, *pcursor - query, query);
|
||||
free(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static query_node_t* match_string(const query_field_t* field,
|
||||
char not, char opcode,
|
||||
const char** pcursor,
|
||||
const char* query)
|
||||
{
|
||||
char match[64];
|
||||
char* dst = match;
|
||||
int left = sizeof(match);
|
||||
const char* cursor = *pcursor;
|
||||
query_type_t op = qot_is;
|
||||
query_node_t* node;
|
||||
|
||||
if(opcode != ':')
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal operation on string: %c at index %d: %s\n",
|
||||
opcode, cursor - query - 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(*cursor == '*')
|
||||
{
|
||||
op = qot_ends;
|
||||
cursor++;
|
||||
}
|
||||
|
||||
while(*cursor && *cursor != '\'')
|
||||
{
|
||||
if(--left == 0)
|
||||
{
|
||||
DPRINTF(ERR_LOG, "string too long at index %d: %s\n",
|
||||
cursor - query, query);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(*cursor == '\\')
|
||||
{
|
||||
switch(*++cursor)
|
||||
{
|
||||
case '*':
|
||||
case '\'':
|
||||
case '\\':
|
||||
*dst++ = *cursor++;
|
||||
break;
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Illegal escape: %c (0%o) at index %d: %s\n",
|
||||
*cursor, *cursor, cursor - query, query);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
*dst++ = *cursor++;
|
||||
}
|
||||
|
||||
if(dst[-1] == '*')
|
||||
{
|
||||
op = (op == qot_is) ? qot_begins : qot_contains;
|
||||
dst--;
|
||||
}
|
||||
|
||||
*dst = 0;
|
||||
|
||||
node = (query_node_t*) calloc(1, sizeof(*node));
|
||||
node->type = op;
|
||||
node->left.field = field;
|
||||
node->right.str = strdup(match);
|
||||
|
||||
*pcursor = cursor;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int query_test(query_node_t* query, void* target)
|
||||
{
|
||||
switch(query->type)
|
||||
{
|
||||
/* conjunction */
|
||||
case qot_and:
|
||||
return (query_test(query->left.node, target) &&
|
||||
query_test(query->right.node, target));
|
||||
|
||||
case qot_or:
|
||||
return (query_test(query->left.node, target) ||
|
||||
query_test(query->right.node, target));
|
||||
|
||||
/* negation */
|
||||
case qot_not:
|
||||
return !query_test(query->left.node, target);
|
||||
|
||||
/* arithmetic */
|
||||
case qot_eq:
|
||||
case qot_ne:
|
||||
case qot_le:
|
||||
case qot_lt:
|
||||
case qot_ge:
|
||||
case qot_gt:
|
||||
return arith_query(query, target);
|
||||
|
||||
/* string */
|
||||
case qot_is:
|
||||
case qot_begins:
|
||||
case qot_ends:
|
||||
case qot_contains:
|
||||
return string_query(query, target);
|
||||
break;
|
||||
|
||||
/* constants */
|
||||
case qot_const:
|
||||
return query->left.constant;
|
||||
}
|
||||
}
|
||||
|
||||
void query_free(query_node_t* query)
|
||||
{
|
||||
if(0 != query)
|
||||
{
|
||||
switch(query->type)
|
||||
{
|
||||
/* conjunction */
|
||||
case qot_and:
|
||||
case qot_or:
|
||||
query_free(query->left.node);
|
||||
query_free(query->right.node);
|
||||
break;
|
||||
|
||||
/* negation */
|
||||
case qot_not:
|
||||
query_free(query->left.node);
|
||||
break;
|
||||
|
||||
/* arithmetic */
|
||||
case qot_eq:
|
||||
case qot_ne:
|
||||
case qot_le:
|
||||
case qot_lt:
|
||||
case qot_ge:
|
||||
case qot_gt:
|
||||
break;
|
||||
|
||||
/* string */
|
||||
case qot_is:
|
||||
case qot_begins:
|
||||
case qot_ends:
|
||||
case qot_contains:
|
||||
free(query->right.str);
|
||||
break;
|
||||
|
||||
/* constants */
|
||||
case qot_const:
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Illegal query type: %d\n", query->type);
|
||||
break;
|
||||
}
|
||||
|
||||
free(query);
|
||||
}
|
||||
}
|
||||
|
||||
static const query_field_t* find_field(const char* name, const query_field_t* fields)
|
||||
{
|
||||
while(fields->name && strcasecmp(fields->name, name))
|
||||
fields++;
|
||||
|
||||
if(fields->name == 0)
|
||||
{
|
||||
DPRINTF(ERR_LOG, "Illegal query field: %s\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
static int arith_query(query_node_t* query, void* target)
|
||||
{
|
||||
const query_field_t* field = query->left.field;
|
||||
|
||||
switch(field->type)
|
||||
{
|
||||
case qft_i32:
|
||||
{
|
||||
int tv = * (int*) ((size_t) target + field->offset);
|
||||
|
||||
tv -= query->right.i32;
|
||||
|
||||
switch(query->type)
|
||||
{
|
||||
case qot_eq: return tv == 0;
|
||||
case qot_ne: return tv != 0;
|
||||
case qot_le: return tv <= 0;
|
||||
case qot_lt: return tv < 0;
|
||||
case qot_ge: return tv >= 0;
|
||||
case qot_gt: return tv > 0;
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "illegal query type: %d\n", query->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case qft_i64:
|
||||
{
|
||||
long long tv = * (long long*) ((size_t) target + field->offset);
|
||||
|
||||
tv -= query->right.i32;
|
||||
|
||||
switch(query->type)
|
||||
{
|
||||
case qot_eq: return tv == 0;
|
||||
case qot_ne: return tv != 0;
|
||||
case qot_le: return tv <= 0;
|
||||
case qot_lt: return tv < 0;
|
||||
case qot_ge: return tv >= 0;
|
||||
case qot_gt: return tv > 0;
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "illegal query type: %d\n", query->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "illegal field type: %d\n", field->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int string_query(query_node_t* query, void* target)
|
||||
{
|
||||
const query_field_t* field = query->left.field;
|
||||
const char* ts;
|
||||
|
||||
if(field->type != qft_string)
|
||||
{
|
||||
DPRINTF(ERR_LOG, "illegal field type: %d\n", field->type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ts = * (const char**) ((size_t) target + field->offset);
|
||||
|
||||
if(0 == ts)
|
||||
return strlen(query->right.str) == 0;
|
||||
|
||||
switch(query->type)
|
||||
{
|
||||
case qot_is:
|
||||
return !strcasecmp(query->right.str, ts);
|
||||
|
||||
case qot_begins:
|
||||
return !strncasecmp(query->right.str, ts, strlen(query->right.str));
|
||||
|
||||
case qot_ends:
|
||||
{
|
||||
int start = strlen(ts) - strlen(query->right.str);
|
||||
|
||||
if(start < 0)
|
||||
return 0;
|
||||
|
||||
return !strcasecmp(query->right.str, ts + start);
|
||||
}
|
||||
|
||||
case qot_contains:
|
||||
return !strcasestr(ts, query->right.str);
|
||||
|
||||
default:
|
||||
DPRINTF(ERR_LOG, "Illegal query type: %d\n", query->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void query_dump(FILE* fp, query_node_t* query, int depth)
|
||||
{
|
||||
static const char* labels[] = {
|
||||
"NOP",
|
||||
"and",
|
||||
"or",
|
||||
"not",
|
||||
"==",
|
||||
"!=",
|
||||
"<=",
|
||||
"<",
|
||||
">=",
|
||||
">",
|
||||
"eq",
|
||||
"beginswith",
|
||||
"endwith",
|
||||
"contains",
|
||||
"constant"
|
||||
};
|
||||
|
||||
switch(query->type)
|
||||
{
|
||||
case qot_and:
|
||||
case qot_or:
|
||||
fprintf(fp, "%*s(%s\n", depth, "", labels[query->type]);
|
||||
query_dump(fp, query->left.node, depth + 4);
|
||||
query_dump(fp, query->right.node, depth + 4);
|
||||
fprintf(fp, "%*s)\n", depth, "");
|
||||
break;
|
||||
|
||||
case qot_not:
|
||||
fprintf(fp, "%*s(not\n", depth, "");
|
||||
query_dump(fp, query->left.node, depth + 4);
|
||||
fprintf(fp, "%*s)\n", depth, "");
|
||||
break;
|
||||
|
||||
/* arithmetic */
|
||||
case qot_eq:
|
||||
case qot_ne:
|
||||
case qot_le:
|
||||
case qot_lt:
|
||||
case qot_ge:
|
||||
case qot_gt:
|
||||
if(query->left.field->type == qft_i32)
|
||||
fprintf(fp, "%*s(%s %s %d)\n",
|
||||
depth, "", labels[query->type],
|
||||
query->left.field->name, query->right.i32);
|
||||
else
|
||||
fprintf(fp, "%*s(%s %s %q)\n",
|
||||
depth, "", labels[query->type],
|
||||
query->left.field->name, query->right.i64);
|
||||
break;
|
||||
|
||||
/* string */
|
||||
case qot_is:
|
||||
case qot_begins:
|
||||
case qot_ends:
|
||||
case qot_contains:
|
||||
fprintf(fp, "%*s(%s %s \"%s\")\n",
|
||||
depth, "", labels[query->type],
|
||||
query->left.field->name, query->right.str);
|
||||
break;
|
||||
|
||||
/* constants */
|
||||
case qot_const:
|
||||
fprintf(fp, "%*s(%s)\n", depth, "", query->left.constant ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
char* query_unescape(const char* src)
|
||||
{
|
||||
char* copy = malloc(strlen(src) + 1);
|
||||
char* dst = copy;
|
||||
|
||||
while(*src)
|
||||
{
|
||||
if(*src == '%')
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
if(*++src)
|
||||
{
|
||||
if(isdigit(*src))
|
||||
val = val * 16 + *src - '0';
|
||||
else
|
||||
val = val * 16 + tolower(*src) - 'a' + 10;
|
||||
}
|
||||
|
||||
if(*++src)
|
||||
{
|
||||
if(isdigit(*src))
|
||||
val = val * 16 + *src - '0';
|
||||
else
|
||||
val = val * 16 + tolower(*src) - 'a' + 10;
|
||||
}
|
||||
|
||||
src++;
|
||||
*dst++ = val;
|
||||
}
|
||||
else
|
||||
*dst++ = *src++;
|
||||
}
|
||||
|
||||
*dst++ = 0;
|
||||
|
||||
return copy;
|
||||
}
|
71
src/query.h
Normal file
71
src/query.h
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef __query__
|
||||
#define __query__
|
||||
|
||||
typedef enum
|
||||
{
|
||||
/* node opcodes */
|
||||
qot_empty,
|
||||
|
||||
/* conjunctions */
|
||||
qot_and,
|
||||
qot_or,
|
||||
|
||||
/* negation */
|
||||
qot_not,
|
||||
|
||||
/* arithmetic */
|
||||
qot_eq,
|
||||
qot_ne,
|
||||
qot_le,
|
||||
qot_lt,
|
||||
qot_ge,
|
||||
qot_gt,
|
||||
|
||||
/* string */
|
||||
qot_is,
|
||||
qot_begins,
|
||||
qot_ends,
|
||||
qot_contains,
|
||||
|
||||
/* constant opcode */
|
||||
qot_const,
|
||||
|
||||
/* field types */
|
||||
qft_i32,
|
||||
qft_i64,
|
||||
qft_string
|
||||
|
||||
} query_type_t;
|
||||
|
||||
typedef struct query_field_ query_field_t;
|
||||
struct query_field_
|
||||
{
|
||||
query_type_t type;
|
||||
const char* name;
|
||||
int offset;
|
||||
};
|
||||
|
||||
typedef struct query_node_ query_node_t;
|
||||
struct query_node_
|
||||
{
|
||||
query_type_t type;
|
||||
union {
|
||||
query_node_t* node;
|
||||
const query_field_t* field;
|
||||
int constant;
|
||||
} left;
|
||||
union {
|
||||
query_node_t* node;
|
||||
int i32;
|
||||
long long i64;
|
||||
char* str;
|
||||
} right;
|
||||
};
|
||||
|
||||
query_node_t* query_build(const char* query,
|
||||
const query_field_t* fields);
|
||||
int query_test(query_node_t* query, void* target);
|
||||
void query_free(query_node_t* query);
|
||||
void query_dump(FILE* fp, query_node_t* query, int depth);
|
||||
|
||||
#endif
|
@ -356,6 +356,10 @@ void *ws_mainthread(void *arg) {
|
||||
void ws_close(WS_CONNINFO *pwsc) {
|
||||
WS_PRIVATE *pwsp = (WS_PRIVATE *)pwsc->pwsp;
|
||||
|
||||
/* DWB: update the status so it doesn't fill up with no longer
|
||||
relevant entries */
|
||||
config_set_status(pwsc, 0, NULL);
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Thread %d: Terminating\n",pwsc->threadno);
|
||||
DPRINTF(ERR_DEBUG,"Thread %d: Freeing request headers\n",pwsc->threadno);
|
||||
ws_freearglist(&pwsc->request_headers);
|
||||
@ -521,7 +525,7 @@ int ws_getheaders(WS_CONNINFO *pwsc) {
|
||||
DPRINTF(ERR_DEBUG,"Thread %d: Adding header *%s=%s*\n",
|
||||
pwsc->threadno,first,last);
|
||||
|
||||
if(ws_addarg(&pwsc->request_headers,first,last)) {
|
||||
if(ws_addarg(&pwsc->request_headers,first,"%s",last)) {
|
||||
DPRINTF(ERR_FATAL,"Thread %d: Out of memory\n",
|
||||
pwsc->threadno);
|
||||
pwsc->error=ENOMEM;
|
||||
@ -567,7 +571,7 @@ int ws_getgetvars(WS_CONNINFO *pwsc, char *string) {
|
||||
} else {
|
||||
DPRINTF(ERR_DEBUG,"Thread %d: Adding arg %s = %s\n",
|
||||
pwsc->threadno,first,middle);
|
||||
ws_addarg(&pwsc->request_vars,first,middle);
|
||||
ws_addarg(&pwsc->request_vars,first,"%s",middle);
|
||||
}
|
||||
|
||||
if(!last) {
|
||||
@ -613,7 +617,9 @@ void *ws_dispatcher(void *arg) {
|
||||
* and decide where to dispatch it
|
||||
*/
|
||||
|
||||
if((readlinetimed(pwsc->fd,buffer,sizeof(buffer),30.0)) < 1) {
|
||||
/* DWB: set timeout to 30 minutes as advertised in the
|
||||
server-info response. */
|
||||
if((readlinetimed(pwsc->fd,buffer,sizeof(buffer),1800.0)) < 1) {
|
||||
pwsc->error=errno;
|
||||
pwsc->close=1;
|
||||
DPRINTF(ERR_WARN,"Thread %d: could not read: %s\n",
|
||||
@ -623,6 +629,7 @@ void *ws_dispatcher(void *arg) {
|
||||
}
|
||||
|
||||
DPRINTF(ERR_DEBUG,"Thread %d: got request\n",pwsc->threadno);
|
||||
DPRINTF(ERR_DEBUG - 1, "Request: %s", buffer);
|
||||
|
||||
first=last=buffer;
|
||||
strsep(&last," ");
|
||||
@ -1057,10 +1064,13 @@ char *ws_urldecode(char *string) {
|
||||
|
||||
while(*src) {
|
||||
switch(*src) {
|
||||
#if 0
|
||||
/* DWB - space gets converted to %20, not +, this definitely breaks compatibility with iTunes */
|
||||
case '+':
|
||||
*dst++=' ';
|
||||
src++;
|
||||
break;
|
||||
#endif
|
||||
case '%':
|
||||
/* this is hideous */
|
||||
src++;
|
||||
|
Loading…
Reference in New Issue
Block a user