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