1/*
2 * $Id: daap.c,v 1.1 2009-06-30 02:31:08 steven Exp $
3 * Build daap structs for replies
4 *
5 * Copyright (C) 2003 Ron Pedde (ron@pedde.com)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 */
21
22#ifdef HAVE_CONFIG_H
23#  include "config.h"
24#endif
25
26#include <errno.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31#include <sys/select.h>
32#include <sys/time.h>
33#include <sys/types.h>
34#include <assert.h>
35
36#include "configfile.h"
37#include "db-memory.h"
38#include "daap-proto.h"
39#include "daap.h"
40#include "err.h"
41#include "daapd.h"
42
43#include "query.h"
44
45typedef struct tag_daap_items {
46    int type;
47    char *tag;
48    char *description;
49} DAAP_ITEMS;
50
51DAAP_ITEMS taglist[] = {
52	{ 0x05, "miid", "dmap.itemid" },
53	{ 0x09, "minm", "dmap.itemname" },
54	{ 0x01, "mikd", "dmap.itemkind" },
55	{ 0x07, "mper", "dmap.persistentid" },
56	{ 0x0C, "mcon", "dmap.container" },
57	{ 0x05, "mcti", "dmap.containeritemid" },
58	{ 0x05, "mpco", "dmap.parentcontainerid" },
59	{ 0x05, "mstt", "dmap.status" },
60	{ 0x09, "msts", "dmap.statusstring" },
61	{ 0x05, "mimc", "dmap.itemcount" },
62	{ 0x05, "mctc", "dmap.containercount" },
63	{ 0x05, "mrco", "dmap.returnedcount" },
64	{ 0x05, "mtco", "dmap.specifiedtotalcount" },
65	{ 0x0C, "mlcl", "dmap.listing" },
66	{ 0x0C, "mlit", "dmap.listingitem" },
67	{ 0x0C, "mbcl", "dmap.bag" },
68	{ 0x0C, "mdcl", "dmap.dictionary" },
69	{ 0x0C, "msrv", "dmap.serverinforesponse" },
70	{ 0x01, "msau", "dmap.authenticationmethod" },
71	{ 0x01, "mslr", "dmap.loginrequired" },
72	{ 0x0B, "mpro", "dmap.protocolversion" },
73	{ 0x01, "msal", "dmap.supportsautologout" },
74	{ 0x01, "msup", "dmap.supportsupdate" },
75	{ 0x01, "mspi", "dmap.supportspersistentids" },
76	{ 0x01, "msex", "dmap.supportsextensions" },
77	{ 0x01, "msbr", "dmap.supportsbrowse" },
78	{ 0x01, "msqy", "dmap.supportsquery" },
79	{ 0x01, "msix", "dmap.supportsindex" },
80	{ 0x01, "msrs", "dmap.supportsresolve" },
81	{ 0x05, "mstm", "dmap.timeoutinterval" },
82	{ 0x05, "msdc", "dmap.databasescount" },
83	{ 0x0C, "mlog", "dmap.loginresponse" },
84	{ 0x05, "mlid", "dmap.sessionid" },
85	{ 0x0C, "mupd", "dmap.updateresponse" },
86	{ 0x05, "musr", "dmap.serverrevision" },
87	{ 0x01, "muty", "dmap.updatetype" },
88	{ 0x0C, "mudl", "dmap.deletedidlisting" },
89	{ 0x0C, "mccr", "dmap.contentcodesresponse" },
90	{ 0x05, "mcnm", "dmap.contentcodesnumber" },
91	{ 0x09, "mcna", "dmap.contentcodesname" },
92	{ 0x03, "mcty", "dmap.contentcodestype" },
93	{ 0x0B, "apro", "daap.protocolversion" },
94	{ 0x0C, "avdb", "daap.serverdatabases" },
95	{ 0x0C, "abro", "daap.databasebrowse" },
96	{ 0x0C, "abal", "daap.browsealbumlisting" },
97	{ 0x0C, "abar", "daap.browseartistlisting" },
98	{ 0x0C, "abcp", "daap.browsecomposerlisting" },
99	{ 0x0C, "abgn", "daap.browsegenrelisting" },
100	{ 0x0C, "adbs", "daap.databasesongs" },
101	{ 0x09, "asal", "daap.songalbum" },
102	{ 0x09, "asar", "daap.songartist" },
103	{ 0x03, "asbt", "daap.songbeatsperminute" },
104	{ 0x03, "asbr", "daap.songbitrate" },
105	{ 0x09, "ascm", "daap.songcomment" },
106	{ 0x01, "asco", "daap.songcompilation" },
107	{ 0x09, "ascp", "daap.songcomposer" },
108	{ 0x0A, "asda", "daap.songdateadded" },
109	{ 0x0A, "asdm", "daap.songdatemodified" },
110	{ 0x03, "asdc", "daap.songdisccount" },
111	{ 0x03, "asdn", "daap.songdiscnumber" },
112	{ 0x01, "asdb", "daap.songdisabled" },
113	{ 0x09, "aseq", "daap.songeqpreset" },
114	{ 0x09, "asfm", "daap.songformat" },
115	{ 0x09, "asgn", "daap.songgenre" },
116	{ 0x09, "asdt", "daap.songdescription" },
117	{ 0x02, "asrv", "daap.songrelativevolume" },
118	{ 0x05, "assr", "daap.songsamplerate" },
119	{ 0x05, "assz", "daap.songsize" },
120	{ 0x05, "asst", "daap.songstarttime" },
121	{ 0x05, "assp", "daap.songstoptime" },
122	{ 0x05, "astm", "daap.songtime" },
123	{ 0x03, "astc", "daap.songtrackcount" },
124	{ 0x03, "astn", "daap.songtracknumber" },
125	{ 0x01, "asur", "daap.songuserrating" },
126	{ 0x03, "asyr", "daap.songyear" },
127	{ 0x01, "asdk", "daap.songdatakind" },
128	{ 0x09, "asul", "daap.songdataurl" },
129	{ 0x0C, "aply", "daap.databaseplaylists" },
130	{ 0x01, "abpl", "daap.baseplaylist" },
131	{ 0x0C, "apso", "daap.playlistsongs" },
132	{ 0x0C, "arsv", "daap.resolve" },
133	{ 0x0C, "arif", "daap.resolveinfo" },
134	{ 0x05, "aeNV", "com.apple.itunes.norm-volume" },
135	{ 0x01, "aeSP", "com.apple.itunes.smart-playlist" },
136	{ 0x00, NULL,   NULL }
137};
138
139#define OFFSET_OF(__type, __field)	((size_t) (&((__type*) 0)->__field))
140
141static query_field_t	song_fields[] = {
142    { qft_string,	"dmap.itemname",	OFFSET_OF(MP3FILE, title) },
143    { qft_i32,		"dmap.itemid",		OFFSET_OF(MP3FILE, id) },
144    { qft_string,	"daap.songalbum",	OFFSET_OF(MP3FILE, album) },
145    { qft_string,	"daap.songartist",	OFFSET_OF(MP3FILE, artist) },
146    { qft_i32,		"daap.songbitrate",	OFFSET_OF(MP3FILE, bitrate) },
147    { qft_string,	"daap.songcomment",	OFFSET_OF(MP3FILE, comment) },
148    { qft_i32,  	"daap.songcompilation",	OFFSET_OF(MP3FILE, compilation) },
149    { qft_string,	"daap.songcomposer",	OFFSET_OF(MP3FILE, composer) },
150    { qft_i32,  	"daap.songdatakind",    OFFSET_OF(MP3FILE, data_kind) },
151    { qft_string,	"daap.songdataurl",	OFFSET_OF(MP3FILE, url) },
152    { qft_i32,		"daap.songdateadded",	OFFSET_OF(MP3FILE, time_added) },
153    { qft_i32,		"daap.songdatemodified",OFFSET_OF(MP3FILE, time_modified) },
154    { qft_string,	"daap.songdescription",	OFFSET_OF(MP3FILE, description) },
155    { qft_i32,		"daap.songdisccount",	OFFSET_OF(MP3FILE, total_discs) },
156    { qft_i32,		"daap.songdiscnumber",	OFFSET_OF(MP3FILE, disc) },
157    { qft_string,	"daap.songformat",	OFFSET_OF(MP3FILE, type) },
158    { qft_string,	"daap.songgenre",	OFFSET_OF(MP3FILE, genre) },
159    { qft_i32,		"daap.songsamplerate",	OFFSET_OF(MP3FILE, samplerate) },
160    { qft_i32,		"daap.songsize",	OFFSET_OF(MP3FILE, file_size) },
161    //    { qft_i32_const,	"daap.songstarttime",	0 },
162    { qft_i32,		"daap.songstoptime",	OFFSET_OF(MP3FILE, song_length)  },
163    { qft_i32,		"daap.songtime",	OFFSET_OF(MP3FILE, song_length) },
164    { qft_i32,		"daap.songtrackcount",	OFFSET_OF(MP3FILE, total_tracks) },
165    { qft_i32,		"daap.songtracknumber",	OFFSET_OF(MP3FILE, track) },
166    { qft_i32,		"daap.songyear",	OFFSET_OF(MP3FILE, year) },
167    { 0 }
168};
169
170/* Forwards */
171
172int daap_add_mdcl(DAAP_BLOCK *root, char *tag, char *name, short int number) {
173    DAAP_BLOCK *mdcl;
174    int g=1;
175
176    mdcl=daap_add_empty(root,"mdcl");
177    if(mdcl) {
178	g=(int)daap_add_string(mdcl,"mcnm",tag);
179	g = g && daap_add_string(mdcl,"mcna",name);
180	g = g && daap_add_short(mdcl,"mcty",number);
181    }
182
183    return (mdcl ? g : 0);
184}
185
186/*
187 * daap_response_content_codes
188 *
189 * handle the daap block for the /content-codes URI
190 *
191 * This might more easily be done by just emitting a binary
192 * of the content-codes from iTunes, since this really
193 * isn't dynamic
194 */
195
196DAAP_BLOCK *daap_response_content_codes(void) {
197    DAAP_BLOCK *root;
198    DAAP_ITEMS *current=taglist;
199    int g=1;
200
201    DPRINTF(E_DBG,L_DAAP,"Preparing to get content codes\n");
202
203    root=daap_add_empty(NULL,"mccr");
204    if(root) {
205	g = (int)daap_add_int(root,"mstt",200);
206
207	while(current->type) {
208	    g = g && daap_add_mdcl(root,current->tag,current->description,
209				   current->type);
210	    current++;
211	}
212    }
213
214    if(!g) {
215	daap_free(root);
216	return NULL;
217    }
218
219    return root;
220}
221
222
223/*
224 * daap_response_login
225 *
226 * handle the daap block for the /login URI
227 */
228
229DAAP_BLOCK *daap_response_login(char *hostname) {
230    DAAP_BLOCK *root;
231    int g=1;
232    int session=0;
233
234    DPRINTF(E_DBG,L_DAAP,"Preparing to send login response\n");
235
236    root=daap_add_empty(NULL,"mlog");
237    if(root) {
238	g = (int)daap_add_int(root,"mstt",200);
239	session=config_get_next_session();
240	g = g && daap_add_int(root,"mlid",session);
241    }
242
243    if(!g) {
244	daap_free(root);
245	return NULL;
246    }
247
248    DPRINTF(E_LOG,L_DAAP,"%s logging in as session %d\n",hostname,session);
249
250    return root;
251}
252
253/*
254 * daap_response_songlist
255 *
256 * handle the daap block for the /databases/x/items URI
257 */
258
259// fields requestable with meta=... these are really used as bit
260// numbers in a long long, but are defined this way to simplify
261// eventual implementation on platforms without long long support
262typedef enum {
263    // generic meta data
264    metaItemId,
265    metaItemName,
266    metaItemKind,
267    metaPersistentId,
268    metaContainerItemId,
269    metaParentContainerId,
270
271    firstTypeSpecificMetaId,
272
273    // song meta data
274    metaSongAlbum = firstTypeSpecificMetaId,
275    metaSongArtist,
276    metaSongBPM,		/* beats per minute */
277    metaSongBitRate,
278    metaSongComment,
279    metaSongCompilation,
280    metaSongComposer,
281    metaSongDataKind,
282    metaSongDataURL,
283    metaSongDateAdded,
284    metaSongDateModified,
285    metaSongDescription,
286    metaSongDisabled,
287    metaSongDiscCount,
288    metaSongDiscNumber,
289    metaSongEqPreset,
290    metaSongFormat,
291    metaSongGenre,
292    metaSongGrouping,
293    metaSongRelativeVolume,
294    metaSongSampleRate,
295    metaSongSize,
296    metaSongStartTime,
297    metaSongStopTime,
298    metaSongTime,
299    metaSongTrackCount,
300    metaSongTrackNumber,
301    metaSongUserRating,
302    metaSongYear
303} MetaFieldName_t;
304
305// structure mapping meta= tag names to bit numbers
306typedef struct
307{
308    const char*		tag;
309    MetaFieldName_t	bit;
310} MetaDataMap;
311
312// the dmap based tags, defined psuedo separately because they're also
313// needed for DPAP, not that that's at all relevant here
314#define	INCLUDE_GENERIC_META_IDS				\
315    { "dmap.itemid",		metaItemId },			\
316    { "dmap.itemname",		metaItemName },			\
317    { "dmap.itemkind",		metaItemKind },			\
318    { "dmap.persistentid",	metaPersistentId },		\
319    { "dmap.containeritemid",	metaContainerItemId },		\
320    { "dmap.parentcontainerid",	metaParentContainerId }
321
322// map the string names specified in the meta= tag to bit numbers
323static MetaDataMap	gSongMetaDataMap[] = {
324    INCLUDE_GENERIC_META_IDS,
325    { "daap.songalbum",		metaSongAlbum },
326    { "daap.songartist",	metaSongArtist },
327    { "daap.songbitrate",	metaSongBitRate },
328    { "daap.songbeatsperminute",metaSongBPM },
329    { "daap.songcomment",	metaSongComment },
330    { "daap.songcompilation",	metaSongCompilation },
331    { "daap.songcomposer",	metaSongComposer },
332    { "daap.songdatakind",	metaSongDataKind },
333    { "daap.songdataurl",	metaSongDataURL },
334    { "daap.songdateadded",	metaSongDateAdded },
335    { "daap.songdatemodified",	metaSongDateModified },
336    { "daap.songdescription",	metaSongDescription },
337    { "daap.songdisabled",	metaSongDisabled },
338    { "daap.songdisccount",	metaSongDiscCount },
339    { "daap.songdiscnumber",	metaSongDiscNumber },
340    { "daap.songeqpreset",	metaSongEqPreset },
341    { "daap.songformat",	metaSongFormat },
342    { "daap.songgenre",		metaSongGenre },
343    { "daap.songgrouping",	metaSongGrouping },
344    { "daap.songrelativevolume",metaSongRelativeVolume },
345    { "daap.songsamplerate",	metaSongSampleRate },
346    { "daap.songsize",		metaSongSize },
347    { "daap.songstarttime",	metaSongStartTime },
348    { "daap.songstoptime",	metaSongStopTime },
349    { "daap.songtime",		metaSongTime },
350    { "daap.songtrackcount",	metaSongTrackCount },
351    { "daap.songtracknumber",	metaSongTrackNumber },
352    { "daap.songuserrating",	metaSongUserRating },
353    { "daap.songyear",		metaSongYear },
354    { 0,			0 }
355};
356
357typedef unsigned long long	MetaField_t;
358
359// turn the meta= parameter into a bitfield representing the requested
360// fields.  The format is actually meta=<tag>[,<tag>...] where <tag>
361// is any of the strings in the table above
362MetaField_t encodeMetaRequest(char* meta, MetaDataMap* map)
363{
364    MetaField_t		bits = 0;
365    char* start;
366    char* end;
367    MetaDataMap* m;
368
369    for(start = meta ; *start ; start = end)
370    {
371	int	len;
372
373	if(0 == (end = strchr(start, ',')))
374	    end = start + strlen(start);
375
376	len = end - start;
377
378	if(*end != 0)
379	    end++;
380
381	for(m = map ; m->tag ; ++m)
382	    if(!strncmp(m->tag, start, len))
383		break;
384
385	if(m->tag)
386	    bits |= (((MetaField_t) 1) << m->bit);
387	else
388	    DPRINTF(E_WARN,L_DAAP,"Unknown meta code: %.*s\n", len, start);
389    }
390
391    DPRINTF(E_DBG, L_DAAP, "meta codes: %llu\n", bits);
392
393    return bits;
394}
395
396int wantsMeta(MetaField_t meta, MetaFieldName_t fieldNo)
397{
398    return 0 != (meta & (((MetaField_t) 1) << fieldNo));
399}
400
401DAAP_BLOCK *daap_response_songlist(char* metaStr, char* query) {
402    DAAP_BLOCK *root;
403    int g=1;
404    DAAP_BLOCK *mlcl;
405    DAAP_BLOCK *mlit;
406    ENUMHANDLE henum;
407    MP3FILE *current;
408    MetaField_t meta;
409
410    query_node_t*	filter = 0;
411    int			songs = 0;
412
413    DPRINTF(E_DBG,L_DAAP,"enter daap_response_songlist\n");
414
415    // if the meta tag is specified, encode it, if it's not specified
416    // we're given the latitude to select our own subset, for
417    // simplicity we just include everything.
418    if(0 == metaStr)
419	meta = (MetaField_t) -1ll;
420    else
421	meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
422
423    if(0 != query) {
424	filter = query_build(query, song_fields);
425	DPRINTF(E_INF,L_DAAP|L_QRY,"query: %s\n", query);
426	if(err_debuglevel >= E_INF) /* this is broken */
427	    query_dump(stderr,filter, 0);
428    }
429
430    DPRINTF(E_DBG,L_DAAP|L_DB,"Preparing to send db items\n");
431
432    henum=db_enum_begin();
433    if((!henum) && (db_get_song_count())) {
434	DPRINTF(E_DBG,L_DAAP|L_DB,"Can't get enum handle - exiting daap_response_songlist\n");
435	return NULL;
436    }
437
438    root=daap_add_empty(NULL,"adbs");
439    if(root) {
440	g = (int)daap_add_int(root,"mstt",200);
441	g = g && daap_add_char(root,"muty",0);
442	g = g && daap_add_int(root,"mtco",0);
443	g = g && daap_add_int(root,"mrco",0);
444
445	mlcl=daap_add_empty(root,"mlcl");
446
447	if(mlcl) {
448	    while(g && (current=db_enum(&henum))) {
449		if(filter == 0 || query_test(filter, current))
450		{
451		    DPRINTF(E_DBG,L_DAAP|L_DB,"Got entry for %s\n",current->fname);
452		    // song entry generation extracted for usage with
453		    // playlists as well
454		    g = 0 != daap_add_song_entry(mlcl, current, meta);
455		    songs++;
456		}
457	    }
458	} else g=0;
459    }
460
461    db_enum_end(henum);
462
463    if(filter != 0)
464	query_free(filter);
465
466    if(!g) {
467	DPRINTF(E_DBG,L_DAAP|L_DB,"Error enumerating db - exiting daap_response_songlist\n");
468	daap_free(root);
469	return NULL;
470    }
471
472    DPRINTF(E_DBG,L_DAAP|L_DB,"Successfully enumerated database - %d items\n",songs);
473
474    daap_set_int(root, "mtco", songs);
475    daap_set_int(root, "mrco", songs);
476
477    DPRINTF(E_DBG,L_DAAP,"Exiting daap_response_songlist\n");
478    return root;
479}
480
481
482//
483// extracted song entry generation used by both database item lists
484// and play list item lists
485//
486DAAP_BLOCK* daap_add_song_entry(DAAP_BLOCK* mlcl, MP3FILE* song, MetaField_t meta)
487{
488    DAAP_BLOCK* mlit;
489    int g = 1;
490
491    mlit=daap_add_empty(mlcl,"mlit");
492    if(mlit) {
493	if(wantsMeta(meta, metaItemKind))
494	    g = g && daap_add_char(mlit,"mikd",song->item_kind); /* audio */
495
496	if(wantsMeta(meta, metaSongDataKind))
497	    g = g && daap_add_char(mlit,"asdk",song->data_kind); /* local file */
498
499	if(wantsMeta(meta, metaSongDataURL))
500	    g = g && daap_add_string(mlit,"asul",song->url);
501
502	if(song->album && (wantsMeta(meta, metaSongAlbum)))
503	    g = g && daap_add_string(mlit,"asal",song->album);
504
505	if(song->artist && wantsMeta(meta, metaSongArtist))
506	    g = g && daap_add_string(mlit,"asar",song->artist);
507
508	if(song->bpm && (wantsMeta(meta, metaSongBPM)))
509	    g = g && daap_add_short(mlit,"asbt",song->bpm); /* bpm */
510
511	if(song->bitrate && (wantsMeta(meta, metaSongBitRate)))
512	    g = g && daap_add_short(mlit,"asbr",song->bitrate); /* bitrate!! */
513
514	if(song->comment && (wantsMeta(meta, metaSongComment)))
515	    g = g && daap_add_string(mlit,"ascm",song->comment); /* comment */
516
517        if(song->compilation && (wantsMeta(meta, metaSongCompilation)))
518            g = g && daap_add_char(mlit,"asco",song->compilation); /* compilation */
519
520	if(song->composer && (wantsMeta(meta, metaSongComposer)))
521	    g = g && daap_add_string(mlit,"ascp",song->composer); /* composer */
522
523	if(song->grouping && (wantsMeta(meta, metaSongGrouping)))
524	    g = g && daap_add_string(mlit,"agrp",song->grouping); /* grouping */
525
526	if(song->time_added && (wantsMeta(meta, metaSongDateAdded)))
527	    g = g && daap_add_int(mlit,"asda",song->time_added); /* added */
528
529	if(song->time_modified && (wantsMeta(meta, metaSongDateModified)))
530	    g = g && daap_add_int(mlit,"asdm",song->time_modified); /* modified */
531
532	if(song->total_discs && (wantsMeta(meta, metaSongDiscCount)))
533	    /* # of discs */
534	    g = g && daap_add_short(mlit,"asdc",song->total_discs);
535
536	if(song->disc && (wantsMeta(meta, metaSongDiscNumber)))
537	    /* disc number */
538	    g = g && daap_add_short(mlit,"asdn",song->disc);
539
540	// asdk must be early in the item, moved to the top
541	// g = g && daap_add_char(mlit,"asdk",0); /* song datakind? */
542	// aseq - null string!
543
544	if(song->genre && (wantsMeta(meta, metaSongGenre)))
545	    g = g && daap_add_string(mlit,"asgn",song->genre); /* genre */
546
547	if(wantsMeta(meta, metaItemId))
548	    g = g && daap_add_int(mlit,"miid",song->id); /* id */
549
550	if(wantsMeta(meta, metaPersistentId))
551	    g = g && daap_add_long(mlit,"mper",0,song->id);
552
553	/* these quite go hand in hand */
554	if(wantsMeta(meta, metaSongFormat))
555	    g = g && daap_add_string(mlit,"asfm",song->type); /* song format */
556
557	if(wantsMeta(meta, metaSongDescription))
558	    g = g && daap_add_string(mlit, "asdt",song->description);
559
560	if(wantsMeta(meta, metaItemName))
561	    g = g && daap_add_string(mlit,"minm",song->title); /* descr */
562
563	// mper (long)
564	// g = g && daap_add_char(mlit,"asdb",0); /* disabled */
565	// g = g && daap_add_char(mlit,"asrv",0); /* rel vol */
566	if(song->samplerate && (wantsMeta(meta, metaSongSampleRate)))
567	    g = g && daap_add_int(mlit,"assr",song->samplerate); /* samp rate */
568
569	if(song->file_size && (wantsMeta(meta, metaSongSize)))
570	    g = g && daap_add_int(mlit,"assz",song->file_size); /* Size! */
571
572	if(wantsMeta(meta, metaSongStartTime))
573	    g = g && daap_add_int(mlit,"asst",0); /* song start time? */
574	if(wantsMeta(meta, metaSongStopTime))
575	    g = g && daap_add_int(mlit,"assp",0); /* song stop time */
576
577	if(song->song_length && (wantsMeta(meta, metaSongTime)))
578	    g = g && daap_add_int(mlit,"astm",song->song_length); /* song time */
579
580	if(song->total_tracks && (wantsMeta(meta, metaSongTrackCount)))
581	    g = g && daap_add_short(mlit,"astc",song->total_tracks); /* track count */
582
583	if(song->track && (wantsMeta(meta, metaSongTrackNumber)))
584	    g = g && daap_add_short(mlit,"astn",song->track); /* track number */
585
586	// g = g && daap_add_char(mlit,"asur",3); /* rating */
587	if(song->year && (wantsMeta(meta, metaSongYear)))
588	    g = g && daap_add_short(mlit,"asyr",song->year);
589    }
590
591    if(g == 0)
592    {
593	daap_free(mlit);
594	mlit = 0;
595    }
596
597    return mlit;
598}
599
600/*
601 * daap_response_update
602 *
603 * handle the daap block for the /update URI
604 */
605
606DAAP_BLOCK *daap_response_update(int fd, int clientver) {
607    DAAP_BLOCK *root;
608    int g=1;
609    fd_set rset;
610    struct timeval tv;
611    int result;
612
613    DPRINTF(E_DBG,L_DAAP,"Preparing to send update response\n");
614
615    while(clientver == db_version()) {
616	FD_ZERO(&rset);
617	FD_SET(fd,&rset);
618
619	tv.tv_sec=30;
620	tv.tv_usec=0;
621
622	result=select(fd+1,&rset,NULL,NULL,&tv);
623	if(FD_ISSET(fd,&rset)) {
624	    /* can't be ready for read, must be error */
625	    DPRINTF(E_DBG,L_DAAP,"Socket closed?\n");
626
627	    return NULL;
628	}
629    }
630
631    root=daap_add_empty(NULL,"mupd");
632    if(root) {
633	g = (int)daap_add_int(root,"mstt",200);
634	/* theoretically, this would go up if the db changes? */
635	g = g && daap_add_int(root,"musr",db_version());
636    }
637
638    if(!g) {
639	daap_free(root);
640	return NULL;
641    }
642
643    return root;
644}
645
646
647/*
648 * daap_response_playlists
649 *
650 * handle the daap block for the /databases/containers URI
651 */
652DAAP_BLOCK *daap_response_playlists(char *name) {
653    DAAP_BLOCK *root=NULL;
654    DAAP_BLOCK *mlcl=NULL;
655    DAAP_BLOCK *mlit=NULL;
656    int g=1;
657    int playlistid;
658    ENUMHANDLE henum;
659
660    DPRINTF(E_DBG,L_DAAP,"Preparing to send playlists\n");
661
662    root=daap_add_empty(NULL,"aply");
663    if(root) {
664	g = (int)daap_add_int(root,"mstt",200);
665	g = g && daap_add_char(root,"muty",0);
666	g = g && daap_add_int(root,"mtco",1 + db_get_playlist_count());
667	g = g && daap_add_int(root,"mrco",1 + db_get_playlist_count());
668	mlcl=daap_add_empty(root,"mlcl");
669	if(mlcl) {
670	    mlit=daap_add_empty(mlcl,"mlit");
671	    if(mlit) {
672		g = g && daap_add_int(mlit,"miid",0x1);
673		g = g && daap_add_long(mlit,"mper",0,1);
674		g = g && daap_add_string(mlit,"minm",name);
675		g = g && daap_add_int(mlit,"mimc",db_get_song_count());
676	    }
677
678	    g = g && mlit;
679
680	    /* add the rest of the playlists */
681	    henum=db_playlist_enum_begin();
682	    while(henum) {
683		playlistid=db_playlist_enum(&henum);
684		DPRINTF(E_DBG,L_DAAP|L_PL,"Returning playlist %d\n",playlistid);
685		DPRINTF(E_DBG,L_DAAP|L_PL,"  -- Songs: %d\n",
686			db_get_playlist_entry_count(playlistid));
687		DPRINTF(E_DBG,L_DAAP|L_PL,"  -- Smart: %s\n",
688			db_get_playlist_is_smart(playlistid) ?
689			"Yes" : "No");
690		mlit=daap_add_empty(mlcl,"mlit");
691		if(mlit) {
692		    g = g && daap_add_int(mlit,"miid",playlistid);
693		    g = g && daap_add_long(mlit,"mper",0,playlistid);
694		    g = g && daap_add_string(mlit,"minm",db_get_playlist_name(playlistid));
695		    g = g && daap_add_int(mlit,"mimc",db_get_playlist_entry_count(playlistid));
696		    if(db_get_playlist_is_smart(playlistid)) {
697			g = g && daap_add_char(mlit,"aeSP",0x1);
698		    }
699		}
700		g = g && mlit;
701	    }
702	    db_playlist_enum_end(henum);
703	}
704
705    }
706
707    g = g && mlcl;
708
709    if(!g) {
710	DPRINTF(E_INF,L_DAAP,"Memory problem.  Bailing\n");
711	daap_free(root);
712	return NULL;
713    }
714
715    return root;
716}
717
718/*
719 * daap_response_dbinfo
720 *
721 * handle the daap block for the /databases URI
722 */
723
724DAAP_BLOCK *daap_response_dbinfo(char *name) {
725    DAAP_BLOCK *root=NULL;
726    DAAP_BLOCK *mlcl=NULL;
727    DAAP_BLOCK *mlit=NULL;
728    int g=1;
729
730    DPRINTF(E_DBG,L_DAAP|L_DB,"Preparing to send db info\n");
731
732    root=daap_add_empty(NULL,"avdb");
733    if(root) {
734	g = (int)daap_add_int(root,"mstt",200);
735	g = g && daap_add_char(root,"muty",0);
736	g = g && daap_add_int(root,"mtco",1);
737	g = g && daap_add_int(root,"mrco",1);
738	mlcl=daap_add_empty(root,"mlcl");
739	if(mlcl) {
740	    mlit=daap_add_empty(mlcl,"mlit");
741	    if(mlit) {
742		g = g && daap_add_int(mlit,"miid",1);
743		g = g && daap_add_long(mlit,"mper",0,1);
744		g = g && daap_add_string(mlit,"minm",name);
745		g = g && daap_add_int(mlit,"mimc",db_get_song_count()); /* songs */
746		g = g && daap_add_int(mlit,"mctc",1 + db_get_playlist_count()); /* playlists */
747	    }
748	}
749    }
750
751    g = g && mlcl && mlit;
752
753    if(!g) {
754	DPRINTF(E_INF,L_DAAP,"Memory problem.  Bailing\n");
755	daap_free(root);
756	return NULL;
757    }
758
759    DPRINTF(E_DBG,L_DAAP|L_DB,"Sent db info... %d songs, %d playlists\n",db_get_song_count(),
760	    db_get_playlist_count());
761
762    return root;
763}
764
765/*
766 * daap_response_server_info
767 *
768 * handle the daap block for the /server-info URI
769 */
770DAAP_BLOCK *daap_response_server_info(char *name, char *client_version) {
771    DAAP_BLOCK *root;
772    int g=1;
773    int mpro = 2 << 16;
774    int apro = 3 << 16;
775
776    DPRINTF(E_DBG,L_DAAP,"Preparing to send server-info for client ver %s\n",client_version);
777
778    root=daap_add_empty(NULL,"msrv");
779
780    if(root) {
781	if((client_version) && (!strcmp(client_version,"1.0"))) {
782	    mpro = 1 << 16;
783	    apro = 1 << 16;
784	}
785
786	if((client_version) && (!strcmp(client_version,"2.0"))) {
787	    mpro = 1 << 16;
788	    apro = 2 << 16;
789	}
790
791	g = (int)daap_add_int(root,"mstt",200); /* result */
792	g = g && daap_add_int(root,"mpro",mpro); /* dmap proto ? */
793	g = g && daap_add_int(root,"apro",apro); /* daap protocol */
794
795	g = g && daap_add_string(root,"minm",name); /* server name */
796
797#if 0
798	/* DWB: login isn't actually required since the session id
799	   isn't recorded, and isn't actually used for anything */
800	/* logon is always required, even if a password isn't */
801	g = g && daap_add_char(root,"mslr",1);
802#endif
803
804	/* authentication method is 0 for nothing, 1 for name and
805	   password, 2 for password only */
806	g = g && daap_add_char(root,"msau", config.readpassword != NULL ? 2 : 0);
807
808	/* actual time out seems faster then 30 minutes */
809	g = g && daap_add_int(root,"mstm",1800); /* timeout  - iTunes=1800 */
810
811	/* presence of most of the support* variables indicates
812	   support, the actual value is required to be zero, I've
813	   commented out the ones I don't believe are actually
814	   supported */
815	g = g && daap_add_char(root,"msex",0); /* extensions */
816	g = g && daap_add_char(root,"msix",0); /* indexing? */
817
818	g = g && daap_add_char(root,"msbr",0); /* browsing */
819	g = g && daap_add_char(root,"msqy",0); /* queries */
820
821	g = g && daap_add_char(root,"msup",0); /* update */
822
823#if 0
824	g = g && daap_add_char(root,"mspi",0); /* persistant ids */
825	g = g && daap_add_char(root,"msal",0); /* autologout */
826	g = g && daap_add_char(root,"msrs",0); /* resolve?  req. persist id */
827#endif
828        g = g && daap_add_int(root,"msdc",1); /* database count */
829    }
830
831    if(!g) {
832	daap_free(root);
833	return NULL;
834    }
835
836    return root;
837}
838
839
840/*
841 * daap_response_playlist_items
842 *
843 * given a playlist number, return the items on the playlist
844 */
845DAAP_BLOCK *daap_response_playlist_items(unsigned int playlist, char* metaStr, char* query) {
846    DAAP_BLOCK *root;
847    DAAP_BLOCK *mlcl;
848    DAAP_BLOCK *mlit;
849    ENUMHANDLE henum;
850    MP3FILE *current;
851    unsigned long int itemid;
852    int g=1;
853    unsigned long long meta;
854    query_node_t*	filter = 0;
855    int			songs = 0;
856
857    // if no meta information is specifically requested, return only
858    // the base play list information.  iTunes only requests the base
859    // information as it rebuilds the entire database locally so it's
860    // just replicated information
861    if(0 == metaStr)
862	meta = ((1ll << metaItemId) |
863		(1ll << metaItemName) |
864		(1ll << metaItemKind) |
865		(1ll << metaContainerItemId) |
866		(1ll << metaParentContainerId));
867    else
868	meta = encodeMetaRequest(metaStr, gSongMetaDataMap);
869
870    if(0 != query) {
871	filter = query_build(query, song_fields);
872	DPRINTF(E_INF,L_DAAP|L_QRY,"query: %s\n",query);
873	if(err_debuglevel >= E_INF) /* this is broken */
874	    query_dump(stderr,filter, 0);
875    }
876
877    DPRINTF(E_DBG,L_DAAP|L_PL,"Preparing to send playlist items for pl #%d\n",playlist);
878
879    if(playlist == 1) {
880	henum=db_enum_begin();
881    } else {
882	henum=db_playlist_items_enum_begin(playlist);
883    }
884
885    /* we can allow an empty playlist...
886    if(!henum)
887	return NULL;
888    */
889
890    root=daap_add_empty(NULL,"apso");
891    if(root) {
892	g = (int)daap_add_int(root,"mstt",200);
893	g = g && daap_add_char(root,"muty",0);
894	g = g && daap_add_int(root,"mtco",0);
895	g = g && daap_add_int(root,"mrco",0);
896
897	mlcl=daap_add_empty(root,"mlcl");
898
899	if(mlcl) {
900	    if(playlist == 1) {
901		while((current=db_enum(&henum))) {
902		    if(0 == filter || query_test(filter, current))
903		    {
904			songs++;
905			mlit=daap_add_song_entry(mlcl, current, meta);
906			if(0 != mlit) {
907			    if(wantsMeta(meta, metaContainerItemId))
908				g = g && daap_add_int(mlit,"mcti",current->id);
909			} else g=0;
910		    }
911		}
912	    } else { /* other playlist */
913		while((itemid=db_playlist_items_enum(&henum)) != -1) {
914		    current = db_find(itemid);
915		    if(0 != current) {
916			if(0 == filter || query_test(filter, current))
917			{
918			    songs++;
919			    DPRINTF(E_DBG,L_DAAP|L_PL,"Adding itemid %lu\n",itemid);
920			    mlit=daap_add_song_entry(mlcl,current,meta);
921			    if(0 != mlit) {
922				if(wantsMeta(meta, metaContainerItemId)) // current->id?
923				    //				    g = g && daap_add_int(mlit,"mcti",playlist);
924				    g = g && daap_add_int(mlit,"mcti",current->id);
925			    } else g = 0;
926			}
927			db_dispose(current);
928			free(current);
929		    } else g = 0;
930		}
931	    }
932	} else g=0;
933    }
934
935    if(playlist == 1)
936	db_enum_end(henum);
937    else
938	db_playlist_items_enum_end(henum);
939
940    if(0 != filter)
941	query_free(filter);
942
943    if(!g) {
944	daap_free(root);
945	return NULL;
946    }
947
948    DPRINTF(E_DBG,L_DAAP|L_PL,"Sucessfully enumerated %d items\n",songs);
949
950    daap_set_int(root, "mtco", songs);
951    daap_set_int(root, "mrco", songs);
952
953    return root;
954}
955
956//
957// handle the index= parameter
958// format is:
959//	index=<item>		a single item from the list by index
960//	index=<l>-<h>		a range of items from the list by
961//				index from l to h inclusive
962//	index=<l>-		a range of items from the list by
963//				index from l to the end of the list
964//	index=-<n>		the last <n> items from the list
965//
966void daap_handle_index(DAAP_BLOCK* block, const char* index)
967{
968    int		first;
969    int		count;
970    int		size;
971    char*	ptr;
972    DAAP_BLOCK*	list;
973    DAAP_BLOCK*	item;
974    DAAP_BLOCK**back;
975    int		n;
976
977    // get the actual list
978    if(0 == (list = daap_find(block, "mlcl")))
979	return;
980
981    // count the items in the list
982    for(size = 0, item = list->children ; item ; item = item->next)
983	if(!strncmp(item->tag, "mlit", 4))
984	    size++;
985
986    // range start
987    n = strtol(index, &ptr, 10);
988
989    // "-n": tail range, keep the last n entries
990    if(n < 0)
991    {
992	n *= -1;
993
994	// if we have too many entries, figure out which to keep
995	if(n < size)
996	{
997	    first = size - n;
998	    count = n;
999	}
1000
1001	// if we don't have enough entries, keep what we have
1002	else
1003	{
1004	    first = 0;
1005	    count = size;
1006	}
1007    }
1008
1009    // "n": single item
1010    else if(0 == *ptr)
1011    {
1012	// item exists, return one item at the appropriate index
1013	if(n < size)
1014	{
1015	    first = n;
1016	    count = 1;
1017	}
1018
1019	// item doesn't exist, return zero items
1020	else
1021	{
1022	    first = 0;
1023	    count = 0;
1024	}
1025    }
1026
1027    // "x-y": true range
1028    else if('-' == *ptr)
1029    {
1030	// record range start
1031	first = n;
1032
1033	// "x-": x to end
1034	if(*++ptr == 0)
1035	    n = size;
1036
1037	// record range end
1038	else
1039	{
1040	    n = strtol(ptr, &ptr, 10) + 1;
1041
1042	    // wanting more than there is, return fewer
1043	    if(n > size)
1044		n = size;
1045	}
1046
1047	count = n - first;
1048    }
1049
1050    // update the returned record count entry, it's required, so
1051    // should have already be created
1052    daap_set_int(block, "mrco", count);
1053
1054    DPRINTF(E_INF,L_DAAP|L_IND, "index:%s first:%d count:%d\n", index, first, count);
1055
1056    // remove the first first entries
1057    for(back = &list->children ; *back && first ; )
1058	if(!strncmp((**back).tag, "mlit", 4))
1059	{
1060	    DPRINTF(E_DBG,L_DAAP|L_IND, "first:%d removing\n", first);
1061	    daap_remove(*back);
1062	    first--;
1063	}
1064	else
1065	    back = &(**back).next;
1066
1067    // keep the next count items
1068    for( ; *back && count ; back = &(**back).next)
1069	if(!strncmp((**back).tag, "mlit", 4))
1070	{
1071	    DPRINTF(E_DBG,L_DAAP|L_IND,"count:%d keeping\n", count);
1072	    count--;
1073	}
1074
1075    // remove the rest of items
1076    while(*back)
1077    {
1078	if(!strncmp((**back).tag, "mlit", 4))
1079	{
1080	    DPRINTF(E_DBG,L_DAAP|L_IND,"removing spare\n");
1081	    daap_remove(*back);
1082	}
1083	else
1084	    back = &(**back).next;
1085    }
1086}
1087
1088typedef struct _browse_item browse_item;
1089struct _browse_item
1090{
1091    char*		name;
1092    browse_item*	next;
1093};
1094
1095static void add_browse_item(browse_item** root, char* name)
1096{
1097    browse_item*	item;
1098
1099    while(0 != (item = *root) && strcasecmp(item->name, name) < 0)
1100	root = &item->next;
1101
1102    if(item && strcasecmp(item->name, name) == 0)
1103	return;
1104
1105    item = calloc(1, sizeof(browse_item));
1106    item->name = strdup(name);
1107    item->next = *root;
1108    *root = item;
1109}
1110
1111static void free_browse_items(browse_item* root)
1112{
1113    while(0 != root)
1114    {
1115	browse_item*	next = root->next;
1116
1117	free(root->name);
1118	free(root);
1119
1120	root = next;
1121    }
1122}
1123
1124static int count_browse_items(browse_item* root)
1125{
1126    int	count = 0;
1127
1128    while(0 != root)
1129    {
1130	root = root->next;
1131	count++;
1132    }
1133
1134    return count;
1135}
1136
1137/* in theory each type of browse has a separate subset of these fields
1138   which can be used for filtering, but it's just not worth the effort
1139   and doesn't save anything */
1140static query_field_t browse_fields[] = {
1141    { qft_string,	"daap.songartist",	OFFSET_OF(MP3FILE, artist) },
1142    { qft_string,	"daap.songalbum",	OFFSET_OF(MP3FILE, album) },
1143    { qft_string,	"daap.songgenre",	OFFSET_OF(MP3FILE, genre) },
1144    { qft_string,	"daap.songcomposer",	OFFSET_OF(MP3FILE, composer) },
1145    { 0 }
1146};
1147
1148DAAP_BLOCK* daap_response_browse(const char* name, const char* filter)
1149{
1150    MP3FILE*		current;
1151    ENUMHANDLE		henum;
1152    size_t		field;
1153    char*		l_type;
1154    browse_item*	items = 0;
1155    browse_item*	item;
1156    DAAP_BLOCK*		root = 0;
1157    query_node_t*	query = 0;
1158
1159    if(!strcmp(name, "artists"))
1160    {
1161	field = OFFSET_OF(MP3FILE, artist);
1162	l_type = "abar";
1163    }
1164    else if(!strcmp(name, "genres"))
1165    {
1166	field = OFFSET_OF(MP3FILE, genre);
1167	l_type = "abgn";
1168    }
1169    else if(!strcmp(name, "albums"))
1170    {
1171	field = OFFSET_OF(MP3FILE, album);
1172	l_type = "abal";
1173    }
1174    else if(!strcmp(name, "composers"))
1175    {
1176	field = OFFSET_OF(MP3FILE, composer);
1177	l_type = "abcp";
1178    }
1179    else
1180    {
1181	DPRINTF(E_WARN,L_DAAP|L_BROW,"Invalid browse request: %s\n", name);
1182	return NULL;
1183    }
1184
1185    if(0 != filter &&
1186       0 == (query = query_build(filter, browse_fields)))
1187	return NULL;
1188
1189    if(query) {
1190	DPRINTF(E_INF,L_DAAP|L_BROW|L_QRY,"query: %s\n",filter);
1191	if(err_debuglevel >= E_INF) /* this is broken */
1192	    query_dump(stderr,query, 0);
1193    }
1194
1195    if(0 == (henum = db_enum_begin()))
1196	return NULL;
1197
1198    while((current = db_enum(&henum)))
1199    {
1200	if(0 == query || query_test(query, current))
1201	{
1202	    char*	name = * (char**) ((size_t) current + field);
1203
1204	    if(0 != name)
1205		add_browse_item(&items, name);
1206	}
1207    }
1208
1209    db_enum_end(henum);
1210
1211    if(0 != (root = daap_add_empty(0, "abro")))
1212    {
1213	int		count = count_browse_items(items);
1214	DAAP_BLOCK*	mlcl;
1215
1216	if(!daap_add_int(root, "mstt", 200) ||
1217	   !daap_add_int(root, "mtco", count) ||
1218	   !daap_add_int(root, "mrco", count) ||
1219	   0 == (mlcl = daap_add_empty(root, l_type)))
1220	    goto error;
1221
1222	for(item = items ; item ; item = item->next)
1223	{
1224	    if(!daap_add_string(mlcl, "mlit", item->name))
1225		goto error;
1226	}
1227    }
1228
1229    free_browse_items(items);
1230
1231    if(0 != query)
1232	query_free(query);
1233
1234    return root;
1235
1236 error:
1237    free_browse_items(items);
1238
1239    if(0 != query)
1240	query_free(query);
1241
1242    if(root != 0)
1243	daap_free(root);
1244
1245    return NULL;
1246}
1247