"Fossies" - the Fresh Open Source Software Archive

Member "minidlna-1.3.0/tivo_commands.c" (24 Nov 2020, 21277 Bytes) of package /linux/privat/minidlna-1.3.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tivo_commands.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.2.1_vs_1.3.0.

    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 #include <sys/stat.h>
   26 
   27 #include "event.h"
   28 #include "tivo_utils.h"
   29 #include "upnpglobalvars.h"
   30 #include "upnphttp.h"
   31 #include "upnpsoap.h"
   32 #include "utils.h"
   33 #include "sql.h"
   34 #include "log.h"
   35 
   36 static void
   37 SendRootContainer(struct upnphttp *h)
   38 {
   39     char *resp;
   40     int len;
   41 
   42     len = xasprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
   43             "<TiVoContainer>"
   44              "<Details>"
   45               "<ContentType>x-container/tivo-server</ContentType>"
   46               "<SourceFormat>x-container/folder</SourceFormat>"
   47               "<TotalDuration>0</TotalDuration>"
   48               "<TotalItems>3</TotalItems>"
   49               "<Title>%s</Title>"
   50              "</Details>"
   51              "<ItemStart>0</ItemStart>"
   52              "<ItemCount>3</ItemCount>"
   53              "<Item>"
   54               "<Details>"
   55                "<ContentType>x-container/tivo-photos</ContentType>"
   56                "<SourceFormat>x-container/folder</SourceFormat>"
   57                "<Title>Pictures on %s</Title>"
   58               "</Details>"
   59               "<Links>"
   60                "<Content>"
   61                 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=3</Url>"
   62                "</Content>"
   63               "</Links>"
   64              "</Item>"
   65              "<Item>"
   66               "<Details>"
   67                "<ContentType>x-container/tivo-music</ContentType>"
   68                "<SourceFormat>x-container/folder</SourceFormat>"
   69                "<Title>Music on %s</Title>"
   70               "</Details>"
   71               "<Links>"
   72                "<Content>"
   73                 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=1</Url>"
   74                "</Content>"
   75               "</Links>"
   76              "</Item>"
   77              "<Item>"
   78               "<Details>"
   79                "<ContentType>x-container/tivo-videos</ContentType>"
   80                "<SourceFormat>x-container/folder</SourceFormat>"
   81                "<Title>Videos on %s</Title>"
   82               "</Details>"
   83               "<Links>"
   84                "<Content>"
   85                 "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=2</Url>"
   86                         "<ContentType>x-container/tivo-videos</ContentType>"
   87                "</Content>"
   88               "</Links>"
   89              "</Item>"
   90             "</TiVoContainer>",
   91                     friendly_name, friendly_name, friendly_name, friendly_name);
   92     BuildResp_upnphttp(h, resp, len);
   93     free(resp);
   94     SendResp_upnphttp(h);
   95 }
   96 
   97 static void
   98 SendFormats(struct upnphttp *h, const char *sformat)
   99 {
  100     char *resp;
  101     int len;
  102 
  103     len = xasprintf(&resp, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
  104             "<TiVoFormats>"
  105              "<Format>"
  106                "<ContentType>video/x-tivo-mpeg</ContentType>"
  107                "<Description/>"
  108              "</Format>"
  109              "<Format>"
  110                "<ContentType>%s</ContentType>"
  111                "<Description/>"
  112              "</Format>"
  113             "</TiVoFormats>", sformat);
  114     BuildResp_upnphttp(h, resp, len);
  115     free(resp);
  116     SendResp_upnphttp(h);
  117 }
  118 
  119 static char *
  120 tivo_unescape_tag(char *tag)
  121 {
  122     modifyString(tag, "&amp;amp;", "&amp;", 1);
  123     modifyString(tag, "&amp;amp;lt;", "&lt;", 1);
  124     modifyString(tag, "&amp;lt;", "&lt;", 1);
  125     modifyString(tag, "&amp;amp;gt;", "&gt;", 1);
  126     modifyString(tag, "&amp;gt;", "&gt;", 1);
  127     modifyString(tag, "&amp;quot;", "&quot;", 1);
  128     return tag;
  129 }
  130 
  131 #define FLAG_SEND_RESIZED  0x01
  132 #define FLAG_NO_PARAMS     0x02
  133 #define FLAG_VIDEO         0x04
  134 static int
  135 callback(void *args, int argc, char **argv, char **azColName)
  136 {
  137     struct Response *passed_args = (struct Response *)args;
  138     char *id = argv[0], *class = argv[1], *detailID = argv[2], *size = argv[3], *title = argv[4], *duration = argv[5],
  139              *bitrate = argv[6], *sampleFrequency = argv[7], *artist = argv[8], *album = argv[9], *genre = argv[10],
  140              *comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14];
  141     struct string_s *str = passed_args->str;
  142 
  143     if( strncmp(class, "item", 4) == 0 )
  144     {
  145         int flags = 0;
  146         tivo_unescape_tag(title);
  147         if( strncmp(mime, "audio", 5) == 0 )
  148         {
  149             flags |= FLAG_NO_PARAMS;
  150             strcatf(str, "<Item><Details>"
  151                          "<ContentType>%s</ContentType>"
  152                          "<SourceFormat>%s</SourceFormat>"
  153                          "<SourceSize>%s</SourceSize>",
  154                          "audio/*", mime, size);
  155             strcatf(str, "<SongTitle>%s</SongTitle>", title);
  156             if( date )
  157                 strcatf(str, "<AlbumYear>%.*s</AlbumYear>", 4, date);
  158         }
  159         else if( strcmp(mime, "image/jpeg") == 0 )
  160         {
  161             flags |= FLAG_SEND_RESIZED;
  162             strcatf(str, "<Item><Details>"
  163                          "<ContentType>%s</ContentType>"
  164                          "<SourceFormat>%s</SourceFormat>"
  165                          "<SourceSize>%s</SourceSize>",
  166                          "image/*", mime, size);
  167             if( date )
  168             {
  169                 struct tm tm;
  170                 memset(&tm, 0, sizeof(tm));
  171                 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not
  172                 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
  173                 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
  174             }
  175             if( comment )
  176                 strcatf(str, "<Caption>%s</Caption>", comment);
  177         }
  178         else if( strncmp(mime, "video", 5) == 0 )
  179         {
  180             char *episode;
  181             flags |= FLAG_VIDEO;
  182             strcatf(str, "<Item><Details>"
  183                          "<ContentType>%s</ContentType>"
  184                          "<SourceFormat>%s</SourceFormat>"
  185                          "<SourceSize>%s</SourceSize>",
  186                          mime, mime, size);
  187             episode = strstr(title, " - ");
  188             if( episode )
  189             {
  190                 strcatf(str, "<EpisodeTitle>%s</EpisodeTitle>", episode+3);
  191                 *episode = '\0';
  192             }
  193             if( date )
  194             {
  195                 struct tm tm;
  196                 memset(&tm, 0, sizeof(tm));
  197                 tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not
  198                 strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
  199                 strcatf(str, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
  200             }
  201             if( comment )
  202                 strcatf(str, "<Description>%s</Description>", tivo_unescape_tag(comment));
  203         }
  204         else
  205         {
  206             return 0;
  207         }
  208         strcatf(str, "<Title>%s</Title>", title);
  209         if( artist ) {
  210             strcatf(str, "<ArtistName>%s</ArtistName>", tivo_unescape_tag(artist));
  211         }
  212         if( album ) {
  213             strcatf(str, "<AlbumTitle>%s</AlbumTitle>", tivo_unescape_tag(album));
  214         }
  215         if( genre ) {
  216             strcatf(str, "<MusicGenre>%s</MusicGenre>", tivo_unescape_tag(genre));
  217         }
  218         if( resolution ) {
  219             char *width = strsep(&resolution, "x");
  220             strcatf(str, "<SourceWidth>%s</SourceWidth>"
  221                                "<SourceHeight>%s</SourceHeight>",
  222                                width, resolution);
  223         }
  224         if( duration ) {
  225             strcatf(str, "<Duration>%d</Duration>",
  226                   atoi(strrchr(duration, '.')+1) + (1000*atoi(strrchr(duration, ':')+1))
  227                   + (60000*atoi(strrchr(duration, ':')-2)) + (3600000*atoi(duration)));
  228         }
  229         if( bitrate ) {
  230             strcatf(str, "<SourceBitRate>%s</SourceBitRate>", bitrate);
  231         }
  232         if( sampleFrequency ) {
  233             strcatf(str, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency);
  234         }
  235         strcatf(str, "</Details><Links>"
  236                      "<Content>"
  237                        "<ContentType>%s</ContentType>"
  238                        "<Url>/%s/%s.%s</Url>%s"
  239                      "</Content>",
  240                      mime,
  241                      (flags & FLAG_SEND_RESIZED) ? "Resized" : "MediaItems",
  242                      detailID, mime_to_ext(mime),
  243                      (flags & FLAG_NO_PARAMS) ? "<AcceptsParams>No</AcceptsParams>" : "");
  244         if( flags & FLAG_VIDEO )
  245         {
  246             strcatf(str, "<CustomIcon>"
  247                            "<ContentType>image/*</ContentType>"
  248                            "<Url>urn:tivo:image:save-until-i-delete-recording</Url>"
  249                          "</CustomIcon>");
  250         }
  251         strcatf(str, "</Links>");
  252     }
  253     else if( strncmp(class, "container", 9) == 0 )
  254     {
  255         int count;
  256         /* Determine the number of children */
  257 #ifdef __sparc__ /* Adding filters on large containers can take a long time on slow processors */
  258         count = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", id);
  259 #else
  260         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 "
  261                                       " (MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')"
  262                                       " or CLASS glob 'container*')", id);
  263 #endif
  264         strcatf(str, "<Item>"
  265                      "<Details>"
  266                        "<ContentType>x-container/folder</ContentType>"
  267                        "<SourceFormat>x-container/folder</SourceFormat>"
  268                        "<Title>%s</Title>"
  269                        "<TotalItems>%d</TotalItems>"
  270                      "</Details>"
  271                      "<Links>"
  272                        "<Content>"
  273                          "<Url>/TiVoConnect?Command=QueryContainer&amp;Container=%s</Url>"
  274                          "<ContentType>x-tivo-container/folder</ContentType>"
  275                        "</Content>"
  276                      "</Links>",
  277                      tivo_unescape_tag(title), count, id);
  278     }
  279     strcatf(str, "</Item>");
  280 
  281     passed_args->returned++;
  282 
  283     return 0;
  284 }
  285 
  286 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE," \
  287                    " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE," \
  288                    " d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.DISC, d.TRACK "
  289 
  290 static void
  291 SendItemDetails(struct upnphttp *h, int64_t item)
  292 {
  293     char *sql;
  294     char *zErrMsg = NULL;
  295     struct Response args;
  296     struct string_s str;
  297     int ret;
  298     memset(&args, 0, sizeof(args));
  299     memset(&str, 0, sizeof(str));
  300 
  301     str.data = malloc(32768);
  302     str.size = 32768;
  303     str.off = sprintf(str.data, "<?xml version='1.0' encoding='UTF-8' ?>\n<TiVoItem>");
  304     args.str = &str;
  305     args.requested = 1;
  306     xasprintf(&sql, SELECT_COLUMNS
  307                    "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
  308                " where o.DETAIL_ID = %lld group by o.DETAIL_ID", (long long)item);
  309     DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
  310     ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
  311     free(sql);
  312     if( ret != SQLITE_OK )
  313     {
  314         DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg);
  315         sqlite3_free(zErrMsg);
  316     }
  317     strcatf(&str, "</TiVoItem>");
  318 
  319     BuildResp_upnphttp(h, str.data, str.off);
  320     free(str.data);
  321     SendResp_upnphttp(h);
  322 }
  323 
  324 static void
  325 SendContainer(struct upnphttp *h, const char *objectID, int itemStart, int itemCount, char *anchorItem,
  326               int anchorOffset, int recurse, char *sortOrder, char *filter, unsigned long int randomSeed)
  327 {
  328     char *resp = malloc(262144);
  329     char *sql, *item, *saveptr;
  330     char *zErrMsg = NULL;
  331     char **result;
  332     char *title, *which;
  333     char what[10], order[96]={0}, order2[96]={0}, myfilter[256]={0};
  334     char str_buf[1024];
  335     char type[8];
  336     char groupBy[19] = {0};
  337     struct Response args;
  338     struct string_s str;
  339     int totalMatches = 0;
  340     int i, ret;
  341     memset(&args, 0, sizeof(args));
  342     memset(&str, 0, sizeof(str));
  343 
  344     args.str = &str;
  345     str.data = resp+1024;
  346     str.size = 262144-1024;
  347     if( itemCount >= 0 )
  348     {
  349         args.requested = itemCount;
  350     }
  351     else
  352     {
  353         if( itemCount == -100 )
  354             itemCount = 1;
  355         args.requested = itemCount * -1;
  356     }
  357 
  358     switch( *objectID )
  359     {
  360         case '1':
  361             strcpy(type, "music");
  362             break;
  363         case '2':
  364             strcpy(type, "videos");
  365             break;
  366         case '3':
  367             strcpy(type, "photos");
  368             break;
  369         default:
  370             strcpy(type, "server");
  371             break;
  372     }
  373 
  374     if( strlen(objectID) == 1 )
  375     {
  376         switch( *objectID )
  377         {
  378             case '1':
  379                 xasprintf(&title, "Music on %s", friendly_name);
  380                 break;
  381             case '2':
  382                 xasprintf(&title, "Videos on %s", friendly_name);
  383                 break;
  384             case '3':
  385                 xasprintf(&title, "Pictures on %s", friendly_name);
  386                 break;
  387             default:
  388                 xasprintf(&title, "Unknown on %s", friendly_name);
  389                 break;
  390         }
  391     }
  392     else
  393     {
  394         item = sql_get_text_field(db, "SELECT NAME from OBJECTS where OBJECT_ID = '%q'", objectID);
  395         if( item )
  396         {
  397             title = escape_tag(item, 1);
  398             sqlite3_free(item);
  399         }
  400         else
  401             title = strdup("UNKNOWN");
  402     }
  403 
  404     if( recurse )
  405     {
  406         which = sqlite3_mprintf("OBJECT_ID glob '%q$*'", objectID);
  407         strcpy(groupBy, "group by DETAIL_ID");
  408     }
  409     else
  410     {
  411         which = sqlite3_mprintf("PARENT_ID = '%q'", objectID);
  412     }
  413 
  414     if( sortOrder )
  415     {
  416         if( strcasestr(sortOrder, "Random") )
  417         {
  418             sprintf(order, "tivorandom(%lu)", randomSeed);
  419             if( itemCount < 0 )
  420                 sprintf(order2, "tivorandom(%lu) DESC", randomSeed);
  421             else
  422                 sprintf(order2, "tivorandom(%lu)", randomSeed);
  423         }
  424         else
  425         {
  426             short title_state = 0;
  427             item = strtok_r(sortOrder, ",", &saveptr);
  428             while( item != NULL )
  429             {
  430                 int reverse=0;
  431                 if( *item == '!' )
  432                 {
  433                     reverse = 1;
  434                     item++;
  435                 }
  436                 if( strcasecmp(item, "Type") == 0 )
  437                 {
  438                     strcat(order, "CLASS");
  439                     strcat(order2, "CLASS");
  440                 }
  441                 else if( strcasecmp(item, "Title") == 0 )
  442                 {
  443                     /* Explicitly sort music by track then title. */
  444                     if( title_state < 2 && *objectID == '1' )
  445                     {
  446                         if( !title_state )
  447                         {
  448                             strcat(order, "DISC");
  449                             strcat(order2, "DISC");
  450                             title_state = 1;
  451                         }
  452                         else
  453                         {
  454                             strcat(order, "TRACK");
  455                             strcat(order2, "TRACK");
  456                             title_state = 2;
  457                         }
  458                     }
  459                     else
  460                     {
  461                         strcat(order, "TITLE");
  462                         strcat(order2, "TITLE");
  463                         title_state = -1;
  464                     }
  465                 }
  466                 else if( strcasecmp(item, "CreationDate") == 0 ||
  467                          strcasecmp(item, "CaptureDate") == 0 )
  468                 {
  469                     strcat(order, "DATE");
  470                     strcat(order2, "DATE");
  471                 }
  472                 else
  473                 {
  474                     DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item);
  475                     goto unhandled_order;
  476                 }
  477 
  478                 if( reverse )
  479                 {
  480                     strcat(order, " DESC");
  481                     if( itemCount >= 0 )
  482                         strcat(order2, " DESC");
  483                     else
  484                         strcat(order2, " ASC");
  485                 }
  486                 else
  487                 {
  488                     strcat(order, " ASC");
  489                     if( itemCount >= 0 )
  490                         strcat(order2, " ASC");
  491                     else
  492                         strcat(order2, " DESC");
  493                 }
  494                 strcat(order, ", ");
  495                 strcat(order2, ", ");
  496                 unhandled_order:
  497                 if( title_state <= 0 )
  498                     item = strtok_r(NULL, ",", &saveptr);
  499             }
  500             if( title_state != -1 )
  501             {
  502                 strcat(order, "TITLE ASC, ");
  503                 if( itemCount >= 0 )
  504                     strcat(order2, "TITLE ASC, ");
  505                 else
  506                     strcat(order2, "TITLE DESC, ");
  507             }
  508             strcat(order, "DETAIL_ID ASC");
  509             if( itemCount >= 0 )
  510                 strcat(order2, "DETAIL_ID ASC");
  511             else
  512                 strcat(order2, "DETAIL_ID DESC");
  513         }
  514     }
  515     else
  516     {
  517         sprintf(order, "CLASS, NAME, DETAIL_ID");
  518         if( itemCount < 0 )
  519             sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC");
  520         else
  521             sprintf(order2, "CLASS, NAME, DETAIL_ID");
  522     }
  523 
  524     if( filter )
  525     {
  526         item = strtok_r(filter, ",", &saveptr);
  527         for( i=0; item != NULL; i++ )
  528         {
  529             if( i )
  530             {
  531                 strcat(myfilter, " or ");
  532             }
  533             if( (strcasecmp(item, "x-container/folder") == 0) ||
  534                 (strncasecmp(item, "x-tivo-container/", 17) == 0) )
  535             {
  536                 strcat(myfilter, "CLASS glob 'container*'");
  537             }
  538             else if( strncasecmp(item, "image", 5) == 0 )
  539             {
  540                 strcat(myfilter, "MIME = 'image/jpeg'");
  541             }
  542             else if( strncasecmp(item, "audio", 5) == 0 )
  543             {
  544                 strcat(myfilter, "MIME = 'audio/mpeg'");
  545             }
  546             else if( strncasecmp(item, "video", 5) == 0 )
  547             {
  548                 strcat(myfilter, "MIME in ('video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')");
  549             }
  550             else
  551             {
  552                 DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item);
  553                 if( i )
  554                 {
  555                     ret = strlen(myfilter);
  556                     myfilter[ret-4] = '\0';
  557                 }
  558                 i--;
  559             }
  560             item = strtok_r(NULL, ",", &saveptr);
  561         }
  562     }
  563     else
  564     {
  565         strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts') or CLASS glob 'container*'");
  566     }
  567 
  568     if( anchorItem )
  569     {
  570         if( strstr(anchorItem, "QueryContainer") )
  571         {
  572             strcpy(what, "OBJECT_ID");
  573             saveptr = strrchr(anchorItem, '=');
  574             if( saveptr )
  575                 anchorItem = saveptr + 1;
  576         }
  577         else
  578         {
  579             strcpy(what, "DETAIL_ID");
  580         }
  581         sqlite3Prng.isInit = 0;
  582         sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
  583                               " where %s and (%s)"
  584                                   " %s"
  585                               " order by %s", what, which, myfilter, groupBy, order2);
  586         DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
  587         if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret )
  588         {
  589             for( i=1; i<=ret; i++ )
  590             {
  591                 if( strcmp(anchorItem, result[i]) == 0 )
  592                 {
  593                     if( itemCount < 0 )
  594                         itemStart = ret - i + itemCount;
  595                     else
  596                         itemStart += i;
  597                     break;
  598                 }
  599             }
  600             sqlite3_free_table(result);
  601         }
  602         sqlite3_free(sql);
  603     }
  604     args.start = itemStart+anchorOffset;
  605     sqlite3Prng.isInit = 0;
  606 
  607     ret = sql_get_int_field(db, "SELECT count(distinct DETAIL_ID) "
  608                                 "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
  609                                 " where %s and (%s)",
  610                                 which, myfilter);
  611     totalMatches = (ret > 0) ? ret : 0;
  612     if( itemCount < 0 && !itemStart && !anchorOffset )
  613     {
  614         args.start = totalMatches + itemCount;
  615     }
  616 
  617     sql = sqlite3_mprintf(SELECT_COLUMNS
  618                           "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
  619                       " where %s and (%s)"
  620                           " %s"
  621                   " order by %s limit %d, %d",
  622                           which, myfilter, groupBy, order, args.start, args.requested);
  623     DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
  624     ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
  625     sqlite3_free(sql);
  626     if( ret != SQLITE_OK )
  627     {
  628         DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg);
  629         sqlite3_free(zErrMsg);
  630         Send500(h);
  631         sqlite3_free(which);
  632         free(title);
  633         free(resp);
  634         return;
  635     }
  636     strcatf(&str, "</TiVoContainer>");
  637 
  638     ret = sprintf(str_buf, "<?xml version='1.0' encoding='UTF-8' ?>\n"
  639                    "<TiVoContainer>"
  640                              "<Details>"
  641                                "<ContentType>x-container/tivo-%s</ContentType>"
  642                                "<SourceFormat>x-container/folder</SourceFormat>"
  643                                "<TotalItems>%d</TotalItems>"
  644                                "<Title>%s</Title>"
  645                              "</Details>"
  646                              "<ItemStart>%d</ItemStart>"
  647                              "<ItemCount>%d</ItemCount>",
  648                              type, totalMatches, title, args.start, args.returned);
  649     str.data -= ret;
  650     memcpy(str.data, &str_buf, ret);
  651     str.size = str.off+ret;
  652     free(title);
  653     sqlite3_free(which);
  654     BuildResp_upnphttp(h, str.data, str.size);
  655     free(resp);
  656     SendResp_upnphttp(h);
  657 }
  658 
  659 void
  660 ProcessTiVoCommand(struct upnphttp *h, const char *orig_path)
  661 {
  662     char *path;
  663     char *key, *val;
  664     char *saveptr = NULL, *item;
  665     char *command = NULL, *container = NULL, *anchorItem = NULL;
  666     char *sortOrder = NULL, *filter = NULL, *sformat = NULL;
  667     int64_t detailItem=0;
  668     int itemStart=0, itemCount=-100, anchorOffset=0, recurse=0;
  669     unsigned long int randomSeed=0;
  670 
  671     path = strdup(orig_path);
  672     DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
  673 
  674     item = strtok_r( path, "&", &saveptr );
  675     while( item != NULL )
  676     {
  677         if( *item == '\0' )
  678         {
  679             item = strtok_r( NULL, "&", &saveptr );
  680             continue;
  681         }
  682         decodeString(item, 1);
  683         val = item;
  684         key = strsep(&val, "=");
  685         decodeString(val, 1);
  686         DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
  687         if( strcasecmp(key, "Command") == 0 )
  688         {
  689             command = val;
  690         }
  691         else if( strcasecmp(key, "Container") == 0 )
  692         {
  693             container = val;
  694         }
  695         else if( strcasecmp(key, "ItemStart") == 0 )
  696         {
  697             itemStart = atoi(val);
  698         }
  699         else if( strcasecmp(key, "ItemCount") == 0 )
  700         {
  701             itemCount = atoi(val);
  702         }
  703         else if( strcasecmp(key, "AnchorItem") == 0 )
  704         {
  705             anchorItem = basename(val);
  706         }
  707         else if( strcasecmp(key, "AnchorOffset") == 0 )
  708         {
  709             anchorOffset = atoi(val);
  710         }
  711         else if( strcasecmp(key, "Recurse") == 0 )
  712         {
  713             recurse = strcasecmp("yes", val) == 0 ? 1 : 0;
  714         }
  715         else if( strcasecmp(key, "SortOrder") == 0 )
  716         {
  717             sortOrder = val;
  718         }
  719         else if( strcasecmp(key, "Filter") == 0 )
  720         {
  721             filter = val;
  722         }
  723         else if( strcasecmp(key, "RandomSeed") == 0 )
  724         {
  725             randomSeed = strtoul(val, NULL, 10);
  726         }
  727         else if( strcasecmp(key, "Url") == 0 )
  728         {
  729             if( val )
  730                 detailItem = strtoll(basename(val), NULL, 10);
  731         }
  732         else if( strcasecmp(key, "SourceFormat") == 0 )
  733         {
  734             sformat = val;
  735         }
  736         else if( strcasecmp(key, "Format") == 0 || // Only send XML
  737                  strcasecmp(key, "SerialNum") == 0 || // Unused for now
  738                  strcasecmp(key, "DoGenres") == 0 ) // Not sure what this is, so ignore it
  739         {
  740             ;;
  741         }
  742         else
  743         {
  744             DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key);
  745         }
  746         item = strtok_r( NULL, "&", &saveptr );
  747     }
  748     if( anchorItem )
  749     {
  750         strip_ext(anchorItem);
  751     }
  752 
  753     if( command )
  754     {
  755         if( strcmp(command, "QueryContainer") == 0 )
  756         {
  757             if( !container || (strcmp(container, "/") == 0) )
  758             {
  759                 SendRootContainer(h);
  760             }
  761             else
  762             {
  763                 SendContainer(h, container, itemStart, itemCount, anchorItem,
  764                               anchorOffset, recurse, sortOrder, filter, randomSeed);
  765             }
  766         }
  767         else if( strcmp(command, "QueryItem") == 0 )
  768         {
  769             SendItemDetails(h, detailItem);
  770         }
  771         else if( strcmp(command, "QueryFormats") == 0 )
  772         {
  773             SendFormats(h, sformat);
  774         }
  775         else
  776         {
  777             DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command);
  778             Send501(h);
  779             free(path);
  780             return;
  781         }
  782     }
  783     free(path);
  784     CloseSocket_upnphttp(h);
  785 }
  786 #endif // TIVO_SUPPORT