1 | /* MiniDLNA media server |
---|
2 | * Copyright (C) 2009 Justin Maggard |
---|
3 | * |
---|
4 | * This file is part of MiniDLNA. |
---|
5 | * |
---|
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. |
---|
9 | * |
---|
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. |
---|
14 | * |
---|
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/>. |
---|
17 | */ |
---|
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> |
---|
25 | |
---|
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" |
---|
33 | |
---|
34 | void |
---|
35 | SendRootContainer(struct upnphttp * h) |
---|
36 | { |
---|
37 | char * resp; |
---|
38 | int len; |
---|
39 | |
---|
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&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&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&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); |
---|
93 | } |
---|
94 | |
---|
95 | char * |
---|
96 | unescape_tag(char * tag) |
---|
97 | { |
---|
98 | modifyString(tag, "&amp;", "&", 0); |
---|
99 | modifyString(tag, "&amp;lt;", "<", 0); |
---|
100 | modifyString(tag, "&lt;", "<", 0); |
---|
101 | modifyString(tag, "&amp;gt;", ">", 0); |
---|
102 | modifyString(tag, "&gt;", ">", 0); |
---|
103 | return tag; |
---|
104 | } |
---|
105 | |
---|
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) |
---|
110 | { |
---|
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; |
---|
116 | |
---|
117 | if( strncmp(class, "item", 4) == 0 ) |
---|
118 | { |
---|
119 | int flags = 0; |
---|
120 | unescape_tag(title); |
---|
121 | if( strncmp(mime, "audio", 5) == 0 ) |
---|
122 | { |
---|
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 ) |
---|
130 | { |
---|
131 | strcatf(str, "<AlbumYear>%.*s</AlbumYear>", 4, date); |
---|
132 | } |
---|
133 | } |
---|
134 | else if( strcmp(mime, "image/jpeg") == 0 ) |
---|
135 | { |
---|
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 ) |
---|
142 | { |
---|
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)); |
---|
148 | } |
---|
149 | if( comment ) |
---|
150 | { |
---|
151 | strcatf(str, "<Caption>%s</Caption>", comment); |
---|
152 | } |
---|
153 | } |
---|
154 | else if( strncmp(mime, "video", 5) == 0 ) |
---|
155 | { |
---|
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 ) |
---|
165 | { |
---|
166 | strcatf(str, "<Title>%.*s</Title>" |
---|
167 | "<EpisodeTitle>%s</EpisodeTitle>", |
---|
168 | (int)(episode-title), title, episode+3); |
---|
169 | } |
---|
170 | else |
---|
171 | { |
---|
172 | strcatf(str, "<Title>%s</Title>", title); |
---|
173 | } |
---|
174 | if( date ) |
---|
175 | { |
---|
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)); |
---|
181 | } |
---|
182 | if( comment ) |
---|
183 | { |
---|
184 | strcatf(str, "<Description>%s</Description>", comment); |
---|
185 | } |
---|
186 | } |
---|
187 | else |
---|
188 | { |
---|
189 | return 0; |
---|
190 | } |
---|
191 | strcatf(str, "<Title>%s</Title>", unescape_tag(title)); |
---|
192 | if( artist ) { |
---|
193 | strcatf(str, "<ArtistName>%s</ArtistName>", unescape_tag(artist)); |
---|
194 | } |
---|
195 | if( album ) { |
---|
196 | strcatf(str, "<AlbumTitle>%s</AlbumTitle>", unescape_tag(album)); |
---|
197 | } |
---|
198 | if( genre ) { |
---|
199 | strcatf(str, "<MusicGenre>%s</MusicGenre>", unescape_tag(genre)); |
---|
200 | } |
---|
201 | if( resolution ) { |
---|
202 | char *width = strsep(&resolution, "x"); |
---|
203 | strcatf(str, "<SourceWidth>%s</SourceWidth>" |
---|
204 | "<SourceHeight>%s</SourceHeight>", |
---|
205 | width, resolution); |
---|
206 | } |
---|
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))); |
---|
211 | } |
---|
212 | if( bitrate ) { |
---|
213 | strcatf(str, "<SourceBitRate>%s</SourceBitRate>", bitrate); |
---|
214 | } |
---|
215 | if( sampleFrequency ) { |
---|
216 | strcatf(str, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency); |
---|
217 | } |
---|
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 ) |
---|
225 | { |
---|
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); |
---|
234 | } |
---|
235 | else |
---|
236 | { |
---|
237 | strcatf(str, "</Links>"); |
---|
238 | } |
---|
239 | } |
---|
240 | else if( strncmp(class, "container", 9) == 0 ) |
---|
241 | { |
---|
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&Container=%s</Url>" |
---|
261 | "<ContentType>x-tivo-container/folder</ContentType>" |
---|
262 | "</Content>" |
---|
263 | "</Links>", |
---|
264 | unescape_tag(title), count, id); |
---|
265 | } |
---|
266 | strcatf(str, "</Item>"); |
---|
267 | |
---|
268 | passed_args->returned++; |
---|
269 | |
---|
270 | return 0; |
---|
271 | } |
---|
272 | |
---|
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 " |
---|
276 | |
---|
277 | void |
---|
278 | SendItemDetails(struct upnphttp * h, sqlite_int64 item) |
---|
279 | { |
---|
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)); |
---|
287 | |
---|
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 ) |
---|
300 | { |
---|
301 | DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); |
---|
302 | sqlite3_free(zErrMsg); |
---|
303 | } |
---|
304 | strcatf(&str, "</TiVoItem>"); |
---|
305 | |
---|
306 | BuildResp_upnphttp(h, str.data, str.off); |
---|
307 | free(str.data); |
---|
308 | SendResp_upnphttp(h); |
---|
309 | } |
---|
310 | |
---|
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) |
---|
314 | { |
---|
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)); |
---|
330 | |
---|
331 | args.str = &str; |
---|
332 | str.data = resp+1024; |
---|
333 | str.size = 262144-1024; |
---|
334 | if( itemCount >= 0 ) |
---|
335 | { |
---|
336 | args.requested = itemCount; |
---|
337 | } |
---|
338 | else |
---|
339 | { |
---|
340 | if( itemCount == -100 ) |
---|
341 | itemCount = 1; |
---|
342 | args.requested = itemCount * -1; |
---|
343 | } |
---|
344 | |
---|
345 | switch( *objectID ) |
---|
346 | { |
---|
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; |
---|
359 | } |
---|
360 | |
---|
361 | if( strlen(objectID) == 1 ) |
---|
362 | { |
---|
363 | switch( *objectID ) |
---|
364 | { |
---|
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; |
---|
377 | } |
---|
378 | } |
---|
379 | else |
---|
380 | { |
---|
381 | item = sql_get_text_field(db, "SELECT NAME from OBJECTS where OBJECT_ID = '%s'", objectID); |
---|
382 | if( item ) |
---|
383 | { |
---|
384 | title = escape_tag(item, 1); |
---|
385 | sqlite3_free(item); |
---|
386 | } |
---|
387 | else |
---|
388 | title = strdup("UNKNOWN"); |
---|
389 | } |
---|
390 | |
---|
391 | if( recurse ) |
---|
392 | { |
---|
393 | asprintf(&which, "OBJECT_ID glob '%s$*'", objectID); |
---|
394 | strcpy(groupBy, "group by DETAIL_ID"); |
---|
395 | } |
---|
396 | else |
---|
397 | { |
---|
398 | asprintf(&which, "PARENT_ID = '%s'", objectID); |
---|
399 | } |
---|
400 | |
---|
401 | if( sortOrder ) |
---|
402 | { |
---|
403 | if( strcasestr(sortOrder, "Random") ) |
---|
404 | { |
---|
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); |
---|
410 | } |
---|
411 | else |
---|
412 | { |
---|
413 | short title_state = 0; |
---|
414 | item = strtok_r(sortOrder, ",", &saveptr); |
---|
415 | while( item != NULL ) |
---|
416 | { |
---|
417 | int reverse=0; |
---|
418 | if( *item == '!' ) |
---|
419 | { |
---|
420 | reverse = 1; |
---|
421 | item++; |
---|
422 | } |
---|
423 | if( strcasecmp(item, "Type") == 0 ) |
---|
424 | { |
---|
425 | strcat(order, "CLASS"); |
---|
426 | strcat(order2, "CLASS"); |
---|
427 | } |
---|
428 | else if( strcasecmp(item, "Title") == 0 ) |
---|
429 | { |
---|
430 | /* Explicitly sort music by track then title. */ |
---|
431 | if( title_state < 2 && *objectID == '1' ) |
---|
432 | { |
---|
433 | if( !title_state ) |
---|
434 | { |
---|
435 | strcat(order, "DISC"); |
---|
436 | strcat(order2, "DISC"); |
---|
437 | title_state = 1; |
---|
438 | } |
---|
439 | else |
---|
440 | { |
---|
441 | strcat(order, "TRACK"); |
---|
442 | strcat(order2, "TRACK"); |
---|
443 | title_state = 2; |
---|
444 | } |
---|
445 | } |
---|
446 | else |
---|
447 | { |
---|
448 | strcat(order, "TITLE"); |
---|
449 | strcat(order2, "TITLE"); |
---|
450 | title_state = -1; |
---|
451 | } |
---|
452 | } |
---|
453 | else if( strcasecmp(item, "CreationDate") == 0 || |
---|
454 | strcasecmp(item, "CaptureDate") == 0 ) |
---|
455 | { |
---|
456 | strcat(order, "DATE"); |
---|
457 | strcat(order2, "DATE"); |
---|
458 | } |
---|
459 | else |
---|
460 | { |
---|
461 | DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item); |
---|
462 | goto unhandled_order; |
---|
463 | } |
---|
464 | |
---|
465 | if( reverse ) |
---|
466 | { |
---|
467 | strcat(order, " DESC"); |
---|
468 | if( itemCount >= 0 ) |
---|
469 | strcat(order2, " DESC"); |
---|
470 | else |
---|
471 | strcat(order2, " ASC"); |
---|
472 | } |
---|
473 | else |
---|
474 | { |
---|
475 | strcat(order, " ASC"); |
---|
476 | if( itemCount >= 0 ) |
---|
477 | strcat(order2, " ASC"); |
---|
478 | else |
---|
479 | strcat(order2, " DESC"); |
---|
480 | } |
---|
481 | strcat(order, ", "); |
---|
482 | strcat(order2, ", "); |
---|
483 | unhandled_order: |
---|
484 | if( title_state <= 0 ) |
---|
485 | item = strtok_r(NULL, ",", &saveptr); |
---|
486 | } |
---|
487 | if( title_state != -1 ) |
---|
488 | { |
---|
489 | strcat(order, "TITLE ASC, "); |
---|
490 | if( itemCount >= 0 ) |
---|
491 | strcat(order2, "TITLE ASC, "); |
---|
492 | else |
---|
493 | strcat(order2, "TITLE DESC, "); |
---|
494 | } |
---|
495 | strcat(order, "DETAIL_ID ASC"); |
---|
496 | if( itemCount >= 0 ) |
---|
497 | strcat(order2, "DETAIL_ID ASC"); |
---|
498 | else |
---|
499 | strcat(order2, "DETAIL_ID DESC"); |
---|
500 | } |
---|
501 | } |
---|
502 | else |
---|
503 | { |
---|
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"); |
---|
509 | } |
---|
510 | |
---|
511 | if( filter ) |
---|
512 | { |
---|
513 | item = strtok_r(filter, ",", &saveptr); |
---|
514 | for( i=0; item != NULL; i++ ) |
---|
515 | { |
---|
516 | if( i ) |
---|
517 | { |
---|
518 | strcat(myfilter, " or "); |
---|
519 | } |
---|
520 | if( (strcasecmp(item, "x-container/folder") == 0) || |
---|
521 | (strncasecmp(item, "x-tivo-container/", 17) == 0) ) |
---|
522 | { |
---|
523 | strcat(myfilter, "CLASS glob 'container*'"); |
---|
524 | } |
---|
525 | else if( strncasecmp(item, "image", 5) == 0 ) |
---|
526 | { |
---|
527 | strcat(myfilter, "MIME = 'image/jpeg'"); |
---|
528 | } |
---|
529 | else if( strncasecmp(item, "audio", 5) == 0 ) |
---|
530 | { |
---|
531 | strcat(myfilter, "MIME = 'audio/mpeg'"); |
---|
532 | } |
---|
533 | else if( strncasecmp(item, "video", 5) == 0 ) |
---|
534 | { |
---|
535 | strcat(myfilter, "MIME in ('video/mpeg', 'video/x-tivo-mpeg')"); |
---|
536 | } |
---|
537 | else |
---|
538 | { |
---|
539 | DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item); |
---|
540 | if( i ) |
---|
541 | { |
---|
542 | ret = strlen(myfilter); |
---|
543 | myfilter[ret-4] = '\0'; |
---|
544 | } |
---|
545 | i--; |
---|
546 | } |
---|
547 | item = strtok_r(NULL, ",", &saveptr); |
---|
548 | } |
---|
549 | } |
---|
550 | else |
---|
551 | { |
---|
552 | strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg') or CLASS glob 'container*'"); |
---|
553 | } |
---|
554 | |
---|
555 | if( anchorItem ) |
---|
556 | { |
---|
557 | if( strstr(anchorItem, "QueryContainer") ) |
---|
558 | { |
---|
559 | strcpy(what, "OBJECT_ID"); |
---|
560 | saveptr = strrchr(anchorItem, '='); |
---|
561 | if( saveptr ) |
---|
562 | anchorItem = saveptr + 1; |
---|
563 | } |
---|
564 | else |
---|
565 | { |
---|
566 | strcpy(what, "DETAIL_ID"); |
---|
567 | } |
---|
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 ) |
---|
575 | { |
---|
576 | for( i=1; i<=ret; i++ ) |
---|
577 | { |
---|
578 | if( strcmp(anchorItem, result[i]) == 0 ) |
---|
579 | { |
---|
580 | if( itemCount < 0 ) |
---|
581 | itemStart = ret - i + itemCount; |
---|
582 | else |
---|
583 | itemStart += i; |
---|
584 | break; |
---|
585 | } |
---|
586 | } |
---|
587 | sqlite3_free_table(result); |
---|
588 | } |
---|
589 | sqlite3_free(sql); |
---|
590 | } |
---|
591 | args.start = itemStart+anchorOffset; |
---|
592 | sqlite3Prng.isInit = 0; |
---|
593 | |
---|
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 ) |
---|
600 | { |
---|
601 | args.start = totalMatches + itemCount; |
---|
602 | } |
---|
603 | |
---|
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 ) |
---|
614 | { |
---|
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; |
---|
622 | } |
---|
623 | strcatf(&str, "</TiVoContainer>"); |
---|
624 | |
---|
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); |
---|
644 | } |
---|
645 | |
---|
646 | void |
---|
647 | ProcessTiVoCommand(struct upnphttp * h, const char * orig_path) |
---|
648 | { |
---|
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; |
---|
657 | |
---|
658 | path = strdup(orig_path); |
---|
659 | DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path); |
---|
660 | |
---|
661 | item = strtok_r( path, "&", &saveptr ); |
---|
662 | while( item != NULL ) |
---|
663 | { |
---|
664 | if( *item == '\0' ) |
---|
665 | { |
---|
666 | item = strtok_r( NULL, "&", &saveptr ); |
---|
667 | continue; |
---|
668 | } |
---|
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 ) |
---|
675 | { |
---|
676 | command = val; |
---|
677 | } |
---|
678 | else if( strcasecmp(key, "Container") == 0 ) |
---|
679 | { |
---|
680 | container = val; |
---|
681 | } |
---|
682 | else if( strcasecmp(key, "ItemStart") == 0 ) |
---|
683 | { |
---|
684 | itemStart = atoi(val); |
---|
685 | } |
---|
686 | else if( strcasecmp(key, "ItemCount") == 0 ) |
---|
687 | { |
---|
688 | itemCount = atoi(val); |
---|
689 | } |
---|
690 | else if( strcasecmp(key, "AnchorItem") == 0 ) |
---|
691 | { |
---|
692 | anchorItem = basename(val); |
---|
693 | } |
---|
694 | else if( strcasecmp(key, "AnchorOffset") == 0 ) |
---|
695 | { |
---|
696 | anchorOffset = atoi(val); |
---|
697 | } |
---|
698 | else if( strcasecmp(key, "Recurse") == 0 ) |
---|
699 | { |
---|
700 | recurse = strcasecmp("yes", val) == 0 ? 1 : 0; |
---|
701 | } |
---|
702 | else if( strcasecmp(key, "SortOrder") == 0 ) |
---|
703 | { |
---|
704 | sortOrder = val; |
---|
705 | } |
---|
706 | else if( strcasecmp(key, "Filter") == 0 ) |
---|
707 | { |
---|
708 | filter = val; |
---|
709 | } |
---|
710 | else if( strcasecmp(key, "RandomSeed") == 0 ) |
---|
711 | { |
---|
712 | randomSeed = strtoul(val, NULL, 10); |
---|
713 | } |
---|
714 | else if( strcasecmp(key, "Url") == 0 ) |
---|
715 | { |
---|
716 | if( val ) |
---|
717 | detailItem = strtoll(basename(val), NULL, 10); |
---|
718 | } |
---|
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 |
---|
722 | { |
---|
723 | ;; |
---|
724 | } |
---|
725 | else |
---|
726 | { |
---|
727 | DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key); |
---|
728 | } |
---|
729 | item = strtok_r( NULL, "&", &saveptr ); |
---|
730 | } |
---|
731 | if( anchorItem ) |
---|
732 | { |
---|
733 | strip_ext(anchorItem); |
---|
734 | } |
---|
735 | |
---|
736 | if( command ) |
---|
737 | { |
---|
738 | if( strcmp(command, "QueryContainer") == 0 ) |
---|
739 | { |
---|
740 | if( !container || (strcmp(container, "/") == 0) ) |
---|
741 | { |
---|
742 | SendRootContainer(h); |
---|
743 | } |
---|
744 | else |
---|
745 | { |
---|
746 | SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed); |
---|
747 | } |
---|
748 | } |
---|
749 | else if( strcmp(command, "QueryItem") == 0 ) |
---|
750 | { |
---|
751 | SendItemDetails(h, detailItem); |
---|
752 | } |
---|
753 | else |
---|
754 | { |
---|
755 | DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command); |
---|
756 | Send501(h); |
---|
757 | free(path); |
---|
758 | return; |
---|
759 | } |
---|
760 | } |
---|
761 | free(path); |
---|
762 | CloseSocket_upnphttp(h); |
---|
763 | } |
---|
764 | #endif // TIVO_SUPPORT |
---|