1/* MiniDLNA media server 2 * Copyright (C) 2009 Justin Maggard 3 * 4 * This file is part of MiniDLNA. 5 * 6 * MiniDLNA is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * MiniDLNA is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. 17 */ 18#include "config.h" 19#ifdef TIVO_SUPPORT 20#include <stdio.h> 21#include <stdlib.h> 22#include <string.h> 23#include <libgen.h> 24#include <time.h> 25#include <sys/stat.h> 26 27#include "tivo_utils.h" 28#include "upnpglobalvars.h" 29#include "upnphttp.h" 30#include "upnpsoap.h" 31#include "utils.h" 32#include "sql.h" 33#include "log.h" 34 35static void 36SendRootContainer(struct upnphttp *h) 37{ 38 char *resp; 39 int len; 40 41 len = xasprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n" 42 "<TiVoContainer>" 43 "<Details>" 44 "<ContentType>x-container/tivo-server</ContentType>" 45 "<SourceFormat>x-container/folder</SourceFormat>" 46 "<TotalDuration>0</TotalDuration>" 47 "<TotalItems>3</TotalItems>" 48 "<Title>%s</Title>" 49 "</Details>" 50 "<ItemStart>0</ItemStart>" 51 "<ItemCount>3</ItemCount>" 52 "<Item>" 53 "<Details>" 54 "<ContentType>x-container/tivo-photos</ContentType>" 55 "<SourceFormat>x-container/folder</SourceFormat>" 56 "<Title>Pictures on %s</Title>" 57 "</Details>" 58 "<Links>" 59 "<Content>" 60 "<Url>/TiVoConnect?Command=QueryContainer&Container=3</Url>" 61 "</Content>" 62 "</Links>" 63 "</Item>" 64 "<Item>" 65 "<Details>" 66 "<ContentType>x-container/tivo-music</ContentType>" 67 "<SourceFormat>x-container/folder</SourceFormat>" 68 "<Title>Music on %s</Title>" 69 "</Details>" 70 "<Links>" 71 "<Content>" 72 "<Url>/TiVoConnect?Command=QueryContainer&Container=1</Url>" 73 "</Content>" 74 "</Links>" 75 "</Item>" 76 "<Item>" 77 "<Details>" 78 "<ContentType>x-container/tivo-videos</ContentType>" 79 "<SourceFormat>x-container/folder</SourceFormat>" 80 "<Title>Videos on %s</Title>" 81 "</Details>" 82 "<Links>" 83 "<Content>" 84 "<Url>/TiVoConnect?Command=QueryContainer&Container=2</Url>" 85 "<ContentType>x-container/tivo-videos</ContentType>" 86 "</Content>" 87 "</Links>" 88 "</Item>" 89 "</TiVoContainer>", 90 friendly_name, friendly_name, friendly_name, friendly_name); 91 BuildResp_upnphttp(h, resp, len); 92 free(resp); 93 SendResp_upnphttp(h); 94} 95 96static void 97SendFormats(struct upnphttp *h, const char *sformat) 98{ 99 char *resp; 100 int len; 101 102 len = xasprintf(&resp, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" 103 "<TiVoFormats>" 104 "<Format>" 105 "<ContentType>video/x-tivo-mpeg</ContentType>" 106 "<Description/>" 107 "</Format>" 108 "<Format>" 109 "<ContentType>%s</ContentType>" 110 "<Description/>" 111 "</Format>" 112 "</TiVoFormats>", sformat); 113 BuildResp_upnphttp(h, resp, len); 114 free(resp); 115 SendResp_upnphttp(h); 116} 117 118static char * 119tivo_unescape_tag(char *tag) 120{ 121 modifyString(tag, "&amp;", "&", 1); 122 modifyString(tag, "&amp;lt;", "<", 1); 123 modifyString(tag, "&lt;", "<", 1); 124 modifyString(tag, "&amp;gt;", ">", 1); 125 modifyString(tag, "&gt;", ">", 1); 126 modifyString(tag, "&quot;", """, 1); 127 return tag; 128} 129 130#define FLAG_SEND_RESIZED 0x01 131#define FLAG_NO_PARAMS 0x02 132#define FLAG_VIDEO 0x04 133static int 134callback(void *args, int argc, char **argv, char **azColName) 135{ 136 struct Response *passed_args = (struct Response *)args; 137 char *id = argv[0], *class = argv[1], *detailID = argv[2], *size = argv[3], *title = argv[4], *duration = argv[5], 138 *bitrate = argv[6], *sampleFrequency = argv[7], *artist = argv[8], *album = argv[9], *genre = argv[10], 139 *comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14]; 140 struct string_s *str = passed_args->str; 141 142 if( strncmp(class, "item", 4) == 0 ) 143 { 144 int flags = 0; 145 tivo_unescape_tag(title); 146 if( strncmp(mime, "audio", 5) == 0 ) 147 { 148 flags |= FLAG_NO_PARAMS; 149 strcatf(str, "<Item><Details>" 150 "<ContentType>%s</ContentType>" 151 "<SourceFormat>%s</SourceFormat>" 152 "<SourceSize>%s</SourceSize>", 153 "audio/*", mime, size); 154 strcatf(str, "<SongTitle>%s</SongTitle>", title); 155 if( date ) 156 strcatf(str, "<AlbumYear>%.*s</AlbumYear>", 4, date); 157 } 158 else if( strcmp(mime, "image/jpeg") == 0 ) 159 { 160 flags |= FLAG_SEND_RESIZED; 161 strcatf(str, "<Item><Details>" 162 "<ContentType>%s</ContentType>" 163 "<SourceFormat>%s</SourceFormat>" 164 "<SourceSize>%s</SourceSize>", 165 "image/*", mime, size); 166 if( date ) 167 { 168 struct tm tm; 169 memset(&tm, 0, sizeof(tm)); 170 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not 171 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm); 172 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm)); 173 } 174 if( comment ) 175 strcatf(str, "<Caption>%s</Caption>", comment); 176 } 177 else if( strncmp(mime, "video", 5) == 0 ) 178 { 179 char *episode; 180 flags |= FLAG_VIDEO; 181 strcatf(str, "<Item><Details>" 182 "<ContentType>%s</ContentType>" 183 "<SourceFormat>%s</SourceFormat>" 184 "<SourceSize>%s</SourceSize>", 185 mime, mime, size); 186 episode = strstr(title, " - "); 187 if( episode ) 188 { 189 strcatf(str, "<EpisodeTitle>%s</EpisodeTitle>", episode+3); 190 *episode = '\0'; 191 } 192 if( date ) 193 { 194 struct tm tm; 195 memset(&tm, 0, sizeof(tm)); 196 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not 197 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm); 198 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm)); 199 } 200 if( comment ) 201 strcatf(str, "<Description>%s</Description>", tivo_unescape_tag(comment)); 202 } 203 else 204 { 205 return 0; 206 } 207 strcatf(str, "<Title>%s</Title>", title); 208 if( artist ) { 209 strcatf(str, "<ArtistName>%s</ArtistName>", tivo_unescape_tag(artist)); 210 } 211 if( album ) { 212 strcatf(str, "<AlbumTitle>%s</AlbumTitle>", tivo_unescape_tag(album)); 213 } 214 if( genre ) { 215 strcatf(str, "<MusicGenre>%s</MusicGenre>", tivo_unescape_tag(genre)); 216 } 217 if( resolution ) { 218 char *width = strsep(&resolution, "x"); 219 strcatf(str, "<SourceWidth>%s</SourceWidth>" 220 "<SourceHeight>%s</SourceHeight>", 221 width, resolution); 222 } 223 if( duration ) { 224 strcatf(str, "<Duration>%d</Duration>", 225 atoi(strrchr(duration, '.')+1) + (1000*atoi(strrchr(duration, ':')+1)) 226 + (60000*atoi(strrchr(duration, ':')-2)) + (3600000*atoi(duration))); 227 } 228 if( bitrate ) { 229 strcatf(str, "<SourceBitRate>%s</SourceBitRate>", bitrate); 230 } 231 if( sampleFrequency ) { 232 strcatf(str, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency); 233 } 234 strcatf(str, "</Details><Links>" 235 "<Content>" 236 "<ContentType>%s</ContentType>" 237 "<Url>/%s/%s.%s</Url>%s" 238 "</Content>", 239 mime, 240 (flags & FLAG_SEND_RESIZED) ? "Resized" : "MediaItems", 241 detailID, mime_to_ext(mime), 242 (flags & FLAG_NO_PARAMS) ? "<AcceptsParams>No</AcceptsParams>" : ""); 243 if( flags & FLAG_VIDEO ) 244 { 245 strcatf(str, "<CustomIcon>" 246 "<ContentType>image/*</ContentType>" 247 "<Url>urn:tivo:image:save-until-i-delete-recording</Url>" 248 "</CustomIcon>"); 249 } 250 strcatf(str, "</Links>"); 251 } 252 else if( strncmp(class, "container", 9) == 0 ) 253 { 254 int count; 255 /* Determine the number of children */ 256#ifdef __sparc__ /* Adding filters on large containers can take a long time on slow processors */ 257 count = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", id); 258#else 259 count = sql_get_int_field(db, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' and " 260 " (MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')" 261 " or CLASS glob 'container*')", id); 262#endif 263 strcatf(str, "<Item>" 264 "<Details>" 265 "<ContentType>x-container/folder</ContentType>" 266 "<SourceFormat>x-container/folder</SourceFormat>" 267 "<Title>%s</Title>" 268 "<TotalItems>%d</TotalItems>" 269 "</Details>" 270 "<Links>" 271 "<Content>" 272 "<Url>/TiVoConnect?Command=QueryContainer&Container=%s</Url>" 273 "<ContentType>x-tivo-container/folder</ContentType>" 274 "</Content>" 275 "</Links>", 276 tivo_unescape_tag(title), count, id); 277 } 278 strcatf(str, "</Item>"); 279 280 passed_args->returned++; 281 282 return 0; 283} 284 285#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE," \ 286 " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE," \ 287 " d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.DISC, d.TRACK " 288 289static void 290SendItemDetails(struct upnphttp *h, int64_t item) 291{ 292 char *sql; 293 char *zErrMsg = NULL; 294 struct Response args; 295 struct string_s str; 296 int ret; 297 memset(&args, 0, sizeof(args)); 298 memset(&str, 0, sizeof(str)); 299 300 str.data = malloc(32768); 301 str.size = 32768; 302 str.off = sprintf(str.data, "<?xml version='1.0' encoding='UTF-8' ?>\n<TiVoItem>"); 303 args.str = &str; 304 args.requested = 1; 305 xasprintf(&sql, SELECT_COLUMNS 306 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 307 " where o.DETAIL_ID = %lld group by o.DETAIL_ID", (long long)item); 308 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); 309 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); 310 free(sql); 311 if( ret != SQLITE_OK ) 312 { 313 DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); 314 sqlite3_free(zErrMsg); 315 } 316 strcatf(&str, "</TiVoItem>"); 317 318 BuildResp_upnphttp(h, str.data, str.off); 319 free(str.data); 320 SendResp_upnphttp(h); 321} 322 323static void 324SendContainer(struct upnphttp *h, const char *objectID, int itemStart, int itemCount, char *anchorItem, 325 int anchorOffset, int recurse, char *sortOrder, char *filter, unsigned long int randomSeed) 326{ 327 char *resp = malloc(262144); 328 char *sql, *item, *saveptr; 329 char *zErrMsg = NULL; 330 char **result; 331 char *title, *which; 332 char what[10], order[96]={0}, order2[96]={0}, myfilter[256]={0}; 333 char str_buf[1024]; 334 char type[8]; 335 char groupBy[19] = {0}; 336 struct Response args; 337 struct string_s str; 338 int totalMatches = 0; 339 int i, ret; 340 memset(&args, 0, sizeof(args)); 341 memset(&str, 0, sizeof(str)); 342 343 args.str = &str; 344 str.data = resp+1024; 345 str.size = 262144-1024; 346 if( itemCount >= 0 ) 347 { 348 args.requested = itemCount; 349 } 350 else 351 { 352 if( itemCount == -100 ) 353 itemCount = 1; 354 args.requested = itemCount * -1; 355 } 356 357 switch( *objectID ) 358 { 359 case '1': 360 strcpy(type, "music"); 361 break; 362 case '2': 363 strcpy(type, "videos"); 364 break; 365 case '3': 366 strcpy(type, "photos"); 367 break; 368 default: 369 strcpy(type, "server"); 370 break; 371 } 372 373 if( strlen(objectID) == 1 ) 374 { 375 switch( *objectID ) 376 { 377 case '1': 378 xasprintf(&title, "Music on %s", friendly_name); 379 break; 380 case '2': 381 xasprintf(&title, "Videos on %s", friendly_name); 382 break; 383 case '3': 384 xasprintf(&title, "Pictures on %s", friendly_name); 385 break; 386 default: 387 xasprintf(&title, "Unknown on %s", friendly_name); 388 break; 389 } 390 } 391 else 392 { 393 item = sql_get_text_field(db, "SELECT NAME from OBJECTS where OBJECT_ID = '%q'", objectID); 394 if( item ) 395 { 396 title = escape_tag(item, 1); 397 sqlite3_free(item); 398 } 399 else 400 title = strdup("UNKNOWN"); 401 } 402 403 if( recurse ) 404 { 405 which = sqlite3_mprintf("OBJECT_ID glob '%q$*'", objectID); 406 strcpy(groupBy, "group by DETAIL_ID"); 407 } 408 else 409 { 410 which = sqlite3_mprintf("PARENT_ID = '%q'", objectID); 411 } 412 413 if( sortOrder ) 414 { 415 if( strcasestr(sortOrder, "Random") ) 416 { 417 sprintf(order, "tivorandom(%lu)", randomSeed); 418 if( itemCount < 0 ) 419 sprintf(order2, "tivorandom(%lu) DESC", randomSeed); 420 else 421 sprintf(order2, "tivorandom(%lu)", randomSeed); 422 } 423 else 424 { 425 short title_state = 0; 426 item = strtok_r(sortOrder, ",", &saveptr); 427 while( item != NULL ) 428 { 429 int reverse=0; 430 if( *item == '!' ) 431 { 432 reverse = 1; 433 item++; 434 } 435 if( strcasecmp(item, "Type") == 0 ) 436 { 437 strcat(order, "CLASS"); 438 strcat(order2, "CLASS"); 439 } 440 else if( strcasecmp(item, "Title") == 0 ) 441 { 442 /* Explicitly sort music by track then title. */ 443 if( title_state < 2 && *objectID == '1' ) 444 { 445 if( !title_state ) 446 { 447 strcat(order, "DISC"); 448 strcat(order2, "DISC"); 449 title_state = 1; 450 } 451 else 452 { 453 strcat(order, "TRACK"); 454 strcat(order2, "TRACK"); 455 title_state = 2; 456 } 457 } 458 else 459 { 460 strcat(order, "TITLE"); 461 strcat(order2, "TITLE"); 462 title_state = -1; 463 } 464 } 465 else if( strcasecmp(item, "CreationDate") == 0 || 466 strcasecmp(item, "CaptureDate") == 0 ) 467 { 468 strcat(order, "DATE"); 469 strcat(order2, "DATE"); 470 } 471 else 472 { 473 DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item); 474 goto unhandled_order; 475 } 476 477 if( reverse ) 478 { 479 strcat(order, " DESC"); 480 if( itemCount >= 0 ) 481 strcat(order2, " DESC"); 482 else 483 strcat(order2, " ASC"); 484 } 485 else 486 { 487 strcat(order, " ASC"); 488 if( itemCount >= 0 ) 489 strcat(order2, " ASC"); 490 else 491 strcat(order2, " DESC"); 492 } 493 strcat(order, ", "); 494 strcat(order2, ", "); 495 unhandled_order: 496 if( title_state <= 0 ) 497 item = strtok_r(NULL, ",", &saveptr); 498 } 499 if( title_state != -1 ) 500 { 501 strcat(order, "TITLE ASC, "); 502 if( itemCount >= 0 ) 503 strcat(order2, "TITLE ASC, "); 504 else 505 strcat(order2, "TITLE DESC, "); 506 } 507 strcat(order, "DETAIL_ID ASC"); 508 if( itemCount >= 0 ) 509 strcat(order2, "DETAIL_ID ASC"); 510 else 511 strcat(order2, "DETAIL_ID DESC"); 512 } 513 } 514 else 515 { 516 sprintf(order, "CLASS, NAME, DETAIL_ID"); 517 if( itemCount < 0 ) 518 sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC"); 519 else 520 sprintf(order2, "CLASS, NAME, DETAIL_ID"); 521 } 522 523 if( filter ) 524 { 525 item = strtok_r(filter, ",", &saveptr); 526 for( i=0; item != NULL; i++ ) 527 { 528 if( i ) 529 { 530 strcat(myfilter, " or "); 531 } 532 if( (strcasecmp(item, "x-container/folder") == 0) || 533 (strncasecmp(item, "x-tivo-container/", 17) == 0) ) 534 { 535 strcat(myfilter, "CLASS glob 'container*'"); 536 } 537 else if( strncasecmp(item, "image", 5) == 0 ) 538 { 539 strcat(myfilter, "MIME = 'image/jpeg'"); 540 } 541 else if( strncasecmp(item, "audio", 5) == 0 ) 542 { 543 strcat(myfilter, "MIME = 'audio/mpeg'"); 544 } 545 else if( strncasecmp(item, "video", 5) == 0 ) 546 { 547 strcat(myfilter, "MIME in ('video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')"); 548 } 549 else 550 { 551 DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item); 552 if( i ) 553 { 554 ret = strlen(myfilter); 555 myfilter[ret-4] = '\0'; 556 } 557 i--; 558 } 559 item = strtok_r(NULL, ",", &saveptr); 560 } 561 } 562 else 563 { 564 strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts') or CLASS glob 'container*'"); 565 } 566 567 if( anchorItem ) 568 { 569 if( strstr(anchorItem, "QueryContainer") ) 570 { 571 strcpy(what, "OBJECT_ID"); 572 saveptr = strrchr(anchorItem, '='); 573 if( saveptr ) 574 anchorItem = saveptr + 1; 575 } 576 else 577 { 578 strcpy(what, "DETAIL_ID"); 579 } 580 sqlite3Prng.isInit = 0; 581 sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" 582 " where %s and (%s)" 583 " %s" 584 " order by %s", what, which, myfilter, groupBy, order2); 585 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); 586 if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret ) 587 { 588 for( i=1; i<=ret; i++ ) 589 { 590 if( strcmp(anchorItem, result[i]) == 0 ) 591 { 592 if( itemCount < 0 ) 593 itemStart = ret - i + itemCount; 594 else 595 itemStart += i; 596 break; 597 } 598 } 599 sqlite3_free_table(result); 600 } 601 sqlite3_free(sql); 602 } 603 args.start = itemStart+anchorOffset; 604 sqlite3Prng.isInit = 0; 605 606 ret = sql_get_int_field(db, "SELECT count(distinct DETAIL_ID) " 607 "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" 608 " where %s and (%s)", 609 which, myfilter); 610 totalMatches = (ret > 0) ? ret : 0; 611 if( itemCount < 0 && !itemStart && !anchorOffset ) 612 { 613 args.start = totalMatches + itemCount; 614 } 615 616 sql = sqlite3_mprintf(SELECT_COLUMNS 617 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 618 " where %s and (%s)" 619 " %s" 620 " order by %s limit %d, %d", 621 which, myfilter, groupBy, order, args.start, args.requested); 622 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); 623 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); 624 sqlite3_free(sql); 625 if( ret != SQLITE_OK ) 626 { 627 DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); 628 sqlite3_free(zErrMsg); 629 Send500(h); 630 sqlite3_free(which); 631 free(title); 632 free(resp); 633 return; 634 } 635 strcatf(&str, "</TiVoContainer>"); 636 637 ret = sprintf(str_buf, "<?xml version='1.0' encoding='UTF-8' ?>\n" 638 "<TiVoContainer>" 639 "<Details>" 640 "<ContentType>x-container/tivo-%s</ContentType>" 641 "<SourceFormat>x-container/folder</SourceFormat>" 642 "<TotalItems>%d</TotalItems>" 643 "<Title>%s</Title>" 644 "</Details>" 645 "<ItemStart>%d</ItemStart>" 646 "<ItemCount>%d</ItemCount>", 647 type, totalMatches, title, args.start, args.returned); 648 str.data -= ret; 649 memcpy(str.data, &str_buf, ret); 650 str.size = str.off+ret; 651 free(title); 652 sqlite3_free(which); 653 BuildResp_upnphttp(h, str.data, str.size); 654 free(resp); 655 SendResp_upnphttp(h); 656} 657 658void 659ProcessTiVoCommand(struct upnphttp *h, const char *orig_path) 660{ 661 char *path; 662 char *key, *val; 663 char *saveptr = NULL, *item; 664 char *command = NULL, *container = NULL, *anchorItem = NULL; 665 char *sortOrder = NULL, *filter = NULL, *sformat = NULL; 666 int64_t detailItem=0; 667 int itemStart=0, itemCount=-100, anchorOffset=0, recurse=0; 668 unsigned long int randomSeed=0; 669 670 path = strdup(orig_path); 671 DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path); 672 673 item = strtok_r( path, "&", &saveptr ); 674 while( item != NULL ) 675 { 676 if( *item == '\0' ) 677 { 678 item = strtok_r( NULL, "&", &saveptr ); 679 continue; 680 } 681 decodeString(item, 1); 682 val = item; 683 key = strsep(&val, "="); 684 decodeString(val, 1); 685 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); 686 if( strcasecmp(key, "Command") == 0 ) 687 { 688 command = val; 689 } 690 else if( strcasecmp(key, "Container") == 0 ) 691 { 692 container = val; 693 } 694 else if( strcasecmp(key, "ItemStart") == 0 ) 695 { 696 itemStart = atoi(val); 697 } 698 else if( strcasecmp(key, "ItemCount") == 0 ) 699 { 700 itemCount = atoi(val); 701 } 702 else if( strcasecmp(key, "AnchorItem") == 0 ) 703 { 704 anchorItem = basename(val); 705 } 706 else if( strcasecmp(key, "AnchorOffset") == 0 ) 707 { 708 anchorOffset = atoi(val); 709 } 710 else if( strcasecmp(key, "Recurse") == 0 ) 711 { 712 recurse = strcasecmp("yes", val) == 0 ? 1 : 0; 713 } 714 else if( strcasecmp(key, "SortOrder") == 0 ) 715 { 716 sortOrder = val; 717 } 718 else if( strcasecmp(key, "Filter") == 0 ) 719 { 720 filter = val; 721 } 722 else if( strcasecmp(key, "RandomSeed") == 0 ) 723 { 724 randomSeed = strtoul(val, NULL, 10); 725 } 726 else if( strcasecmp(key, "Url") == 0 ) 727 { 728 if( val ) 729 detailItem = strtoll(basename(val), NULL, 10); 730 } 731 else if( strcasecmp(key, "SourceFormat") == 0 ) 732 { 733 sformat = val; 734 } 735 else if( strcasecmp(key, "Format") == 0 || // Only send XML 736 strcasecmp(key, "SerialNum") == 0 || // Unused for now 737 strcasecmp(key, "DoGenres") == 0 ) // Not sure what this is, so ignore it 738 { 739 ;; 740 } 741 else 742 { 743 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key); 744 } 745 item = strtok_r( NULL, "&", &saveptr ); 746 } 747 if( anchorItem ) 748 { 749 strip_ext(anchorItem); 750 } 751 752 if( command ) 753 { 754 if( strcmp(command, "QueryContainer") == 0 ) 755 { 756 if( !container || (strcmp(container, "/") == 0) ) 757 { 758 SendRootContainer(h); 759 } 760 else 761 { 762 SendContainer(h, container, itemStart, itemCount, anchorItem, 763 anchorOffset, recurse, sortOrder, filter, randomSeed); 764 } 765 } 766 else if( strcmp(command, "QueryItem") == 0 ) 767 { 768 SendItemDetails(h, detailItem); 769 } 770 else if( strcmp(command, "QueryFormats") == 0 ) 771 { 772 SendFormats(h, sformat); 773 } 774 else 775 { 776 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command); 777 Send501(h); 778 free(path); 779 return; 780 } 781 } 782 free(path); 783 CloseSocket_upnphttp(h); 784} 785#endif // TIVO_SUPPORT 786