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