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:
David W. Berry 2004-06-14 19:01:06 +00:00
parent d63435d230
commit 9492a64846
16 changed files with 1420 additions and 207 deletions

View File

@ -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);; *) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);;
esac ],[rend_howl=false]) esac ],[rend_howl=false])
AC_ARG_ENABLE(howl,[ --enable-howl Use the howl mDNS library], AC_ARG_ENABLE(howl,[ --enable-howl Use the howl mDNS library],
[ case "${enableval}" in [ case "${enableval}" in
yes) rend_howl=true; LDFLAGS="${LDFLAGS} -lrendezvous -lcorby -lsalt"; 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;; no) rend_howl=false;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);; *) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);;
esac ],[rend_howl=false]) 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_HOWL, test x$rend_howl = xtrue)
AM_CONDITIONAL(COND_REND_POSIX, test x$rend_howl = xfalse) AM_CONDITIONAL(COND_REND_POSIX, test x$rend_howl = xfalse)

View File

@ -18,19 +18,19 @@ if COND_REND_OSX
ORENDSRC=rend-osx.c ORENDSRC=rend-osx.c
endif 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 \ 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 \ 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 \ 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 \ 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 \ rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \
redblack.c redblack.h dynamic-art.c dynamic-art.h \ 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 \ EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \
mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \ mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \
rend-howl.c rend-posix.c rend-osx.c strcasestr.c strsep.c db-memory.c \ 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 db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c

View File

