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