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&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);
91}
92
93char *
94unescape_tag(char * tag)
95{
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;
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&amp;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