minidlna support now Samsung TV C550/C650 (thx amir909)
[tomato.git] / release / src / router / minidlna / tivo_commands.c
blobf60aa8874fe50417a24a8ef2d4ae5833f966dc41
1 /* MiniDLNA media server
2 * Copyright (C) 2009 Justin Maggard
4 * This file is part of MiniDLNA.
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.
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.
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/>.
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>
26 #include "tivo_utils.h"
27 #include "upnpglobalvars.h"
28 #include "upnphttp.h"
29 #include "upnpsoap.h"
30 #include "utils.h"
31 #include "sql.h"
32 #include "log.h"
34 void
35 SendRootContainer(struct upnphttp * h)
37 char * resp;
38 int len;
40 len = asprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
41 "<TiVoContainer>"
42 "<Details>"
43 "<ContentType>x-container/tivo-server</ContentType>"
44 "<SourceFormat>x-container/folder</SourceFormat>"
45 "<TotalDuration>0</TotalDuration>"
46 "<TotalItems>3</TotalItems>"
47 "<Title>%s</Title>"
48 "</Details>"
49 "<ItemStart>0</ItemStart>"
50 "<ItemCount>3</ItemCount>"
51 "<Item>"
52 "<Details>"
53 "<ContentType>x-container/tivo-photos</ContentType>"
54 "<SourceFormat>x-container/folder</SourceFormat>"
55 "<Title>Pictures on %s</Title>"
56 "</Details>"
57 "<Links>"
58 "<Content>"
59 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=3</Url>"
60 "</Content>"
61 "</Links>"
62 "</Item>"
63 "<Item>"
64 "<Details>"
65 "<ContentType>x-container/tivo-music</ContentType>"
66 "<SourceFormat>x-container/folder</SourceFormat>"
67 "<Title>Music on %s</Title>"
68 "</Details>"
69 "<Links>"
70 "<Content>"
71 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=1</Url>"
72 "</Content>"
73 "</Links>"
74 "</Item>"
75 "<Item>"
76 "<Details>"
77 "<ContentType>x-container/tivo-videos</ContentType>"
78 "<SourceFormat>x-container/folder</SourceFormat>"
79 "<Title>Videos on %s</Title>"
80 "</Details>"
81 "<Links>"
82 "<Content>"
83 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=2</Url>"
84 "<ContentType>x-container/tivo-videos</ContentType>"
85 "</Content>"
86 "</Links>"
87 "</Item>"
88 "</TiVoContainer>",
89 friendly_name, friendly_name, friendly_name, friendly_name);
90 BuildResp_upnphttp(h, resp, len);
91 free(resp);
92 SendResp_upnphttp(h);
95 char *
96 unescape_tag(char * tag)
98 modifyString(tag, "&amp;amp;", "&amp;", 0);
99 modifyString(tag, "&amp;amp;lt;", "&lt;", 0);
100 modifyString(tag, "&amp;lt;", "&lt;", 0);
101 modifyString(tag, "&amp;amp;gt;", "&gt;", 0);
102 modifyString(tag, "&amp;gt;", "&gt;", 0);
103 return tag;
106 #define FLAG_SEND_RESIZED 0x01
107 #define FLAG_NO_PARAMS 0x02
108 #define FLAG_VIDEO 0x04
109 int callback(void *args, int argc, char **argv, char **azColName)
111 struct Response *passed_args = (struct Response *)args;
112 char *id = argv[0], *class = argv[1], *detailID = argv[2], *size = argv[3], *title = argv[4], *duration = argv[5],
113 *bitrate = argv[6], *sampleFrequency = argv[7], *artist = argv[8], *album = argv[9], *genre = argv[10],
114 *comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14], *path = argv[15];
115 struct string_s *str = passed_args->str;
117 if( strncmp(class, "item", 4) == 0 )
119 int flags = 0;
120 unescape_tag(title);
121 if( strncmp(mime, "audio", 5) == 0 )
123 flags |= FLAG_NO_PARAMS;
124 strcatf(str, "<Item><Details>"
125 "<ContentType>audio/*</ContentType>"
126 "<SourceFormat>%s</SourceFormat>"
127 "<SourceSize>%s</SourceSize>"
128 "<SongTitle>%s</SongTitle>", mime, size, title);
129 if( date )
131 strcatf(str, "<AlbumYear>%.*s</AlbumYear>", 4, date);
134 else if( strcmp(mime, "image/jpeg") == 0 )
136 flags |= FLAG_SEND_RESIZED;
137 strcatf(str, "<Item><Details>"
138 "<ContentType>image/*</ContentType>"
139 "<SourceFormat>image/jpeg</SourceFormat>"
140 "<SourceSize>%s</SourceSize>", size);
141 if( date )
143 struct tm tm;
144 memset(&tm, 0, sizeof(tm));
145 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not
146 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
147 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
149 if( comment )
151 strcatf(str, "<Caption>%s</Caption>", comment);
154 else if( strncmp(mime, "video", 5) == 0 )
156 char *episode;
157 flags |= FLAG_NO_PARAMS;
158 flags |= FLAG_VIDEO;
159 strcatf(str, "<Item><Details>"
160 "<ContentType>video/x-tivo-mpeg</ContentType>"
161 "<SourceFormat>%s</SourceFormat>"
162 "<SourceSize>%s</SourceSize>", mime, size);
163 episode = strstr(title, " - ");
164 if( episode )
166 strcatf(str, "<Title>%.*s</Title>"
167 "<EpisodeTitle>%s</EpisodeTitle>",
168 (int)(episode-title), title, episode+3);
170 else
172 strcatf(str, "<Title>%s</Title>", title);
174 if( date )
176 struct tm tm;
177 memset(&tm, 0, sizeof(tm));
178 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not
179 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
180 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
182 if( comment )
184 strcatf(str, "<Description>%s</Description>", comment);
187 else
189 return 0;
191 strcatf(str, "<Title>%s</Title>", unescape_tag(title));
192 if( artist ) {
193 strcatf(str, "<ArtistName>%s</ArtistName>", unescape_tag(artist));
195 if( album ) {
196 strcatf(str, "<AlbumTitle>%s</AlbumTitle>", unescape_tag(album));
198 if( genre ) {
199 strcatf(str, "<MusicGenre>%s</MusicGenre>", unescape_tag(genre));
201 if( resolution ) {
202 char *width = strsep(&resolution, "x");
203 strcatf(str, "<SourceWidth>%s</SourceWidth>"
204 "<SourceHeight>%s</SourceHeight>",
205 width, resolution);
207 if( duration ) {
208 strcatf(str, "<Duration>%d</Duration>",
209 atoi(strrchr(duration, '.')+1) + (1000*atoi(strrchr(duration, ':')+1))
210 + (60000*atoi(strrchr(duration, ':')-2)) + (3600000*atoi(duration)));
212 if( bitrate ) {
213 strcatf(str, "<SourceBitRate>%s</SourceBitRate>", bitrate);
215 if( sampleFrequency ) {
216 strcatf(str, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency);
218 strcatf(str, "</Details><Links><Content>"
219 "<ContentType>%s</ContentType>"
220 "<Url>/%s/%s.dat</Url>%s</Content>",
221 mime,
222 (flags & FLAG_SEND_RESIZED)?"Resized":"MediaItems", detailID,
223 (flags & FLAG_NO_PARAMS)?"<AcceptsParams>No</AcceptsParams>":"");
224 if( flags & FLAG_VIDEO )
226 char *esc_name = escape_tag(basename(path), 1);
227 strcatf(str, "<CustomIcon>"
228 "<ContentType>video/*</ContentType>"
229 "<Url>urn:tivo:image:save-until-i-delete-recording</Url>"
230 "</CustomIcon>"
231 "<Push><Container>Videos</Container></Push>"
232 "<File>%s</File> </Links>", esc_name);
233 free(esc_name);
235 else
237 strcatf(str, "</Links>");
240 else if( strncmp(class, "container", 9) == 0 )
242 int count;
243 /* Determine the number of children */
244 #ifdef __sparc__ /* Adding filters on large containers can take a long time on slow processors */
245 count = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", id);
246 #else
247 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 "
248 " (MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg')"
249 " or CLASS glob 'container*')", id);
250 #endif
251 strcatf(str, "<Item>"
252 "<Details>"
253 "<ContentType>x-container/folder</ContentType>"
254 "<SourceFormat>x-container/folder</SourceFormat>"
255 "<Title>%s</Title>"
256 "<TotalItems>%d</TotalItems>"
257 "</Details>"
258 "<Links>"
259 "<Content>"
260 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=%s</Url>"
261 "<ContentType>x-tivo-container/folder</ContentType>"
262 "</Content>"
263 "</Links>",
264 unescape_tag(title), count, id);
266 strcatf(str, "</Item>");
268 passed_args->returned++;
270 return 0;
273 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE," \
274 " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE," \
275 " d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.PATH, d.DISC, d.TRACK "
277 void
278 SendItemDetails(struct upnphttp * h, sqlite_int64 item)
280 char *sql;
281 char *zErrMsg = NULL;
282 struct Response args;
283 struct string_s str;
284 int ret;
285 memset(&args, 0, sizeof(args));
286 memset(&str, 0, sizeof(str));
288 str.data = malloc(32768);
289 str.size = 32768;
290 str.off = sprintf(str.data, "<?xml version='1.0' encoding='UTF-8' ?>\n<TiVoItem>");
291 args.str = &str;
292 args.requested = 1;
293 asprintf(&sql, SELECT_COLUMNS
294 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
295 " where o.DETAIL_ID = %lld group by o.DETAIL_ID", item);
296 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
297 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
298 free(sql);
299 if( ret != SQLITE_OK )
301 DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg);
302 sqlite3_free(zErrMsg);
304 strcatf(&str, "</TiVoItem>");
306 BuildResp_upnphttp(h, str.data, str.off);
307 free(str.data);
308 SendResp_upnphttp(h);
311 void
312 SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int itemCount, char * anchorItem,
313 int anchorOffset, int recurse, char * sortOrder, char * filter, unsigned long int randomSeed)
315 char *resp = malloc(262144);
316 char *sql, *item, *saveptr;
317 char *zErrMsg = NULL;
318 char **result;
319 char *title, *which;
320 char what[10], order[96]={0}, order2[96]={0}, myfilter[256]={0};
321 char str_buf[1024];
322 char type[8];
323 char groupBy[19] = {0};
324 struct Response args;
325 struct string_s str;
326 int totalMatches = 0;
327 int i, ret;
328 memset(&args, 0, sizeof(args));
329 memset(&str, 0, sizeof(str));
331 args.str = &str;
332 str.data = resp+1024;
333 str.size = 262144-1024;
334 if( itemCount >= 0 )
336 args.requested = itemCount;
338 else
340 if( itemCount == -100 )
341 itemCount = 1;
342 args.requested = itemCount * -1;
345 switch( *objectID )
347 case '1':
348 strcpy(type, "music");
349 break;
350 case '2':
351 strcpy(type, "videos");
352 break;
353 case '3':
354 strcpy(type, "photos");
355 break;
356 default:
357 strcpy(type, "server");
358 break;
361 if( strlen(objectID) == 1 )
363 switch( *objectID )
365 case '1':
366 asprintf(&title, "Music on %s", friendly_name);
367 break;
368 case '2':
369 asprintf(&title, "Videos on %s", friendly_name);
370 break;
371 case '3':
372 asprintf(&title, "Pictures on %s", friendly_name);
373 break;
374 default:
375 asprintf(&title, "Unknown on %s", friendly_name);
376 break;
379 else
381 item = sql_get_text_field(db, "SELECT NAME from OBJECTS where OBJECT_ID = '%s'", objectID);
382 if( item )
384 title = escape_tag(item, 1);
385 sqlite3_free(item);
387 else
388 title = strdup("UNKNOWN");
391 if( recurse )
393 asprintf(&which, "OBJECT_ID glob '%s$*'", objectID);
394 strcpy(groupBy, "group by DETAIL_ID");
396 else
398 asprintf(&which, "PARENT_ID = '%s'", objectID);
401 if( sortOrder )
403 if( strcasestr(sortOrder, "Random") )
405 sprintf(order, "tivorandom(%lu)", randomSeed);
406 if( itemCount < 0 )
407 sprintf(order2, "tivorandom(%lu) DESC", randomSeed);
408 else
409 sprintf(order2, "tivorandom(%lu)", randomSeed);
411 else
413 short title_state = 0;
414 item = strtok_r(sortOrder, ",", &saveptr);
415 while( item != NULL )
417 int reverse=0;
418 if( *item == '!' )
420 reverse = 1;
421 item++;
423 if( strcasecmp(item, "Type") == 0 )
425 strcat(order, "CLASS");
426 strcat(order2, "CLASS");
428 else if( strcasecmp(item, "Title") == 0 )
430 /* Explicitly sort music by track then title. */
431 if( title_state < 2 && *objectID == '1' )
433 if( !title_state )
435 strcat(order, "DISC");
436 strcat(order2, "DISC");
437 title_state = 1;
439 else
441 strcat(order, "TRACK");
442 strcat(order2, "TRACK");
443 title_state = 2;
446 else
448 strcat(order, "TITLE");
449 strcat(order2, "TITLE");
450 title_state = -1;
453 else if( strcasecmp(item, "CreationDate") == 0 ||
454 strcasecmp(item, "CaptureDate") == 0 )
456 strcat(order, "DATE");
457 strcat(order2, "DATE");
459 else
461 DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item);
462 goto unhandled_order;
465 if( reverse )
467 strcat(order, " DESC");
468 if( itemCount >= 0 )
469 strcat(order2, " DESC");
470 else
471 strcat(order2, " ASC");
473 else
475 strcat(order, " ASC");
476 if( itemCount >= 0 )
477 strcat(order2, " ASC");
478 else
479 strcat(order2, " DESC");
481 strcat(order, ", ");
482 strcat(order2, ", ");
483 unhandled_order:
484 if( title_state <= 0 )
485 item = strtok_r(NULL, ",", &saveptr);
487 if( title_state != -1 )
489 strcat(order, "TITLE ASC, ");
490 if( itemCount >= 0 )
491 strcat(order2, "TITLE ASC, ");
492 else
493 strcat(order2, "TITLE DESC, ");
495 strcat(order, "DETAIL_ID ASC");
496 if( itemCount >= 0 )
497 strcat(order2, "DETAIL_ID ASC");
498 else
499 strcat(order2, "DETAIL_ID DESC");
502 else
504 sprintf(order, "CLASS, NAME, DETAIL_ID");
505 if( itemCount < 0 )
506 sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC");
507 else
508 sprintf(order2, "CLASS, NAME, DETAIL_ID");
511 if( filter )
513 item = strtok_r(filter, ",", &saveptr);
514 for( i=0; item != NULL; i++ )
516 if( i )
518 strcat(myfilter, " or ");
520 if( (strcasecmp(item, "x-container/folder") == 0) ||
521 (strncasecmp(item, "x-tivo-container/", 17) == 0) )
523 strcat(myfilter, "CLASS glob 'container*'");
525 else if( strncasecmp(item, "image", 5) == 0 )
527 strcat(myfilter, "MIME = 'image/jpeg'");
529 else if( strncasecmp(item, "audio", 5) == 0 )
531 strcat(myfilter, "MIME = 'audio/mpeg'");
533 else if( strncasecmp(item, "video", 5) == 0 )
535 strcat(myfilter, "MIME in ('video/mpeg', 'video/x-tivo-mpeg')");
537 else
539 DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item);
540 if( i )
542 ret = strlen(myfilter);
543 myfilter[ret-4] = '\0';
545 i--;
547 item = strtok_r(NULL, ",", &saveptr);
550 else
552 strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg') or CLASS glob 'container*'");
555 if( anchorItem )
557 if( strstr(anchorItem, "QueryContainer") )
559 strcpy(what, "OBJECT_ID");
560 saveptr = strrchr(anchorItem, '=');
561 if( saveptr )
562 anchorItem = saveptr + 1;
564 else
566 strcpy(what, "DETAIL_ID");
568 sqlite3Prng.isInit = 0;
569 sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
570 " where %s and (%s)"
571 " %s"
572 " order by %s", what, which, myfilter, groupBy, order2);
573 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
574 if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret )
576 for( i=1; i<=ret; i++ )
578 if( strcmp(anchorItem, result[i]) == 0 )
580 if( itemCount < 0 )
581 itemStart = ret - i + itemCount;
582 else
583 itemStart += i;
584 break;
587 sqlite3_free_table(result);
589 sqlite3_free(sql);
591 args.start = itemStart+anchorOffset;
592 sqlite3Prng.isInit = 0;
594 ret = sql_get_int_field(db, "SELECT count(distinct DETAIL_ID) "
595 "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
596 " where %s and (%s)",
597 which, myfilter);
598 totalMatches = (ret > 0) ? ret : 0;
599 if( itemCount < 0 && !itemStart && !anchorOffset )
601 args.start = totalMatches + itemCount;
604 sql = sqlite3_mprintf(SELECT_COLUMNS
605 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
606 " where %s and (%s)"
607 " %s"
608 " order by %s limit %d, %d",
609 which, myfilter, groupBy, order, args.start, args.requested);
610 DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
611 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
612 sqlite3_free(sql);
613 if( ret != SQLITE_OK )
615 DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg);
616 sqlite3_free(zErrMsg);
617 Send500(h);
618 free(title);
619 free(which);
620 free(resp);
621 return;
623 strcatf(&str, "</TiVoContainer>");
625 ret = sprintf(str_buf, "<?xml version='1.0' encoding='UTF-8' ?>\n"
626 "<TiVoContainer>"
627 "<Details>"
628 "<ContentType>x-container/tivo-%s</ContentType>"
629 "<SourceFormat>x-container/folder</SourceFormat>"
630 "<TotalItems>%d</TotalItems>"
631 "<Title>%s</Title>"
632 "</Details>"
633 "<ItemStart>%d</ItemStart>"
634 "<ItemCount>%d</ItemCount>",
635 type, totalMatches, title, args.start, args.returned);
636 str.data -= ret;
637 memcpy(str.data, &str_buf, ret);
638 str.size = str.off+ret;
639 free(title);
640 free(which);
641 BuildResp_upnphttp(h, str.data, str.size);
642 free(resp);
643 SendResp_upnphttp(h);
646 void
647 ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
649 char *path;
650 char *key, *val;
651 char *saveptr = NULL, *item;
652 char *command = NULL, *container = NULL, *anchorItem = NULL;
653 char *sortOrder = NULL, *filter = NULL;
654 sqlite_int64 detailItem=0;
655 int itemStart=0, itemCount=-100, anchorOffset=0, recurse=0;
656 unsigned long int randomSeed=0;
658 path = strdup(orig_path);
659 DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
661 item = strtok_r( path, "&", &saveptr );
662 while( item != NULL )
664 if( *item == '\0' )
666 item = strtok_r( NULL, "&", &saveptr );
667 continue;
669 decodeString(item, 1);
670 val = item;
671 key = strsep(&val, "=");
672 decodeString(val, 1);
673 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
674 if( strcasecmp(key, "Command") == 0 )
676 command = val;
678 else if( strcasecmp(key, "Container") == 0 )
680 container = val;
682 else if( strcasecmp(key, "ItemStart") == 0 )
684 itemStart = atoi(val);
686 else if( strcasecmp(key, "ItemCount") == 0 )
688 itemCount = atoi(val);
690 else if( strcasecmp(key, "AnchorItem") == 0 )
692 anchorItem = basename(val);
694 else if( strcasecmp(key, "AnchorOffset") == 0 )
696 anchorOffset = atoi(val);
698 else if( strcasecmp(key, "Recurse") == 0 )
700 recurse = strcasecmp("yes", val) == 0 ? 1 : 0;
702 else if( strcasecmp(key, "SortOrder") == 0 )
704 sortOrder = val;
706 else if( strcasecmp(key, "Filter") == 0 )
708 filter = val;
710 else if( strcasecmp(key, "RandomSeed") == 0 )
712 randomSeed = strtoul(val, NULL, 10);
714 else if( strcasecmp(key, "Url") == 0 )
716 if( val )
717 detailItem = strtoll(basename(val), NULL, 10);
719 else if( strcasecmp(key, "Format") == 0 || // Only send XML
720 strcasecmp(key, "SerialNum") == 0 || // Unused for now
721 strcasecmp(key, "DoGenres") == 0 ) // Not sure what this is, so ignore it
725 else
727 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key);
729 item = strtok_r( NULL, "&", &saveptr );
731 if( anchorItem )
733 strip_ext(anchorItem);
736 if( command )
738 if( strcmp(command, "QueryContainer") == 0 )
740 if( !container || (strcmp(container, "/") == 0) )
742 SendRootContainer(h);
744 else
746 SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed);
749 else if( strcmp(command, "QueryItem") == 0 )
751 SendItemDetails(h, detailItem);
753 else
755 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command);
756 Send501(h);
757 free(path);
758 return;
761 free(path);
762 CloseSocket_upnphttp(h);
764 #endif // TIVO_SUPPORT