MythTV  0.26-pre
upnpcds.cpp
Go to the documentation of this file.
00001 
00002 // Program Name: upnpcds.cpp
00003 // Created     : Oct. 24, 2005
00004 //
00005 // Purpose     : uPnp Content Directory Service
00006 //
00007 // Copyright (c) 2005 David Blain <dblain@mythtv.org>
00008 //
00009 // Licensed under the GPL v2 or later, see COPYING for details                    
00010 //
00012 
00013 #include <cmath>
00014 #include <algorithm>
00015 using namespace std;
00016 
00017 #include "upnp.h"
00018 #include "upnpcds.h"
00019 #include "upnputil.h"
00020 #include "mythlogging.h"
00021 
00022 #define DIDL_LITE_BEGIN "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">"
00023 #define DIDL_LITE_END   "</DIDL-Lite>";
00024 
00026 //
00028 
00029 void UPnpCDSExtensionResults::Add( CDSObject *pObject )
00030 {
00031     if (pObject)
00032         m_List.append( pObject );
00033 }
00034 
00036 //
00038 
00039 QString UPnpCDSExtensionResults::GetResultXML(FilterMap &filter)
00040 {
00041     QString sXML;
00042 
00043     CDSObjects::const_iterator it = m_List.begin();
00044     for (; it != m_List.end(); ++it)
00045         sXML += (*it)->toXml(filter);
00046 
00047     return sXML;
00048 }
00049 
00051 //
00053 
00054 UPnpCDS::UPnpCDS( UPnpDevice *pDevice, const QString &sSharePath )
00055   : Eventing( "UPnpCDS", "CDS_Event", sSharePath )
00056 {
00057     m_root.m_eType      = OT_Container;
00058     m_root.m_sId        = "0";
00059     m_root.m_sParentId  = "-1";
00060     m_root.m_sTitle     = "MythTV";
00061     m_root.m_sClass     = "object.container";
00062     m_root.m_bRestricted= true;
00063     m_root.m_bSearchable= true;
00064 
00065     AddVariable( new StateVariable< QString        >( "TransferIDs"       , true ) );
00066     AddVariable( new StateVariable< QString        >( "ContainerUpdateIDs", true ) );
00067     AddVariable( new StateVariable< unsigned short >( "SystemUpdateID"    , true ) );
00068 
00069     SetValue< unsigned short >( "SystemUpdateID", 1 );
00070 
00071     QString sUPnpDescPath = UPnp::GetConfiguration()->GetValue( "UPnP/DescXmlPath", sSharePath );
00072 
00073     m_sServiceDescFileName = sUPnpDescPath + "CDS_scpd.xml";
00074     m_sControlUrl          = "/CDS_Control";
00075 
00076 
00077     // Add our Service Definition to the device.
00078 
00079     RegisterService( pDevice );
00080 }
00081 
00083 //
00085 
00086 UPnpCDS::~UPnpCDS()
00087 {
00088     while (!m_extensions.empty())
00089     {
00090         delete m_extensions.back();
00091         m_extensions.pop_back();
00092     }
00093 }
00094 
00096 //
00098 
00099 UPnpCDSMethod UPnpCDS::GetMethod( const QString &sURI )
00100 {
00101     if (sURI == "GetServDesc"           ) return CDSM_GetServiceDescription;
00102     if (sURI == "Browse"                ) return CDSM_Browse               ;
00103     if (sURI == "Search"                ) return CDSM_Search               ;
00104     if (sURI == "GetSearchCapabilities" ) return CDSM_GetSearchCapabilities;
00105     if (sURI == "GetSortCapabilities"   ) return CDSM_GetSortCapabilities  ;
00106     if (sURI == "GetSystemUpdateID"     ) return CDSM_GetSystemUpdateID    ;
00107 
00108     return(  CDSM_Unknown );
00109 }
00110 
00112 //
00114 
00115 UPnpCDSBrowseFlag UPnpCDS::GetBrowseFlag( const QString &sFlag )
00116 {
00117     if (sFlag == "BrowseMetadata"       ) return( CDS_BrowseMetadata        );
00118     if (sFlag == "BrowseDirectChildren" ) return( CDS_BrowseDirectChildren  );
00119 
00120     return( CDS_BrowseUnknown );
00121 }
00122 
00124 //
00126 
00127 void UPnpCDS::RegisterExtension  ( UPnpCDSExtension *pExtension )
00128 {
00129     if (pExtension != NULL )
00130         m_extensions.append( pExtension );
00131 }
00132 
00134 //
00136 
00137 void UPnpCDS::UnregisterExtension( UPnpCDSExtension *pExtension )
00138 {
00139     if (pExtension)
00140     {
00141         delete pExtension;
00142         m_extensions.removeAll(pExtension);
00143     }
00144 }
00145 
00147 //
00149 
00150 QStringList UPnpCDS::GetBasePaths()
00151 {
00152     return Eventing::GetBasePaths() << m_sControlUrl;
00153 }
00154 
00156 //
00158 
00159 bool UPnpCDS::ProcessRequest( HTTPRequest *pRequest )
00160 {
00161     if (pRequest)
00162     {
00163         if (Eventing::ProcessRequest( pRequest ))
00164             return true;
00165 
00166         if ( pRequest->m_sBaseUrl != m_sControlUrl )
00167         {
00168 #if 0
00169             LOG(VB_UPNP, LOG_DEBUG,
00170                 QString("UPnpCDS::ProcessRequest - BaseUrl (%1) not ours...")
00171                     .arg(pRequest->m_sBaseUrl));
00172 #endif
00173             return false;
00174         }
00175 
00176         switch( GetMethod( pRequest->m_sMethod ) )
00177         {
00178             case CDSM_GetServiceDescription :
00179                 pRequest->FormatFileResponse( m_sServiceDescFileName );
00180                 break;
00181             case CDSM_Browse                :
00182                 HandleBrowse( pRequest );
00183                 break;
00184             case CDSM_Search                :
00185                 HandleSearch( pRequest );
00186                 break;
00187             case CDSM_GetSearchCapabilities :
00188                 HandleGetSearchCapabilities( pRequest );
00189                 break;
00190             case CDSM_GetSortCapabilities   :
00191                 HandleGetSortCapabilities( pRequest );
00192                 break;
00193             case CDSM_GetSystemUpdateID     :
00194                 HandleGetSystemUpdateID( pRequest );
00195                 break;
00196             default:
00197                 UPnp::FormatErrorResponse( pRequest, UPnPResult_InvalidAction );
00198                 break;
00199         }
00200 
00201         return true;
00202     }
00203 
00204     return false;
00205 }
00206 
00207 static UPnpCDSClientException clientExceptions[] = {
00208     // Windows Media Player version 12
00209     { CDS_ClientWMP, 
00210       "User-Agent",
00211       "Windows-Media-Player/" },
00212     // Windows Media Player version < 12
00213     { CDS_ClientWMP,
00214       "User-Agent",
00215       "Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x" },
00216     // XBMC
00217     { CDS_ClientXBMC,
00218       "User-Agent",
00219       "Platinum/" },
00220     // XBox 360
00221     { CDS_ClientXBox,
00222       "User-Agent",
00223       "Xbox" },
00224     // Sony Blu-ray players
00225     { CDS_ClientSonyDB,
00226       "X-AV-Client-Info",
00227       "cn=\"Sony Corporation\"; mn=\"Blu-ray Disc Player\"" },
00228 };
00229 static uint clientExceptionCount = sizeof(clientExceptions) /
00230                                    sizeof(clientExceptions[0]);
00231 
00232 void UPnpCDS::DetermineClient( HTTPRequest *pRequest,
00233                                UPnpCDSRequest *pCDSRequest )
00234 {
00235     pCDSRequest->m_eClient = CDS_ClientDefault;
00236     pCDSRequest->m_nClientVersion = 0;
00237     bool found = false;
00238 
00239     // Do we know this client string?
00240     for ( uint i = 0; !found && i < clientExceptionCount; i++ )
00241     {
00242         UPnpCDSClientException *except = &clientExceptions[i];
00243 
00244         QString sHeaderValue = pRequest->GetHeaderValue(except->sHeaderKey, "");
00245         int idx = sHeaderValue.indexOf(except->sHeaderValue);
00246         if (idx != -1)
00247         {
00248             pCDSRequest->m_eClient = except->nClientType;
00249 
00250             idx += except->sHeaderValue.length();
00251  
00252             // If we have a / at the end of the string then we 
00253             // increment the string to skip over it
00254             if ( sHeaderValue[idx] == '/')
00255             {
00256                 idx++;
00257             }
00258 
00259             // Now find the version number
00260             QString version = sHeaderValue.mid(idx).trimmed();
00261             idx = version.indexOf( '.' );
00262             if (idx != -1)
00263             {
00264                 idx = version.indexOf( '.', idx + 1 );
00265             }
00266             if (idx != -1)
00267             {
00268                 version = version.left( idx );
00269             }
00270             idx = version.indexOf( ' ' );
00271             if (idx != -1)
00272             {
00273                 version = version.left( idx );
00274             }
00275 
00276             pCDSRequest->m_nClientVersion = version.toDouble();
00277 
00278             LOG(VB_UPNP, LOG_INFO,
00279                 QString("DetermineClient %1:%2 Identified as %3 version %4")
00280                     .arg(except->sHeaderKey) .arg(sHeaderValue)
00281                     .arg(pCDSRequest->m_eClient)
00282                     .arg(pCDSRequest->m_nClientVersion));
00283             found = true;
00284         }
00285     }
00286 }
00287 
00288 
00290 //
00292 
00293 void UPnpCDS::HandleBrowse( HTTPRequest *pRequest )
00294 {
00295     UPnpCDSExtensionResults *pResult  = NULL;
00296     UPnpCDSRequest           request;
00297 
00298     DetermineClient( pRequest, &request );
00299     request.m_sObjectId         = pRequest->m_mapParams[ "ObjectID"      ];
00300     request.m_sContainerID      = pRequest->m_mapParams[ "ContainerID"   ];
00301     request.m_sParentId         = "0";
00302     request.m_eBrowseFlag       =
00303         GetBrowseFlag( pRequest->m_mapParams[ "BrowseFlag"    ] );
00304     request.m_sFilter           = pRequest->m_mapParams[ "Filter"        ];
00305     request.m_nStartingIndex    =
00306         pRequest->m_mapParams[ "StartingIndex" ].toLong();
00307     request.m_nRequestedCount   =
00308         pRequest->m_mapParams[ "RequestedCount"].toLong();
00309     request.m_sSortCriteria     = pRequest->m_mapParams[ "SortCriteria"  ];
00310 
00311 #if 0
00312     LOG(VB_UPNP, LOG_DEBUG, QString("UPnpCDS::ProcessRequest \n"
00313                                     ": url            = %1 \n"
00314                                     ": Method         = %2 \n"
00315                                     ": ObjectId       = %3 \n"
00316                                     ": BrowseFlag     = %4 \n"
00317                                     ": Filter         = %5 \n"
00318                                     ": StartingIndex  = %6 \n"
00319                                     ": RequestedCount = %7 \n"
00320                                     ": SortCriteria   = %8 " )
00321                        .arg( pRequest->m_sBaseUrl     )
00322                        .arg( pRequest->m_sMethod      )
00323                        .arg( request.m_sObjectId      )
00324                        .arg( request.m_eBrowseFlag    )
00325                        .arg( request.m_sFilter        )
00326                        .arg( request.m_nStartingIndex )
00327                        .arg( request.m_nRequestedCount)
00328                        .arg( request.m_sSortCriteria  ));
00329 #endif
00330 
00331     UPnPResultCode eErrorCode      = UPnPResult_CDS_NoSuchObject;
00332     QString        sErrorDesc      = "";
00333     short          nNumberReturned = 0;
00334     short          nTotalMatches   = 0;
00335     short          nUpdateID       = 0;
00336     QString        sResultXML;
00337     FilterMap filter =  (FilterMap) request.m_sFilter.split(',');
00338 
00339     LOG(VB_UPNP, LOG_INFO,
00340         QString("UPnpCDS::HandleBrowse ObjectID=%1, ContainerId=%2")
00341             .arg(request.m_sObjectId) .arg(request.m_sContainerID));
00342 
00343     if (request.m_sObjectId == "0")
00344     {
00345         // ------------------------------------------------------------------
00346         // This is for the root object... lets handle it.
00347         // ------------------------------------------------------------------
00348 
00349         switch( request.m_eBrowseFlag )
00350         {
00351             case CDS_BrowseMetadata:
00352             {
00353                 // -----------------------------------------------------------
00354                 // Return Root Object Only
00355                 // -----------------------------------------------------------
00356 
00357                 eErrorCode      = UPnPResult_Success;
00358                 nNumberReturned = 1;
00359                 nTotalMatches   = 1;
00360                 nUpdateID       = m_root.m_nUpdateId;
00361 
00362                 m_root.SetChildCount( m_extensions.count() );
00363 
00364                 sResultXML      = m_root.toXml(filter);
00365 
00366                 break;
00367             }
00368 
00369             case CDS_BrowseDirectChildren:
00370             {
00371                 // Loop Through each extension and Build the Root Folders
00372 
00373                 // -=>TODO: Need to handle StartingIndex & RequestedCount
00374 
00375                 eErrorCode      = UPnPResult_Success;
00376                 nTotalMatches   = m_extensions.count();
00377                 nUpdateID       = m_root.m_nUpdateId;
00378 
00379                 if (request.m_nRequestedCount == 0)
00380                     request.m_nRequestedCount = nTotalMatches;
00381 
00382                 short nStart = Max( request.m_nStartingIndex, short( 0 ));
00383                 short nCount = Min( nTotalMatches, request.m_nRequestedCount );
00384 
00385                 UPnpCDSRequest       childRequest;
00386 
00387                 DetermineClient( pRequest, &request );
00388                 childRequest.m_sParentId         = "0";
00389                 childRequest.m_eBrowseFlag       = CDS_BrowseMetadata;
00390                 childRequest.m_sFilter           = "";
00391                 childRequest.m_nStartingIndex    = 0;
00392                 childRequest.m_nRequestedCount   = 1;
00393                 childRequest.m_sSortCriteria     = "";
00394 
00395                 for (uint i = nStart;
00396                      (i < (uint)m_extensions.size()) &&
00397                          (nNumberReturned < nCount);
00398                      i++)
00399                 {
00400                     UPnpCDSExtension *pExtension = m_extensions[i];
00401                     childRequest.m_sObjectId = pExtension->m_sExtensionId;
00402 
00403                     pResult = pExtension->Browse( &childRequest );
00404 
00405                     if (pResult != NULL)
00406                     {
00407                         if (pResult->m_eErrorCode == UPnPResult_Success)
00408                         {
00409                             sResultXML  += pResult->GetResultXML(filter);
00410                             nNumberReturned ++;
00411                         }
00412 
00413                         delete pResult;
00414                     }
00415                 }
00416 
00417                 break;
00418             }
00419             default: break;
00420         }
00421     }
00422     else
00423     {
00424         // ------------------------------------------------------------------
00425         // Look for a CDS Extension that knows how to handle this ObjectID
00426         // ------------------------------------------------------------------
00427 
00428         UPnpCDSExtensionList::iterator it = m_extensions.begin();
00429         for (; (it != m_extensions.end()) && !pResult; ++it)
00430         {
00431             LOG(VB_UPNP, LOG_INFO,
00432                 QString("UPNP Browse : Searching for : %1  / ObjectID : %2")
00433                     .arg((*it)->m_sExtensionId).arg(request.m_sObjectId));
00434 
00435             pResult = (*it)->Browse(&request);
00436         }
00437 
00438         if (pResult != NULL)
00439         {
00440             eErrorCode  = pResult->m_eErrorCode;
00441             sErrorDesc  = pResult->m_sErrorDesc;
00442 
00443             if (eErrorCode == UPnPResult_Success)
00444             {
00445                 nNumberReturned = pResult->m_List.count();
00446                 nTotalMatches   = pResult->m_nTotalMatches;
00447                 nUpdateID       = pResult->m_nUpdateID;
00448                 sResultXML      = pResult->GetResultXML(filter);
00449             }
00450 
00451             delete pResult;
00452         }
00453     }
00454 
00455     // ----------------------------------------------------------------------
00456     // Output Results of Browse Method
00457     // ----------------------------------------------------------------------
00458 
00459     if (eErrorCode == UPnPResult_Success)
00460     {
00461         NameValues list;
00462 
00463         QString sResults = DIDL_LITE_BEGIN;
00464         sResults += sResultXML;
00465         sResults += DIDL_LITE_END;
00466 
00467         list.push_back(NameValue("Result",         sResults));
00468         list.push_back(NameValue("NumberReturned", nNumberReturned));
00469         list.push_back(NameValue("TotalMatches",   nTotalMatches));
00470         list.push_back(NameValue("UpdateID",       nUpdateID));
00471 
00472         pRequest->FormatActionResponse(list);
00473     }
00474     else
00475         UPnp::FormatErrorResponse ( pRequest, eErrorCode, sErrorDesc );
00476 
00477 }
00478 
00480 //
00482 
00483 void UPnpCDS::HandleSearch( HTTPRequest *pRequest )
00484 {
00485     UPnpCDSExtensionResults *pResult  = NULL;
00486     UPnpCDSRequest           request;
00487 
00488     UPnPResultCode eErrorCode      = UPnPResult_InvalidAction;
00489     QString       sErrorDesc      = "";
00490     short         nNumberReturned = 0;
00491     short         nTotalMatches   = 0;
00492     short         nUpdateID       = 0;
00493     QString       sResultXML;
00494 
00495     DetermineClient( pRequest, &request );
00496     request.m_sObjectId         = pRequest->m_mapParams[ "ObjectID"      ];
00497     request.m_sContainerID      = pRequest->m_mapParams[ "ContainerID"   ];
00498     request.m_sFilter           = pRequest->m_mapParams[ "Filter"        ];
00499     request.m_nStartingIndex    =
00500         pRequest->m_mapParams[ "StartingIndex" ].toLong();
00501     request.m_nRequestedCount   =
00502         pRequest->m_mapParams[ "RequestedCount"].toLong();
00503     request.m_sSortCriteria     = pRequest->m_mapParams[ "SortCriteria"  ];
00504     request.m_sSearchCriteria   = pRequest->m_mapParams[ "SearchCriteria"];
00505 
00506     LOG(VB_UPNP, LOG_INFO,
00507         QString("UPnpCDS::HandleSearch ObjectID=%1, ContainerId=%2")
00508             .arg(request.m_sObjectId) .arg(request.m_sContainerID));
00509 
00510     // ----------------------------------------------------------------------
00511     // Break the SearchCriteria into it's parts
00512     // -=>TODO: This DOES NOT handle ('s or other complex expressions
00513     // ----------------------------------------------------------------------
00514 
00515     QRegExp  rMatch( "\\b(or|and)\\b" );
00516     rMatch.setCaseSensitivity(Qt::CaseInsensitive);
00517 
00518     request.m_sSearchList  = request.m_sSearchCriteria.split(
00519         rMatch, QString::SkipEmptyParts);
00520     request.m_sSearchClass = "object";  // Default to all objects.
00521 
00522     // ----------------------------------------------------------------------
00523     // -=>TODO: Need to process all expressions in searchCriteria... for now,
00524     //          Just focus on the "upnp:class derivedfrom" expression
00525     // ----------------------------------------------------------------------
00526 
00527     for ( QStringList::Iterator it  = request.m_sSearchList.begin();
00528                                 it != request.m_sSearchList.end();
00529                               ++it )
00530     {
00531         if ((*it).contains("upnp:class derivedfrom", Qt::CaseInsensitive))
00532         {
00533             QStringList sParts = (*it).split(' ', QString::SkipEmptyParts);
00534 
00535             if (sParts.count() > 2)
00536             {
00537                 request.m_sSearchClass = sParts[2].trimmed();
00538                 request.m_sSearchClass.remove( '"' );
00539 
00540                 break;
00541             }
00542         }
00543     }
00544 
00545     // ----------------------------------------------------------------------
00546 
00547 
00548     LOG(VB_UPNP, LOG_INFO, QString("UPnpCDS::ProcessRequest \n"
00549                                     ": url            = %1 \n"
00550                                     ": Method         = %2 \n"
00551                                     ": ObjectId       = %3 \n"
00552                                     ": SearchCriteria = %4 \n"
00553                                     ": Filter         = %5 \n"
00554                                     ": StartingIndex  = %6 \n"
00555                                     ": RequestedCount = %7 \n"
00556                                     ": SortCriteria   = %8 \n"
00557                                     ": SearchClass    = %9" )
00558                        .arg( pRequest->m_sBaseUrl     )
00559                        .arg( pRequest->m_sMethod      )
00560                        .arg( request.m_sObjectId      )
00561                        .arg( request.m_sSearchCriteria)
00562                        .arg( request.m_sFilter        )
00563                        .arg( request.m_nStartingIndex )
00564                        .arg( request.m_nRequestedCount)
00565                        .arg( request.m_sSortCriteria  )
00566                        .arg( request.m_sSearchClass   ));
00567 
00568 #if 0
00569     bool bSearchDone = false;
00570 #endif
00571 
00572     UPnpCDSExtensionList::iterator it = m_extensions.begin();
00573     for (; (it != m_extensions.end()) && !pResult; ++it)
00574         pResult = (*it)->Search(&request);
00575 
00576     if (pResult != NULL)
00577     {
00578         eErrorCode  = pResult->m_eErrorCode;
00579         sErrorDesc  = pResult->m_sErrorDesc;
00580 
00581         if (eErrorCode == UPnPResult_Success)
00582         {
00583             FilterMap filter =  (FilterMap) request.m_sFilter.split(',');
00584             nNumberReturned = pResult->m_List.count();
00585             nTotalMatches   = pResult->m_nTotalMatches;
00586             nUpdateID       = pResult->m_nUpdateID;
00587             sResultXML      = pResult->GetResultXML(filter);
00588 #if 0
00589             bSearchDone = true;
00590 #endif
00591         }
00592 
00593         delete pResult;
00594     }
00595 
00596 #if 0
00597     nUpdateID       = 0;
00598     LOG(VB_UPNP, LOG_DEBUG, sResultXML);
00599 #endif
00600 
00601     if (eErrorCode == UPnPResult_Success)
00602     {
00603         NameValues list;
00604         QString sResults = DIDL_LITE_BEGIN;
00605         sResults += sResultXML;
00606         sResults += DIDL_LITE_END;
00607 
00608         list.push_back(NameValue("Result",         sResults));
00609         list.push_back(NameValue("NumberReturned", nNumberReturned));
00610         list.push_back(NameValue("TotalMatches",   nTotalMatches));
00611         list.push_back(NameValue("UpdateID",       nUpdateID));
00612 
00613         pRequest->FormatActionResponse(list);
00614     }
00615     else
00616         UPnp::FormatErrorResponse( pRequest, eErrorCode, sErrorDesc );
00617 }
00618 
00620 //
00622 
00623 void UPnpCDS::HandleGetSearchCapabilities( HTTPRequest *pRequest )
00624 {
00625     NameValues list;
00626 
00627     LOG(VB_UPNP, LOG_INFO,
00628         QString("UPnpCDS::ProcessRequest : %1 : %2")
00629             .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
00630 
00631     // -=>TODO: Need to implement based on CDS Extension Capabilities
00632 
00633     list.push_back(
00634         NameValue("SearchCaps",
00635                   "dc:title,dc:creator,dc:date,upnp:class,res@size"));
00636 
00637     pRequest->FormatActionResponse(list);
00638 }
00639 
00641 //
00643 
00644 void UPnpCDS::HandleGetSortCapabilities( HTTPRequest *pRequest )
00645 {
00646     NameValues list;
00647 
00648     LOG(VB_UPNP, LOG_INFO,
00649         QString("UPnpCDS::ProcessRequest : %1 : %2")
00650             .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
00651 
00652     // -=>TODO: Need to implement based on CDS Extension Capabilities
00653 
00654     list.push_back(
00655         NameValue("SortCaps",
00656                   "dc:title,dc:creator,dc:date,upnp:class,res@size"));
00657 
00658     pRequest->FormatActionResponse(list);
00659 }
00660 
00662 //
00664 
00665 void UPnpCDS::HandleGetSystemUpdateID( HTTPRequest *pRequest )
00666 {
00667     NameValues list;
00668 
00669     LOG(VB_UPNP, LOG_INFO,
00670         QString("UPnpCDS::ProcessRequest : %1 : %2")
00671             .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
00672 
00673     unsigned short nId = GetValue<unsigned short>("SystemUpdateID");
00674 
00675     list.push_back(NameValue("Id", nId));
00676 
00677     pRequest->FormatActionResponse(list);
00678 }
00679 
00680 
00681 
00684 //
00685 // UPnpCDSExtension Implementation
00686 //
00689 
00691 //
00693 
00694 bool UPnpCDSExtension::IsBrowseRequestForUs( UPnpCDSRequest *pRequest )
00695 {
00696     if (!pRequest->m_sObjectId.startsWith(m_sExtensionId, Qt::CaseSensitive))
00697         return false;
00698 
00699     return true;
00700 }
00701 
00703 //
00705 
00706 UPnpCDSExtensionResults *UPnpCDSExtension::Browse( UPnpCDSRequest *pRequest )
00707 {
00708     // -=>TODO: Need to add Filter & Sorting Support.
00709     // -=>TODO: Need to add Sub-Folder/Category Support!!!!!
00710 
00711     if (!IsBrowseRequestForUs( pRequest ))
00712         return( NULL );
00713 
00714     // ----------------------------------------------------------------------
00715     // Parse out request object's path
00716     // ----------------------------------------------------------------------
00717 
00718     QStringList idPath = pRequest->m_sObjectId.section('=',0,0)
00719                              .split("/", QString::SkipEmptyParts);
00720 
00721     QString key = pRequest->m_sObjectId.section('=',1);
00722 
00723     if (idPath.isEmpty())
00724         return( NULL );
00725 
00726     // ----------------------------------------------------------------------
00727     // Process based on location in hierarchy
00728     // ----------------------------------------------------------------------
00729 
00730     UPnpCDSExtensionResults *pResults = new UPnpCDSExtensionResults();
00731 
00732     if (pResults != NULL)
00733     {
00734         if (!key.isEmpty())
00735             idPath.last().append(QString("=%1").arg(key));
00736         else
00737         {
00738             if (pRequest->m_sObjectId.contains("item"))
00739             {
00740                 idPath.removeLast();
00741                 idPath = idPath.last().split(" ", QString::SkipEmptyParts);
00742                 idPath = idPath.first().split('?', QString::SkipEmptyParts);
00743 
00744                 if (idPath[0].startsWith(QString("Id")))
00745                     idPath[0] = QString("item=%1")
00746                                  .arg(idPath[0].right(idPath[0].length() - 2));
00747             }
00748         }
00749 
00750         QString sLast = idPath.last();
00751 
00752         pRequest->m_sParentId = pRequest->m_sObjectId;
00753 
00754         if (sLast == m_sExtensionId)
00755             return ProcessRoot(pRequest, pResults, idPath);
00756 
00757         if (sLast == "0")
00758             return ProcessAll(pRequest, pResults, idPath);
00759 
00760         if (sLast.startsWith(QString("key") , Qt::CaseSensitive))
00761             return ProcessKey(pRequest, pResults, idPath);
00762 
00763         if (sLast.startsWith(QString("item"), Qt::CaseSensitive))
00764             return ProcessItem(pRequest, pResults, idPath);
00765 
00766         int nNodeIdx = sLast.toInt();
00767 
00768         if ((nNodeIdx > 0) && (nNodeIdx < GetRootCount()))
00769             return ProcessContainer(pRequest, pResults, nNodeIdx, idPath);
00770 
00771         pResults->m_eErrorCode = UPnPResult_CDS_NoSuchObject;
00772         pResults->m_sErrorDesc = "";
00773     }
00774 
00775     return( pResults );
00776 }
00777 
00779 //
00781 
00782 bool UPnpCDSExtension::IsSearchRequestForUs( UPnpCDSRequest *pRequest )
00783 {
00784     if ( !m_sClass.startsWith( pRequest->m_sSearchClass ))
00785         return false;
00786 
00787     return true;
00788 }
00789 
00791 //
00793 
00794 UPnpCDSExtensionResults *UPnpCDSExtension::Search( UPnpCDSRequest *pRequest )
00795 {
00796     // -=>TODO: Need to add Filter & Sorting Support.
00797     // -=>TODO: Need to add Sub-Folder/Category Support!!!!!
00798 
00799     QStringList sEmptyList;
00800     LOG(VB_UPNP, LOG_INFO,
00801         QString("UPnpCDSExtension::Search : m_sClass = %1 : "
00802                 "m_sSearchClass = %2")
00803             .arg(m_sClass).arg(pRequest->m_sSearchClass));
00804 
00805     if ( !IsSearchRequestForUs( pRequest ))
00806     {
00807         LOG(VB_UPNP, LOG_INFO,
00808             QString("UPnpCDSExtension::Search - Not For Us : "
00809                     "m_sClass = %1 : m_sSearchClass = %2")
00810                 .arg(m_sClass).arg(pRequest->m_sSearchClass));
00811         return NULL;
00812     }
00813 
00814     UPnpCDSExtensionResults *pResults = new UPnpCDSExtensionResults();
00815 
00816     CreateItems( pRequest, pResults, 0, "", false );
00817 
00818     return pResults;
00819 }
00820 
00822 //
00824 
00825 QString UPnpCDSExtension::RemoveToken( const QString &sToken,
00826                                        const QString &sStr, int num )
00827 {
00828     QString sResult( "" );
00829     int     nPos = -1;
00830 
00831     for (int nIdx=0; nIdx < num; nIdx++)
00832     {
00833         if ((nPos = sStr.lastIndexOf( sToken, nPos )) == -1)
00834             break;
00835     }
00836 
00837     if (nPos > 0)
00838         sResult = sStr.left( nPos );
00839 
00840     return sResult;
00841 }
00842 
00844 //
00846 
00847 UPnpCDSExtensionResults *
00848     UPnpCDSExtension::ProcessRoot( UPnpCDSRequest          *pRequest,
00849                                    UPnpCDSExtensionResults *pResults,
00850                                    QStringList             &/*idPath*/ )
00851 {
00852     pResults->m_nTotalMatches   = 0;
00853     pResults->m_nUpdateID       = 1;
00854 
00855     short nRootCount = GetRootCount();
00856 
00857     switch( pRequest->m_eBrowseFlag )
00858     {
00859         case CDS_BrowseMetadata:
00860         {
00861             // --------------------------------------------------------------
00862             // Return Root Object Only
00863             // --------------------------------------------------------------
00864 
00865             pResults->m_nTotalMatches   = 1;
00866             pResults->m_nUpdateID       = 1;
00867 
00868             CDSObject *pRoot = CreateContainer( m_sExtensionId, m_sName, "0");
00869 
00870             pRoot->SetChildCount( nRootCount );
00871 
00872             pResults->Add( pRoot );
00873 
00874             break;
00875         }
00876 
00877         case CDS_BrowseDirectChildren:
00878         {
00879             LOG(VB_UPNP, LOG_DEBUG, "CDS_BrowseDirectChildren");
00880             pResults->m_nUpdateID     = 1;
00881             pResults->m_nTotalMatches = nRootCount ;
00882 
00883             if ( pRequest->m_nRequestedCount == 0)
00884                 pRequest->m_nRequestedCount = nRootCount ;
00885 
00886             short nStart = max(pRequest->m_nStartingIndex, short(0));
00887             short nEnd   = min(nRootCount,
00888                                short(nStart + pRequest->m_nRequestedCount));
00889 
00890             if (nStart < nRootCount)
00891             {
00892                 for (short nIdx = nStart; nIdx < nEnd; nIdx++)
00893                 {
00894                     UPnpCDSRootInfo *pInfo = GetRootInfo( nIdx );
00895                     if (pInfo != NULL)
00896                     {
00897                         QString sId = QString("%1/%2")
00898                                           .arg(pRequest->m_sObjectId)
00899                                           .arg(nIdx);
00900 
00901                         CDSObject *pItem =
00902                             CreateContainer( sId, QObject::tr( pInfo->title ),
00903                                              m_sExtensionId );
00904 
00905                         pItem->SetChildCount( GetDistinctCount( pInfo ) );
00906 
00907                         pResults->Add( pItem );
00908                     }
00909                 }
00910             }
00911         }
00912 
00913         case CDS_BrowseUnknown:
00914         default:
00915             break;
00916     }
00917 
00918     return pResults;
00919 }
00920 
00921 
00923 //
00925 
00926 UPnpCDSExtensionResults *
00927     UPnpCDSExtension::ProcessAll ( UPnpCDSRequest          *pRequest,
00928                                    UPnpCDSExtensionResults *pResults,
00929                                    QStringList             &/*idPath*/ )
00930 {
00931     pResults->m_nTotalMatches   = 0;
00932     pResults->m_nUpdateID       = 1;
00933 
00934     // ----------------------------------------------------------------------
00935     //
00936     // ----------------------------------------------------------------------
00937 
00938     switch( pRequest->m_eBrowseFlag )
00939     {
00940         case CDS_BrowseMetadata:
00941         {
00942             // --------------------------------------------------------------
00943             // Return Container Object Only
00944             // --------------------------------------------------------------
00945 
00946             UPnpCDSRootInfo *pInfo = GetRootInfo( 0 );
00947 
00948             if (pInfo != NULL)
00949             {
00950                 pResults->m_nTotalMatches   = 1;
00951                 pResults->m_nUpdateID       = 1;
00952 
00953                 CDSObject *pItem =
00954                     CreateContainer( pRequest->m_sObjectId,
00955                                      QObject::tr( pInfo->title ),
00956                                      m_sExtensionId );
00957 
00958                 pItem->SetChildCount( GetDistinctCount( pInfo ) );
00959 
00960                 pResults->Add( pItem );
00961             }
00962 
00963             break;
00964         }
00965 
00966         case CDS_BrowseDirectChildren:
00967         {
00968             CreateItems( pRequest, pResults, 0, "", false );
00969 
00970             break;
00971         }
00972 
00973         case CDS_BrowseUnknown:
00974         default:
00975             break;
00976     }
00977 
00978     return pResults;
00979 }
00980 
00982 //
00984 
00985 UPnpCDSExtensionResults *
00986     UPnpCDSExtension::ProcessItem(UPnpCDSRequest          *pRequest,
00987                                   UPnpCDSExtensionResults *pResults,
00988                                   QStringList             &idPath)
00989 {
00990     pResults->m_nTotalMatches   = 0;
00991     pResults->m_nUpdateID       = 1;
00992 
00993     // ----------------------------------------------------------------------
00994     //
00995     // ----------------------------------------------------------------------
00996 #if 0
00997     LOG(VB_UPNP, LOG_INFO, QString("UPnpCDSExtension::ProcessItem : %1")
00998                                .arg(idPath));
00999 #endif
01000     switch( pRequest->m_eBrowseFlag )
01001     {
01002         case CDS_BrowseMetadata:
01003         {
01004             // --------------------------------------------------------------
01005             // Return 1 Item
01006             // --------------------------------------------------------------
01007 
01008             QStringMap  mapParams;
01009             QString     sParams = idPath.last().section( '?', 1, 1 );
01010             sParams.replace("&amp;", "&");
01011 
01012             HTTPRequest::GetParameters( sParams, mapParams );
01013 
01014             MSqlQuery query(MSqlQuery::InitCon());
01015 
01016             if (query.isConnected())
01017             {
01018                 BuildItemQuery( query, mapParams );
01019 
01020                 if (query.exec() && query.next())
01021                 {
01022                     pRequest->m_sObjectId =
01023                         RemoveToken( "/", pRequest->m_sObjectId, 1 );
01024 
01025                     AddItem( pRequest, pRequest->m_sObjectId, pResults, false,
01026                              query );
01027                     pResults->m_nTotalMatches = 1;
01028                 }
01029             }
01030             break;
01031         }
01032         case CDS_BrowseDirectChildren:
01033         {
01034             // Items don't have any children.
01035             break;
01036         }
01037     }
01038 
01039     return pResults;
01040 }
01041 
01043 //
01045 
01046 UPnpCDSExtensionResults *
01047     UPnpCDSExtension::ProcessKey( UPnpCDSRequest          *pRequest,
01048                                   UPnpCDSExtensionResults *pResults,
01049                                   QStringList             &idPath )
01050 {
01051     pResults->m_nTotalMatches   = 0;
01052     pResults->m_nUpdateID       = 1;
01053 
01054     // ----------------------------------------------------------------------
01055     //
01056     // ----------------------------------------------------------------------
01057 
01058     QString sKey = idPath.takeLast().section( '=', 1, 1 );
01059     sKey = QUrl::fromPercentEncoding(sKey.toUtf8());
01060 
01061     if (!sKey.isEmpty())
01062     {
01063         int nNodeIdx = idPath.takeLast().toInt();
01064 
01065         switch( pRequest->m_eBrowseFlag )
01066         {
01067             case CDS_BrowseMetadata:
01068             {
01069                 UPnpCDSRootInfo *pInfo = GetRootInfo( nNodeIdx );
01070 
01071                 if (pInfo == NULL)
01072                     return pResults;
01073 
01074                 pRequest->m_sParentId =
01075                     RemoveToken( "/", pRequest->m_sObjectId, 1 );
01076 
01077                 // ----------------------------------------------------------
01078                 // Since Key is not always the title, we need to lookup title.
01079                 // ----------------------------------------------------------
01080 
01081                 MSqlQuery query(MSqlQuery::InitCon());
01082 
01083                 if (query.isConnected())
01084                 {
01085                     QString sSQL = QString(pInfo->sql) .arg(pInfo->where);
01086 
01087                     // -=>TODO: There is a problem when called for an Item,
01088                     //          instead of a container
01089                     //          sKey = '<KeyName>/item?ChanId'
01090                     //          which is incorrect.
01091 
01092                     query.prepare  ( sSQL );
01093                     query.bindValue( ":KEY", sKey );
01094 
01095                     if (query.exec() && query.next())
01096                     {
01097                         // ----------------------------------------------
01098                         // Return Container Object Only
01099                         // ----------------------------------------------
01100 
01101                         pResults->m_nTotalMatches   = 1;
01102                         pResults->m_nUpdateID       = 1;
01103 
01104                         CDSObject *pItem =
01105                             CreateContainer( pRequest->m_sObjectId,
01106                                              query.value(1).toString(),
01107                                              pRequest->m_sParentId );
01108 
01109                         pItem->SetChildCount( GetDistinctCount( pInfo ));
01110 
01111                         pResults->Add( pItem );
01112                     }
01113                 }
01114                 break;
01115             }
01116 
01117             case CDS_BrowseDirectChildren:
01118             {
01119                 CreateItems( pRequest, pResults, nNodeIdx, sKey, true );
01120 
01121                 break;
01122             }
01123 
01124             case CDS_BrowseUnknown:
01125                 default:
01126                 break;
01127         }
01128     }
01129 
01130     return pResults;
01131 }
01132 
01134 //
01136 
01137 UPnpCDSExtensionResults *
01138     UPnpCDSExtension::ProcessContainer( UPnpCDSRequest          *pRequest,
01139                                         UPnpCDSExtensionResults *pResults,
01140                                         int                      nNodeIdx,
01141                                         QStringList             &/*idPath*/ )
01142 {
01143     pResults->m_nUpdateID     = 1;
01144     pResults->m_nTotalMatches = 0;
01145 
01146     UPnpCDSRootInfo *pInfo = GetRootInfo( nNodeIdx );
01147 
01148     if (pInfo == NULL)
01149         return pResults;
01150 
01151     switch( pRequest->m_eBrowseFlag )
01152     {
01153         case CDS_BrowseMetadata:
01154         {
01155             // --------------------------------------------------------------
01156             // Return Container Object Only
01157             // --------------------------------------------------------------
01158 
01159             pResults->m_nTotalMatches   = 1;
01160             pResults->m_nUpdateID       = 1;
01161 
01162             CDSObject *pItem = CreateContainer( pRequest->m_sObjectId,
01163                                                 QObject::tr( pInfo->title ),
01164                                                 m_sExtensionId );
01165 
01166             pItem->SetChildCount( GetDistinctCount( pInfo ));
01167 
01168             pResults->Add( pItem );
01169             break;
01170         }
01171 
01172         case CDS_BrowseDirectChildren:
01173         {
01174             pResults->m_nTotalMatches = GetDistinctCount( pInfo );
01175             pResults->m_nUpdateID     = 1;
01176 
01177             if (pRequest->m_nRequestedCount == 0)
01178                 pRequest->m_nRequestedCount = SHRT_MAX;
01179 
01180             MSqlQuery query(MSqlQuery::InitCon());
01181 
01182             if (query.isConnected())
01183             {
01184                 // Remove where clause placeholder.
01185                 QString sSQL = pInfo->sql;
01186 
01187                 sSQL.remove( "%1" );
01188                 sSQL += QString( " LIMIT %2, %3" )
01189                            .arg( pRequest->m_nStartingIndex  )
01190                            .arg( pRequest->m_nRequestedCount );
01191 
01192                 query.prepare( sSQL );
01193 
01194                 if (query.exec())
01195                 {
01196                     while(query.next())
01197                     {
01198                         QString sKey   = query.value(0).toString();
01199                         QString sTitle = query.value(1).toString();
01200                         long    nCount = query.value(2).toInt();
01201 
01202                         if (sTitle.length() == 0)
01203                             sTitle = "(undefined)";
01204 
01205                         QString sId = QString( "%1/key=%2" )
01206                                          .arg( pRequest->m_sParentId )
01207                                          .arg( sKey );
01208 
01209                         CDSObject *pRoot =
01210                             CreateContainer(sId, sTitle, pRequest->m_sParentId);
01211 
01212                         pRoot->SetChildCount( nCount );
01213 
01214                         pResults->Add( pRoot );
01215                     }
01216                 }
01217             }
01218             break;
01219         }
01220 
01221         case CDS_BrowseUnknown:
01222             break;
01223     }
01224 
01225     return pResults;
01226 }
01227 
01229 //
01231 
01232 int UPnpCDSExtension::GetDistinctCount( UPnpCDSRootInfo *pInfo )
01233 {
01234     int nCount = 0;
01235 
01236     if ((pInfo == NULL) || (pInfo->column == NULL))
01237         return 0;
01238 
01239     MSqlQuery query(MSqlQuery::InitCon());
01240 
01241     if (query.isConnected())
01242     {
01243         // Note: Tried to use Bind, however it would not allow me to use it
01244         //       for column & table names
01245 
01246         QString sSQL;
01247 
01248         if (strncmp( pInfo->column, "*", 1) == 0)
01249         {
01250             sSQL = QString( "SELECT count( %1 ) FROM %2" )
01251                       .arg( pInfo->column )
01252                       .arg( GetTableName( pInfo->column ));
01253         }
01254         else
01255         {
01256             sSQL = QString( "SELECT count( DISTINCT %1 ) FROM %2" )
01257                       .arg( pInfo->column )
01258                       .arg( GetTableName( pInfo->column ) );
01259         }
01260 
01261         query.prepare( sSQL );
01262 
01263         if (query.exec() && query.next())
01264         {
01265             nCount = query.value(0).toInt();
01266         }
01267     }
01268 
01269     return( nCount );
01270 }
01271 
01273 //
01275 
01276 int UPnpCDSExtension::GetCount( const QString &sColumn, const QString &sKey )
01277 {
01278     int nCount = 0;
01279 
01280     MSqlQuery query(MSqlQuery::InitCon());
01281 
01282     if (query.isConnected())
01283     {
01284         QString sSQL = QString("SELECT count( %1 ) FROM %2")
01285                        .arg( sColumn ).arg( GetTableName( sColumn ) );
01286 
01287         if ( sKey.length() )
01288             sSQL += " WHERE " + sColumn + " = :KEY";
01289 
01290         query.prepare( sSQL );
01291         if ( sKey.length() )
01292             query.bindValue( ":KEY", sKey );
01293 
01294         if (query.exec() && query.next())
01295         {
01296             nCount = query.value(0).toInt();
01297         }
01298         LOG(VB_UPNP, LOG_DEBUG, "UPnpCDSExtension::GetCount() - " +
01299                                 sSQL + " = " + QString::number(nCount));
01300     }
01301 
01302     return( nCount );
01303 }
01304 
01306 //
01308 
01309 void UPnpCDSExtension::CreateItems( UPnpCDSRequest          *pRequest,
01310                                     UPnpCDSExtensionResults *pResults,
01311                                     int                      nNodeIdx,
01312                                     const QString           &sKey,
01313                                     bool                     bAddRef )
01314 {
01315     pResults->m_nTotalMatches = 0;
01316     pResults->m_nUpdateID     = 1;
01317 
01318     UPnpCDSRootInfo *pInfo = GetRootInfo( nNodeIdx );
01319 
01320     if (pInfo == NULL)
01321         return;
01322 
01323     pResults->m_nTotalMatches = GetCount( pInfo->column, sKey );
01324     pResults->m_nUpdateID     = 1;
01325 
01326     if (pRequest->m_nRequestedCount == 0)
01327         pRequest->m_nRequestedCount = SHRT_MAX;
01328 
01329     MSqlQuery query(MSqlQuery::InitCon());
01330 
01331     if (query.isConnected())
01332     {
01333         QString sWhere( "" );
01334         QString sOrder( "" );
01335 
01336         if ( sKey.length() > 0)
01337         {
01338            sWhere = QString( "WHERE %1=:KEY " )
01339                        .arg( pInfo->column );
01340         }
01341 
01342         QString orderColumn( pInfo->orderColumn );
01343         if (orderColumn.length() != 0) {
01344             sOrder = QString( "ORDER BY %1 " )
01345                        .arg( orderColumn );
01346         }
01347 
01348         QString sSQL = QString( "%1 %2 LIMIT %3, %4" )
01349                           .arg( GetItemListSQL( pInfo->column )  )
01350                           .arg( sWhere + sOrder )
01351                           .arg( pRequest->m_nStartingIndex  )
01352                           .arg( pRequest->m_nRequestedCount );
01353 
01354         query.prepare  ( sSQL );
01355         if ( sKey.length() )
01356             query.bindValue(":KEY", sKey );
01357 
01358         if (query.exec())
01359         {
01360             while(query.next())
01361                 AddItem( pRequest, pRequest->m_sObjectId, pResults, bAddRef,
01362                          query );
01363         }
01364     }
01365 }
01366 
01367 // vim:ts=4:sw=4:ai:et:si:sts=4
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends