mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-26 22:23:17 -05:00
1245 lines
34 KiB
C
1245 lines
34 KiB
C
/*
|
|
* $Id$
|
|
* Build daap structs for replies
|
|
*
|
|
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
|
|
#include "configfile.h"
|
|
#include "db-memory.h"
|
|
#include "daap-proto.h"
|
|
#include "daap.h"
|
|
#include "err.h"
|
|
#include "daapd.h"
|
|
|
|
#include "query.h"
|
|
|
|
typedef struct tag_daap_items {
|
|
int type;
|
|
char *tag;
|
|
char *description;
|
|
} DAAP_ITEMS;
|
|
|
|
DAAP_ITEMS taglist[] = {
|
|
{ 0x05, "miid", "dmap.itemid" },
|
|
{ 0x09, "minm", "dmap.itemname" },
|
|
{ 0x01, "mikd", "dmap.itemkind" },
|
|
{ 0x07, "mper", "dmap.persistentid" },
|
|
{ 0x0C, "mcon", "dmap.container" },
|
|
{ 0x05, "mcti", "dmap.containeritemid" },
|
|
{ 0x05, "mpco", "dmap.parentcontainerid" },
|
|
{ 0x05, "mstt", "dmap.status" },
|
|
{ 0x09, "msts", "dmap.statusstring" },
|
|
{ 0x05, "mimc", "dmap.itemcount" },
|
|
{ 0x05, "mctc", "dmap.containercount" },
|
|
{ 0x05, "mrco", "dmap.returnedcount" },
|
|
{ 0x05, "mtco", "dmap.specifiedtotalcount" },
|
|
{ 0x0C, "mlcl", "dmap.listing" },
|
|
{ 0x0C, "mlit", "dmap.listingitem" },
|
|
{ 0x0C, "mbcl", "dmap.bag" },
|
|
{ 0x0C, "mdcl", "dmap.dictionary" },
|
|
{ 0x0C, "msrv", "dmap.serverinforesponse" },
|
|
{ 0x01, "msau", "dmap.authenticationmethod" },
|
|
{ 0x01, "mslr", "dmap.loginrequired" },
|
|
{ 0x0B, "mpro", "dmap.protocolversion" },
|
|
{ 0x01, "msal", "dmap.supportsautologout" },
|
|
{ 0x01, "msup", "dmap.supportsupdate" },
|
|
{ 0x01, "mspi", "dmap.supportspersistentids" },
|
|
{ 0x01, "msex", "dmap.supportsextensions" },
|
|
{ 0x01, "msbr", "dmap.supportsbrowse" },
|
|
{ 0x01, "msqy", "dmap.supportsquery" },
|
|
{ 0x01, "msix", "dmap.supportsindex" },
|
|
{ 0x01, "msrs", "dmap.supportsresolve" },
|
|
{ 0x05, "mstm", "dmap.timeoutinterval" },
|
|
{ 0x05, "msdc", "dmap.databasescount" },
|
|
{ 0x0C, "mlog", "dmap.loginresponse" },
|
|
{ 0x05, "mlid", "dmap.sessionid" },
|
|
{ 0x0C, "mupd", "dmap.updateresponse" },
|
|
{ 0x05, "musr", "dmap.serverrevision" },
|
|
{ 0x01, "muty", "dmap.updatetype" },
|
|
{ 0x0C, "mudl", "dmap.deletedidlisting" },
|
|
{ 0x0C, "mccr", "dmap.contentcodesresponse" },
|
|
{ 0x05, "mcnm", "dmap.contentcodesnumber" },
|
|
{ 0x09, "mcna", "dmap.contentcodesname" },
|
|
{ 0x03, "mcty", "dmap.contentcodestype" },
|
|
{ 0x0B, "apro", "daap.protocolversion" },
|
|
{ 0x0C, "avdb", "daap.serverdatabases" },
|
|
{ 0x0C, "abro", "daap.databasebrowse" },
|
|
{ 0x0C, "abal", "daap.browsealbumlisting" },
|
|
{ 0x0C, "abar", "daap.browseartistlisting" },
|
|
{ 0x0C, "abcp", "daap.browsecomposerlisting" },
|
|
{ 0x0C, "abgn", "daap.browsegenrelisting" },
|
|
{ 0x0C, "adbs", "daap.databasesongs" },
|
|
{ 0x09, "asal", "daap.songalbum" },
|
|
{ 0x09, "asar", "daap.songartist" },
|
|
{ 0x03, "asbt", "daap.songbeatsperminute" },
|
|
{ 0x03, "asbr", "daap.songbitrate" },
|
|
{ 0x09, "ascm", "daap.songcomment" },
|
|
{ 0x01, "asco", "daap.songcompilation" },
|
|
{ 0x09, "ascp", "daap.songcomposer" },
|
|
{ 0x0A, "asda", "daap.songdateadded" },
|
|
{ 0x0A, "asdm", "daap.songdatemodified" },
|
|
{ 0x03, "asdc", "daap.songdisccount" },
|
|
{ 0x03, "asdn", "daap.songdiscnumber" },
|
|
{ 0x01, "asdb", "daap.songdisabled" },
|
|
{ 0x09, "aseq", "daap.songeqpreset" },
|
|
{ 0x09, "asfm", "daap.songformat" },
|
|
{ 0x09, "asgn", "daap.songgenre" },
|
|
{ 0x09, "asdt", "daap.songdescription" },
|
|
{ 0x02, "asrv", "daap.songrelativevolume" },
|
|
{ 0x05, "assr", "daap.songsamplerate" },
|
|
{ 0x05, "assz", "daap.songsize" },
|
|
{ 0x05, "asst", "daap.songstarttime" },
|
|
{ 0x05, "assp", "daap.songstoptime" },
|
|
{ 0x05, "astm", "daap.songtime" },
|
|
{ 0x03, "astc", "daap.songtrackcount" },
|
|
{ 0x03, "astn", "daap.songtracknumber" },
|
|
{ 0x01, "asur", "daap.songuserrating" },
|
|
{ 0x03, "asyr", "daap.songyear" },
|
|
{ 0x01, "asdk", "daap.songdatakind" },
|
|
{ 0x09, "asul", "daap.songdataurl" },
|
|
{ 0x0C, "aply", "daap.databaseplaylists" },
|
|
{ 0x01, "abpl", "daap.baseplaylist" },
|
|
{ 0x0C, "apso", "daap.playlistsongs" },
|
|
{ 0x0C, "arsv", "daap.resolve" },
|
|
{ 0x0C, "arif", "daap.resolveinfo" },
|
|
{ 0x05, "aeNV", "com.apple.itunes.norm-volume" },
|
|
{ 0x01, "aeSP", "com.apple.itunes.smart-playlist" },
|
|
{ 0x00, NULL, NULL }
|
|
};
|
|
|
|
#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, "daap.songcompilation", OFFSET_OF(MP3FILE, compilation) },
|
|
{ qft_string, "daap.songcomposer", OFFSET_OF(MP3FILE, composer) },
|
|
{ qft_i32, "daap.songdatakind", OFFSET_OF(MP3FILE, data_kind) },
|
|
{ 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 }
|
|
};
|
|
|
|
/* Forwards */
|
|
|
|
int daap_add_mdcl(DAAP_BLOCK *root, char *tag, char *name, short int number) {
|
|
DAAP_BLOCK *mdcl;
|
|
int g=1;
|
|
|
|
mdcl=daap_add_empty(root,"mdcl");
|
|
if(mdcl) {
|
|
g=(int)daap_add_string(mdcl,"mcnm",tag);
|
|
g = g && daap_add_string(mdcl,"mcna",name);
|
|
g = g && daap_add_short(mdcl,"mcty",number);
|
|
}
|
|
|
|
return (mdcl ? g : 0);
|
|
}
|
|
|
|
/*
|
|
* daap_response_content_codes
|
|
*
|
|
* handle the daap block for the /content-codes URI
|
|
*
|
|
* This might more easily be done by just emitting a binary
|
|
* of the content-codes from iTunes, since this really
|
|
* isn't dynamic
|
|
*/
|
|
|
|
DAAP_BLOCK *daap_response_content_codes(void) {
|
|
DAAP_BLOCK *root;
|
|
DAAP_ITEMS *current=taglist;
|
|
int g=1;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Preparing to get content codes\n");
|
|
|
|
root=daap_add_empty(NULL,"mccr");
|
|
if(root) {
|
|
g = (int)daap_add_int(root,"mstt",200);
|
|
|
|
while(current->type) {
|
|
g = g && daap_add_mdcl(root,current->tag,current->description,
|
|
current->type);
|
|
current++;
|
|
}
|
|
}
|
|
|
|
if(!g) {
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
/*
|
|
* daap_response_login
|
|
*
|
|
* handle the daap block for the /login URI
|
|
*/
|
|
|
|
DAAP_BLOCK *daap_response_login(char *hostname) {
|
|
DAAP_BLOCK *root;
|
|
int g=1;
|
|
int session=0;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Preparing to send login response\n");
|
|
|
|
root=daap_add_empty(NULL,"mlog");
|
|
if(root) {
|
|
g = (int)daap_add_int(root,"mstt",200);
|
|
session=config_get_next_session();
|
|
g = g && daap_add_int(root,"mlid",session);
|
|
}
|
|
|
|
if(!g) {
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
DPRINTF(E_LOG,L_DAAP,"%s logging in as session %d\n",hostname,session);
|
|
|
|
return root;
|
|
}
|
|
|
|
/*
|
|
* daap_response_songlist
|
|
*
|
|
* handle the daap block for the /databases/x/items URI
|
|
*/
|
|
|
|
// fields requestable with meta=... these are really used as bit
|
|
// numbers in a long long, but are defined this way to simplify
|
|
// eventual implementation on platforms without long long support
|
|
typedef enum {
|
|
// generic meta data
|
|
metaItemId,
|
|
metaItemName,
|
|
metaItemKind,
|
|
metaPersistentId,
|
|
metaContainerItemId,
|
|
metaParentContainerId,
|
|
|
|
firstTypeSpecificMetaId,
|
|
|
|
// song meta data
|
|
metaSongAlbum = firstTypeSpecificMetaId,
|
|
metaSongArtist,
|
|
metaSongBPM, /* beats per minute */
|
|
metaSongBitRate,
|
|
metaSongComment,
|
|
metaSongCompilation,
|
|
metaSongComposer,
|
|
metaSongDataKind,
|
|
metaSongDataURL,
|
|
metaSongDateAdded,
|
|
metaSongDateModified,
|
|
metaSongDescription,
|
|
metaSongDisabled,
|
|
metaSongDiscCount,
|
|
metaSongDiscNumber,
|
|
metaSongEqPreset,
|
|
metaSongFormat,
|
|
metaSongGenre,
|
|
metaSongGrouping,
|
|
metaSongRelativeVolume,
|
|
metaSongSampleRate,
|
|
metaSongSize,
|
|
metaSongStartTime,
|
|
metaSongStopTime,
|
|
metaSongTime,
|
|
metaSongTrackCount,
|
|
metaSongTrackNumber,
|
|
metaSongUserRating,
|
|
metaSongYear
|
|
} MetaFieldName_t;
|
|
|
|
// structure mapping meta= tag names to bit numbers
|
|
typedef struct
|
|
{
|
|
const char* tag;
|
|
MetaFieldName_t bit;
|
|
} MetaDataMap;
|
|
|
|
// the dmap based tags, defined psuedo separately because they're also
|
|
// needed for DPAP, not that that's at all relevant here
|
|
#define INCLUDE_GENERIC_META_IDS \
|
|
{ "dmap.itemid", metaItemId }, \
|
|
{ "dmap.itemname", metaItemName }, \
|
|
{ "dmap.itemkind", metaItemKind }, \
|
|
{ "dmap.persistentid", metaPersistentId }, \
|
|
{ "dmap.containeritemid", metaContainerItemId }, \
|
|
{ "dmap.parentcontainerid", metaParentContainerId }
|
|
|
|
// map the string names specified in the meta= tag to bit numbers
|
|
static MetaDataMap gSongMetaDataMap[] = {
|
|
INCLUDE_GENERIC_META_IDS,
|
|
{ "daap.songalbum", metaSongAlbum },
|
|
{ "daap.songartist", metaSongArtist },
|
|
{ "daap.songbitrate", metaSongBitRate },
|
|
{ "daap.songbeatsperminute",metaSongBPM },
|
|
{ "daap.songcomment", metaSongComment },
|
|
{ "daap.songcompilation", metaSongCompilation },
|
|
{ "daap.songcomposer", metaSongComposer },
|
|
{ "daap.songdatakind", metaSongDataKind },
|
|
{ "daap.songdataurl", metaSongDataURL },
|
|
{ "daap.songdateadded", metaSongDateAdded },
|
|
{ "daap.songdatemodified", metaSongDateModified },
|
|
{ "daap.songdescription", metaSongDescription },
|
|
{ "daap.songdisabled", metaSongDisabled },
|
|
{ "daap.songdisccount", metaSongDiscCount },
|
|
{ "daap.songdiscnumber", metaSongDiscNumber },
|
|
{ "daap.songeqpreset", metaSongEqPreset },
|
|
{ "daap.songformat", metaSongFormat },
|
|
{ "daap.songgenre", metaSongGenre },
|
|
{ "daap.songgrouping", metaSongGrouping },
|
|
{ "daap.songrelativevolume",metaSongRelativeVolume },
|
|
{ "daap.songsamplerate", metaSongSampleRate },
|
|
{ "daap.songsize", metaSongSize },
|
|
{ "daap.songstarttime", metaSongStartTime },
|
|
{ "daap.songstoptime", metaSongStopTime },
|
|
{ "daap.songtime", metaSongTime },
|
|
{ "daap.songtrackcount", metaSongTrackCount },
|
|
{ "daap.songtracknumber", metaSongTrackNumber },
|
|
{ "daap.songuserrating", metaSongUserRating },
|
|
{ "daap.songyear", metaSongYear },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
typedef unsigned long long MetaField_t;
|
|
|
|
// turn the meta= parameter into a bitfield representing the requested
|
|
// fields. The format is actually meta=<tag>[,<tag>...] where <tag>
|
|
// is any of the strings in the table above
|
|
MetaField_t encodeMetaRequest(char* meta, MetaDataMap* map)
|
|
{
|
|
MetaField_t bits = 0;
|
|
char* start;
|
|
char* end;
|
|
MetaDataMap* m;
|
|
|
|
for(start = meta ; *start ; start = end)
|
|
{
|
|
int len;
|
|
|
|
if(0 == (end = strchr(start, ',')))
|
|
end = start + strlen(start);
|
|
|
|
len = end - start;
|
|
|
|
if(*end != 0)
|
|
end++;
|
|
|
|
for(m = map ; m->tag ; ++m)
|
|
if(!strncmp(m->tag, start, len))
|
|
break;
|
|
|
|
if(m->tag)
|
|
bits |= (((MetaField_t) 1) << m->bit);
|
|
else
|
|
DPRINTF(E_WARN,L_DAAP,"Unknown meta code: %.*s\n", len, start);
|
|
}
|
|
|
|
DPRINTF(E_DBG, L_DAAP, "meta codes: %llu\n", bits);
|
|
|
|
return bits;
|
|
}
|
|
|
|
int wantsMeta(MetaField_t meta, MetaFieldName_t fieldNo)
|
|
{
|
|
return 0 != (meta & (((MetaField_t) 1) << fieldNo));
|
|
}
|
|
|
|
DAAP_BLOCK *daap_response_songlist(char* metaStr, char* query) {
|
|
DAAP_BLOCK *root;
|
|
int g=1;
|
|
DAAP_BLOCK *mlcl;
|
|
DAAP_BLOCK *mlit;
|
|
ENUMHANDLE henum;
|
|
MP3FILE *current;
|
|
MetaField_t meta;
|
|
|
|
query_node_t* filter = 0;
|
|
int songs = 0;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"enter daap_response_songlist\n");
|
|
|
|
// if the meta tag is specified, encode it, if it's not specified
|
|
// we're given the latitude to select our own subset, for
|
|
// simplicity we just include everything.
|
|
if(0 == metaStr)
|
|
meta = (MetaField_t) -1ll;
|
|
else
|
|
meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
|
|
|
|
if(0 != query) {
|
|
filter = query_build(query, song_fields);
|
|
DPRINTF(E_INF,L_DAAP|L_QRY,"query: %s\n", query);
|
|
if(err_debuglevel >= E_INF) /* this is broken */
|
|
query_dump(stderr,filter, 0);
|
|
}
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Preparing to send db items\n");
|
|
|
|
henum=db_enum_begin();
|
|
if((!henum) && (db_get_song_count())) {
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Can't get enum handle - exiting daap_response_songlist\n");
|
|
return NULL;
|
|
}
|
|
|
|
root=daap_add_empty(NULL,"adbs");
|
|
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",0);
|
|
g = g && daap_add_int(root,"mrco",0);
|
|
|
|
mlcl=daap_add_empty(root,"mlcl");
|
|
|
|
if(mlcl) {
|
|
while(g && (current=db_enum(&henum))) {
|
|
if(filter == 0 || query_test(filter, current))
|
|
{
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"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(henum);
|
|
|
|
if(filter != 0)
|
|
query_free(filter);
|
|
|
|
if(!g) {
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Error enumerating db - exiting daap_response_songlist\n");
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Successfully enumerated database - %d items\n",songs);
|
|
|
|
daap_set_int(root, "mtco", songs);
|
|
daap_set_int(root, "mrco", songs);
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Exiting daap_response_songlist\n");
|
|
return root;
|
|
}
|
|
|
|
|
|
//
|
|
// extracted song entry generation used by both database item lists
|
|
// and play list item lists
|
|
//
|
|
DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, MetaField_t meta)
|
|
{
|
|
DAAP_BLOCK* mlit;
|
|
int g = 1;
|
|
|
|
mlit=daap_add_empty(mlcl,"mlit");
|
|
if(mlit) {
|
|
if(wantsMeta(meta, metaItemKind))
|
|
g = g && daap_add_char(mlit,"mikd",song->item_kind); /* audio */
|
|
|
|
if(wantsMeta(meta, metaSongDataKind))
|
|
g = g && daap_add_char(mlit,"asdk",song->data_kind); /* local file */
|
|
|
|
if(wantsMeta(meta, metaSongDataURL))
|
|
g = g && daap_add_string(mlit,"asul",song->url);
|
|
|
|
if(song->album && (wantsMeta(meta, metaSongAlbum)))
|
|
g = g && daap_add_string(mlit,"asal",song->album);
|
|
|
|
if(song->artist && wantsMeta(meta, metaSongArtist))
|
|
g = g && daap_add_string(mlit,"asar",song->artist);
|
|
|
|
if(song->bpm && (wantsMeta(meta, metaSongBPM)))
|
|
g = g && daap_add_short(mlit,"asbt",song->bpm); /* bpm */
|
|
|
|
if(song->bitrate && (wantsMeta(meta, metaSongBitRate)))
|
|
g = g && daap_add_short(mlit,"asbr",song->bitrate); /* bitrate!! */
|
|
|
|
if(song->comment && (wantsMeta(meta, metaSongComment)))
|
|
g = g && daap_add_string(mlit,"ascm",song->comment); /* comment */
|
|
|
|
if(song->compilation && (wantsMeta(meta, metaSongCompilation)))
|
|
g = g && daap_add_char(mlit,"asco",song->compilation); /* compilation */
|
|
|
|
if(song->composer && (wantsMeta(meta, metaSongComposer)))
|
|
g = g && daap_add_string(mlit,"ascp",song->composer); /* composer */
|
|
|
|
if(song->grouping && (wantsMeta(meta, metaSongGrouping)))
|
|
g = g && daap_add_string(mlit,"agrp",song->grouping); /* grouping */
|
|
|
|
if(song->time_added && (wantsMeta(meta, metaSongDateAdded)))
|
|
g = g && daap_add_int(mlit,"asda",song->time_added); /* added */
|
|
|
|
if(song->time_modified && (wantsMeta(meta, metaSongDateModified)))
|
|
g = g && daap_add_int(mlit,"asdm",song->time_modified); /* modified */
|
|
|
|
if(song->total_discs && (wantsMeta(meta, metaSongDiscCount)))
|
|
/* # of discs */
|
|
g = g && daap_add_short(mlit,"asdc",song->total_discs);
|
|
|
|
if(song->disc && (wantsMeta(meta, metaSongDiscNumber)))
|
|
/* disc number */
|
|
g = g && daap_add_short(mlit,"asdn",song->disc);
|
|
|
|
// asdk must be early in the item, moved to the top
|
|
// g = g && daap_add_char(mlit,"asdk",0); /* song datakind? */
|
|
// aseq - null string!
|
|
|
|
if(song->genre && (wantsMeta(meta, metaSongGenre)))
|
|
g = g && daap_add_string(mlit,"asgn",song->genre); /* genre */
|
|
|
|
if(wantsMeta(meta, metaItemId))
|
|
g = g && daap_add_int(mlit,"miid",song->id); /* id */
|
|
|
|
if(wantsMeta(meta, metaPersistentId))
|
|
g = g && daap_add_long(mlit,"mper",0,song->id);
|
|
|
|
/* these quite go hand in hand */
|
|
if(wantsMeta(meta, metaSongFormat))
|
|
g = g && daap_add_string(mlit,"asfm",song->type); /* song format */
|
|
|
|
if(wantsMeta(meta, metaSongDescription))
|
|
g = g && daap_add_string(mlit, "asdt",song->description);
|
|
|
|
if(wantsMeta(meta, metaItemName))
|
|
g = g && daap_add_string(mlit,"minm",song->title); /* descr */
|
|
|
|
// mper (long)
|
|
// g = g && daap_add_char(mlit,"asdb",0); /* disabled */
|
|
// g = g && daap_add_char(mlit,"asrv",0); /* rel vol */
|
|
if(song->samplerate && (wantsMeta(meta, metaSongSampleRate)))
|
|
g = g && daap_add_int(mlit,"assr",song->samplerate); /* samp rate */
|
|
|
|
if(song->file_size && (wantsMeta(meta, metaSongSize)))
|
|
g = g && daap_add_int(mlit,"assz",song->file_size); /* Size! */
|
|
|
|
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); /* song stop time */
|
|
|
|
if(song->song_length && (wantsMeta(meta, metaSongTime)))
|
|
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 */
|
|
|
|
if(song->track && (wantsMeta(meta, metaSongTrackNumber)))
|
|
g = g && daap_add_short(mlit,"astn",song->track); /* track number */
|
|
|
|
// g = g && daap_add_char(mlit,"asur",3); /* rating */
|
|
if(song->year && (wantsMeta(meta, metaSongYear)))
|
|
g = g && daap_add_short(mlit,"asyr",song->year);
|
|
}
|
|
|
|
if(g == 0)
|
|
{
|
|
daap_free(mlit);
|
|
mlit = 0;
|
|
}
|
|
|
|
return mlit;
|
|
}
|
|
|
|
/*
|
|
* daap_response_update
|
|
*
|
|
* handle the daap block for the /update URI
|
|
*/
|
|
|
|
DAAP_BLOCK *daap_response_update(int fd, int clientver) {
|
|
DAAP_BLOCK *root;
|
|
int g=1;
|
|
fd_set rset;
|
|
struct timeval tv;
|
|
int result;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Preparing to send update response\n");
|
|
|
|
while(clientver == db_version()) {
|
|
FD_ZERO(&rset);
|
|
FD_SET(fd,&rset);
|
|
|
|
tv.tv_sec=30;
|
|
tv.tv_usec=0;
|
|
|
|
result=select(fd+1,&rset,NULL,NULL,&tv);
|
|
if(FD_ISSET(fd,&rset)) {
|
|
/* can't be ready for read, must be error */
|
|
DPRINTF(E_DBG,L_DAAP,"Socket closed?\n");
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
root=daap_add_empty(NULL,"mupd");
|
|
if(root) {
|
|
g = (int)daap_add_int(root,"mstt",200);
|
|
/* theoretically, this would go up if the db changes? */
|
|
g = g && daap_add_int(root,"musr",db_version());
|
|
}
|
|
|
|
if(!g) {
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
/*
|
|
* daap_response_playlists
|
|
*
|
|
* handle the daap block for the /databases/containers URI
|
|
*/
|
|
DAAP_BLOCK *daap_response_playlists(char *name) {
|
|
DAAP_BLOCK *root=NULL;
|
|
DAAP_BLOCK *mlcl=NULL;
|
|
DAAP_BLOCK *mlit=NULL;
|
|
int g=1;
|
|
int playlistid;
|
|
ENUMHANDLE henum;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Preparing to send playlists\n");
|
|
|
|
root=daap_add_empty(NULL,"aply");
|
|
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",1 + db_get_playlist_count());
|
|
g = g && daap_add_int(root,"mrco",1 + db_get_playlist_count());
|
|
mlcl=daap_add_empty(root,"mlcl");
|
|
if(mlcl) {
|
|
mlit=daap_add_empty(mlcl,"mlit");
|
|
if(mlit) {
|
|
g = g && daap_add_int(mlit,"miid",0x1);
|
|
g = g && daap_add_long(mlit,"mper",0,1);
|
|
g = g && daap_add_string(mlit,"minm",name);
|
|
g = g && daap_add_int(mlit,"mimc",db_get_song_count());
|
|
}
|
|
|
|
g = g && mlit;
|
|
|
|
/* add the rest of the playlists */
|
|
henum=db_playlist_enum_begin();
|
|
while(henum) {
|
|
playlistid=db_playlist_enum(&henum);
|
|
DPRINTF(E_DBG,L_DAAP|L_PL,"Returning playlist %d\n",playlistid);
|
|
DPRINTF(E_DBG,L_DAAP|L_PL," -- Songs: %d\n",
|
|
db_get_playlist_entry_count(playlistid));
|
|
DPRINTF(E_DBG,L_DAAP|L_PL," -- Smart: %s\n",
|
|
db_get_playlist_is_smart(playlistid) ?
|
|
"Yes" : "No");
|
|
mlit=daap_add_empty(mlcl,"mlit");
|
|
if(mlit) {
|
|
g = g && daap_add_int(mlit,"miid",playlistid);
|
|
g = g && daap_add_long(mlit,"mper",0,playlistid);
|
|
g = g && daap_add_string(mlit,"minm",db_get_playlist_name(playlistid));
|
|
g = g && daap_add_int(mlit,"mimc",db_get_playlist_entry_count(playlistid));
|
|
if(db_get_playlist_is_smart(playlistid)) {
|
|
g = g && daap_add_char(mlit,"aeSP",0x1);
|
|
}
|
|
}
|
|
g = g && mlit;
|
|
}
|
|
db_playlist_enum_end(henum);
|
|
}
|
|
|
|
}
|
|
|
|
g = g && mlcl;
|
|
|
|
if(!g) {
|
|
DPRINTF(E_INF,L_DAAP,"Memory problem. Bailing\n");
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
/*
|
|
* daap_response_dbinfo
|
|
*
|
|
* handle the daap block for the /databases URI
|
|
*/
|
|
|
|
DAAP_BLOCK *daap_response_dbinfo(char *name) {
|
|
DAAP_BLOCK *root=NULL;
|
|
DAAP_BLOCK *mlcl=NULL;
|
|
DAAP_BLOCK *mlit=NULL;
|
|
int g=1;
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Preparing to send db info\n");
|
|
|
|
root=daap_add_empty(NULL,"avdb");
|
|
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",1);
|
|
g = g && daap_add_int(root,"mrco",1);
|
|
mlcl=daap_add_empty(root,"mlcl");
|
|
if(mlcl) {
|
|
mlit=daap_add_empty(mlcl,"mlit");
|
|
if(mlit) {
|
|
g = g && daap_add_int(mlit,"miid",1);
|
|
g = g && daap_add_long(mlit,"mper",0,1);
|
|
g = g && daap_add_string(mlit,"minm",name);
|
|
g = g && daap_add_int(mlit,"mimc",db_get_song_count()); /* songs */
|
|
g = g && daap_add_int(mlit,"mctc",1 + db_get_playlist_count()); /* playlists */
|
|
}
|
|
}
|
|
}
|
|
|
|
g = g && mlcl && mlit;
|
|
|
|
if(!g) {
|
|
DPRINTF(E_INF,L_DAAP,"Memory problem. Bailing\n");
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_DB,"Sent db info... %d songs, %d playlists\n",db_get_song_count(),
|
|
db_get_playlist_count());
|
|
|
|
return root;
|
|
}
|
|
|
|
/*
|
|
* daap_response_server_info
|
|
*
|
|
* handle the daap block for the /server-info URI
|
|
*/
|
|
DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
|
|
DAAP_BLOCK *root;
|
|
int g=1;
|
|
|
|
DPRINTF(E_DBG,L_DAAP,"Preparing to send server-info for client ver %s\n",client_version);
|
|
|
|
root=daap_add_empty(NULL,"msrv");
|
|
|
|
if(root) {
|
|
g = (int)daap_add_int(root,"mstt",200); /* result */
|
|
if((!client_version)||(!strcmp(client_version,"3.0"))) {
|
|
g = g && daap_add_int(root,"mpro",2 << 16); /* dmap proto ? */
|
|
g = g && daap_add_int(root,"apro",3 << 16); /* daap protocol */
|
|
} else {
|
|
if(!strcmp(client_version,"1.0")) {
|
|
g = g && daap_add_int(root,"mpro",1 << 16); /* dmap proto ? */
|
|
g = g && daap_add_int(root,"apro",1 << 16); /* daap protocol */
|
|
} else if(!strcmp(client_version,"2.0")) {
|
|
g = g && daap_add_int(root,"mpro",1 << 16); /* dmap proto ? */
|
|
g = g && daap_add_int(root,"apro",2 << 16); /* daap protocol */
|
|
}
|
|
}
|
|
|
|
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 */
|
|
g = g && daap_add_char(root,"msau", config.readpassword != NULL ? 2 : 0);
|
|
|
|
/* actual time out seems faster then 30 minutes */
|
|
g = g && daap_add_int(root,"mstm",1800); /* timeout - iTunes=1800 */
|
|
|
|
/* presence of most of the support* variables indicates
|
|
support, the actual value is required to be zero, I've
|
|
commented out the ones I don't believe are actually
|
|
supported */
|
|
g = g && daap_add_char(root,"msex",0); /* extensions */
|
|
g = g && daap_add_char(root,"msix",0); /* indexing? */
|
|
|
|
g = g && daap_add_char(root,"msbr",0); /* browsing */
|
|
g = g && daap_add_char(root,"msqy",0); /* queries */
|
|
|
|
g = g && daap_add_char(root,"msup",0); /* update */
|
|
|
|
#if 0
|
|
g = g && daap_add_char(root,"mspi",0); /* persistant ids */
|
|
g = g && daap_add_char(root,"msal",0); /* autologout */
|
|
g = g && daap_add_char(root,"msrs",0); /* resolve? req. persist id */
|
|
#endif
|
|
g = g && daap_add_int(root,"msdc",1); /* database count */
|
|
}
|
|
|
|
if(!g) {
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
/*
|
|
* daap_response_playlist_items
|
|
*
|
|
* given a playlist number, return the items on the playlist
|
|
*/
|
|
DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr, char* query) {
|
|
DAAP_BLOCK *root;
|
|
DAAP_BLOCK *mlcl;
|
|
DAAP_BLOCK *mlit;
|
|
ENUMHANDLE henum;
|
|
MP3FILE *current;
|
|
unsigned long int itemid;
|
|
int g=1;
|
|
unsigned long long meta;
|
|
query_node_t* filter = 0;
|
|
int songs = 0;
|
|
|
|
// if no meta information is specifically requested, return only
|
|
// the base play list information. iTunes only requests the base
|
|
// information as it rebuilds the entire database locally so it's
|
|
// just replicated information
|
|
if(0 == metaStr)
|
|
meta = ((1ll << metaItemId) |
|
|
(1ll << metaItemName) |
|
|
(1ll << metaItemKind) |
|
|
(1ll << metaContainerItemId) |
|
|
(1ll << metaParentContainerId));
|
|
else
|
|
meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
|
|
|
|
if(0 != query) {
|
|
filter = query_build(query, song_fields);
|
|
DPRINTF(E_INF,L_DAAP|L_QRY,"query: %s\n",query);
|
|
if(err_debuglevel >= E_INF) /* this is broken */
|
|
query_dump(stderr,filter, 0);
|
|
}
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_PL,"Preparing to send playlist items for pl #%d\n",playlist);
|
|
|
|
if(playlist == 1) {
|
|
henum=db_enum_begin();
|
|
} else {
|
|
henum=db_playlist_items_enum_begin(playlist);
|
|
}
|
|
|
|
/* we can allow an empty playlist...
|
|
if(!henum)
|
|
return NULL;
|
|
*/
|
|
|
|
root=daap_add_empty(NULL,"apso");
|
|
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",0);
|
|
g = g && daap_add_int(root,"mrco",0);
|
|
|
|
mlcl=daap_add_empty(root,"mlcl");
|
|
|
|
if(mlcl) {
|
|
if(playlist == 1) {
|
|
while((current=db_enum(&henum))) {
|
|
if(0 == filter || query_test(filter, current))
|
|
{
|
|
songs++;
|
|
mlit=daap_add_song_entry(mlcl, current, meta);
|
|
if(0 != mlit) {
|
|
if(wantsMeta(meta, metaContainerItemId))
|
|
g = g && daap_add_int(mlit,"mcti",current->id);
|
|
} else g=0;
|
|
}
|
|
}
|
|
} else { /* other playlist */
|
|
while((itemid=db_playlist_items_enum(&henum)) != -1) {
|
|
current = db_find(itemid);
|
|
if(0 != current) {
|
|
if(0 == filter || query_test(filter, current))
|
|
{
|
|
songs++;
|
|
DPRINTF(E_DBG,L_DAAP|L_PL,"Adding itemid %lu\n",itemid);
|
|
mlit=daap_add_song_entry(mlcl,current,meta);
|
|
if(0 != mlit) {
|
|
if(wantsMeta(meta, metaContainerItemId)) // current->id?
|
|
// g = g && daap_add_int(mlit,"mcti",playlist);
|
|
g = g && daap_add_int(mlit,"mcti",current->id);
|
|
} else g = 0;
|
|
}
|
|
db_dispose(current);
|
|
free(current);
|
|
} else g = 0;
|
|
}
|
|
}
|
|
} else g=0;
|
|
}
|
|
|
|
if(playlist == 1)
|
|
db_enum_end(henum);
|
|
else
|
|
db_playlist_items_enum_end(henum);
|
|
|
|
if(0 != filter)
|
|
query_free(filter);
|
|
|
|
if(!g) {
|
|
daap_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
DPRINTF(E_DBG,L_DAAP|L_PL,"Sucessfully enumerated %d items\n",songs);
|
|
|
|
daap_set_int(root, "mtco", songs);
|
|
daap_set_int(root, "mrco", songs);
|
|
|
|
return root;
|
|
}
|
|
|
|
//
|
|
// handle the index= parameter
|
|
// format is:
|
|
// index=<item> a single item from the list by index
|
|
// index=<l>-<h> a range of items from the list by
|
|
// index from l to h inclusive
|
|
// index=<l>- a range of items from the list by
|
|
// index from l to the end of the list
|
|
// index=-<n> the last <n> items from the list
|
|
//
|
|
void daap_handle_index(DAAP_BLOCK* block, const char* index)
|
|
{
|
|
int first;
|
|
int count;
|
|
int size;
|
|
char* ptr;
|
|
DAAP_BLOCK* list;
|
|
DAAP_BLOCK* item;
|
|
DAAP_BLOCK**back;
|
|
int n;
|
|
|
|
// get the actual list
|
|
if(0 == (list = daap_find(block, "mlcl")))
|
|
return;
|
|
|
|
// count the items in the list
|
|
for(size = 0, item = list->children ; item ; item = item->next)
|
|
if(!strncmp(item->tag, "mlit", 4))
|
|
size++;
|
|
|
|
// range start
|
|
n = strtol(index, &ptr, 10);
|
|
|
|
// "-n": tail range, keep the last n entries
|
|
if(n < 0)
|
|
{
|
|
n *= -1;
|
|
|
|
// if we have too many entries, figure out which to keep
|
|
if(n < size)
|
|
{
|
|
first = size - n;
|
|
count = n;
|
|
}
|
|
|
|
// if we don't have enough entries, keep what we have
|
|
else
|
|
{
|
|
first = 0;
|
|
count = size;
|
|
}
|
|
}
|
|
|
|
// "n": single item
|
|
else if(0 == *ptr)
|
|
{
|
|
// item exists, return one item at the appropriate index
|
|
if(n < size)
|
|
{
|
|
first = n;
|
|
count = 1;
|
|
}
|
|
|
|
// item doesn't exist, return zero items
|
|
else
|
|
{
|
|
first = 0;
|
|
count = 0;
|
|
}
|
|
}
|
|
|
|
// "x-y": true range
|
|
else if('-' == *ptr)
|
|
{
|
|
// record range start
|
|
first = n;
|
|
|
|
// "x-": x to end
|
|
if(*++ptr == 0)
|
|
n = size;
|
|
|
|
// record range end
|
|
else
|
|
{
|
|
n = strtol(ptr, &ptr, 10) + 1;
|
|
|
|
// wanting more than there is, return fewer
|
|
if(n > size)
|
|
n = size;
|
|
}
|
|
|
|
count = n - first;
|
|
}
|
|
|
|
// update the returned record count entry, it's required, so
|
|
// should have already be created
|
|
daap_set_int(block, "mrco", count);
|
|
|
|
DPRINTF(E_INF,L_DAAP|L_IND, "index:%s first:%d count:%d\n", index, first, count);
|
|
|
|
// remove the first first entries
|
|
for(back = &list->children ; *back && first ; )
|
|
if(!strncmp((**back).tag, "mlit", 4))
|
|
{
|
|
DPRINTF(E_DBG,L_DAAP|L_IND, "first:%d removing\n", first);
|
|
daap_remove(*back);
|
|
first--;
|
|
}
|
|
else
|
|
back = &(**back).next;
|
|
|
|
// keep the next count items
|
|
for( ; *back && count ; back = &(**back).next)
|
|
if(!strncmp((**back).tag, "mlit", 4))
|
|
{
|
|
DPRINTF(E_DBG,L_DAAP|L_IND,"count:%d keeping\n", count);
|
|
count--;
|
|
}
|
|
|
|
// remove the rest of items
|
|
while(*back)
|
|
{
|
|
if(!strncmp((**back).tag, "mlit", 4))
|
|
{
|
|
DPRINTF(E_DBG,L_DAAP|L_IND,"removing spare\n");
|
|
daap_remove(*back);
|
|
}
|
|
else
|
|
back = &(**back).next;
|
|
}
|
|
}
|
|
|
|
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) && strcasecmp(item->name, name) < 0)
|
|
root = &item->next;
|
|
|
|
if(item && strcasecmp(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;
|
|
}
|
|
|
|
/* 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 }
|
|
};
|
|
|
|
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(E_WARN,L_DAAP|L_BROW,"Invalid browse request: %s\n", name);
|
|
return NULL;
|
|
}
|
|
|
|
if(0 != filter &&
|
|
0 == (query = query_build(filter, browse_fields)))
|
|
return NULL;
|
|
|
|
if(query) {
|
|
DPRINTF(E_INF,L_DAAP|L_BROW|L_QRY,"query: %s\n",filter);
|
|
if(err_debuglevel >= E_INF) /* this is broken */
|
|
query_dump(stderr,query, 0);
|
|
}
|
|
|
|
if(0 == (henum = db_enum_begin()))
|
|
return NULL;
|
|
|
|
while((current = db_enum(&henum)))
|
|
{
|
|
if(0 == query || query_test(query, current))
|
|
{
|
|
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);
|
|
|
|
if(0 != query)
|
|
query_free(query);
|
|
|
|
return root;
|
|
|
|
error:
|
|
free_browse_items(items);
|
|
|
|
if(0 != query)
|
|
query_free(query);
|
|
|
|
if(root != 0)
|
|
daap_free(root);
|
|
|
|
return NULL;
|
|
}
|