|
MythTV
0.26-pre
|
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("&", "&"); 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
1.7.6.1