@ -167,9 +167,12 @@ int config_read(char *file) {
config.runas=NULL; config.runas=NULL;
config.artfilename=NULL; config.artfilename=NULL;
config.logfile=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)) { while(fgets(buffer,MAX_LINE,fin)) {
if(*buffer != '#') { if(*buffer != '#') {
@ -195,6 +198,9 @@ int config_read(char *file) {
switch(pce->type) { switch(pce->type) {
case CONFIG_TYPE_STRING: 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); *((char **)(pce->var)) = (void*)strdup(value);
break; break;
case CONFIG_TYPE_INT: case CONFIG_TYPE_INT:

View File

@ -336,3 +336,20 @@ void daap_free(DAAP_BLOCK *root) {
} }
return; 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;
}

View File

@ -51,5 +51,8 @@ void daap_remove(DAAP_BLOCK* root);
// search a block's direct children for a block with a given tag // search a block's direct children for a block with a given tag
DAAP_BLOCK *daap_find(DAAP_BLOCK *parent, char* 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 #endif

View File

@ -40,6 +40,10 @@
#include "err.h" #include "err.h"
#include "daapd.h" #include "daapd.h"
#ifdef OPT_QUERY
#include "query.h"
#endif
typedef struct tag_daap_items { typedef struct tag_daap_items {
int type; int type;
char *tag; char *tag;
@ -134,6 +138,39 @@ DAAP_ITEMS taglist[] = {
{ 0x00, NULL, NULL } { 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 */ /* Forwards */
int daap_add_mdcl(DAAP_BLOCK *root, char *tag, char *name, short int number) { 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; break;
if(m->tag) if(m->tag)
bits |= (1 << m->bit); bits |= (((MetaField_t) 1) << m->bit);
else else
DPRINTF(ERR_WARN, "Unknown meta code: %*s\n", len, start); 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) 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; DAAP_BLOCK *root;
int g=1; int g=1;
DAAP_BLOCK *mlcl; DAAP_BLOCK *mlcl;
@ -373,7 +410,11 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
ENUMHANDLE henum; ENUMHANDLE henum;
MP3FILE *current; MP3FILE *current;
MetaField_t meta; 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 // if the meta tag is specified, encode it, if it's not specified
// we're given the latitude to select our own subset, for // we're given the latitude to select our own subset, for
// simplicity we just include everything. // simplicity we just include everything.
@ -382,6 +423,18 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
else else
meta = encodeMetaRequest(metaStr, gSongMetaDataMap); 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"); DPRINTF(ERR_DEBUG,"Preparing to send db items\n");
henum=db_enum_begin(); henum=db_enum_begin();
@ -394,22 +447,33 @@ DAAP_BLOCK *daap_response_songlist(char* metaStr) {
if(root) { if(root) {
g = (int)daap_add_int(root,"mstt",200); g = (int)daap_add_int(root,"mstt",200);
g = g && daap_add_char(root,"muty",0); 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,"mtco",0);
g = g && daap_add_int(root,"mrco",db_get_song_count()); g = g && daap_add_int(root,"mrco",0);
mlcl=daap_add_empty(root,"mlcl"); mlcl=daap_add_empty(root,"mlcl");
if(mlcl) { if(mlcl) {
while(g && (current=db_enum(&henum))) { while(g && (current=db_enum(&henum))) {
DPRINTF(ERR_DEBUG,"Got entry for %s\n",current->fname); #ifdef OPT_QUERY
// song entry generation extracted for usage with if(filter == 0 || query_test(filter, current))
// playlists as well #endif
g = 0 != daap_add_song_entry(mlcl, current, meta); {
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; } else g=0;
} }
db_enum_end(); db_enum_end(henum);
#ifdef OPT_QUERY
if(filter != 0)
query_free(filter);
#endif
if(!g) { if(!g) {
DPRINTF(ERR_DEBUG,"Error enumerating database\n"); 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"); DPRINTF(ERR_DEBUG,"Successfully enumerated database\n");
daap_set_int(root, "mtco", songs);
daap_set_int(root, "mrco", songs);
return root; 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"); mlit=daap_add_empty(mlcl,"mlit");
if(mlit) { if(mlit) {
if(wantsMeta(meta, metaItemKind)) 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)) if(wantsMeta(meta, metaSongDataKind))
g = g && daap_add_char(mlit,"asdk",0); /* local file */ g = g && daap_add_char(mlit,"asdk",0); /* local file */
if(song->album && (wantsMeta(meta, metaSongAlbum))) if(song->album && (wantsMeta(meta, metaSongAlbum)))
g = g && daap_add_string(mlit,"asal",song->album); g = g && daap_add_string(mlit,"asal",song->album);
if(wantsMeta(meta, metaSongArtist)) if(song->artist && wantsMeta(meta, metaSongArtist))
{ g = g && daap_add_string(mlit,"asar",song->artist);
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) {
g = g && daap_add_string(mlit,"asar",song->artist);
}
}
// g = g && daap_add_short(mlit,"asbt",0); /* bpm */ // g = g && daap_add_short(mlit,"asbt",0); /* bpm */
if(song->bitrate && (wantsMeta(meta, metaSongBitRate))) 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 */ /* these quite go hand in hand */
if(wantsMeta(meta, metaSongFormat)) 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)) if(wantsMeta(meta, metaSongDescription))
{ g = g && daap_add_string(mlit, "asdt",song->description);
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 */
}
if(wantsMeta(meta, metaItemName)) if(wantsMeta(meta, metaItemName))
{ g = g && daap_add_string(mlit,"minm",song->title); /* descr */
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) // mper (long)
// g = g && daap_add_char(mlit,"asdb",0); /* disabled */ // 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)) if(wantsMeta(meta, metaSongStartTime))
g = g && daap_add_int(mlit,"asst",0); /* song start time? */ g = g && daap_add_int(mlit,"asst",0); /* song start time? */
if(wantsMeta(meta, metaSongStopTime)) 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))) 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))) if(song->total_tracks && (wantsMeta(meta, metaSongTrackCount)))
g = g && daap_add_short(mlit,"astc",song->total_tracks); /* track count */ 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; 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 */ 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 */ /* logon is always required, even if a password isn't */
g = g && daap_add_char(root,"mslr",1); g = g && daap_add_char(root,"mslr",1);
#endif
/* authentication method is 0 for nothing, 1 for name and /* authentication method is 0 for nothing, 1 for name and
password, 2 for password only */ password, 2 for password only */
@ -786,12 +808,17 @@ DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
supported */ supported */
g = g && daap_add_char(root,"msex",0); /* extensions */ g = g && daap_add_char(root,"msex",0); /* extensions */
g = g && daap_add_char(root,"msix",0); /* indexing? */ 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 #if 0
g = g && daap_add_char(root,"msal",0); /* autologout */ g = g && daap_add_char(root,"msal",0); /* autologout */
g = g && daap_add_char(root,"msup",0); /* update */ 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,"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 */ g = g && daap_add_char(root,"msrs",0); /* resolve? req. persist id */
#endif #endif
g = g && daap_add_int(root,"msdc",1); /* database count */ 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 * 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 *root;
DAAP_BLOCK *mlcl; DAAP_BLOCK *mlcl;
DAAP_BLOCK *mlit; DAAP_BLOCK *mlit;
@ -820,6 +847,10 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
int itemid; int itemid;
int g=1; int g=1;
unsigned long long meta; 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 // if no meta information is specifically requested, return only
// the base play list information. iTunes only requests the base // 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 else
meta = encodeMetaRequest(metaStr, gSongMetaDataMap); 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); DPRINTF(ERR_DEBUG,"Preparing to send playlist items for pl #%d\n",playlist);
if(playlist == 1) { if(playlist == 1) {
@ -857,22 +900,34 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
if(mlcl) { if(mlcl) {
if(playlist == 1) { if(playlist == 1) {
while((current=db_enum(&henum))) { while((current=db_enum(&henum))) {
mlit=daap_add_song_entry(mlcl, current, meta); #ifdef OPT_QUERY
if(0 != mlit) { if(0 == filter || query_test(filter, current))
if(wantsMeta(meta, metaContainerItemId)) #endif
g = g && daap_add_int(mlit,"mcti",playlist); {
} else g=0; 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 */ } else { /* other playlist */
while((itemid=db_playlist_items_enum(&henum)) != -1) { while((itemid=db_playlist_items_enum(&henum)) != -1) {
current = db_find(itemid); current = db_find(itemid);
if(0 != current) { if(0 != current) {
DPRINTF(ERR_DEBUG,"Adding itemid %d\n",itemid); #ifdef OPT_QUERY
mlit=daap_add_song_entry(mlcl,current,meta); if(0 == filter || query_test(filter, current))
if(0 != mlit) { {
if(wantsMeta(meta, metaContainerItemId)) #endif
g = g && daap_add_int(mlit,"mcti",playlist); songs++;
} else g = 0; 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; } else g = 0;
} }
} }
@ -880,15 +935,23 @@ DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr) {
} }
if(playlist == 1) if(playlist == 1)
db_enum_end(); db_enum_end(henum);
else else
db_playlist_items_enum_end(); db_playlist_items_enum_end(henum);
#ifdef OPT_QUERY
if(0 != filter)
query_free(filter);
#endif
if(!g) { if(!g) {
daap_free(root); daap_free(root);
return NULL; return NULL;
} }
daap_set_int(root, "mtco", songs);
daap_set_int(root, "mrco", songs);
return root; 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 // update the returned record count entry, it's required, so
// should have already be created // should have already be created
assert(0 != (item = daap_find(block, "mrco"))); daap_set_int(block, "mrco", count);
item->svalue[0] = count >> 24;
item->svalue[1] = count >> 16;
item->svalue[2] = count >> 8;
item->svalue[3] = count;
DPRINTF(ERR_INFO, "index:%s first:%d count:%d\n", index, first, 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 ; ) for(back = &list->children ; *back && first ; )
if(!strncmp((**back).tag, "mlit", 4)) 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); daap_remove(*back);
first--; first--;
} }
@ -1012,7 +1070,7 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
for( ; *back && count ; back = &(**back).next) for( ; *back && count ; back = &(**back).next)
if(!strncmp((**back).tag, "mlit", 4)) if(!strncmp((**back).tag, "mlit", 4))
{ {
DPRINTF(ERR_INFO, "count:%d keeping\n", count); DPRINTF(ERR_DEBUG, "count:%d keeping\n", count);
count--; count--;
} }
@ -1021,10 +1079,182 @@ void daap_handle_index(DAAP_BLOCK* block, const char* index)
{ {
if(!strncmp((**back).tag, "mlit", 4)) if(!strncmp((**back).tag, "mlit", 4))
{ {
DPRINTF(ERR_INFO, "removing spare\n"); DPRINTF(ERR_DEBUG, "removing spare\n");
daap_remove(*back); daap_remove(*back);
} }
else else
back = &(**back).next; 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

View File

@ -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_content_codes(void);
DAAP_BLOCK *daap_response_login(char *hostname); DAAP_BLOCK *daap_response_login(char *hostname);
DAAP_BLOCK *daap_response_update(int fd, int clientver); 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_playlists(char *name);
DAAP_BLOCK *daap_response_dbinfo(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); 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); 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_ */ #endif /* _DAAP_H_ */

View File

@ -40,6 +40,7 @@
#define RB_FREE #define RB_FREE
#include "redblack.h" #include "redblack.h"
#include "db-memory.h"
/* /*
* Defines * Defines
@ -61,10 +62,11 @@
/* /*
* Typedefs * Typedefs
*/ */
typedef struct tag_mp3record { typedef struct tag_mp3record MP3RECORD;
struct tag_mp3record {
MP3FILE mp3file; MP3FILE mp3file;
datum key; MP3RECORD* next;
} MP3RECORD; };
typedef struct tag_playlistentry { typedef struct tag_playlistentry {
unsigned int id; unsigned int id;
@ -116,6 +118,10 @@ typedef struct tag_mp3packed {
char data[1]; char data[1];
} MP3PACKED; } MP3PACKED;
typedef struct {
MP3RECORD* root;
MP3RECORD* next;
} MP3HELPER;;
/* /*
* Globals * Globals
@ -127,7 +133,6 @@ int db_playlist_count=0;
DB_PLAYLIST db_playlists; DB_PLAYLIST db_playlists;
pthread_rwlock_t db_rwlock; /* OSX doesn't have PTHREAD_RWLOCK_INITIALIZER */ pthread_rwlock_t db_rwlock; /* OSX doesn't have PTHREAD_RWLOCK_INITIALIZER */
pthread_once_t db_initlock=PTHREAD_ONCE_INIT; pthread_once_t db_initlock=PTHREAD_ONCE_INIT;
MP3RECORD db_enum_helper;
GDBM_FILE db_songs; GDBM_FILE db_songs;
struct rbtree *db_removed; struct rbtree *db_removed;
@ -138,7 +143,7 @@ struct rbtree *db_removed;
int db_start_initial_update(void); int db_start_initial_update(void);
int db_end_initial_update(void); int db_end_initial_update(void);
int db_is_empty(void); int db_is_empty(void);
int db_open(char *parameters); int db_open(char *parameters, int reload);
int db_init(); int db_init();
int db_deinit(void); int db_deinit(void);
int db_version(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); int db_unpackrecord(datum *pdatum, MP3FILE *pmp3);
datum *db_packrecord(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_song_count(void);
int db_get_playlist_count(void); int db_get_playlist_count(void);
int db_get_playlist_is_smart(int playlistid); 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 * 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]; char db_path[PATH_MAX + 1];
if(pthread_once(&db_initlock,db_init_once)) if(pthread_once(&db_initlock,db_init_once))
return -1; return -1;
snprintf(db_path,sizeof(db_path),"%s/%s",parameters,"songs.gdb"); 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); 0600,NULL);
if(!db_songs) { if(!db_songs) {
DPRINTF(ERR_FATAL,"Could not open songs database (%s)\n", 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]); pmp3->grouping=strdup(&ppacked->data[offset]);
offset += ppacked->grouping_len; offset += ppacked->grouping_len;
make_composite_tags(pmp3);
return 0; return 0;
} }
@ -771,19 +768,45 @@ void db_freefile(MP3FILE *pmp3) {
MAYBEFREE(pmp3->orchestra); MAYBEFREE(pmp3->orchestra);
MAYBEFREE(pmp3->conductor); MAYBEFREE(pmp3->conductor);
MAYBEFREE(pmp3->grouping); MAYBEFREE(pmp3->grouping);
MAYBEFREE(pmp3->description);
} }
/* /*
* db_enum_begin * db_enum_begin
* *
* Begin to walk through an enum of * Begin to walk through an enum of the database. In order to retain
* the database. * 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 * this should be done quickly, as we'll be holding
* a reader lock on the db * 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; int err;
MP3HELPER* helper;
datum key, next;
if((err=pthread_rwlock_rdlock(&db_rwlock))) { if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(ERR_FATAL,"Cannot lock rwlock\n"); DPRINTF(ERR_FATAL,"Cannot lock rwlock\n");
@ -791,13 +814,39 @@ MP3RECORD *db_enum_begin(void) {
return NULL; return NULL;
} }
memset((void*)&db_enum_helper,0x00,sizeof(db_enum_helper)); helper = calloc(1, sizeof(MP3HELPER));
db_enum_helper.key=gdbm_firstkey(db_songs);
MEMNOTIFY(db_enum_helper.key.dptr); for(key = gdbm_firstkey(db_songs) ; key.dptr ; key = next)
if(!db_enum_helper.key.dptr) {
return NULL; MP3RECORD* entry = calloc(1, sizeof(MP3RECORD));
MP3RECORD** root;
datum data;
return &db_enum_helper; 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 * Start enumerating playlists
*/ */
DB_PLAYLIST *db_playlist_enum_begin(void) { ENUMHANDLE db_playlist_enum_begin(void) {
int err; int err;
DB_PLAYLIST *current; DB_PLAYLIST *current;
@ -828,7 +877,7 @@ DB_PLAYLIST *db_playlist_enum_begin(void) {
* *
* Start enumerating playlist items * Start enumerating playlist items
*/ */
DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid) { ENUMHANDLE db_playlist_items_enum_begin(int playlistid) {
DB_PLAYLIST *current; DB_PLAYLIST *current;
int err; int err;
@ -854,40 +903,16 @@ DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid) {
* *
* Walk to the next entry * Walk to the next entry
*/ */
MP3FILE *db_enum(MP3RECORD **current) { MP3FILE *db_enum(ENUMHANDLE *current) {
datum nextkey; MP3HELPER* helper = *(MP3HELPER**) current;
datum data; MP3RECORD* record = helper->next;
if(db_enum_helper.key.dptr) { if(helper->next == 0)
db_freefile(&db_enum_helper.mp3file); return 0;
/* Got the key, let's fetch it */ helper->next = helper->next->next;
data=gdbm_fetch(db_songs,db_enum_helper.key);
MEMNOTIFY(data.dptr);
if(!data.dptr) {
DPRINTF(ERR_FATAL,"Inconsistant database.\n");
}
if(db_unpackrecord(&data,&db_enum_helper.mp3file)) { return &record->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;
} }
/* /*
@ -895,7 +920,8 @@ MP3FILE *db_enum(MP3RECORD **current) {
* *
* walk to the next entry * 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; int retval;
DB_PLAYLIST *p; DB_PLAYLIST *p;
@ -917,7 +943,8 @@ int db_playlist_enum(DB_PLAYLIST **current) {
* *
* walk to the next entry * 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; int retval;
if(*current) { if(*current) {
@ -932,14 +959,25 @@ int db_playlist_items_enum(DB_PLAYLISTENTRY **current) {
/* /*
* db_enum_end * 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) { int db_enum_end(ENUMHANDLE handle) {
db_freefile(&db_enum_helper.mp3file); MP3HELPER* helper = (MP3HELPER*) handle;
if(db_enum_helper.key.dptr) MP3RECORD* record;
free(db_enum_helper.key.dptr);
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 * quit walking the database
*/ */
int db_playlist_enum_end(void) { int db_playlist_enum_end(ENUMHANDLE handle) {
return pthread_rwlock_unlock(&db_rwlock); return pthread_rwlock_unlock(&db_rwlock);
} }
@ -956,7 +994,7 @@ int db_playlist_enum_end(void) {
* *
* Quit walking the database * 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); return pthread_rwlock_unlock(&db_rwlock);
} }

View File

@ -389,6 +389,8 @@ int db_add(MP3FILE *mp3file) {
return -1; return -1;
} }
make_composite_tags(&pnew->mp3file);
pnew->next=db_root.next; pnew->next=db_root.next;
db_root.next=pnew; db_root.next=pnew;
@ -421,6 +423,7 @@ void db_freerecord(MP3RECORD *mp3record) {
MAYBEFREE(mp3record->mp3file.orchestra); MAYBEFREE(mp3record->mp3file.orchestra);
MAYBEFREE(mp3record->mp3file.conductor); MAYBEFREE(mp3record->mp3file.conductor);
MAYBEFREE(mp3record->mp3file.grouping); MAYBEFREE(mp3record->mp3file.grouping);
MAYBEFREE(mp3record->mp3file.description);
free(mp3record); free(mp3record);
} }

View File

@ -29,7 +29,7 @@ typedef void* ENUMHANDLE;
extern int db_start_initial_update(void); extern int db_start_initial_update(void);
extern int db_end_initial_update(void); extern int db_end_initial_update(void);
extern int db_is_empty(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_init(void);
extern int db_deinit(void); extern int db_deinit(void);
extern int db_version(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 int db_add_playlist_song(unsigned int playlistid, unsigned int itemid);
extern ENUMHANDLE db_enum_begin(void); extern ENUMHANDLE db_enum_begin(void);
extern MP3FILE *db_enum(ENUMHANDLE *current); extern MP3FILE *db_enum(ENUMHANDLE *handle);
extern int db_enum_end(void); extern int db_enum_end(ENUMHANDLE handle);
extern MP3FILE *db_find(int id); extern MP3FILE *db_find(int id);
extern int db_get_song_count(void); 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 ENUMHANDLE db_playlist_enum_begin(void);
extern int db_playlist_enum(ENUMHANDLE *current); 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 ENUMHANDLE db_playlist_items_enum_begin(int playlistid);
extern int db_playlist_items_enum(ENUMHANDLE *current); 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); extern char *db_get_playlist_name(int playlistid);

View File

@ -51,7 +51,13 @@
#include "playlist.h" #include "playlist.h"
#include "dynamic-art.h" #include "dynamic-art.h"
#ifndef DEFAULT_CONFIGFILE
#define DEFAULT_CONFIGFILE "/etc/mt-daapd.conf" #define DEFAULT_CONFIGFILE "/etc/mt-daapd.conf"
#endif
#ifndef PIDFILE
#define PIDFILE "/var/run/mt-daapd.pid"
#endif
#ifndef SIGCLD #ifndef SIGCLD
# define SIGCLD SIGCHLD # define SIGCLD SIGCHLD
@ -67,6 +73,7 @@ CONFIG config;
*/ */
RETSIGTYPE sig_child(int signal); RETSIGTYPE sig_child(int signal);
int daemon_start(int reap_children); int daemon_start(int reap_children);
void write_pid_file(void);
/* /*
* daap_auth * daap_auth
@ -167,6 +174,7 @@ void daap_handler(WS_CONNINFO *pwsc) {
* /databases/id/containers, which returns a container * /databases/id/containers, which returns a container
* /databases/id/containers/id/items, which returns playlist elements * /databases/id/containers/id/items, which returns playlist elements
* /databases/id/items/id.mp3, to spool an mp3 * /databases/id/items/id.mp3, to spool an mp3
* /databases/id/browse/category
*/ */
uri = strdup(pwsc->uri); uri = strdup(pwsc->uri);
@ -198,7 +206,9 @@ void daap_handler(WS_CONNINFO *pwsc) {
/* songlist */ /* songlist */
free(uri); free(uri);
// pass the meta field request for processing // 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"); config_set_status(pwsc,session_id,"Sending songlist");
} else if (strncasecmp(last,"containers/",11)==0) { } else if (strncasecmp(last,"containers/",11)==0) {
/* playlist elements */ /* playlist elements */
@ -213,7 +223,8 @@ void daap_handler(WS_CONNINFO *pwsc) {
playlist_index=atoi(first); playlist_index=atoi(first);
// pass the meta list info for processing // pass the meta list info for processing
root=daap_response_playlist_items(playlist_index, root=daap_response_playlist_items(playlist_index,
ws_getvar(pwsc,"meta")); ws_getvar(pwsc,"meta"),
ws_getvar(pwsc,"query"));
} }
free(uri); free(uri);
config_set_status(pwsc,session_id,"Sending playlist info"); config_set_status(pwsc,session_id,"Sending playlist info");
@ -222,6 +233,14 @@ void daap_handler(WS_CONNINFO *pwsc) {
free(uri); free(uri);
root=daap_response_playlists(config.servername); root=daap_response_playlists(config.servername);
config_set_status(pwsc,session_id,"Sending playlist info"); 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 // content type (dmap tagged) should only be used on
// dmap protocol requests, not the actually song data // dmap protocol requests, not the actually song data
if(pmp3->type) 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,"Content-Length","%ld",(long)file_len);
ws_addresponseheader(pwsc,"Connection","Close"); ws_addresponseheader(pwsc,"Connection","Close");
@ -539,13 +558,14 @@ int main(int argc, char *argv[]) {
WSHANDLE server; WSHANDLE server;
int parseonly=0; int parseonly=0;
int foreground=0; int foreground=0;
int reload=0;
int start_time; int start_time;
int end_time; int end_time;
config.use_mdns=1; config.use_mdns=1;
err_debuglevel=1; err_debuglevel=1;
while((option=getopt(argc,argv,"d:c:mpf")) != -1) { while((option=getopt(argc,argv,"d:c:mpfr")) != -1) {
switch(option) { switch(option) {
case 'd': case 'd':
err_debuglevel=atoi(optarg); err_debuglevel=atoi(optarg);
@ -566,6 +586,10 @@ int main(int argc, char *argv[]) {
parseonly=1; parseonly=1;
break; break;
case 'r':
reload=1;
break;
default: default:
usage(argv[0]); usage(argv[0]);
exit(EXIT_FAILURE); 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)); 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)); 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"); DPRINTF(ERR_LOG,"Starting mp3 scan\n");
if(scan_init(config.mp3dir)) { if(scan_init(config.mp3dir)) {
DPRINTF(ERR_FATAL,"Error scanning MP3 files: %s\n",strerror(errno)); DPRINTF(ERR_FATAL,"Error scanning MP3 files: %s\n",strerror(errno));
@ -724,3 +756,22 @@ int main(int argc, char *argv[]) {
return EXIT_SUCCESS; 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);
}

View File

@ -261,6 +261,8 @@ int scan_freetags(MP3FILE *pmp3);
void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb); 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 scan_music_file(char *path, struct dirent *pde, struct stat *psb);
void make_composite_tags(MP3FILE *pmp3);
/* /*
* scan_init * scan_init
* *
@ -452,14 +454,17 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) {
mp3file.path=mp3_path; mp3file.path=mp3_path;
mp3file.fname=pde->d_name; mp3file.fname=pde->d_name;
if(strlen(pde->d_name) > 4) 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; mp3file.id=psb->st_ino;
/* Do the tag lookup here */ /* Do the tag lookup here */
if(!scan_gettags(mp3file.path,&mp3file) && if(!scan_gettags(mp3file.path,&mp3file) &&
!scan_get_fileinfo(mp3file.path,&mp3file)) { !scan_get_fileinfo(mp3file.path,&mp3file)) {
make_composite_tags(&mp3file);
db_add(&mp3file); db_add(&mp3file);
pl_eval(&mp3file); /* FIXME: move to db_add? */ pl_eval(&mp3file); /* FIXME: move to db_add? */
} else { } else {
@ -630,11 +635,11 @@ int scan_gettags(char *file, MP3FILE *pmp3) {
* in turn, just in case the extensions are wrong/lying * 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); return scan_get_aactags(file,pmp3);
/* should handle mp3 in the same way */ /* should handle mp3 in the same way */
if(!strcasecmp(pmp3->type,".mp3")) if(!strcasecmp(pmp3->type,"mp3"))
return scan_get_mp3tags(file,pmp3); return scan_get_mp3tags(file,pmp3);
/* maybe this is an extension that we've manually /* maybe this is an extension that we've manually
@ -661,7 +666,7 @@ int scan_get_mp3tags(char *file, MP3FILE *pmp3) {
char *tmp; char *tmp;
int got_numeric_genre; 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; return 0;
pid3file=id3_file_open(file,ID3_FILE_MODE_READONLY); 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")) { } else if(!strcmp(pid3frame->id,"TDRC")) {
pmp3->year = atoi(utf8_text); pmp3->year = atoi(utf8_text);
DPRINTF(ERR_DEBUG," Year: %d\n",pmp3->year); 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->orchestra);
MAYBEFREE(pmp3->conductor); MAYBEFREE(pmp3->conductor);
MAYBEFREE(pmp3->grouping); MAYBEFREE(pmp3->grouping);
MAYBEFREE(pmp3->description);
return 0; return 0;
} }
@ -821,10 +830,10 @@ int scan_get_fileinfo(char *file, MP3FILE *pmp3) {
FILE *infile; FILE *infile;
off_t file_size; off_t file_size;
if(strcasestr(".aac.m4a.m4p.mp4",pmp3->type)) if(strcasestr("aacm4am4pmp4",pmp3->type))
return scan_get_aacfileinfo(file,pmp3); return scan_get_aacfileinfo(file,pmp3);
if(!strcasecmp(pmp3->type,".mp3")) if(!strcasecmp(pmp3->type,"mp3"))
return scan_get_mp3fileinfo(file,pmp3); return scan_get_mp3fileinfo(file,pmp3);
/* a file we don't know anything about... ogg or aiff maybe */ /* 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); fseek(infile,16,SEEK_CUR);
fread((void*)&temp_int,1,sizeof(int),infile); fread((void*)&temp_int,1,sizeof(int),infile);
temp_int=ntohl(temp_int); temp_int=ntohl(temp_int);
/* DWB: use ms time instead of sec */
if(temp_int > 720000) if(temp_int > 720000)
pmp3->song_length=temp_int / 90000; /* PlayFair? */ pmp3->song_length=temp_int / 90; /* PlayFair? */
else else
pmp3->song_length=temp_int/600; pmp3->song_length=temp_int * 1000 / 600;
DPRINTF(ERR_DEBUG,"Song length: %d seconds\n",temp_int/600);
DPRINTF(ERR_DEBUG,"Song length: %d seconds\n", pmp3->song_length / 1000);
} }
} }
fclose(infile); fclose(infile);
@ -903,7 +914,6 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
off_t fp_size=0; off_t fp_size=0;
off_t file_size; off_t file_size;
unsigned char buffer[1024]; unsigned char buffer[1024];
int time_seconds=0;
int index; int index;
int layer_index; int layer_index;
int sample_index; int sample_index;
@ -1019,11 +1029,17 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
DPRINTF(ERR_DEBUG," Bit Rate: %d\n",bitrate); DPRINTF(ERR_DEBUG," Bit Rate: %d\n",bitrate);
/* guesstimate the file length */ /* 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 */ 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 { } else {
/* FIXME: should really scan forward to next sync frame */ /* FIXME: should really scan forward to next sync frame */
fclose(infile); fclose(infile);
@ -1035,3 +1051,48 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
fclose(infile); fclose(infile);
return 0; 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;
}

View File

@ -55,8 +55,14 @@ typedef struct tag_mp3file {
int got_id3; int got_id3;
unsigned int id; unsigned int id;
/* generated fields */
char* description; /* long file type */
int item_kind; /* song or movie */
} MP3FILE; } MP3FILE;
extern int scan_init(char *path); extern int scan_init(char *path);
extern void make_composite_tags(MP3FILE *song);
#endif /* _MP3_SCANNER_H_ */ #endif /* _MP3_SCANNER_H_ */

700
src/query.c Normal file
View 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
View 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

View File

@ -356,6 +356,10 @@ void *ws_mainthread(void *arg) {
void ws_close(WS_CONNINFO *pwsc) { void ws_close(WS_CONNINFO *pwsc) {
WS_PRIVATE *pwsp = (WS_PRIVATE *)pwsc->pwsp; 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: Terminating\n",pwsc->threadno);
DPRINTF(ERR_DEBUG,"Thread %d: Freeing request headers\n",pwsc->threadno); DPRINTF(ERR_DEBUG,"Thread %d: Freeing request headers\n",pwsc->threadno);
ws_freearglist(&pwsc->request_headers); 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", DPRINTF(ERR_DEBUG,"Thread %d: Adding header *%s=%s*\n",
pwsc->threadno,first,last); 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", DPRINTF(ERR_FATAL,"Thread %d: Out of memory\n",
pwsc->threadno); pwsc->threadno);
pwsc->error=ENOMEM; pwsc->error=ENOMEM;
@ -567,7 +571,7 @@ int ws_getgetvars(WS_CONNINFO *pwsc, char *string) {
} else { } else {
DPRINTF(ERR_DEBUG,"Thread %d: Adding arg %s = %s\n", DPRINTF(ERR_DEBUG,"Thread %d: Adding arg %s = %s\n",
pwsc->threadno,first,middle); pwsc->threadno,first,middle);
ws_addarg(&pwsc->request_vars,first,middle); ws_addarg(&pwsc->request_vars,first,"%s",middle);
} }
if(!last) { if(!last) {
@ -613,7 +617,9 @@ void *ws_dispatcher(void *arg) {
* and decide where to dispatch it * 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->error=errno;
pwsc->close=1; pwsc->close=1;
DPRINTF(ERR_WARN,"Thread %d: could not read: %s\n", 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,"Thread %d: got request\n",pwsc->threadno);
DPRINTF(ERR_DEBUG - 1, "Request: %s", buffer);
first=last=buffer; first=last=buffer;
strsep(&last," "); strsep(&last," ");
@ -1057,10 +1064,13 @@ char *ws_urldecode(char *string) {
while(*src) { while(*src) {
switch(*src) { switch(*src) {
#if 0
/* DWB - space gets converted to %20, not +, this definitely breaks compatibility with iTunes */
case '+': case '+':
*dst++=' '; *dst++=' ';
src++; src++;
break; break;
#endif
case '%': case '%':
/* this is hideous */ /* this is hideous */
src++; src++;