MythTV  master
upnpcdsmusic.cpp
Go to the documentation of this file.
1 // Program Name: upnpcdsmusic.cpp
3 //
4 // Purpose - uPnp Content Directory Extension for Music
5 //
6 // Created By : David Blain Created On : Jan. 24, 2005
7 // Modified By : Modified On:
8 //
10 
11 #include <climits>
12 
13 #include <QFileInfo>
14 #include <QUrl>
15 #include <QUrlQuery>
16 
17 #include "storagegroup.h"
18 #include "upnpcdsmusic.h"
19 #include "httprequest.h"
20 #include "mythcorecontext.h"
21 #include "upnphelpers.h"
22 
59  : UPnpCDSExtension( QObject::tr("Music"), "Music",
60  "object.item.audioItem.musicTrack" )
61 {
62  QString sServerIp = gCoreContext->GetBackendServerIP();
63  int sPort = gCoreContext->GetBackendStatusPort();
64  m_uriBase.setScheme("http");
65  m_uriBase.setHost(sServerIp);
66  m_uriBase.setPort(sPort);
67 
68  // ShortCuts
69  m_shortcuts.insert(UPnPShortcutFeature::MUSIC, "Music");
70  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALL, "Music/Track");
71  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ALBUMS, "Music/Album");
72  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_ARTISTS, "Music/Artist");
73  m_shortcuts.insert(UPnPShortcutFeature::MUSIC_GENRES, "Music/Genre");
74 }
75 
77 //
79 
81 {
82  if (m_pRoot)
83  return;
84 
86  m_sName,
87  "0");
88 
89  QString containerId = m_sExtensionId + "/%1";
90 
91  // HACK: I'm not entirely happy with this solution, but it's at least
92  // tidier than passing through half a dozen extra args to Load[Foo]
93  // or having yet more methods just to load the counts
94  auto *pRequest = new UPnpCDSRequest();
95  pRequest->m_nRequestedCount = 0; // We don't want to load any results, we just want the TotalCount
96  auto *pResult = new UPnpCDSExtensionResults();
97  IDTokenMap tokens;
98  // END HACK
99 
100  // -----------------------------------------------------------------------
101  // All Tracks
102  // -----------------------------------------------------------------------
103  CDSObject* pContainer = CDSObject::CreateContainer ( containerId.arg("Track"),
104  QObject::tr("All Tracks"),
105  m_sExtensionId, // Parent Id
106  nullptr );
107  // HACK
108  LoadTracks(pRequest, pResult, tokens);
109  pContainer->SetChildCount(pResult->m_nTotalMatches);
110  pContainer->SetChildContainerCount(0);
111  // END HACK
112  m_pRoot->AddChild(pContainer);
113 
114  // -----------------------------------------------------------------------
115  // By Artist
116  // -----------------------------------------------------------------------
117  pContainer = CDSObject::CreateContainer ( containerId.arg("Artist"),
118  QObject::tr("Artist"),
119  m_sExtensionId, // Parent Id
120  nullptr );
121  // HACK
122  LoadArtists(pRequest, pResult, tokens);
123  pContainer->SetChildCount(pResult->m_nTotalMatches);
124  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
125  // END HACK
126  m_pRoot->AddChild(pContainer);
127 
128  // -----------------------------------------------------------------------
129  // By Album
130  // -----------------------------------------------------------------------
131  pContainer = CDSObject::CreateContainer ( containerId.arg("Album"),
132  QObject::tr("Album"),
133  m_sExtensionId, // Parent Id
134  nullptr );
135  // HACK
136  LoadAlbums(pRequest, pResult, tokens);
137  pContainer->SetChildCount(pResult->m_nTotalMatches);
138  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
139  // END HACK
140  m_pRoot->AddChild(pContainer);
141 
142  // -----------------------------------------------------------------------
143  // By Genre
144  // -----------------------------------------------------------------------
145  pContainer = CDSObject::CreateContainer ( containerId.arg("Genre"),
146  QObject::tr("Genre"),
147  m_sExtensionId, // Parent Id
148  nullptr );
149  // HACK
150  LoadGenres(pRequest, pResult, tokens);
151  pContainer->SetChildCount(pResult->m_nTotalMatches);
152  pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
153  // END HACK
154  m_pRoot->AddChild(pContainer);
155 
156  // -----------------------------------------------------------------------
157  // By Directory
158  // -----------------------------------------------------------------------
159 // pContainer = CDSObject::CreateStorageSystem ( containerId.arg("Directory"),
160 // QObject::tr("Directory"),
161 // m_sExtensionId, // Parent Id
162 // nullptr );
163 // // HACK
164 // LoadDirectories(pRequest, pResult, tokens);
165 // pContainer->SetChildCount(pResult->m_nTotalMatches);
166 // pContainer->SetChildContainerCount(pResult->m_nTotalMatches);
167 // // END HACK
168 // m_pRoot->AddChild(pContainer);
169 
170  // -----------------------------------------------------------------------
171 
172  // HACK
173  delete pRequest;
174  delete pResult;
175  // END HACK
176 }
177 
179 //
181 
183 {
184  // ----------------------------------------------------------------------
185  // See if we need to modify the request for compatibility
186  // ----------------------------------------------------------------------
187 
188  // Xbox360 compatibility code.
189 
190 // if (pRequest->m_eClient == CDS_ClientXBox &&
191 // pRequest->m_sContainerID == "7")
192 // {
193 // pRequest->m_sObjectId = "Music";
194 //
195 // LOG(VB_UPNP, LOG_INFO,
196 // "UPnpCDSMusic::IsBrowseRequestForUs - Yes, ContainerId == 7");
197 //
198 // return true;
199 // }
200 //
201 // if ((pRequest->m_sObjectId.isEmpty()) &&
202 // (!pRequest->m_sContainerID.isEmpty()))
203 // pRequest->m_sObjectId = pRequest->m_sContainerID;
204 
205  LOG(VB_UPNP, LOG_INFO,
206  "UPnpCDSMusic::IsBrowseRequestForUs - Not sure... Calling base class.");
207 
208  return UPnpCDSExtension::IsBrowseRequestForUs( pRequest );
209 }
210 
212 //
214 
216 {
217  // ----------------------------------------------------------------------
218  // See if we need to modify the request for compatibility
219  // ----------------------------------------------------------------------
220 
221  // XBox 360 compatibility code
222 
223 // if (pRequest->m_eClient == CDS_ClientXBox &&
224 // pRequest->m_sContainerID == "7")
225 // {
226 // pRequest->m_sObjectId = "Music/1";
227 // pRequest->m_sSearchCriteria = "object.container.album.musicAlbum";
228 // pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
229 //
230 // LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
231 //
232 // return true;
233 // }
234 //
235 // if (pRequest->m_sContainerID == "4")
236 // {
237 // pRequest->m_sObjectId = "Music";
238 // pRequest->m_sSearchCriteria = "object.item.audioItem.musicTrack";
239 // pRequest->m_sSearchList.append( pRequest->m_sSearchCriteria );
240 //
241 // LOG(VB_UPNP, LOG_INFO, "UPnpCDSMusic::IsSearchRequestForUs... Yes.");
242 //
243 // return true;
244 // }
245 //
246 // if ((pRequest->m_sObjectId.isEmpty()) &&
247 // (!pRequest->m_sContainerID.isEmpty()))
248 // pRequest->m_sObjectId = pRequest->m_sContainerID;
249 
250  LOG(VB_UPNP, LOG_INFO,
251  "UPnpCDSMusic::IsSearchRequestForUs.. Don't know, calling base class.");
252 
253  return UPnpCDSExtension::IsSearchRequestForUs( pRequest );
254 }
255 
257 //
259 
261  UPnpCDSExtensionResults* pResults,
262  const IDTokenMap& tokens, const QString& currentToken)
263 {
264  if (currentToken.isEmpty())
265  {
266  LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Final "
267  "token missing from id: %1")
268  .arg(pRequest->m_sParentId));
269  return false;
270  }
271 
272  // Root or Root + 1
273  if (tokens[currentToken].isEmpty())
274  {
275  CDSObject *container = nullptr;
276 
277  if (pRequest->m_sObjectId == m_sExtensionId)
278  container = GetRoot();
279  else
280  container = GetRoot()->GetChild(pRequest->m_sObjectId);
281 
282  if (container)
283  {
284  pResults->Add(container);
285  pResults->m_nTotalMatches = 1;
286  return true;
287  }
288 
289  LOG(VB_GENERAL, LOG_ERR, QString("UPnpCDSMusic::LoadMetadata: Requested "
290  "object cannot be found: %1")
291  .arg(pRequest->m_sObjectId));
292  return false;
293  }
294  if (currentToken == "genre")
295  {
296  // Genre is presently a top tier node, since it doesn't appear
297  // below Artist/Album/etc we don't need to pass through
298  // the ids for filtering
299  return LoadGenres(pRequest, pResults, tokens);
300  }
301  if (currentToken == "artist")
302  {
303  return LoadArtists(pRequest, pResults, tokens);
304  }
305  if (currentToken == "album")
306  {
307  return LoadAlbums(pRequest, pResults, tokens);
308  }
309  if (currentToken == "track")
310  {
311  return LoadTracks(pRequest, pResults, tokens);
312  }
313 
314  LOG(VB_GENERAL, LOG_ERR,
315  QString("UPnpCDSMusic::LoadMetadata(): "
316  "Unhandled metadata request for '%1'.").arg(currentToken));
317  return false;
318 }
319 
321 //
323 
325  UPnpCDSExtensionResults* pResults,
326  const IDTokenMap& tokens, const QString& currentToken)
327 {
328  if (currentToken.isEmpty() || currentToken == m_sExtensionId.toLower())
329  {
330  // Root
331  pResults->Add(GetRoot()->GetChildren());
332  pResults->m_nTotalMatches = GetRoot()->GetChildCount();
333  return true;
334  }
335  if (currentToken == "track")
336  {
337  return LoadTracks(pRequest, pResults, tokens);
338  }
339  if (currentToken == "genre")
340  {
341  if (tokens["genre"].toInt() > 0)
342  return LoadArtists(pRequest, pResults, tokens);
343  return LoadGenres(pRequest, pResults, tokens);
344  }
345  if (currentToken == "artist")
346  {
347  if (tokens["artist"].toInt() > 0)
348  return LoadAlbums(pRequest, pResults, tokens);
349  return LoadArtists(pRequest, pResults, tokens);
350  }
351  if (currentToken == "album")
352  {
353  if (tokens["album"].toInt() > 0)
354  return LoadTracks(pRequest, pResults, tokens);
355  return LoadAlbums(pRequest, pResults, tokens);
356  }
357  LOG(VB_GENERAL, LOG_ERR,
358  QString("UPnpCDSMusic::LoadChildren(): "
359  "Unhandled metadata request for '%1'.").arg(currentToken));
360 
361  return false;
362 }
363 
365 //
367 
369 {
370  QUrl artURI = m_uriBase;
371  artURI.setPath("/Content/GetAlbumArt");
372  QUrlQuery artQuery;
373  artQuery.addQueryItem("Id", QString::number(nSongID));
374  artURI.setQuery(artQuery);
375 
376  QList<Property*> propList = pItem->GetProperties("albumArtURI");
377  if (propList.size() >= 4)
378  {
379  // Prefer JPEG over PNG here, although PNG is allowed DLNA requires JPEG
380  // and crucially the filesizes are smaller which speeds up loading times
381  // over the network
382 
383  // We MUST include the thumbnail size, but since some clients may use the
384  // first image they see and the thumbnail is tiny, instead return the
385  // medium first. The large could be very large, which is no good if the
386  // client is pulling images for an entire list at once!
387 
388  // Medium
389  Property *pProp = propList.at(0);
390  if (pProp)
391  {
392  // Must be no more than 1024x768
393  QUrl mediumURI = artURI;
394  QUrlQuery mediumQuery(mediumURI.query());
395  mediumQuery.addQueryItem("Width", "1024");
396  mediumQuery.addQueryItem("Height", "768");
397  mediumURI.setQuery(mediumQuery);
398  pProp->SetValue(mediumURI.toEncoded());
399  pProp->AddAttribute("dlna:profileID", "JPEG_MED");
400  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
401  }
402 
403  // Thumbnail
404  pProp = propList.at(1);
405  if (pProp)
406  {
407  // At least one albumArtURI must be a ThumbNail (TN) no larger
408  // than 160x160, and it must also be a jpeg
409  QUrl thumbURI = artURI;
410  QUrlQuery thumbQuery(thumbURI.query());
411  thumbQuery.addQueryItem("Width", "160");
412  thumbQuery.addQueryItem("Height", "160");
413  thumbURI.setQuery(thumbQuery);
414  pProp->SetValue(thumbURI.toEncoded());
415  pProp->AddAttribute("dlna:profileID", "JPEG_TN");
416  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
417  }
418 
419  // Small
420  pProp = propList.at(2);
421  if (pProp)
422  {
423  // Must be no more than 640x480
424  QUrl smallURI = artURI;
425  QUrlQuery smallQuery(smallURI.query());
426  smallQuery.addQueryItem("Width", "640");
427  smallQuery.addQueryItem("Height", "480");
428  smallURI.setQuery(smallQuery);
429  pProp->SetValue(smallURI.toEncoded());
430  pProp->AddAttribute("dlna:profileID", "JPEG_SM");
431  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
432  }
433 
434  // Large
435  pProp = propList.at(3);
436  if (pProp)
437  {
438  // Must be no more than 4096x4096 - for our purposes, just return
439  // a fullsize image
440  pProp->SetValue(artURI.toEncoded());
441  pProp->AddAttribute("dlna:profileID", "JPEG_LRG");
442  pProp->AddAttribute("xmlns:dlna", "urn:schemas-dlna-org:metadata-1-0");
443  }
444  }
445  else
446  {
447  LOG(VB_GENERAL, LOG_ERR, QString("Unable to designate album artwork "
448  "for '%1' with class '%2' and id '%3'")
449  .arg(pItem->m_sId)
450  .arg(pItem->m_sClass)
451  .arg(nSongID));
452  }
453 }
454 
456 //
458 
460  UPnpCDSExtensionResults *pResults,
461  const IDTokenMap& tokens)
462 {
463  QString sRequestId = pRequest->m_sObjectId;
464 
465  uint16_t nCount = pRequest->m_nRequestedCount;
466  uint16_t nOffset = pRequest->m_nStartingIndex;
467 
468  // We must use a dedicated connection to get an acccurate value from
469  // FOUND_ROWS()
471 
472  QString sql = "SELECT SQL_CALC_FOUND_ROWS "
473  "a.album_id, a.album_name, t.artist_name, a.year, "
474  "a.compilation, s.song_id, g.genre, "
475  "COUNT(a.album_id), w.albumart_id "
476  "FROM music_albums a "
477  "LEFT JOIN music_artists t ON a.artist_id=t.artist_id "
478  "LEFT JOIN music_songs s ON a.album_id=s.album_id "
479  "LEFT JOIN music_genres g ON s.genre_id=g.genre_id "
480  "LEFT JOIN music_albumart w ON s.song_id=w.song_id "
481  "%1 " // WHERE clauses
482  "GROUP BY a.album_id "
483  "ORDER BY a.album_name "
484  "LIMIT :OFFSET,:COUNT";
485 
486  QStringList clauses;
487  QString whereString = BuildWhereClause(clauses, tokens);
488 
489  query.prepare(sql.arg(whereString));
490 
491  BindValues(query, tokens);
492 
493  query.bindValue(":OFFSET", nOffset);
494  query.bindValue(":COUNT", nCount);
495 
496  if (!query.exec())
497  return false;
498 
499  while (query.next())
500  {
501  int nAlbumID = query.value(0).toInt();
502  QString sAlbumName = query.value(1).toString();
503  QString sArtist = query.value(2).toString();
504  QString sYear = query.value(3).toString();
505  bool bCompilation = query.value(4).toBool();
506  int nSongId = query.value(5).toInt(); // TODO: Allow artwork lookups by album ID
507  QString sGenre = query.value(6).toString();
508  int nTrackCount = query.value(7).toInt();
509  int nAlbumArtID = query.value(8).toInt();
510 
511  CDSObject* pContainer = CDSObject::CreateMusicAlbum( CreateIDString(sRequestId, "Album", nAlbumID),
512  sAlbumName,
513  pRequest->m_sParentId,
514  nullptr );
515  pContainer->SetPropValue("artist", sArtist);
516  pContainer->SetPropValue("date", sYear);
517  pContainer->SetPropValue("genre", sGenre);
518  pContainer->SetChildCount(nTrackCount);
519  pContainer->SetChildContainerCount(0);
520  if (bCompilation)
521  {
522  // Do nothing for now
523  }
524 
525  // Artwork
526 
527  if (nAlbumArtID > 0)
528  PopulateArtworkURIS(pContainer, nSongId);
529 
530  pResults->Add(pContainer);
531  pContainer->DecrRef();
532  }
533 
534  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
535  // at least the size of this result set
536  if (query.size() > 0)
537  pResults->m_nTotalMatches = query.size();
538 
539  // Fetch the total number of matches ignoring any LIMITs
540  query.prepare("SELECT FOUND_ROWS()");
541  if (query.exec() && query.next())
542  pResults->m_nTotalMatches = query.value(0).toUInt();
543 
544  return true;
545 }
546 
548 //
550 
552  UPnpCDSExtensionResults *pResults,
553  const IDTokenMap& tokens)
554 {
555  QString sRequestId = pRequest->m_sObjectId;
556 
557  uint16_t nCount = pRequest->m_nRequestedCount;
558  uint16_t nOffset = pRequest->m_nStartingIndex;
559 
560  // We must use a dedicated connection to get an accurate value from
561  // FOUND_ROWS()
563 
564  QString sql = "SELECT SQL_CALC_FOUND_ROWS "
565  "t.artist_id, t.artist_name, CONCAT_WS(',', g.genre), "
566  "COUNT(DISTINCT a.album_id) "
567  "FROM music_artists t "
568  "LEFT JOIN music_albums a ON a.artist_id = t.artist_id "
569  "JOIN music_songs s ON t.artist_id = s.artist_id "
570  "LEFT JOIN music_genres g ON s.genre_id = g.genre_id "
571  "%1 " // WHERE clauses
572  "GROUP BY t.artist_id "
573  "ORDER BY t.artist_name "
574  "LIMIT :OFFSET,:COUNT";
575 
576 
577  QStringList clauses;
578  QString whereString = BuildWhereClause(clauses, tokens);
579 
580  query.prepare(sql.arg(whereString));
581 
582  BindValues(query, tokens);
583 
584  query.bindValue(":OFFSET", nOffset);
585  query.bindValue(":COUNT", nCount);
586 
587  if (!query.exec())
588  return false;
589 
590  while (query.next())
591  {
592  int nArtistId = query.value(0).toInt();
593  QString sArtistName = query.value(1).toString();
594  QStringList sGenres = query.value(2).toString().split(',');
595  int nAlbumCount = query.value(3).toInt();
596 
597  CDSObject* pContainer = CDSObject::CreateMusicArtist( CreateIDString(sRequestId, "Artist", nArtistId),
598  sArtistName,
599  pRequest->m_sParentId,
600  nullptr );
601 // TODO: Add SetPropValues option for multi-value properties
602 // QStringList::Iterator it;
603 // for (it = sGenres.begin(); it != sGenres.end(); ++it)
604 // {
605 // pContainer->SetPropValues("genre", sGenres);
606 // }
607  pContainer->SetChildCount(nAlbumCount);
608  pContainer->SetChildContainerCount(nAlbumCount);
609 
610  // Artwork
611  //PopulateArtistURIS(pContainer, nArtistId);
612 
613  pResults->Add(pContainer);
614  pContainer->DecrRef();
615  }
616 
617  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
618  // at least the size of this result set
619  if (query.size() > 0)
620  pResults->m_nTotalMatches = query.size();
621 
622  // Fetch the total number of matches ignoring any LIMITs
623  query.prepare("SELECT FOUND_ROWS()");
624  if (query.exec() && query.next())
625  pResults->m_nTotalMatches = query.value(0).toUInt();
626 
627  return true;
628 }
629 
631 //
633 
635  UPnpCDSExtensionResults *pResults,
636  const IDTokenMap& tokens )
637 {
638  QString sRequestId = pRequest->m_sObjectId;
639 
640  uint16_t nCount = pRequest->m_nRequestedCount;
641  uint16_t nOffset = pRequest->m_nStartingIndex;
642 
643  // We must use a dedicated connection to get an acccurate value from
644  // FOUND_ROWS()
646 
647  QString sql = "SELECT SQL_CALC_FOUND_ROWS g.genre_id, g.genre, "
648  "COUNT( DISTINCT t.artist_id ) "
649  "FROM music_genres g "
650  "LEFT JOIN music_songs s ON g.genre_id = s.genre_id "
651  "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
652  "%1 " // WHERE clauses
653  "GROUP BY g.genre_id "
654  "ORDER BY g.genre "
655  "LIMIT :OFFSET,:COUNT";
656 
657  QStringList clauses;
658  QString whereString = BuildWhereClause(clauses, tokens);
659 
660  query.prepare(sql.arg(whereString));
661 
662  BindValues(query, tokens);
663 
664  query.bindValue(":OFFSET", nOffset);
665  query.bindValue(":COUNT", nCount);
666 
667  if (!query.exec())
668  return false;
669 
670  while (query.next())
671  {
672  int nGenreId = query.value(0).toInt();
673  QString sGenreName = query.value(1).toString();
674  int nArtistCount = query.value(2).toInt();
675 
676  CDSObject* pContainer = CDSObject::CreateMusicGenre( CreateIDString(sRequestId, "Genre", nGenreId),
677  sGenreName,
678  pRequest->m_sParentId,
679  nullptr );
680  pContainer->SetPropValue("description", sGenreName);
681 
682  pContainer->SetChildCount(nArtistCount);
683  pContainer->SetChildContainerCount(nArtistCount);
684 
685  pResults->Add(pContainer);
686  pContainer->DecrRef();
687  }
688 
689  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
690  // at least the size of this result set
691  if (query.size() > 0)
692  pResults->m_nTotalMatches = query.size();
693 
694  // Fetch the total number of matches ignoring any LIMITs
695  query.prepare("SELECT FOUND_ROWS()");
696  if (query.exec() && query.next())
697  pResults->m_nTotalMatches = query.value(0).toUInt();
698 
699  return true;
700 }
701 
703 //
705 
707  UPnpCDSExtensionResults *pResults,
708  const IDTokenMap& tokens)
709 {
710  QString sRequestId = pRequest->m_sObjectId;
711 
712  uint16_t nCount = pRequest->m_nRequestedCount;
713  uint16_t nOffset = pRequest->m_nStartingIndex;
714 
715  // We must use a dedicated connection to get an acccurate value from
716  // FOUND_ROWS()
718 
719  QString sql = "SELECT SQL_CALC_FOUND_ROWS s.song_id, t.artist_name, "
720  "a.album_name, s.name, "
721  "g.genre, s.year, s.track, "
722  "s.description, s.filename, s.length, s.size, "
723  "s.numplays, s.lastplay, w.albumart_id "
724  "FROM music_songs s "
725  "LEFT JOIN music_artists t ON t.artist_id = s.artist_id "
726  "LEFT JOIN music_albums a ON a.album_id = s.album_id "
727  "LEFT JOIN music_genres g ON g.genre_id = s.genre_id "
728  "LEFT JOIN music_albumart w ON s.song_id = w.song_id "
729  "%1 " // WHERE clauses
730  "GROUP BY s.song_id "
731  "ORDER BY t.artist_name, a.album_name, s.track "
732  "LIMIT :OFFSET,:COUNT";
733 
734  QStringList clauses;
735  QString whereString = BuildWhereClause(clauses, tokens);
736 
737  query.prepare(sql.arg(whereString));
738 
739  BindValues(query, tokens);
740 
741  query.bindValue(":OFFSET", nOffset);
742  query.bindValue(":COUNT", nCount);
743 
744  if (!query.exec())
745  return false;
746 
747  while (query.next())
748  {
749  int nId = query.value( 0).toInt();
750  QString sArtist = query.value( 1).toString();
751  QString sAlbum = query.value( 2).toString();
752  QString sTitle = query.value( 3).toString();
753  QString sGenre = query.value( 4).toString();
754  int nYear = query.value( 5).toInt();
755  int nTrackNum = query.value( 6).toInt();
756  QString sDescription = query.value( 7).toString();
757  QString sFileName = query.value( 8).toString();
758  uint32_t nLengthMS = query.value( 9).toUInt();
759  uint64_t nFileSize = query.value(10).toULongLong();
760 
761  int nPlaybackCount = query.value(11).toInt();
762  QDateTime lastPlayedTime = query.value(12).toDateTime();
763  int nAlbumArtID = query.value(13).toInt();
764 
765  CDSObject* pItem = CDSObject::CreateMusicTrack( CreateIDString(sRequestId, "Track", nId),
766  sTitle,
767  pRequest->m_sParentId,
768  nullptr );
769 
770  // Only add the reference ID for items which are not in the
771  // 'All Tracks' container
772  QString sRefIDBase = QString("%1/Track").arg(m_sExtensionId);
773  if ( pRequest->m_sParentId != sRefIDBase )
774  {
775  QString sRefId = QString( "%1=%2")
776  .arg( sRefIDBase )
777  .arg( nId );
778 
779  pItem->SetPropValue( "refID", sRefId );
780  }
781 
782  pItem->SetPropValue( "genre" , sGenre );
783  pItem->SetPropValue( "description" , sTitle );
784  pItem->SetPropValue( "longDescription" , sDescription);
785 
786  pItem->SetPropValue( "artist" , sArtist );
787  pItem->SetPropValue( "creator" , sArtist );
788  pItem->SetPropValue( "album" , sAlbum );
789  pItem->SetPropValue( "originalTrackNumber" , QString::number(nTrackNum));
790  if (nYear > 0 && nYear < 9999)
791  pItem->SetPropValue( "date", QDate(nYear,1,1).toString(Qt::ISODate));
792 
793  pItem->SetPropValue( "playbackCount" , QString::number(nPlaybackCount));
794  pItem->SetPropValue( "lastPlaybackTime" , UPnPDateTime::DateTimeFormat(lastPlayedTime));
795 
796  // Artwork
797  if (nAlbumArtID > 0)
798  PopulateArtworkURIS(pItem, nId);
799 
800  // ----------------------------------------------------------------------
801  // Add Music Resource Element based on File extension (HTTP)
802  // ----------------------------------------------------------------------
803 
804  QFileInfo fInfo( sFileName );
805 
806  QUrl resURI = m_uriBase;
807  QUrlQuery resQuery;
808  resURI.setPath("/Content/GetMusic");
809  resQuery.addQueryItem("Id", QString::number(nId));
810  resURI.setQuery(resQuery);
811 
812  QString sMimeType = HTTPRequest::GetMimeType( fInfo.suffix() );
813 
814  QString sProtocol = DLNA::ProtocolInfoString(UPNPProtocol::kHTTP,
815  sMimeType);
816 
817  Resource *pResource = pItem->AddResource( sProtocol, resURI.toEncoded() );
818 
819  pResource->AddAttribute( "duration" , UPnPDateTime::resDurationFormat(nLengthMS) );
820  if (nFileSize > 0)
821  pResource->AddAttribute( "size" , QString::number( nFileSize) );
822 
823  pResults->Add(pItem);
824  pItem->DecrRef();
825  }
826 
827  // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
828  // at least the size of this result set
829  if (query.size() > 0)
830  pResults->m_nTotalMatches = query.size();
831 
832  // Fetch the total number of matches ignoring any LIMITs
833  query.prepare("SELECT FOUND_ROWS()");
834  if (query.exec() && query.next())
835  pResults->m_nTotalMatches = query.value(0).toUInt();
836 
837  return true;
838 }
839 
841 //
843 
844 QString UPnpCDSMusic::BuildWhereClause( QStringList clauses,
845  IDTokenMap tokens)
846 {
847  if (tokens["track"].toInt() > 0)
848  clauses.append("s.song_id=:TRACK_ID");
849  if (tokens["album"].toInt() > 0)
850  clauses.append("s.album_id=:ALBUM_ID");
851  if (tokens["artist"].toInt() > 0)
852  clauses.append("s.artist_id=:ARTIST_ID");
853  if (tokens["genre"].toInt() > 0)
854  clauses.append("s.genre_id=:GENRE_ID");
855  if (tokens["year"].toInt() > 0)
856  clauses.append("s.year=:YEAR");
857  if (tokens["directory"].toInt() > 0)
858  clauses.append("s.directory_id=:DIRECTORY_ID");
859 
860 // if (tokens["album"].toInt() > 0)
861 // clauses.append("a.album_id=:ALBUM_ID");
862 // if (tokens["artist"].toInt() > 0)
863 // clauses.append("a.artist_id=:ARTIST_ID");
864 
865  QString whereString;
866  if (!clauses.isEmpty())
867  {
868  whereString = " WHERE ";
869  whereString.append(clauses.join(" AND "));
870  }
871 
872  return whereString;
873 }
874 
876 //
878 
880  IDTokenMap tokens)
881 {
882  if (tokens["track"].toInt() > 0)
883  query.bindValue(":TRACK_ID", tokens["track"]);
884  if (tokens["album"].toInt() > 0)
885  query.bindValue(":ALBUM_ID", tokens["album"]);
886  if (tokens["artist"].toInt() > 0)
887  query.bindValue(":ARTIST_ID", tokens["artist"]);
888  if (tokens["genre"].toInt() > 0)
889  query.bindValue(":GENRE_ID", tokens["genre"]);
890  if (tokens["year"].toInt() > 0)
891  query.bindValue(":YEAR", tokens["year"]);
892  if (tokens["directory"].toInt() > 0)
893  query.bindValue(":DIRECTORY_ID", tokens["directory"]);
894 }
895 
896 // vim:ts=4:sw=4:ai:et:si:sts=4
CDSObject::CreateMusicGenre
static CDSObject * CreateMusicGenre(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:885
CDSObject::GetProperties
QList< Property * > GetProperties(const QString &sName)
Definition: upnpcdsobjects.cpp:99
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
UPnpCDSRequest::m_nRequestedCount
uint16_t m_nRequestedCount
Definition: upnpcds.h:80
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:203
UPnpCDSMusic::CreateRoot
void CreateRoot() override
Definition: upnpcdsmusic.cpp:80
UPnpCDSExtension
Definition: upnpcds.h:203
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:125
UPNPProtocol::kHTTP
@ kHTTP
Definition: upnphelpers.h:129
UPnpCDSMusic::UPnpCDSMusic
UPnpCDSMusic()
Music Extension for UPnP ContentDirectory Service.
Definition: upnpcdsmusic.cpp:58
UPnpCDSMusic::IsSearchRequestForUs
bool IsSearchRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:215
CDSObject
Definition: upnpcdsobjects.h:184
UPnpCDSRequest::m_sParentId
QString m_sParentId
Definition: upnpcds.h:85
UPnpCDSMusic::LoadMetadata
bool LoadMetadata(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken) override
Fetch just the metadata for the item identified in the request.
Definition: upnpcdsmusic.cpp:260
UPnPDateTime::DateTimeFormat
QString DateTimeFormat(const QDateTime &dateTime)
Date-Time Format.
Definition: upnphelpers.cpp:43
MythCoreContext::GetBackendStatusPort
int GetBackendStatusPort(void)
Returns the locally defined backend status port.
Definition: mythcorecontext.cpp:1101
Resource
Definition: upnpcdsobjects.h:103
UPnpCDSMusic::PopulateArtworkURIS
void PopulateArtworkURIS(CDSObject *pItem, int songID)
Definition: upnpcdsmusic.cpp:368
UPnpCDSExtension::CreateIDString
static QString CreateIDString(const QString &RequestId, const QString &Name, int Value)
Definition: upnpcds.cpp:1057
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
arg
arg(title).arg(filename).arg(doDelete))
UPnpCDSExtension::m_sName
QString m_sName
Definition: upnpcds.h:207
UPnpCDSRequest::m_nStartingIndex
uint16_t m_nStartingIndex
Definition: upnpcds.h:79
UPnPShortcutFeature::MUSIC_ALL
@ MUSIC_ALL
Definition: upnpcds.h:160
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
UPnpCDSExtension::IsSearchRequestForUs
virtual bool IsSearchRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:896
Resource::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:120
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
DLNA::ProtocolInfoString
QString ProtocolInfoString(UPNPProtocol::TransferProtocol protocol, const QString &mimeType, const QSize resolution, double videoFrameRate, const QString &container, const QString &videoCodec, const QString &audioCodec, bool isTranscoded)
Create a properly formatted string for the 4th field of res@protocolInfo.
Definition: upnphelpers.cpp:315
CDSObject::SetChildContainerCount
void SetChildContainerCount(uint32_t nCount)
Allows the caller to set childContainerCount without having to load children.
Definition: upnpcdsobjects.cpp:255
Property
Definition: upnpcdsobjects.h:45
UPnpCDSMusic::LoadChildren
bool LoadChildren(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens, const QString &currentToken) override
Fetch the children of the container identified in the request.
Definition: upnpcdsmusic.cpp:324
Property::AddAttribute
void AddAttribute(const QString &sName, const QString &sValue)
Definition: upnpcdsobjects.h:85
UPnpCDSExtension::m_shortcuts
CDSShortCutList m_shortcuts
Definition: upnpcds.h:210
toString
QString toString(MarkTypes type)
Definition: programtypes.cpp:26
UPnPShortcutFeature::MUSIC
@ MUSIC
Definition: upnpcds.h:151
MSqlQuery::kDedicatedConnection
@ kDedicatedConnection
Definition: mythdbcon.h:217
Property::SetValue
void SetValue(const QString &value)
Definition: upnpcdsobjects.h:70
HTTPRequest::GetMimeType
static QString GetMimeType(const QString &sFileExtension)
Definition: httprequest.cpp:1045
UPnpCDSExtensionResults::m_nTotalMatches
uint16_t m_nTotalMatches
Definition: upnpcds.h:113
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
UPnpCDSExtensionResults::Add
void Add(CDSObject *pObject)
Definition: upnpcds.cpp:30
CDSObject::SetPropValue
void SetPropValue(const QString &sName, const QString &sValue, const QString &type="")
Definition: upnpcdsobjects.cpp:117
MythCoreContext::GetBackendServerIP
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
Definition: mythcorecontext.cpp:1025
UPnpCDSMusic::LoadTracks
bool LoadTracks(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:706
UPnpCDSMusic::IsBrowseRequestForUs
bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest) override
Definition: upnpcdsmusic.cpp:182
UPnpCDSMusic::LoadArtists
static bool LoadArtists(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:551
UPnpCDSExtension::m_pRoot
CDSObject * m_pRoot
Definition: upnpcds.h:248
storagegroup.h
CDSObject::SetChildCount
void SetChildCount(uint32_t nCount)
Allows the caller to set childCount without having to load children.
Definition: upnpcdsobjects.cpp:232
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
CDSObject::AddChild
CDSObject * AddChild(CDSObject *pChild)
Definition: upnpcdsobjects.cpp:165
UPnPShortcutFeature::MUSIC_ALBUMS
@ MUSIC_ALBUMS
Definition: upnpcds.h:152
CDSObject::m_sId
QString m_sId
Definition: upnpcdsobjects.h:192
UPnpCDSRequest::m_sObjectId
QString m_sObjectId
Definition: upnpcds.h:75
UPnpCDSMusic::BindValues
static void BindValues(MSqlQuery &query, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:879
CDSObject::CreateMusicTrack
static CDSObject * CreateMusicTrack(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:521
upnphelpers.h
UPnpCDSMusic::LoadGenres
static bool LoadGenres(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:634
UPnpCDSExtension::GetRoot
virtual CDSObject * GetRoot()
Definition: upnpcds.cpp:1090
mythcorecontext.h
CDSObject::AddResource
Resource * AddResource(const QString &sProtocol, const QString &sURI)
Definition: upnpcdsobjects.cpp:205
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:14
IDTokenMap
QMap< QString, QString > IDTokenMap
Definition: upnpcds.h:199
UPnpCDSMusic::LoadAlbums
bool LoadAlbums(const UPnpCDSRequest *pRequest, UPnpCDSExtensionResults *pResults, const IDTokenMap &tokens)
Definition: upnpcdsmusic.cpp:459
CDSObject::CreateContainer
static CDSObject * CreateContainer(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:468
UPnPShortcutFeature::MUSIC_ARTISTS
@ MUSIC_ARTISTS
Definition: upnpcds.h:153
CDSObject::m_sClass
QString m_sClass
Definition: upnpcdsobjects.h:195
CDSObject::CreateMusicAlbum
static CDSObject * CreateMusicAlbum(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:835
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
UPnpCDSMusic::BuildWhereClause
static QString BuildWhereClause(QStringList clauses, IDTokenMap tokens)
Definition: upnpcdsmusic.cpp:844
upnpcdsmusic.h
UPnpCDSExtensionResults
Definition: upnpcds.h:106
UPnPDateTime::resDurationFormat
QString resDurationFormat(uint32_t msec)
res@duration Format B.2.1.4 res@duration - UPnP ContentDirectory Service 2008, 2013
Definition: upnphelpers.cpp:81
UPnpCDSMusic::m_uriBase
QUrl m_uriBase
Definition: upnpcdsmusic.h:47
UPnPShortcutFeature::MUSIC_GENRES
@ MUSIC_GENRES
Definition: upnpcds.h:154
CDSObject::GetChildCount
uint32_t GetChildCount(void) const
Return the number of children in this container.
Definition: upnpcdsobjects.cpp:219
CDSObject::GetChild
CDSObject * GetChild(const QString &sID)
Definition: upnpcdsobjects.cpp:184
httprequest.h
query
MSqlQuery query(MSqlQuery::InitCon())
UPnpCDSRequest
Definition: upnpcds.h:72
CDSObject::CreateMusicArtist
static CDSObject * CreateMusicArtist(const QString &sId, const QString &sTitle, const QString &sParentId, CDSObject *pObject=nullptr)
Definition: upnpcdsobjects.cpp:955
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
UPnpCDSExtension::m_sExtensionId
QString m_sExtensionId
Definition: upnpcds.h:206
UPnpCDSExtension::IsBrowseRequestForUs
virtual bool IsBrowseRequestForUs(UPnpCDSRequest *pRequest)
Definition: upnpcds.cpp:809