MythTV  master
upnpcds.cpp
Go to the documentation of this file.
1 // Program Name: upnpcds.cpp
3 // Created : Oct. 24, 2005
4 //
5 // Purpose : uPnp Content Directory Service
6 //
7 // Copyright (c) 2005 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include <algorithm>
14 #include <cmath>
15 #include <cstdint>
16 using namespace std;
17 
18 #include "upnp.h"
19 #include "upnpcds.h"
20 #include "upnputil.h"
21 #include "mythlogging.h"
22 #include "mythversion.h"
23 
24 #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/\">"
25 #define DIDL_LITE_END "</DIDL-Lite>";
26 
28 //
30 
32 {
33  if (pObject)
34  {
35  pObject->IncrRef();
36  m_List.append( pObject );
37  }
38 }
39 
41 //
43 
45 {
46  foreach (auto & object, objects)
47  {
48  object->IncrRef();
49  m_List.append( object );
50  }
51 }
52 
54 //
56 
58  bool ignoreChildren)
59 {
60  QString sXML;
61 
62  foreach (auto item, m_List)
63  sXML += item->toXml(filter, ignoreChildren);
64 
65  return sXML;
66 }
67 
69 //
71 
72 UPnpCDS::UPnpCDS( UPnpDevice *pDevice, const QString &sSharePath )
73  : Eventing( "UPnpCDS", "CDS_Event", sSharePath )
74 {
76  m_root.m_sId = "0";
77  m_root.m_sParentId = "-1";
78  m_root.m_sTitle = "MythTV";
79  m_root.m_sClass = "object.container";
80  m_root.m_bRestricted = true;
81  m_root.m_bSearchable = true;
82 
83  AddVariable( new StateVariable< QString >( "TransferIDs" , true ) );
84  AddVariable( new StateVariable< QString >( "ContainerUpdateIDs", true ) );
85  AddVariable( new StateVariable< uint16_t >( "SystemUpdateID" , true ) );
86  AddVariable( new StateVariable< QString >( "ServiceResetToken" , true ) );
87 
88  SetValue< uint16_t >( "SystemUpdateID", 0 );
89  // ServiceResetToken must be unique (never repeat) and it must change when
90  // the backend restarts (all internal state is reset)
91  //
92  // The current date + time fits the criteria.
93  SetValue< QString >( "ServiceResetToken",
94  QDateTime::currentDateTimeUtc().toString(Qt::ISODate) );
95 
96  QString sUPnpDescPath = UPnp::GetConfiguration()->GetValue( "UPnP/DescXmlPath", sSharePath );
97 
98  m_sServiceDescFileName = sUPnpDescPath + "CDS_scpd.xml";
99  m_sControlUrl = "/CDS_Control";
100 
103 
104  // Add our Service Definition to the device.
105 
106  RegisterService( pDevice );
107 
108  // ContentDirectoryService uses a different schema definition for the FeatureList
109  // to the ConnectionManager, although they appear to be identical
111  "urn:schemas-upnp-org:av:avs" ));
112  m_features.AddAttribute(NameValue( "xmlns:xsi",
113  "http://www.w3.org/2001/XMLSchema-instance" ));
114  m_features.AddAttribute(NameValue( "xsi:schemaLocation",
115  "urn:schemas-upnp-org:av:avs "
116  "http://www.upnp.org/schemas/av/avs.xsd" ));
117 }
118 
120 //
122 
124 {
125  while (!m_extensions.isEmpty())
126  {
127  delete m_extensions.takeLast();
128  }
129 }
130 
132 //
134 
135 UPnpCDSMethod UPnpCDS::GetMethod( const QString &sURI )
136 {
137  if (sURI == "GetServDesc" ) return CDSM_GetServiceDescription;
138  if (sURI == "Browse" ) return CDSM_Browse ;
139  if (sURI == "Search" ) return CDSM_Search ;
140  if (sURI == "GetSearchCapabilities" ) return CDSM_GetSearchCapabilities;
141  if (sURI == "GetSortCapabilities" ) return CDSM_GetSortCapabilities ;
142  if (sURI == "GetSystemUpdateID" ) return CDSM_GetSystemUpdateID ;
143  if (sURI == "X_GetFeatureList" || // HACK: Samsung
144  sURI == "GetFeatureList" ) return CDSM_GetFeatureList ;
145  if (sURI == "GetServiceResetToken" ) return CDSM_GetServiceResetToken ;
146 
147  return( CDSM_Unknown );
148 }
149 
151 //
153 
155 {
156  if (sFlag == "BrowseMetadata" ) return( CDS_BrowseMetadata );
157  if (sFlag == "BrowseDirectChildren" ) return( CDS_BrowseDirectChildren );
158 
159  return( CDS_BrowseUnknown );
160 }
161 
163 //
165 
167 {
168  if (pExtension)
169  {
170  m_extensions.append( pExtension );
171 
172  CDSShortCutList shortcuts = pExtension->GetShortCuts();
173  CDSShortCutList::iterator it;
174  for (it = shortcuts.begin(); it != shortcuts.end(); ++it)
175  {
176  RegisterShortCut(it.key(), it.value());
177  }
178  }
179 }
180 
182 //
184 
186 {
187  if (pExtension)
188  {
189  m_extensions.removeAll(pExtension);
190  delete pExtension;
191  }
192 }
193 
195 //
197 
199  const QString& objectID)
200 {
201  m_pShortCuts->AddShortCut(type, objectID);
202 }
203 
205 //
207 
209 {
210  m_features.AddFeature(feature); // m_features takes ownership
211 }
212 
214 //
216 
218 {
220 }
221 
223 //
225 
227 {
228  if (pRequest)
229  {
230  if (Eventing::ProcessRequest( pRequest ))
231  return true;
232 
233  if ( pRequest->m_sBaseUrl != m_sControlUrl )
234  {
235 #if 0
236  LOG(VB_UPNP, LOG_DEBUG,
237  QString("UPnpCDS::ProcessRequest - BaseUrl (%1) not ours...")
238  .arg(pRequest->m_sBaseUrl));
239 #endif
240  return false;
241  }
242 
243  switch( GetMethod( pRequest->m_sMethod ) )
244  {
247  break;
248  case CDSM_Browse :
249  HandleBrowse( pRequest );
250  break;
251  case CDSM_Search :
252  HandleSearch( pRequest );
253  break;
255  HandleGetSearchCapabilities( pRequest );
256  break;
258  HandleGetSortCapabilities( pRequest );
259  break;
261  HandleGetSystemUpdateID( pRequest );
262  break;
263  case CDSM_GetFeatureList :
264  HandleGetFeatureList( pRequest );
265  break;
267  HandleGetServiceResetToken( pRequest );
268  break;
269  default:
271  break;
272  }
273 
274  return true;
275  }
276 
277  return false;
278 }
279 
281  // Windows Media Player version 12
282  { CDS_ClientWMP,
283  "User-Agent",
284  "Windows-Media-Player/" },
285  // Windows Media Player version < 12
286  { CDS_ClientWMP,
287  "User-Agent",
288  "Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x" },
289  // XBMC
290  { CDS_ClientXBMC,
291  "User-Agent",
292  "Platinum/" },
293  // XBox 360
294  { CDS_ClientXBox,
295  "User-Agent",
296  "Xbox" },
297  // Sony Blu-ray players
299  "X-AV-Client-Info",
300  "cn=\"Sony Corporation\"; mn=\"Blu-ray Disc Player\"" },
301 };
303  sizeof(clientExceptions[0]);
304 
306  UPnpCDSRequest *pCDSRequest )
307 {
308  pCDSRequest->m_eClient = CDS_ClientDefault;
309  pCDSRequest->m_nClientVersion = 0;
310  bool found = false;
311 
312  // Do we know this client string?
313  for ( uint i = 0; !found && i < clientExceptionCount; i++ )
314  {
316 
317  QString sHeaderValue = pRequest->GetRequestHeader(except->sHeaderKey, "");
318  int idx = sHeaderValue.indexOf(except->sHeaderValue);
319  if (idx != -1)
320  {
321  pCDSRequest->m_eClient = except->nClientType;
322 
323  idx += except->sHeaderValue.length();
324 
325  // If we have a / at the end of the string then we
326  // increment the string to skip over it
327  if ( sHeaderValue[idx] == '/')
328  {
329  idx++;
330  }
331 
332  // Now find the version number
333  QString version = sHeaderValue.mid(idx).trimmed();
334  idx = version.indexOf( '.' );
335  if (idx != -1)
336  {
337  idx = version.indexOf( '.', idx + 1 );
338  }
339  if (idx != -1)
340  {
341  version = version.left( idx );
342  }
343  idx = version.indexOf( ' ' );
344  if (idx != -1)
345  {
346  version = version.left( idx );
347  }
348 
349  pCDSRequest->m_nClientVersion = version.toDouble();
350 
351  LOG(VB_UPNP, LOG_INFO,
352  QString("DetermineClient %1:%2 Identified as %3 version %4")
353  .arg(except->sHeaderKey) .arg(sHeaderValue)
354  .arg(pCDSRequest->m_eClient)
355  .arg(pCDSRequest->m_nClientVersion));
356  found = true;
357  }
358  }
359 }
360 
361 
363 //
365 
367 {
368  UPnpCDSExtensionResults *pResult = nullptr;
369  UPnpCDSRequest request;
370 
371  DetermineClient( pRequest, &request );
372  request.m_sObjectId = pRequest->m_mapParams[ "objectid" ];
373  request.m_sParentId = "0";
374  request.m_eBrowseFlag =
375  GetBrowseFlag( pRequest->m_mapParams[ "browseflag" ] );
376  request.m_sFilter = pRequest->m_mapParams[ "filter" ];
377  request.m_nStartingIndex = Max(pRequest->m_mapParams[ "startingindex" ].toUShort(),
378  uint16_t(0));
379  request.m_nRequestedCount =
380  pRequest->m_mapParams[ "requestedcount"].toUShort();
381  if (request.m_nRequestedCount == 0)
382  request.m_nRequestedCount = UINT16_MAX;
383  request.m_sSortCriteria = pRequest->m_mapParams[ "sortcriteria" ];
384 
385 
386  LOG(VB_UPNP, LOG_DEBUG, QString("UPnpCDS::ProcessRequest \n"
387  ": url = %1 \n"
388  ": Method = %2 \n"
389  ": ObjectId = %3 \n"
390  ": BrowseFlag = %4 \n"
391  ": Filter = %5 \n"
392  ": StartingIndex = %6 \n"
393  ": RequestedCount = %7 \n"
394  ": SortCriteria = %8 " )
395  .arg( pRequest->m_sBaseUrl )
396  .arg( pRequest->m_sMethod )
397  .arg( request.m_sObjectId )
398  .arg( request.m_eBrowseFlag )
399  .arg( request.m_sFilter )
400  .arg( request.m_nStartingIndex )
401  .arg( request.m_nRequestedCount)
402  .arg( request.m_sSortCriteria ));
403 
405  QString sErrorDesc = "";
406  uint16_t nNumberReturned = 0;
407  uint16_t nTotalMatches = 0;
408  uint16_t nUpdateID = 0;
409  QString sResultXML;
410  FilterMap filter = request.m_sFilter.split(',');
411 
412  LOG(VB_UPNP, LOG_INFO,
413  QString("UPnpCDS::HandleBrowse ObjectID=%1")
414  .arg(request.m_sObjectId));
415 
416  if (request.m_sObjectId == "0")
417  {
418  // ------------------------------------------------------------------
419  // This is for the root object... lets handle it.
420  // ------------------------------------------------------------------
421 
422  switch( request.m_eBrowseFlag )
423  {
424  case CDS_BrowseMetadata:
425  {
426  // -----------------------------------------------------------
427  // Return Root Object Only
428  // -----------------------------------------------------------
429 
430  eErrorCode = UPnPResult_Success;
431  nNumberReturned = 1;
432  nTotalMatches = 1;
433  nUpdateID = m_root.m_nUpdateId;
434 
435  m_root.SetChildCount( m_extensions.count() );
437 
438  sResultXML = m_root.toXml(filter);
439 
440  break;
441  }
442 
444  {
445  // Loop Through each extension and Build the Root Folders
446 
447  eErrorCode = UPnPResult_Success;
448  nTotalMatches = m_extensions.count();
449  nUpdateID = m_root.m_nUpdateId;
450 
451  if (request.m_nRequestedCount == 0)
452  request.m_nRequestedCount = nTotalMatches;
453 
454  uint16_t nStart = Max( request.m_nStartingIndex, uint16_t( 0 ));
455  uint16_t nCount = Min( nTotalMatches, request.m_nRequestedCount );
456 
457  DetermineClient( pRequest, &request );
458 
459  for (uint i = nStart;
460  (i < (uint)m_extensions.size()) &&
461  (nNumberReturned < nCount);
462  i++)
463  {
464  UPnpCDSExtension *pExtension = m_extensions[i];
465  CDSObject* pExtensionRoot = pExtension->GetRoot();
466  sResultXML += pExtensionRoot->toXml(filter, true); // Ignore Children
467  nNumberReturned ++;
468  }
469 
470  break;
471  }
472  default: break;
473  }
474  }
475  else
476  {
477  // ------------------------------------------------------------------
478  // Look for a CDS Extension that knows how to handle this ObjectID
479  // ------------------------------------------------------------------
480 
481  UPnpCDSExtensionList::iterator it = m_extensions.begin();
482  for (; (it != m_extensions.end()) && !pResult; ++it)
483  {
484  LOG(VB_UPNP, LOG_INFO,
485  QString("UPNP Browse : Searching for : %1 / ObjectID : %2")
486  .arg((*it)->m_sExtensionId).arg(request.m_sObjectId));
487 
488  pResult = (*it)->Browse(&request);
489  }
490 
491  if (pResult != nullptr)
492  {
493  eErrorCode = pResult->m_eErrorCode;
494  sErrorDesc = pResult->m_sErrorDesc;
495 
496  if (eErrorCode == UPnPResult_Success)
497  {
498  nNumberReturned = pResult->m_List.count();
499  nTotalMatches = pResult->m_nTotalMatches;
500  nUpdateID = pResult->m_nUpdateID;
501  if (request.m_eBrowseFlag == CDS_BrowseMetadata)
502  sResultXML = pResult->GetResultXML(filter, true); // Ignore children
503  else
504  sResultXML = pResult->GetResultXML(filter);
505  }
506 
507  delete pResult;
508  pResult = nullptr;
509  }
510  }
511 
512  // ----------------------------------------------------------------------
513  // Output Results of Browse Method
514  // ----------------------------------------------------------------------
515 
516  if (eErrorCode == UPnPResult_Success)
517  {
518  NameValues list;
519 
520  QString sResults = DIDL_LITE_BEGIN;
521  sResults += sResultXML;
522  sResults += DIDL_LITE_END;
523 
524  list.push_back(NameValue("Result", sResults));
525  list.push_back(NameValue("NumberReturned", nNumberReturned));
526  list.push_back(NameValue("TotalMatches", nTotalMatches));
527  list.push_back(NameValue("UpdateID", nUpdateID));
528 
529  pRequest->FormatActionResponse(list);
530  }
531  else
532  UPnp::FormatErrorResponse ( pRequest, eErrorCode, sErrorDesc );
533 
534 }
535 
537 //
539 
541 {
542  UPnpCDSExtensionResults *pResult = nullptr;
543  UPnpCDSRequest request;
544 
546  QString sErrorDesc = "";
547  uint16_t nNumberReturned = 0;
548  uint16_t nTotalMatches = 0;
549  uint16_t nUpdateID = 0;
550  QString sResultXML;
551 
552  DetermineClient( pRequest, &request );
553  request.m_sObjectId = pRequest->m_mapParams[ "objectid" ];
554  request.m_sContainerID = pRequest->m_mapParams[ "containerid" ];
555  request.m_sFilter = pRequest->m_mapParams[ "filter" ];
556  request.m_nStartingIndex =
557  pRequest->m_mapParams[ "startingindex" ].toLong();
558  request.m_nRequestedCount =
559  pRequest->m_mapParams[ "requestedcount"].toLong();
560  request.m_sSortCriteria = pRequest->m_mapParams[ "sortcriteria" ];
561  request.m_sSearchCriteria = pRequest->m_mapParams[ "searchcriteria"];
562 
563  LOG(VB_UPNP, LOG_INFO,
564  QString("UPnpCDS::HandleSearch ObjectID=%1, ContainerId=%2")
565  .arg(request.m_sObjectId) .arg(request.m_sContainerID));
566 
567  // ----------------------------------------------------------------------
568  // Break the SearchCriteria into it's parts
569  // -=>TODO: This DOES NOT handle ('s or other complex expressions
570  // ----------------------------------------------------------------------
571 
572  QRegExp rMatch( "\\b(or|and)\\b" );
573  rMatch.setCaseSensitivity(Qt::CaseInsensitive);
574 
575  request.m_sSearchList = request.m_sSearchCriteria.split(
576  rMatch, QString::SkipEmptyParts);
577  request.m_sSearchClass = "object"; // Default to all objects.
578 
579  // ----------------------------------------------------------------------
580  // -=>TODO: Need to process all expressions in searchCriteria... for now,
581  // Just focus on the "upnp:class derivedfrom" expression
582  // ----------------------------------------------------------------------
583 
584  for ( QStringList::Iterator it = request.m_sSearchList.begin();
585  it != request.m_sSearchList.end();
586  ++it )
587  {
588  if ((*it).contains("upnp:class derivedfrom", Qt::CaseInsensitive))
589  {
590  QStringList sParts = (*it).split(' ', QString::SkipEmptyParts);
591 
592  if (sParts.count() > 2)
593  {
594  request.m_sSearchClass = sParts[2].trimmed();
595  request.m_sSearchClass.remove( '"' );
596 
597  break;
598  }
599  }
600  }
601 
602  // ----------------------------------------------------------------------
603 
604 
605  LOG(VB_UPNP, LOG_INFO, QString("UPnpCDS::ProcessRequest \n"
606  ": url = %1 \n"
607  ": Method = %2 \n"
608  ": ObjectId = %3 \n"
609  ": SearchCriteria = %4 \n"
610  ": Filter = %5 \n"
611  ": StartingIndex = %6 \n"
612  ": RequestedCount = %7 \n"
613  ": SortCriteria = %8 \n"
614  ": SearchClass = %9" )
615  .arg( pRequest->m_sBaseUrl )
616  .arg( pRequest->m_sMethod )
617  .arg( request.m_sObjectId )
618  .arg( request.m_sSearchCriteria)
619  .arg( request.m_sFilter )
620  .arg( request.m_nStartingIndex )
621  .arg( request.m_nRequestedCount)
622  .arg( request.m_sSortCriteria )
623  .arg( request.m_sSearchClass ));
624 
625 #if 0
626  bool bSearchDone = false;
627 #endif
628 
629  UPnpCDSExtensionList::iterator it = m_extensions.begin();
630  for (; (it != m_extensions.end()) && !pResult; ++it)
631  pResult = (*it)->Search(&request);
632 
633  if (pResult != nullptr)
634  {
635  eErrorCode = pResult->m_eErrorCode;
636  sErrorDesc = pResult->m_sErrorDesc;
637 
638  if (eErrorCode == UPnPResult_Success)
639  {
640  FilterMap filter = request.m_sFilter.split(',');
641  nNumberReturned = pResult->m_List.count();
642  nTotalMatches = pResult->m_nTotalMatches;
643  nUpdateID = pResult->m_nUpdateID;
644  sResultXML = pResult->GetResultXML(filter);
645 #if 0
646  bSearchDone = true;
647 #endif
648  }
649 
650  delete pResult;
651  pResult = nullptr;
652  }
653 
654 #if 0
655  nUpdateID = 0;
656  LOG(VB_UPNP, LOG_DEBUG, sResultXML);
657 #endif
658 
659  if (eErrorCode == UPnPResult_Success)
660  {
661  NameValues list;
662  QString sResults = DIDL_LITE_BEGIN;
663  sResults += sResultXML;
664  sResults += DIDL_LITE_END;
665 
666  list.push_back(NameValue("Result", sResults));
667  list.push_back(NameValue("NumberReturned", nNumberReturned));
668  list.push_back(NameValue("TotalMatches", nTotalMatches));
669  list.push_back(NameValue("UpdateID", nUpdateID));
670 
671  pRequest->FormatActionResponse(list);
672  }
673  else
674  UPnp::FormatErrorResponse( pRequest, eErrorCode, sErrorDesc );
675 }
676 
684 {
685  NameValues list;
686 
687  LOG(VB_UPNP, LOG_INFO,
688  QString("UPnpCDS::ProcessRequest : %1 : %2")
689  .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
690 
691  // -=>TODO: Need to implement based on CDS Extension Capabilities
692 
693 // list.push_back(
694 // NameValue("SearchCaps",
695 // "dc:title,dc:creator,dc:date,upnp:class,res@size,"
696 // "res@protocolInfo","@refID"));
697  list.push_back(
698  NameValue("SearchCaps","")); // We don't support any searching
699 
700  pRequest->FormatActionResponse(list);
701 }
702 
710 {
711  NameValues list;
712 
713  LOG(VB_UPNP, LOG_INFO,
714  QString("UPnpCDS::ProcessRequest : %1 : %2")
715  .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
716 
717  // -=>TODO: Need to implement based on CDS Extension Capabilities
718 
719 // list.push_back(
720 // NameValue("SortCaps",
721 // "dc:title,dc:creator,dc:date,upnp:class,res@size,"
722 // "res@protocolInfo,@refID"));
723  list.push_back(
724  NameValue("SortCaps","")); // We don't support any sorting
725 
726  pRequest->FormatActionResponse(list);
727 }
728 
730 //
732 
734 {
735  NameValues list;
736 
737  LOG(VB_UPNP, LOG_INFO,
738  QString("UPnpCDS::ProcessRequest : %1 : %2")
739  .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
740 
741  auto nId = GetValue<uint16_t>("SystemUpdateID");
742 
743  list.push_back(NameValue("Id", nId));
744 
745  pRequest->FormatActionResponse(list);
746 }
747 
749 //
751 
753 {
754  NameValues list;
755  LOG(VB_UPNP, LOG_INFO,
756  QString("UPnpCDS::ProcessRequest : %1 : %2")
757  .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
758 
759  QString sResults = m_features.toXML();
760 
761  list.push_back(NameValue("FeatureList", sResults));
762 
763  pRequest->FormatActionResponse(list);
764 }
765 
767 {
768  NameValues list;
769 
770  LOG(VB_UPNP, LOG_INFO,
771  QString("UPnpCDS::ProcessRequest : %1 : %2")
772  .arg(pRequest->m_sBaseUrl) .arg(pRequest->m_sMethod));
773 
774  QString sToken = GetValue<QString>("ServiceResetToken");
775 
776  list.push_back(NameValue("ResetToken", sToken));
777 
778  pRequest->FormatActionResponse(list);
779 }
780 
783 //
784 // UPnpCDSExtension Implementation
785 //
788 
790 {
791  if (m_pRoot)
792  {
793  m_pRoot->DecrRef();
794  m_pRoot = nullptr;
795  }
796 }
797 
799 //
801 
803 {
804  if (!pRequest->m_sObjectId.startsWith(m_sExtensionId, Qt::CaseSensitive))
805  return false;
806 
807  LOG(VB_UPNP, LOG_INFO, QString("%1: Browse request is for us.").arg(m_sExtensionId));
808 
809  return true;
810 }
811 
813 //
815 
817 {
818  // -=>TODO: Need to add Filter & Sorting Support.
819 
820  if (!IsBrowseRequestForUs( pRequest ))
821  return( nullptr );
822 
823  // ----------------------------------------------------------------------
824  // Split the request ID into token key/value
825  //
826  // Music/Artist=123/Album=15
827  // Music/Genre=32/Artist=616/Album=13/Track=2632
828  // ----------------------------------------------------------------------
829  IDTokenMap tokens = TokenizeIDString(pRequest->m_sObjectId);
830  QString currentToken = GetCurrentToken(pRequest->m_sObjectId).first;
831 
832  LOG(VB_UPNP, LOG_DEBUG, QString("Browse (%1): Current Token '%2'")
833  .arg(m_sExtensionId).arg(currentToken));
834 
835  // ----------------------------------------------------------------------
836  // Process based on location in hierarchy
837  // ----------------------------------------------------------------------
838 
839  auto *pResults = new UPnpCDSExtensionResults();
840 
841  if (pResults != nullptr)
842  {
843  switch( pRequest->m_eBrowseFlag )
844  {
845  case CDS_BrowseMetadata:
846  {
847  if (pRequest->m_nRequestedCount == 0)
848  pRequest->m_nRequestedCount = 1; // This should be the case anyway, but enforce it just in case
849 
850  pRequest->m_sParentId = "0"; // Root
851 
852  // Create parent ID by stripping the last token from the object ID
853  if (pRequest->m_sObjectId.contains("/"))
854  pRequest->m_sParentId = pRequest->m_sObjectId.section("/", 0, -2);
855 
856  LOG(VB_UPNP, LOG_DEBUG, QString("UPnpCDS::Browse: BrowseMetadata (%1)").arg(pRequest->m_sObjectId));
857  if (LoadMetadata(pRequest, pResults, tokens, currentToken))
858  return pResults;
859  pResults->m_eErrorCode = UPnPResult_CDS_NoSuchObject;
860  break;
861  }
862 
864  {
865  pRequest->m_sParentId = pRequest->m_sObjectId;
866  LOG(VB_UPNP, LOG_DEBUG, QString("UPnpCDS::Browse: BrowseDirectChildren (%1)").arg(pRequest->m_sObjectId));
867  if (LoadChildren(pRequest, pResults, tokens, currentToken))
868  return pResults;
869  pResults->m_eErrorCode = UPnPResult_CDS_NoSuchObject;
870  break;
871  }
872 
873  default:
874  {
875  pResults->m_eErrorCode = UPnPResult_CDS_NoSuchObject;
876  pResults->m_sErrorDesc = "";
877  }
878  }
879 
880  }
881 
882  return( pResults );
883 }
884 
886 //
888 
890 {
891  return m_sClass.startsWith( pRequest->m_sSearchClass );
892 }
893 
895 //
897 
899 {
900  // -=>TODO: Need to add Filter & Sorting Support.
901  // -=>TODO: Need to add Sub-Folder/Category Support!!!!!
902 
903  QStringList sEmptyList;
904  LOG(VB_UPNP, LOG_INFO,
905  QString("UPnpCDSExtension::Search : m_sClass = %1 : "
906  "m_sSearchClass = %2")
907  .arg(m_sClass).arg(pRequest->m_sSearchClass));
908 
909  if ( !IsSearchRequestForUs( pRequest ))
910  {
911  LOG(VB_UPNP, LOG_INFO,
912  QString("UPnpCDSExtension::Search - Not For Us : "
913  "m_sClass = %1 : m_sSearchClass = %2")
914  .arg(m_sClass).arg(pRequest->m_sSearchClass));
915  return nullptr;
916  }
917 
918  auto *pResults = new UPnpCDSExtensionResults();
919 
920 // CreateItems( pRequest, pResults, 0, "", false );
921 
922  return pResults;
923 }
924 
926 //
928 
929 QString UPnpCDSExtension::RemoveToken( const QString &sToken,
930  const QString &sStr, int num )
931 {
932  QString sResult( "" );
933  int nPos = -1;
934 
935  for (int nIdx=0; nIdx < num; nIdx++)
936  {
937  if ((nPos = sStr.lastIndexOf( sToken, nPos )) == -1)
938  break;
939  }
940 
941  if (nPos > 0)
942  sResult = sStr.left( nPos );
943 
944  return sResult;
945 }
946 
961  UPnpCDSExtensionResults* /*pResults*/,
962  const IDTokenMap& /*tokens*/,
963  const QString& /*currentToken*/)
964 {
965  return false;
966 }
967 
968 
983  UPnpCDSExtensionResults* /*pResults*/,
984  const IDTokenMap& /*tokens*/,
985  const QString& /*currentToken*/)
986 {
987  return false;
988 }
989 
1005 {
1006  IDTokenMap tokenMap;
1007 
1008  QStringList tokens = Id.split('/');
1009 
1010  QStringList::iterator it;
1011  for (it = tokens.begin() + 1; it < tokens.end(); ++it) // Skip the 'root' token
1012  {
1013 
1014  QString key = (*it).section('=', 0, 0).toLower();
1015  QString value = (*it).section('=', 1, 1);
1016 
1017  tokenMap.insert(key, value);
1018  LOG(VB_UPNP, LOG_DEBUG, QString("Token Key: %1 Value: %2").arg(key)
1019  .arg(value));
1020  }
1021 
1022  return tokenMap;
1023 }
1024 
1025 
1041 {
1042  QStringList tokens = Id.split('/');
1043  QString current = tokens.last();
1044  QString key = current.section('=', 0, 0).toLower();
1045  QString value = current.section('=', 1, 1);
1046 
1047  return IDToken(key, value);
1048 }
1049 
1050 QString UPnpCDSExtension::CreateIDString(const QString &requestId,
1051  const QString &name,
1052  int value)
1053 {
1054  return CreateIDString(requestId, name, QString::number(value));
1055 }
1056 
1057 QString UPnpCDSExtension::CreateIDString(const QString &requestId,
1058  const QString &name,
1059  const QString &value)
1060 {
1061  IDToken currentToken = GetCurrentToken(requestId);
1062  QString currentName = currentToken.first;
1063  QString currentValue = currentToken.second;
1064 
1065  // For metadata requests the request ID will be the ID of the result, so
1066  // we don't need to do anything
1067  if (currentName == name.toLower() && !currentValue.isEmpty() &&
1068  currentValue == value.toLower())
1069  return requestId;
1070  if (currentName == name.toLower() && currentValue.isEmpty())
1071  return QString("%1=%2").arg(requestId).arg(value);
1072  return QString("%1/%2=%3").arg(requestId).arg(name).arg(value);
1073 }
1074 
1076 {
1077  LOG(VB_GENERAL, LOG_CRIT, "UPnpCDSExtension::CreateRoot() called on base class");
1079  m_sName,
1080  "0");
1081 }
1082 
1084 {
1085  if (!m_pRoot)
1086  CreateRoot();
1087 
1088  return m_pRoot;
1089 }
1090 
1092 //
1094 
1096 {
1097  QString xml;
1098 
1099  xml = "<shortcutlist>\r\n";
1100 
1101  QMap<ShortCutType, QString>::iterator it;
1102  for (it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it)
1103  {
1104  ShortCutType type = it.key();
1105  QString objectID = *it;
1106  xml += "<shortcut>\r\n";
1107  xml += QString("<name>%1</name>\r\n").arg(TypeToName(type));
1108  xml += QString("<objectID>%1</objectID>\r\n").arg(HTTPRequest::Encode(objectID));
1109  xml += "</shortcut>\r\n";
1110  }
1111 
1112  xml += "</shortcutlist>\r\n";
1113 
1114  return xml;
1115 }
1116 
1118  const QString &objectID)
1119 {
1120  if (!m_shortcuts.contains(type))
1121  m_shortcuts.insert(type, objectID);
1122  else
1123  {
1124  LOG(VB_GENERAL, LOG_ERR, QString("UPnPCDSShortcuts::AddShortCut(): "
1125  "Attempted to register duplicate "
1126  "shortcut").arg(TypeToName(type)));
1127  }
1128 
1129  return false;
1130 }
1131 
1133 {
1134  QString str;
1135 
1136  switch (type)
1137  {
1138  case MUSIC :
1139  str = "MUSIC";
1140  break;
1141  case MUSIC_ALBUMS :
1142  str = "MUSIC_ALBUMS";
1143  break;
1144  case MUSIC_ARTISTS :
1145  str = "MUSIC_ARTISTS";
1146  break;
1147  case MUSIC_GENRES :
1148  str = "MUSIC_GENRES";
1149  break;
1150  case MUSIC_PLAYLISTS :
1151  str = "MUSIC_PLAYLISTS";
1152  break;
1153  case MUSIC_RECENTLY_ADDED :
1154  str = "MUSIC_RECENTLY_ADDED";
1155  break;
1156  case MUSIC_LAST_PLAYED :
1157  str = "MUSIC_LAST_PLAYED";
1158  break;
1159  case MUSIC_AUDIOBOOKS :
1160  str = "MUSIC_AUDIOBOOKS";
1161  break;
1162  case MUSIC_STATIONS :
1163  str = "MUSIC_STATIONS";
1164  break;
1165  case MUSIC_ALL :
1166  str = "MUSIC_ALL";
1167  break;
1168  case MUSIC_FOLDER_STRUCTURE :
1169  str = "MUSIC_FOLDER_STRUCTURE";
1170  break;
1171 
1172  case IMAGES :
1173  str = "IMAGES";
1174  break;
1175  case IMAGES_YEARS :
1176  str = "IMAGES_YEARS";
1177  break;
1178  case IMAGES_YEARS_MONTH :
1179  str = "IMAGES_YEARS_MONTH";
1180  break;
1181  case IMAGES_ALBUM :
1182  str = "IMAGES_ALBUM";
1183  break;
1184  case IMAGES_SLIDESHOWS :
1185  str = "IMAGES_SLIDESHOWS";
1186  break;
1187  case IMAGES_RECENTLY_ADDED :
1188  str = "IMAGES_RECENTLY_ADDED";
1189  break;
1190  case IMAGES_LAST_WATCHED :
1191  str = "IMAGES_LAST_WATCHED";
1192  break;
1193  case IMAGES_ALL :
1194  str = "IMAGES_ALL";
1195  break;
1197  str = "IMAGES_FOLDER_STRUCTURE";
1198  break;
1199 
1200  case VIDEOS :
1201  str = "VIDEOS";
1202  break;
1203  case VIDEOS_GENRES :
1204  str = "VIDEOS_GENRES";
1205  break;
1206  case VIDEOS_YEARS :
1207  str = "VIDEOS_YEARS";
1208  break;
1209  case VIDEOS_YEARS_MONTH :
1210  str = "VIDEOS_YEARS_MONTH";
1211  break;
1212  case VIDEOS_ALBUM :
1213  str = "VIDEOS_ALBUM";
1214  break;
1215  case VIDEOS_RECENTLY_ADDED :
1216  str = "VIDEOS_RECENTLY_ADDED";
1217  break;
1218  case VIDEOS_LAST_PLAYED :
1219  str = "VIDEOS_LAST_PLAYED";
1220  break;
1221  case VIDEOS_RECORDINGS :
1222  str = "VIDEOS_RECORDINGS";
1223  break;
1224  case VIDEOS_ALL :
1225  str = "VIDEOS_ALL";
1226  break;
1228  case FOLDER_STRUCTURE :
1229  str = "VIDEOS_FOLDER_STRUCTURE";
1230  break;
1231  }
1232 
1233  return str;
1234 }
1235 
1236 // vim:ts=4:sw=4:ai:et:si:sts=4
QStringList GetBasePaths() override
Definition: eventing.cpp:129
QPair< QString, QString > IDToken
Definition: upnpcds.h:200
QStringList m_sSearchList
Definition: upnpcds.h:91
void RegisterExtension(UPnpCDSExtension *pExtension)
Definition: upnpcds.cpp:166
virtual int GetValue(const QString &sSetting, int Default)=0
QString GetResultXML(FilterMap &filter, bool ignoreChildren=false)
Definition: upnpcds.cpp:57
QString m_sParentId
Definition: upnpcds.h:85
virtual CDSObject * GetRoot()
Definition: upnpcds.cpp:1083
bool m_bRestricted
static UPnpCDSBrowseFlag GetBrowseFlag(const QString &sFlag)
Definition: upnpcds.cpp:154
QString m_sBaseUrl
Definition: httprequest.h:125
virtual bool LoadChildren(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken)
Fetch the children of the container identified in the request.
Definition: upnpcds.cpp:982
UPnpCDSBrowseFlag m_eBrowseFlag
Definition: upnpcds.h:86
static CDSObject * CreateContainer(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
QString toString(MarkTypes type)
ShortCutType
Allowed values for the Container Shortcut feature.
Definition: upnpcds.h:149
void RegisterService(UPnpDevice *device)
Creates a UPnpService and adds it to the UPnpDevice's list of services.
static void HandleGetSearchCapabilities(HTTPRequest *pRequest)
Return the list of supported search fields.
Definition: upnpcds.cpp:683
static QString RemoveToken(const QString &sToken, const QString &sStr, int num)
Definition: upnpcds.cpp:929
void HandleGetServiceResetToken(HTTPRequest *pRequest)
Definition: upnpcds.cpp:766
UPnPResultCode
Definition: upnp.h:36
QList< CDSObject * > CDSObjects
void UnregisterExtension(UPnpCDSExtension *pExtension)
Definition: upnpcds.cpp:185
QString m_sFilter
Definition: upnpcds.h:78
virtual bool LoadMetadata(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken)
Fetch just the metadata for the item identified in the request.
Definition: upnpcds.cpp:960
QString m_sId
QMap< ShortCutType, QString > m_shortcuts
Definition: upnpcds.h:192
UPnpCDSClient m_eClient
Definition: upnpcds.h:95
static UPnpCDSClientException clientExceptions[]
Definition: upnpcds.cpp:280
QString m_sMethod
Definition: httprequest.h:127
UPnpCDSBrowseFlag
Definition: upnpcds.h:45
bool m_bSearchable
QString GetRequestHeader(const QString &sKey, QString sDefault)
QString sHeaderValue
Definition: upnpcds.h:66
static QString Encode(const QString &sIn)
void SetChildCount(uint32_t nCount)
Allows the caller to set childCount without having to load children.
void HandleBrowse(HTTPRequest *pRequest)
Definition: upnpcds.cpp:366
static IDToken GetCurrentToken(const QString &Id)
Split the 'Id' String up into tokens and return the last (current) token.
Definition: upnpcds.cpp:1040
QMap< uint, int > FilterMap
virtual CDSShortCutList GetShortCuts()
Definition: upnpcds.h:270
static void DetermineClient(HTTPRequest *pRequest, UPnpCDSRequest *pCDSRequest)
Definition: upnpcds.cpp:305
QString m_sSortCriteria
Definition: upnpcds.h:81
uint16_t m_nTotalMatches
Definition: upnpcds.h:113
UPnpCDS(UPnpDevice *pDevice, const QString &sSharePath)
Definition: upnpcds.cpp:72
QString m_sExtensionId
Definition: upnpcds.h:206
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:281
void RegisterFeature(UPnPFeature *feature)
Definition: upnpcds.cpp:208
QString m_sSearchCriteria
Definition: upnpcds.h:90
uint16_t m_nRequestedCount
Definition: upnpcds.h:80
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
QString m_sObjectId
Definition: upnpcds.h:75
ObjectTypes m_eType
static QString TypeToName(ShortCutType type)
Definition: upnpcds.cpp:1132
static void HandleGetSortCapabilities(HTTPRequest *pRequest)
Return the list of supported sorting fields.
Definition: upnpcds.cpp:709
virtual int IncrRef(void)
Increments reference count.
QString m_sParentId
QString CreateXML() override
Definition: upnpcds.cpp:1095
#define DIDL_LITE_END
Definition: upnpcds.cpp:25
void RegisterShortCut(UPnPShortcutFeature::ShortCutType type, const QString &objectID)
Definition: upnpcds.cpp:198
const T & Max(const T &x, const T &y)
Definition: upnputil.h:35
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
static uint clientExceptionCount
Definition: upnpcds.cpp:302
const T & Min(const T &x, const T &y)
Definition: upnputil.h:30
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void FormatActionResponse(Serializer *ser)
CDSObject m_root
Definition: upnpcds.h:288
unsigned int uint
Definition: compat.h:140
static Configuration * GetConfiguration()
Definition: upnp.cpp:85
UPnpCDSClient nClientType
Definition: upnpcds.h:64
void AddAttribute(const NameValue &attribute)
QString toXml(FilterMap &filter, bool ignoreChildren=false) const
static UPnpCDSMethod GetMethod(const QString &sURI)
Definition: upnpcds.cpp:135
void Add(CDSObject *pObject)
Definition: upnpcds.cpp:31
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: eventing.cpp:141
QStringMap m_mapParams
Definition: httprequest.h:129
Standard UPnP Shortcut feature.
Definition: upnpcds.h:138
void HandleSearch(HTTPRequest *pRequest)
Definition: upnpcds.cpp:540
void HandleGetSystemUpdateID(HTTPRequest *pRequest)
Definition: upnpcds.cpp:733
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: upnpcds.cpp:226
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
Definition: upnpcds.cpp:1050
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:802
UPnPResultCode m_eErrorCode
Definition: upnpcds.h:110
QString m_sName
Definition: upnpcds.h:207
QMap< QString, QString > IDTokenMap
Definition: upnpcds.h:199
QString m_sContainerID
Definition: upnpcds.h:77
UPnpCDSExtensionList m_extensions
Definition: upnpcds.h:287
unsigned short uint16_t
Definition: iso6937tables.h:1
virtual ~UPnpCDSExtension()
Definition: upnpcds.cpp:789
virtual void CreateRoot()
Definition: upnpcds.cpp:1075
QString m_sControlUrl
Definition: upnpcds.h:291
UPnPFeatureList m_features
Definition: upnpcds.h:293
UPnpCDSMethod
Definition: upnpcds.h:32
void AddFeature(UPnPFeature *feature)
uint16_t m_nStartingIndex
Definition: upnpcds.h:79
QStringList GetBasePaths() override
Definition: upnpcds.cpp:217
QString m_sTitle
void AddVariable(StateVariableBase *pBase)
Definition: eventing.h:191
short m_nUpdateId
static IDTokenMap TokenizeIDString(const QString &Id)
Split the 'Id' String up into tokens for handling by each extension.
Definition: upnpcds.cpp:1004
bool AddShortCut(ShortCutType type, const QString &objectID)
Definition: upnpcds.cpp:1117
Default UTC.
Definition: mythdate.h:14
UPnPShortcutFeature * m_pShortCuts
Definition: upnpcds.h:294
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:889
void HandleGetFeatureList(HTTPRequest *pRequest)
Definition: upnpcds.cpp:752
void FormatFileResponse(const QString &sFileName)
QString m_sClass
Definition: upnpcds.h:208
#define DIDL_LITE_BEGIN
Definition: upnpcds.cpp:24
QString m_sSearchClass
Definition: upnpcds.h:92
double m_nClientVersion
Definition: upnpcds.h:96
virtual UPnpCDSExtensionResults * Search(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:898
QString m_sClass
QString m_sServiceDescFileName
Definition: upnpcds.h:290
QMap< UPnPShortcutFeature::ShortCutType, QString > CDSShortCutList
Definition: upnpcds.h:195
~UPnpCDS() override
Definition: upnpcds.cpp:123
CDSObject * m_pRoot
Definition: upnpcds.h:248
virtual UPnpCDSExtensionResults * Browse(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:816
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:41