1 /* MiniDLNA media server
2 * Copyright (C) 2009 Justin Maggard
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.
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.
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
25 #include "tivo_utils.h"
26 #include "upnpglobalvars.h"
34 SendRootContainer(struct upnphttp
* h
)
39 len
= asprintf(&resp
, "<?xml version='1.0' encoding='UTF-8' ?>\n"
42 "<ContentType>x-container/tivo-server</ContentType>"
43 "<SourceFormat>x-container/folder</SourceFormat>"
44 "<TotalDuration>0</TotalDuration>"
45 "<TotalItems>3</TotalItems>"
48 "<ItemStart>0</ItemStart>"
49 "<ItemCount>3</ItemCount>"
52 "<ContentType>x-container/tivo-photos</ContentType>"
53 "<SourceFormat>x-container/folder</SourceFormat>"
54 "<Title>Pictures on %s</Title>"
58 "<Url>/TiVoConnect?Command=QueryContainer&Container=3</Url>"
64 "<ContentType>x-container/tivo-music</ContentType>"
65 "<SourceFormat>x-container/folder</SourceFormat>"
66 "<Title>Music on %s</Title>"
70 "<Url>/TiVoConnect?Command=QueryContainer&Container=1</Url>"
76 "<ContentType>x-container/tivo-videos</ContentType>"
77 "<SourceFormat>x-container/folder</SourceFormat>"
78 "<Title>Videos on %s</Title>"
82 "<Url>/TiVoConnect?Command=QueryContainer&Container=2</Url>"
83 "<ContentType>x-container/tivo-videos</ContentType>"
87 "</TiVoContainer>", friendly_name
, friendly_name
, friendly_name
, friendly_name
);
88 BuildResp_upnphttp(h
, resp
, len
);
94 unescape_tag(char * tag
)
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);
104 #define FLAG_SEND_RESIZED 0x01
105 #define FLAG_NO_PARAMS 0x02
106 #define FLAG_VIDEO 0x04
107 int callback(void *args
, int argc
, char **argv
, char **azColName
)
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];
114 int ret
= 0, flags
= 0, count
;
116 if( strncmp(class, "item", 4) == 0 )
119 if( strncmp(mime
, "audio", 5) == 0 )
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
;
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
;
136 else if( strcmp(mime
, "image/jpeg") == 0 )
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
;
148 memset(&tm
, 0, sizeof(tm
));
149 tm
.tm_isdst
= -1; // Have libc figure out if DST is in effect or not
150 strptime(date
, "%Y-%m-%dT%H:%M:%S", &tm
);
151 ret
= sprintf(str_buf
, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm
));
152 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
153 passed_args
->size
+= ret
;
157 ret
= sprintf(str_buf
, "<Caption>%s</Caption>", comment
);
158 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
159 passed_args
->size
+= ret
;
162 else if( strncmp(mime
, "video", 5) == 0 )
165 flags
|= FLAG_NO_PARAMS
;
167 ret
= sprintf(str_buf
, "<Item><Details>"
168 "<ContentType>video/x-tivo-mpeg</ContentType>"
169 "<SourceFormat>%s</SourceFormat>"
170 "<SourceSize>%s</SourceSize>", mime
, size
);
171 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
172 passed_args
->size
+= ret
;
173 episode
= strstr(title
, " - ");
176 ret
= sprintf(str_buf
, "<Title>%.*s</Title>"
177 "<EpisodeTitle>%s</EpisodeTitle>",
178 episode
-title
, title
, episode
+3);
182 ret
= sprintf(str_buf
, "<Title>%s</Title>", title
);
184 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
185 passed_args
->size
+= ret
;
189 memset(&tm
, 0, sizeof(tm
));
190 tm
.tm_isdst
= -1; // Have libc figure out if DST is in effect or not
191 strptime(date
, "%Y-%m-%dT%H:%M:%S", &tm
);
192 ret
= sprintf(str_buf
, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm
));
193 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
194 passed_args
->size
+= ret
;
198 ret
= sprintf(str_buf
, "<Description>%s</Description>", comment
);
199 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
200 passed_args
->size
+= ret
;
207 ret
= sprintf(str_buf
, "<Title>%s</Title>", unescape_tag(title
));
208 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
209 passed_args
->size
+= ret
;
211 ret
= sprintf(str_buf
, "<ArtistName>%s</ArtistName>", unescape_tag(artist
));
212 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
213 passed_args
->size
+= ret
;
216 ret
= sprintf(str_buf
, "<AlbumTitle>%s</AlbumTitle>", unescape_tag(album
));
217 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
218 passed_args
->size
+= ret
;
221 ret
= sprintf(str_buf
, "<MusicGenre>%s</MusicGenre>", unescape_tag(genre
));
222 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
223 passed_args
->size
+= ret
;
226 char *width
= strsep(&resolution
, "x");
227 ret
= sprintf(str_buf
, "<SourceWidth>%s</SourceWidth>"
228 "<SourceHeight>%s</SourceHeight>",
230 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
231 passed_args
->size
+= ret
;
234 ret
= sprintf(str_buf
, "<Duration>%d</Duration>",
235 atoi(rindex(duration
, '.')+1) + (1000*atoi(rindex(duration
, ':')+1)) + (60000*atoi(rindex(duration
, ':')-2)) + (3600000*atoi(duration
)));
236 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
237 passed_args
->size
+= ret
;
240 ret
= sprintf(str_buf
, "<SourceBitRate>%s</SourceBitRate>", bitrate
);
241 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
242 passed_args
->size
+= ret
;
244 if( sampleFrequency
) {
245 ret
= sprintf(str_buf
, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency
);
246 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
247 passed_args
->size
+= ret
;
249 ret
= sprintf(str_buf
, "</Details><Links><Content>"
250 "<ContentType>%s</ContentType>"
251 "<Url>/%s/%s.dat</Url>%s</Content>",
253 (flags
& FLAG_SEND_RESIZED
)?"Resized":"MediaItems", detailID
,
254 (flags
& FLAG_NO_PARAMS
)?"<AcceptsParams>No</AcceptsParams>":"");
255 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
256 passed_args
->size
+= ret
;
257 if( flags
& FLAG_VIDEO
)
259 char *name
= basename(path
);
260 char *esc_name
= escape_tag(name
);
261 ret
= sprintf(str_buf
, "<CustomIcon>"
262 "<ContentType>video/*</ContentType>"
263 "<Url>urn:tivo:image:save-until-i-delete-recording</Url>"
265 "<Push><Container>Videos</Container></Push>"
266 "<File>%s</File> </Links>", esc_name
?esc_name
:name
);
272 ret
= sprintf(str_buf
, "</Links>");
275 else if( strncmp(class, "container", 9) == 0 )
277 /* Determine the number of children */
278 #ifdef __sparc__ /* Adding filters on large containers can take a long time on slow processors */
279 count
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", id
);
281 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 "
282 " (MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg')"
283 " or CLASS glob 'container*')", id
);
285 ret
= sprintf(str_buf
, "<Item>"
287 "<ContentType>x-container/folder</ContentType>"
288 "<SourceFormat>x-container/folder</SourceFormat>"
290 "<TotalItems>%d</TotalItems>"
294 "<Url>/TiVoConnect?Command=QueryContainer&Container=%s</Url>"
295 "<ContentType>x-tivo-container/folder</ContentType>"
298 unescape_tag(title
), count
, id
);
300 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
301 passed_args
->size
+= ret
;
302 ret
= sprintf(str_buf
, "</Item>");
303 memcpy(passed_args
->resp
+passed_args
->size
, &str_buf
, ret
+1);
304 passed_args
->size
+= ret
;
306 passed_args
->returned
++;
312 SendItemDetails(struct upnphttp
* h
, sqlite_int64 item
)
314 char * resp
= malloc(32768);
316 char *zErrMsg
= NULL
;
317 struct Response args
;
319 memset(&args
, 0, sizeof(args
));
322 args
.size
= sprintf(resp
, "<?xml version='1.0' encoding='UTF-8' ?>\n<TiVoItem>");
324 asprintf(&sql
, "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE,"
325 " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM,"
326 " d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.PATH, d.TRACK "
327 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
328 " where o.DETAIL_ID = %lld group by o.DETAIL_ID", item
);
329 DPRINTF(E_DEBUG
, L_TIVO
, "%s\n", sql
);
330 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
332 if( ret
!= SQLITE_OK
)
334 DPRINTF(E_ERROR
, L_HTTP
, "SQL error: %s\n", zErrMsg
);
335 sqlite3_free(zErrMsg
);
337 strcat(resp
, "</TiVoItem>");
339 BuildResp_upnphttp(h
, resp
, strlen(resp
));
341 SendResp_upnphttp(h
);
345 SendContainer(struct upnphttp
* h
, const char * objectID
, int itemStart
, int itemCount
, char * anchorItem
,
346 int anchorOffset
, int recurse
, char * sortOrder
, char * filter
, unsigned long int randomSeed
)
348 char * resp
= malloc(262144);
349 char *sql
, *item
, *saveptr
;
350 char *zErrMsg
= NULL
;
353 char what
[10], order
[64]={0}, order2
[64]={0}, myfilter
[256]={0};
357 char groupBy
[19] = {0};
358 struct Response args
;
359 int totalMatches
= 0;
361 memset(&args
, 0, sizeof(args
));
362 memset(resp
, 0, sizeof(262144));
368 args
.requested
= itemCount
;
372 if( itemCount
== -100 )
374 args
.requested
= itemCount
* -1;
380 strcpy(type
, "music");
383 strcpy(type
, "videos");
386 strcpy(type
, "photos");
389 strcpy(type
, "server");
393 if( strlen(objectID
) == 1 )
398 asprintf(&title
, "Music on %s", friendly_name
);
401 asprintf(&title
, "Videos on %s", friendly_name
);
404 asprintf(&title
, "Pictures on %s", friendly_name
);
407 asprintf(&title
, "Unknown on %s", friendly_name
);
413 sql
= sqlite3_mprintf("SELECT NAME from OBJECTS where OBJECT_ID = '%s'", objectID
);
414 if( (sql_get_table(db
, sql
, &result
, &ret
, NULL
) == SQLITE_OK
) && ret
)
416 title
= escape_tag(result
[1]);
418 title
= strdup(result
[1]);
421 title
= strdup("UNKNOWN");
423 sqlite3_free_table(result
);
428 asprintf(&which
, "OBJECT_ID glob '%s$*'", objectID
);
429 strcpy(groupBy
, "group by DETAIL_ID");
433 asprintf(&which
, "PARENT_ID = '%s'", objectID
);
438 if( strcasestr(sortOrder
, "Random") )
440 sprintf(order
, "tivorandom(%lu)", randomSeed
);
442 sprintf(order2
, "tivorandom(%lu) DESC", randomSeed
);
444 sprintf(order2
, "tivorandom(%lu)", randomSeed
);
448 short track_done
= 0;
449 item
= strtok_r(sortOrder
, ",", &saveptr
);
450 for( i
=0; item
!= NULL
; i
++ )
458 if( strcasecmp(item
, "Type") == 0 )
460 strcat(order
, "CLASS");
461 strcat(order2
, "CLASS");
463 else if( strcasecmp(item
, "Title") == 0 )
465 /* Explicitly sort music by track then title. */
466 if( !track_done
&& *objectID
== '1' )
468 strcat(order
, "TRACK");
469 strcat(order2
, "TRACK");
474 strcat(order
, "TITLE");
475 strcat(order2
, "TITLE");
479 else if( strcasecmp(item
, "CreationDate") == 0 ||
480 strcasecmp(item
, "CaptureDate") == 0 )
482 strcat(order
, "DATE");
483 strcat(order2
, "DATE");
487 DPRINTF(E_INFO
, L_TIVO
, "Unhandled SortOrder [%s]\n", item
);
488 goto unhandled_order
;
493 strcat(order
, " DESC");
495 strcat(order2
, " DESC");
497 strcat(order2
, " ASC");
501 strcat(order
, " ASC");
503 strcat(order2
, " ASC");
505 strcat(order2
, " DESC");
508 strcat(order2
, ", ");
511 item
= strtok_r(NULL
, ",", &saveptr
);
513 strcat(order
, "TITLE ASC, DETAIL_ID ASC");
515 strcat(order2
, "TITLE ASC, DETAIL_ID ASC");
517 strcat(order2
, "TITLE DESC, DETAIL_ID DESC");
522 sprintf(order
, "CLASS, NAME, DETAIL_ID");
524 sprintf(order2
, "CLASS DESC, NAME DESC, DETAIL_ID DESC");
526 sprintf(order2
, "CLASS, NAME, DETAIL_ID");
531 item
= strtok_r(filter
, ",", &saveptr
);
532 for( i
=0; item
!= NULL
; i
++ )
536 strcat(myfilter
, " or ");
538 if( (strcasecmp(item
, "x-container/folder") == 0) ||
539 (strncasecmp(item
, "x-tivo-container/", 17) == 0) )
541 strcat(myfilter
, "CLASS glob 'container*'");
543 else if( strncasecmp(item
, "image", 5) == 0 )
545 strcat(myfilter
, "MIME = 'image/jpeg'");
547 else if( strncasecmp(item
, "audio", 5) == 0 )
549 strcat(myfilter
, "MIME = 'audio/mpeg'");
551 else if( strncasecmp(item
, "video", 5) == 0 )
553 strcat(myfilter
, "MIME in ('video/mpeg', 'video/x-tivo-mpeg')");
557 DPRINTF(E_INFO
, L_TIVO
, "Unhandled Filter [%s]\n", item
);
560 ret
= strlen(myfilter
);
561 myfilter
[ret
-4] = '\0';
565 item
= strtok_r(NULL
, ",", &saveptr
);
570 strcpy(myfilter
, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg') or CLASS glob 'container*'");
575 if( strstr(anchorItem
, "QueryContainer") )
577 strcpy(what
, "OBJECT_ID");
578 anchorItem
= rindex(anchorItem
, '=')+1;
582 strcpy(what
, "DETAIL_ID");
584 sqlite3Prng
.isInit
= 0;
585 sql
= sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
588 " order by %s", what
, which
, myfilter
, groupBy
, order2
);
589 DPRINTF(E_DEBUG
, L_TIVO
, "%s\n", sql
);
590 if( (sql_get_table(db
, sql
, &result
, &ret
, NULL
) == SQLITE_OK
) && ret
)
592 for( i
=1; i
<=ret
; i
++ )
594 if( strcmp(anchorItem
, result
[i
]) == 0 )
597 itemStart
= ret
- i
+ itemCount
;
603 sqlite3_free_table(result
);
607 args
.start
= itemStart
+anchorOffset
;
608 sqlite3Prng
.isInit
= 0;
610 ret
= sql_get_int_field(db
, "SELECT count(distinct DETAIL_ID) "
611 "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
612 " where %s and (%s)",
614 totalMatches
= (ret
> 0) ? ret
: 0;
616 sql
= sqlite3_mprintf("SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE,"
617 " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM,"
618 " d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.PATH, d.TRACK "
619 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
622 " order by %s limit %d, %d",
623 which
, myfilter
, groupBy
, order
, args
.start
, args
.requested
);
624 DPRINTF(E_DEBUG
, L_TIVO
, "%s\n", sql
);
625 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
627 if( ret
!= SQLITE_OK
)
629 DPRINTF(E_ERROR
, L_HTTP
, "SQL error: %s\n", zErrMsg
);
630 sqlite3_free(zErrMsg
);
633 ret
= sprintf(str_buf
, "<?xml version='1.0' encoding='UTF-8' ?>\n"
636 "<ContentType>x-container/tivo-%s</ContentType>"
637 "<SourceFormat>x-container/folder</SourceFormat>"
638 "<TotalItems>%d</TotalItems>"
641 "<ItemStart>%d</ItemStart>"
642 "<ItemCount>%d</ItemCount>",
643 type
, totalMatches
, title
, args
.start
, args
.returned
);
644 args
.resp
= resp
+1024-ret
;
645 memcpy(args
.resp
, &str_buf
, ret
);
646 ret
= sprintf(str_buf
, "</TiVoContainer>");
647 memcpy(resp
+args
.size
, &str_buf
, ret
+1);
649 args
.size
-= args
.resp
-resp
;
652 BuildResp_upnphttp(h
, args
.resp
, args
.size
);
654 SendResp_upnphttp(h
);
658 ProcessTiVoCommand(struct upnphttp
* h
, const char * orig_path
)
662 char *saveptr
= NULL
, *item
;
663 char *command
= NULL
, *container
= NULL
, *anchorItem
= NULL
;
664 char *sortOrder
= NULL
, *filter
= NULL
;
665 sqlite_int64 detailItem
=0;
666 int itemStart
=0, itemCount
=-100, anchorOffset
=0, recurse
=0;
667 unsigned long int randomSeed
=0;
669 path
= strdup(orig_path
);
670 DPRINTF(E_DEBUG
, L_GENERAL
, "Processing TiVo command %s\n", path
);
672 item
= strtok_r( path
, "&", &saveptr
);
673 while( item
!= NULL
)
675 if( strlen(item
) == 0 )
677 item
= strtok_r( NULL
, "&", &saveptr
);
680 decodeString(item
, 1);
682 key
= strsep(&val
, "=");
683 decodeString(val
, 1);
684 DPRINTF(E_DEBUG
, L_GENERAL
, "%s: %s\n", key
, val
);
685 if( strcasecmp(key
, "Command") == 0 )
689 else if( strcasecmp(key
, "Container") == 0 )
693 else if( strcasecmp(key
, "ItemStart") == 0 )
695 itemStart
= atoi(val
);
697 else if( strcasecmp(key
, "ItemCount") == 0 )
699 itemCount
= atoi(val
);
701 else if( strcasecmp(key
, "AnchorItem") == 0 )
703 anchorItem
= basename(val
);
705 else if( strcasecmp(key
, "AnchorOffset") == 0 )
707 anchorOffset
= atoi(val
);
709 else if( strcasecmp(key
, "Recurse") == 0 )
711 recurse
= strcasecmp("yes", val
) == 0 ? 1 : 0;
713 else if( strcasecmp(key
, "SortOrder") == 0 )
717 else if( strcasecmp(key
, "Filter") == 0 )
721 else if( strcasecmp(key
, "RandomSeed") == 0 )
723 randomSeed
= strtoul(val
, NULL
, 10);
725 else if( strcasecmp(key
, "Url") == 0 )
728 detailItem
= strtoll(basename(val
), NULL
, 10);
730 else if( strcasecmp(key
, "Format") == 0 || // Only send XML
731 strcasecmp(key
, "SerialNum") == 0 || // Unused for now
732 strcasecmp(key
, "DoGenres") == 0 ) // Not sure what this is, so ignore it
738 DPRINTF(E_DEBUG
, L_GENERAL
, "Unhandled parameter [%s]\n", key
);
740 item
= strtok_r( NULL
, "&", &saveptr
);
744 strip_ext(anchorItem
);
749 if( strcmp(command
, "QueryContainer") == 0 )
751 if( !container
|| (strcmp(container
, "/") == 0) )
753 SendRootContainer(h
);
757 SendContainer(h
, container
, itemStart
, itemCount
, anchorItem
, anchorOffset
, recurse
, sortOrder
, filter
, randomSeed
);
760 else if( strcmp(command
, "QueryItem") == 0 )
762 SendItemDetails(h
, detailItem
);
766 DPRINTF(E_DEBUG
, L_GENERAL
, "Unhandled command [%s]\n", command
);
773 CloseSocket_upnphttp(h
);
775 #endif // TIVO_SUPPORT