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