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