MiniDLNA cvs 2010-10-05
[tomato.git] / release / src / router / minidlna / tivo_commands.c
bloba16cb44b8542ba8d18adf2bae7bebc1dbff72233
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
18 #include "config.h"
19 #ifdef TIVO_SUPPORT
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
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"
33 void
34 SendRootContainer(struct upnphttp * h)
36 char * resp;
37 int len;
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>3</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&amp;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&amp;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&amp;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);
93 char *
94 unescape_tag(char * tag)
96 modifyString(tag, "&amp;amp;", "&amp;", 0);
97 modifyString(tag, "&amp;amp;lt;", "&lt;", 0);
98 modifyString(tag, "&amp;lt;", "&lt;", 0);
99 modifyString(tag, "&amp;amp;gt;", "&gt;", 0);
100 modifyString(tag, "&amp;gt;", "&gt;", 0);
101 return tag;
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];
113 char str_buf[4096];
114 int ret = 0, flags = 0, count;
116 if( strncmp(class, "item", 4) == 0 )
118 unescape_tag(title);
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;
129 if( date )
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;
145 if( date )
147 struct tm tm;
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;
155 if( comment )
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 )
164 char *episode;
165 flags |= FLAG_NO_PARAMS;
166 flags |= FLAG_VIDEO;
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, " - ");
174 if( episode )
176 ret = sprintf(str_buf, "<Title>%.*s</Title>"
177 "<EpisodeTitle>%s</EpisodeTitle>",
178 episode-title, title, episode+3);
180 else
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;
186 if( date )
188 struct tm tm;
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;
196 if( comment )
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;
203 else
205 return 0;
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;
210 if( artist ) {
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;
215 if( album ) {
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;
220 if( genre ) {
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;
225 if( resolution ) {
226 char *width = strsep(&resolution, "x");
227 ret = sprintf(str_buf, "<SourceWidth>%s</SourceWidth>"
228 "<SourceHeight>%s</SourceHeight>",
229 width, resolution);
230 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
231 passed_args->size += ret;
233 if( duration ) {
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;
239 if( bitrate ) {
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>",
252 mime,
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>"
264 "</CustomIcon>"
265 "<Push><Container>Videos</Container></Push>"
266 "<File>%s</File> </Links>", esc_name?esc_name:name);
267 if( esc_name )
268 free(esc_name);
270 else
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);
280 #else
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);
284 #endif
285 ret = sprintf(str_buf, "<Item>"
286 "<Details>"
287 "<ContentType>x-container/folder</ContentType>"
288 "<SourceFormat>x-container/folder</SourceFormat>"
289 "<Title>%s</Title>"
290 "<TotalItems>%d</TotalItems>"
291 "</Details>"
292 "<Links>"
293 "<Content>"
294 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=%s</Url>"
295 "<ContentType>x-tivo-container/folder</ContentType>"
296 "</Content>"
297 "</Links>",
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++;
308 return 0;
311 void
312 SendItemDetails(struct upnphttp * h, sqlite_int64 item)
314 char * resp = malloc(32768);
315 char *sql;
316 char *zErrMsg = NULL;
317 struct Response args;
318 int ret;
319 memset(&args, 0, sizeof(args));
321 args.resp = resp;
322 args.size = sprintf(resp, "<?xml version='1.0' encoding='UTF-8' ?>\n<TiVoItem>");
323 args.requested = 1;
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);
331 free(sql);
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));
340 free(resp);
341 SendResp_upnphttp(h);
344 void
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;
351 char **result;
352 char *title = NULL;
353 char what[10], order[64]={0}, order2[64]={0}, myfilter[256]={0};
354 char str_buf[1024];
355 char *which;
356 char type[8];
357 char groupBy[19] = {0};
358 struct Response args;
359 int totalMatches = 0;
360 int i, ret;
361 memset(&args, 0, sizeof(args));
362 memset(resp, 0, sizeof(262144));
364 args.resp = resp;
365 args.size = 1024;
366 if( itemCount >= 0 )
368 args.requested = itemCount;
370 else
372 if( itemCount == -100 )
373 itemCount = 1;
374 args.requested = itemCount * -1;
377 switch( *objectID )
379 case '1':
380 strcpy(type, "music");
381 break;
382 case '2':
383 strcpy(type, "videos");
384 break;
385 case '3':
386 strcpy(type, "photos");
387 break;
388 default:
389 strcpy(type, "server");
390 break;
393 if( strlen(objectID) == 1 )
395 switch( *objectID )
397 case '1':
398 asprintf(&title, "Music on %s", friendly_name);
399 break;
400 case '2':
401 asprintf(&title, "Videos on %s", friendly_name);
402 break;
403 case '3':
404 asprintf(&title, "Pictures on %s", friendly_name);
405 break;
406 default:
407 asprintf(&title, "Unknown on %s", friendly_name);
408 break;
411 else
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]);
417 if( !title )
418 title = strdup(result[1]);
420 else
421 title = strdup("UNKNOWN");
422 sqlite3_free(sql);
423 sqlite3_free_table(result);
426 if( recurse )
428 asprintf(&which, "OBJECT_ID glob '%s$*'", objectID);
429 strcpy(groupBy, "group by DETAIL_ID");
431 else
433 asprintf(&which, "PARENT_ID = '%s'", objectID);
436 if( sortOrder )
438 if( strcasestr(sortOrder, "Random") )
440 sprintf(order, "tivorandom(%lu)", randomSeed);
441 if( itemCount < 0 )
442 sprintf(order2, "tivorandom(%lu) DESC", randomSeed);
443 else
444 sprintf(order2, "tivorandom(%lu)", randomSeed);
446 else
448 short track_done = 0;
449 item = strtok_r(sortOrder, ",", &saveptr);
450 for( i=0; item != NULL; i++ )
452 int reverse=0;
453 if( *item == '!' )
455 reverse = 1;
456 item++;
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");
470 track_done = 1;
472 else
474 strcat(order, "TITLE");
475 strcat(order2, "TITLE");
476 track_done = 0;
479 else if( strcasecmp(item, "CreationDate") == 0 ||
480 strcasecmp(item, "CaptureDate") == 0 )
482 strcat(order, "DATE");
483 strcat(order2, "DATE");
485 else
487 DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item);
488 goto unhandled_order;
491 if( reverse )
493 strcat(order, " DESC");
494 if( itemCount >= 0 )
495 strcat(order2, " DESC");
496 else
497 strcat(order2, " ASC");
499 else
501 strcat(order, " ASC");
502 if( itemCount >= 0 )
503 strcat(order2, " ASC");
504 else
505 strcat(order2, " DESC");
507 strcat(order, ", ");
508 strcat(order2, ", ");
509 unhandled_order:
510 if( !track_done )
511 item = strtok_r(NULL, ",", &saveptr);
513 strcat(order, "TITLE ASC, DETAIL_ID ASC");
514 if( itemCount >= 0 )
515 strcat(order2, "TITLE ASC, DETAIL_ID ASC");
516 else
517 strcat(order2, "TITLE DESC, DETAIL_ID DESC");
520 else
522 sprintf(order, "CLASS, NAME, DETAIL_ID");
523 if( itemCount < 0 )
524 sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC");
525 else
526 sprintf(order2, "CLASS, NAME, DETAIL_ID");
529 if( filter )
531 item = strtok_r(filter, ",", &saveptr);
532 for( i=0; item != NULL; i++ )
534 if( 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')");
555 else
557 DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item);
558 if( i )
560 ret = strlen(myfilter);
561 myfilter[ret-4] = '\0';
563 i--;
565 item = strtok_r(NULL, ",", &saveptr);
568 else
570 strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg') or CLASS glob 'container*'");
573 if( anchorItem )
575 if( strstr(anchorItem, "QueryContainer") )
577 strcpy(what, "OBJECT_ID");
578 anchorItem = rindex(anchorItem, '=')+1;
580 else
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)"
586 " where %s and (%s)"
587 " %s"
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 )
596 if( itemCount < 0 )
597 itemStart = ret - i + itemCount;
598 else
599 itemStart += i;
600 break;
603 sqlite3_free_table(result);
605 sqlite3_free(sql);
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)",
613 which, myfilter);
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)"
620 " where %s and (%s)"
621 " %s"
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);
626 sqlite3_free(sql);
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"
634 "<TiVoContainer>"
635 "<Details>"
636 "<ContentType>x-container/tivo-%s</ContentType>"
637 "<SourceFormat>x-container/folder</SourceFormat>"
638 "<TotalItems>%d</TotalItems>"
639 "<Title>%s</Title>"
640 "</Details>"
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);
648 args.size += ret;
649 args.size -= args.resp-resp;
650 free(title);
651 free(which);
652 BuildResp_upnphttp(h, args.resp, args.size);
653 free(resp);
654 SendResp_upnphttp(h);
657 void
658 ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
660 char *path;
661 char *key, *val;
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 );
678 continue;
680 decodeString(item, 1);
681 val = item;
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 )
687 command = val;
689 else if( strcasecmp(key, "Container") == 0 )
691 container = val;
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 )
715 sortOrder = val;
717 else if( strcasecmp(key, "Filter") == 0 )
719 filter = val;
721 else if( strcasecmp(key, "RandomSeed") == 0 )
723 randomSeed = strtoul(val, NULL, 10);
725 else if( strcasecmp(key, "Url") == 0 )
727 if( val )
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
736 else
738 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key);
740 item = strtok_r( NULL, "&", &saveptr );
742 if( anchorItem )
744 strip_ext(anchorItem);
747 if( command )
749 if( strcmp(command, "QueryContainer") == 0 )
751 if( !container || (strcmp(container, "/") == 0) )
753 SendRootContainer(h);
755 else
757 SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed);
760 else if( strcmp(command, "QueryItem") == 0 )
762 SendItemDetails(h, detailItem);
764 else
766 DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command);
767 Send501(h);
768 free(path);
769 return;
772 free(path);
773 CloseSocket_upnphttp(h);
775 #endif // TIVO_SUPPORT