MythTV  master
v2content.cpp
Go to the documentation of this file.
1 // Program Name: content.cpp
3 // Created : Mar. 7, 2011
4 //
5 // Copyright (c) 2011 David Blain <dblain@mythtv.org>
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 //
25 
26 // C++
27 #include <cmath>
28 
29 // Qt
30 #include <QDir>
31 #include <QImage>
32 #include <QImageWriter>
33 
34 // MythTV
35 #include "libmythbase/compat.h"
38 #include "libmythbase/mythdate.h"
42 #include "libmythbase/remotefile.h"
49 
50 // MythBackend
51 #include "v2content.h"
52 #include "v2serviceUtil.h"
53 
54 // Qt6 has made the QFileInfo::QFileInfo(QString) constructor
55 // explicit, which means that it is no longer possible to use an
56 // initializer list to construct a QFileInfo. Disable that clang-tidy
57 // check for this file so it can still be run on the rest of the file
58 // in the project.
59 //
60 // NOLINTBEGIN(modernize-return-braced-init-list)
61 
62 // This will be initialised in a thread safe manner on first use
64  (CONTENT_HANDLE, V2Content::staticMetaObject, &V2Content::RegisterCustomTypes))
65 
67 {
68  qRegisterMetaType<V2ArtworkInfoList*>("V2ArtworkInfoList");
69  qRegisterMetaType<V2ArtworkInfo*>("V2ArtworkInfo");
70  // qRegisterMetaType<V2LiveStreamInfo*>("V2LiveStreamInfo");
71  // qRegisterMetaType<V2LiveStreamInfoList*>("V2LiveStreamInfoList");
72 }
73 
75 
77 //
79 
80 QFileInfo V2Content::GetFile( const QString &sStorageGroup,
81  const QString &sFileName )
82 {
83  QString sGroup = sStorageGroup;
84 
85  if (sGroup.isEmpty())
86  {
87  LOG(VB_UPNP, LOG_WARNING,
88  "GetFile - StorageGroup missing... using 'Default'");
89  sGroup = "Default";
90  }
91 
92  if (sFileName.isEmpty())
93  {
94  QString sMsg ( "GetFile - FileName missing." );
95 
96  //LOG(VB_UPNP, LOG_ERR, sMsg);
97 
98  throw QString(sMsg);
99  }
100 
101  // ------------------------------------------------------------------
102  // Search for the filename
103  // ------------------------------------------------------------------
104 
105  StorageGroup storage( sGroup );
106  QString sFullFileName = storage.FindFile( sFileName );
107 
108  if (sFullFileName.isEmpty())
109  {
110  LOG(VB_UPNP, LOG_ERR,
111  QString("GetFile - Unable to find %1.").arg(sFileName));
112 
113  return {};
114  }
115 
116  // ----------------------------------------------------------------------
117  // check to see if the file (still) exists
118  // ----------------------------------------------------------------------
119 
120  if (QFile::exists( sFullFileName ))
121  {
122  return QFileInfo( sFullFileName );
123  }
124 
125  LOG(VB_UPNP, LOG_ERR,
126  QString("GetFile - File Does not exist %1.").arg(sFullFileName));
127 
128  return {};
129 }
130 
132 //
134 
135 QFileInfo V2Content::GetImageFile( const QString &sStorageGroup,
136  const QString &sFileName,
137  int nWidth,
138  int nHeight)
139 {
140  QString sGroup = sStorageGroup;
141 
142  if (sGroup.isEmpty())
143  {
144  LOG(VB_UPNP, LOG_WARNING,
145  "GetImageFile - StorageGroup missing... using 'Default'");
146  sGroup = "Default";
147  }
148 
149  if (sFileName.isEmpty())
150  {
151  QString sMsg ( "GetImageFile - FileName missing." );
152 
153  //LOG(VB_UPNP, LOG_WARNING, sMsg);
154 
155  throw QString(sMsg);
156  }
157 
158  // ------------------------------------------------------------------
159  // Search for the filename
160  // ------------------------------------------------------------------
161 
162  StorageGroup storage( sGroup );
163  QString sFullFileName = storage.FindFile( sFileName );
164 
165  if (sFullFileName.isEmpty())
166  {
167  LOG(VB_UPNP, LOG_WARNING,
168  QString("GetImageFile - Unable to find %1.").arg(sFileName));
169 
170  return {};
171  }
172 
173  // ----------------------------------------------------------------------
174  // check to see if the file (still) exists
175  // ----------------------------------------------------------------------
176 
177  if (!QFile::exists( sFullFileName ))
178  {
179  LOG(VB_UPNP, LOG_WARNING,
180  QString("GetImageFile - File Does not exist %1.").arg(sFullFileName));
181  return {};
182  }
183 
184  // ----------------------------------------------------------------------
185  // If no scaling is required return the file info
186  // ----------------------------------------------------------------------
187  if ((nWidth == 0) && (nHeight == 0))
188  return QFileInfo( sFullFileName );
189 
190  // ----------------------------------------------------------------------
191  // Create a filename for the scaled copy
192  // ----------------------------------------------------------------------
193  QString sNewFileName = QString( "%1.%2x%3.jpg" )
194  .arg( sFullFileName )
195  .arg( nWidth )
196  .arg( nHeight );
197 
198  // ----------------------------------------------------------------------
199  // check to see if image is already created.
200  // ----------------------------------------------------------------------
201 
202  if (QFile::exists( sNewFileName ))
203  return QFileInfo( sNewFileName );
204 
205  // ----------------------------------------------------------------------
206  // Must generate Generate Image and save.
207  // ----------------------------------------------------------------------
208 
209  auto *pImage = new QImage( sFullFileName );
210 
211  if (!pImage || pImage->isNull())
212  return {};
213 
214  float fAspect = (float)(pImage->width()) / pImage->height();
215 
216  if ( nWidth == 0 )
217  nWidth = (int)std::rint(nHeight * fAspect);
218 
219  if ( nHeight == 0 )
220  nHeight = (int)std::rint(nWidth / fAspect);
221 
222  QImage img = pImage->scaled( nWidth, nHeight, Qt::KeepAspectRatio,
223  Qt::SmoothTransformation);
224 
225  QByteArray fname = sNewFileName.toLatin1();
226  img.save( fname.constData(), "JPG", 60 );
227 
228  delete pImage;
229 
230  return QFileInfo( sNewFileName );
231 }
232 
234 //
236 
237 QStringList V2Content::GetDirList( const QString &sStorageGroup )
238 {
239 
240  if (sStorageGroup.isEmpty())
241  {
242  QString sMsg( "GetDirList - StorageGroup missing.");
243  LOG(VB_UPNP, LOG_ERR, sMsg);
244 
245  throw QString(sMsg);
246  }
247 
248  StorageGroup sgroup(sStorageGroup);
249 
250  return sgroup.GetDirList("", true);
251 }
252 
254 //
256 
257 QStringList V2Content::GetFileList( const QString &sStorageGroup )
258 {
259 
260  if (sStorageGroup.isEmpty())
261  {
262  QString sMsg( "GetFileList - StorageGroup missing.");
263  LOG(VB_UPNP, LOG_ERR, sMsg);
264 
265  throw QString(sMsg);
266  }
267 
268  StorageGroup sgroup(sStorageGroup);
269 
270  return sgroup.GetFileList("", true);
271 }
272 
274 //
276 
277 QFileInfo V2Content::GetRecordingArtwork ( const QString &sType,
278  const QString &sInetref,
279  int nSeason,
280  int nWidth,
281  int nHeight)
282 {
283  ArtworkMap map = GetArtwork(sInetref, nSeason);
284 
285  if (map.isEmpty())
286  return {};
287 
289  QString sgroup;
290 
291  if (sType.toLower() == "coverart")
292  {
293  sgroup = "Coverart";
295  }
296  else if (sType.toLower() == "fanart")
297  {
298  sgroup = "Fanart";
300  }
301  else if (sType.toLower() == "banner")
302  {
303  sgroup = "Banners";
305  }
306 
307  if (!map.contains(type))
308  return {};
309 
310  QUrl url(map.value(type).url);
311  QString sFileName = url.path();
312 
313  if (sFileName.isEmpty())
314  return {};
315 
316  return GetImageFile( sgroup, sFileName, nWidth, nHeight);
317 }
318 
320 //
322 
324  int chanid,
325  const QDateTime &StartTime)
326 {
327  if ((RecordedId <= 0) &&
328  (chanid <= 0 || !StartTime.isValid()))
329  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
330 
331  // TODO Should use RecordingInfo
332  ProgramInfo pginfo;
333  if (RecordedId > 0)
334  pginfo = ProgramInfo(RecordedId);
335  else
336  pginfo = ProgramInfo(chanid, StartTime.toUTC());
337 
338  return GetProgramArtworkList(pginfo.GetInetRef(), pginfo.GetSeason());
339 }
340 
342  int nSeason )
343 {
344  auto *pArtwork = new V2ArtworkInfoList();
345 
346  V2FillArtworkInfoList (pArtwork, sInetref, nSeason);
347 
348  return pArtwork;
349 }
351 //
353 
354 // NOTE: If you rename this, you must also update upnpcdsvideo.cpp
355 QFileInfo V2Content::GetVideoArtwork( const QString &sType,
356  int nId, int nWidth, int nHeight )
357 {
358  LOG(VB_UPNP, LOG_INFO, QString("GetVideoArtwork ID = %1").arg(nId));
359 
360  QString sgroup = "Coverart";
361  QString column = "coverfile";
362 
363  if (sType.toLower() == "coverart")
364  {
365  sgroup = "Coverart";
366  column = "coverfile";
367  }
368  else if (sType.toLower() == "fanart")
369  {
370  sgroup = "Fanart";
371  column = "fanart";
372  }
373  else if (sType.toLower() == "banner")
374  {
375  sgroup = "Banners";
376  column = "banner";
377  }
378  else if (sType.toLower() == "screenshot")
379  {
380  sgroup = "Screenshots";
381  column = "screenshot";
382  }
383 
384  // ----------------------------------------------------------------------
385  // Read Video artwork file path from database
386  // ----------------------------------------------------------------------
387 
388  MSqlQuery query(MSqlQuery::InitCon());
389 
390  QString querystr = QString("SELECT %1 FROM videometadata WHERE "
391  "intid = :ITEMID").arg(column);
392 
393  query.prepare(querystr);
394  query.bindValue(":ITEMID", nId);
395 
396  if (!query.exec())
397  MythDB::DBError("GetVideoArtwork ", query);
398 
399  if (!query.next())
400  return {};
401 
402  QString sFileName = query.value(0).toString();
403 
404  if (sFileName.isEmpty())
405  return {};
406 
407  return GetImageFile( sgroup, sFileName, nWidth, nHeight );
408 }
409 
411 //
413 
414 QFileInfo V2Content::GetAlbumArt( int nTrackId, int nWidth, int nHeight )
415 {
416  // ----------------------------------------------------------------------
417  // Read AlbumArt file path from database
418  // ----------------------------------------------------------------------
419 
420  MusicMetadata *metadata = MusicMetadata::createFromID(nTrackId);
421 
422  if (!metadata)
423  return {};
424 
425  QString sFullFileName = metadata->getAlbumArtFile();
426  LOG(VB_GENERAL, LOG_DEBUG, QString("GetAlbumArt: %1").arg(sFullFileName));
427 
428  delete metadata;
429 
430  if (!RemoteFile::Exists(sFullFileName))
431  return {};
432 
433  QString sNewFileName = QString( "/tmp/%1.%2x%3.jpg" )
434  .arg( QFileInfo(sFullFileName).fileName() )
435  .arg( nWidth )
436  .arg( nHeight );
437 
438  // ----------------------------------------------------------------------
439  // check to see if albumart image is already created.
440  // ----------------------------------------------------------------------
441 
442  if (QFile::exists( sNewFileName ))
443  return QFileInfo( sNewFileName );
444 
445  // ----------------------------------------------------------------------
446  // Must generate Albumart Image, Generate Image and save.
447  // ----------------------------------------------------------------------
448 
449 
450  QImage img;
451  if (sFullFileName.startsWith("myth://"))
452  {
453  RemoteFile rf(sFullFileName, false, false, 0s);
454  QByteArray data;
455  rf.SaveAs(data);
456 
457  img.loadFromData(data);
458  }
459  else
460  {
461  img.load(sFullFileName);
462  }
463 
464  if (img.isNull())
465  return {};
466 
467  // We don't need to scale if no height and width were specified
468  // but still need to save as jpg if it's in another format
469  if ((nWidth == 0) && (nHeight == 0))
470  {
471  if (!sFullFileName.startsWith("myth://"))
472  {
473  QFileInfo fi(sFullFileName);
474  if (fi.suffix().toLower() == "jpg")
475  return fi;
476  }
477  }
478  else if (nWidth > img.width() && nHeight > img.height())
479  {
480  // Requested dimensions are larger than the source image, so instead of
481  // scaling up which will produce horrible results return the fullsize
482  // image and the user can scale further if they want instead
483  // NOTE: If this behaviour is changed, for example making it optional,
484  // then upnp code will need changing to compensate
485  }
486  else
487  {
488  float fAspect = (float)(img.width()) / img.height();
489 
490  if ( nWidth == 0 || nWidth > img.width() )
491  nWidth = (int)std::rint(nHeight * fAspect);
492 
493  if ( nHeight == 0 || nHeight > img.height() )
494  nHeight = (int)std::rint(nWidth / fAspect);
495 
496  img = img.scaled( nWidth, nHeight, Qt::KeepAspectRatio,
497  Qt::SmoothTransformation);
498  }
499 
500  QString fname = sNewFileName.toLatin1().constData();
501  // Use JPG not PNG for compatibility with the most uPnP devices and
502  // faster loading (smaller file to send over network)
503  if (!img.save( fname, "JPG" ))
504  return {};
505 
506  return QFileInfo( sNewFileName );
507 }
508 
510 //
512 
513 QFileInfo V2Content::GetPreviewImage( int nRecordedId,
514  int nChanId,
515  const QDateTime &StartTime,
516  int nWidth,
517  int nHeight,
518  int nSecsIn,
519  const QString &sFormat )
520 {
521  if ((nRecordedId <= 0) &&
522  (nChanId <= 0 || !StartTime.isValid()))
523  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
524 
525  if (!sFormat.isEmpty()
526  && !QImageWriter::supportedImageFormats().contains(sFormat.toLower().toLocal8Bit()))
527  {
528  throw QString("GetPreviewImage: Specified 'Format' is not supported.");
529  }
530 
531  // ----------------------------------------------------------------------
532  // Read Recording From Database
533  // ----------------------------------------------------------------------
534 
535  // TODO Should use RecordingInfo
536  ProgramInfo pginfo;
537  if (nRecordedId > 0)
538  pginfo = ProgramInfo(nRecordedId);
539  else
540  pginfo = ProgramInfo(nChanId, StartTime.toUTC());
541 
542  if (!pginfo.GetChanID())
543  {
544  LOG(VB_GENERAL, LOG_ERR,
545  QString("GetPreviewImage: No recording for '%1'")
546  .arg(nRecordedId));
547  return {};
548  }
549 
550  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
551  && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
552  {
553  QString sMsg =
554  QString("GetPreviewImage: Wrong Host '%1' request from '%2'")
555  .arg( gCoreContext->GetHostName(),
556  pginfo.GetHostname() );
557 
558  LOG(VB_UPNP, LOG_ERR, sMsg);
559 
560  throw V2HttpRedirectException( pginfo.GetHostname() );
561  }
562 
563  QString sImageFormat = sFormat;
564  if (sImageFormat.isEmpty())
565  sImageFormat = "PNG";
566 
567  QString sFileName = GetPlaybackURL(&pginfo);
568 
569  // ----------------------------------------------------------------------
570  // check to see if default preview image is already created.
571  // ----------------------------------------------------------------------
572 
573  QString sPreviewFileName;
574 
575  auto nSecs = std::chrono::seconds(nSecsIn);
576  if (nSecs <= 0s)
577  {
578  nSecs = -1s;
579  sPreviewFileName = QString("%1.png").arg(sFileName);
580  }
581  else
582  {
583  sPreviewFileName = QString("%1.%2.png").arg(sFileName).arg(nSecsIn);
584  }
585 
586  if (!QFile::exists( sPreviewFileName ))
587  {
588  // ------------------------------------------------------------------
589  // Must generate Preview Image, Generate Image and save.
590  // ------------------------------------------------------------------
591  if (!pginfo.IsLocal() && sFileName.startsWith("/"))
592  pginfo.SetPathname(sFileName);
593 
594  if (!pginfo.IsLocal())
595  return {};
596 
597  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
599  previewgen->SetPreviewTimeAsSeconds( nSecs );
600  previewgen->SetOutputFilename ( sPreviewFileName );
601 
602  bool ok = previewgen->Run();
603 
604  previewgen->deleteLater();
605 
606  if (!ok)
607  return {};
608  }
609 
610  bool bDefaultPixmap = (nWidth == 0) && (nHeight == 0);
611 
612  QString sNewFileName;
613 
614  if (bDefaultPixmap)
615  sNewFileName = sPreviewFileName;
616  else
617  {
618  sNewFileName = QString( "%1.%2.%3x%4.%5" )
619  .arg( sFileName )
620  .arg( nSecsIn )
621  .arg( nWidth == 0 ? -1 : nWidth )
622  .arg( nHeight == 0 ? -1 : nHeight )
623  .arg( sImageFormat.toLower() );
624 
625  // ----------------------------------------------------------------------
626  // check to see if scaled preview image is already created and isn't
627  // out of date
628  // ----------------------------------------------------------------------
629  if (QFile::exists( sNewFileName ))
630  {
631  if (QFileInfo(sPreviewFileName).lastModified() <=
632  QFileInfo(sNewFileName).lastModified())
633  return QFileInfo( sNewFileName );
634  }
635 
636  QImage image = QImage(sPreviewFileName);
637 
638  if (image.isNull())
639  return {};
640 
641  // We can just re-scale the default (full-size version) to avoid
642  // a preview generator run
643  if ( nWidth <= 0 )
644  image = image.scaledToHeight(nHeight, Qt::SmoothTransformation);
645  else if ( nHeight <= 0 )
646  image = image.scaledToWidth(nWidth, Qt::SmoothTransformation);
647  else
648  image = image.scaled(nWidth, nHeight, Qt::IgnoreAspectRatio,
649  Qt::SmoothTransformation);
650 
651  image.save(sNewFileName, sImageFormat.toUpper().toLocal8Bit());
652 
653  // Let anybody update it
654  bool ret = makeFileAccessible(sNewFileName.toLocal8Bit().constData());
655  if (!ret)
656  {
657  LOG(VB_GENERAL, LOG_ERR, "Unable to change permissions on "
658  "preview image. Backends and frontends "
659  "running under different users will be "
660  "unable to access it");
661  }
662  }
663 
664  if (QFile::exists( sNewFileName ))
665  return QFileInfo( sNewFileName );
666 
667  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
669  previewgen->SetPreviewTimeAsSeconds( nSecs );
670  previewgen->SetOutputFilename ( sNewFileName );
671  previewgen->SetOutputSize (QSize(nWidth,nHeight));
672 
673  bool ok = previewgen->Run();
674 
675  previewgen->deleteLater();
676 
677  if (!ok)
678  return {};
679 
680  return QFileInfo( sNewFileName );
681 }
682 
684 //
686 
687 QFileInfo V2Content::GetRecording( int nRecordedId,
688  int nChanId,
689  const QDateTime &StartTime,
690  const QString &Download )
691 {
692  if ((nRecordedId <= 0) &&
693  (nChanId <= 0 || !StartTime.isValid()))
694  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
695 
696  // ------------------------------------------------------------------
697  // Read Recording From Database
698  // ------------------------------------------------------------------
699 
700  // TODO Should use RecordingInfo
701  ProgramInfo pginfo;
702  if (nRecordedId > 0)
703  pginfo = ProgramInfo(nRecordedId);
704  else
705  pginfo = ProgramInfo(nChanId, StartTime.toUTC());
706 
707  if (!pginfo.GetChanID())
708  {
709  LOG(VB_UPNP, LOG_ERR, QString("GetRecording - for '%1' failed")
710  .arg(nRecordedId));
711 
712  return {};
713  }
714 
715  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
716  && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
717  {
718  // We only handle requests for local resources
719 
720  QString sMsg =
721  QString("GetRecording: Wrong Host '%1' request from '%2'.")
722  .arg( gCoreContext->GetHostName(),
723  pginfo.GetHostname() );
724 
725  LOG(VB_UPNP, LOG_ERR, sMsg);
726 
727  throw V2HttpRedirectException( pginfo.GetHostname() );
728  }
729 
730  QString sFileName( GetPlaybackURL(&pginfo) );
731 
732  if (HAS_PARAMv2("Download"))
733  m_request->m_headers->insert("mythtv-download",Download);
734 
735  // ----------------------------------------------------------------------
736  // check to see if the file exists
737  // ----------------------------------------------------------------------
738 
739  if (QFile::exists( sFileName ))
740  return QFileInfo( sFileName );
741 
742  return {};
743 }
744 
746 //
748 
749 QFileInfo V2Content::GetMusic( int nId )
750 {
751  QString sFileName;
752 
753  // ----------------------------------------------------------------------
754  // Load Track's FileName
755  // ----------------------------------------------------------------------
756 
757  MSqlQuery query(MSqlQuery::InitCon());
758 
759  if (query.isConnected())
760  {
761  query.prepare("SELECT CONCAT_WS('/', music_directories.path, "
762  "music_songs.filename) AS filename FROM music_songs "
763  "LEFT JOIN music_directories ON "
764  "music_songs.directory_id="
765  "music_directories.directory_id "
766  "WHERE music_songs.song_id = :KEY");
767 
768  query.bindValue(":KEY", nId );
769 
770  if (!query.exec())
771  {
772  MythDB::DBError("GetMusic()", query);
773  return {};
774  }
775 
776  if (query.next())
777  {
778  sFileName = query.value(0).toString();
779  }
780  }
781 
782  if (sFileName.isEmpty())
783  return {};
784 
785  return GetFile( "Music", sFileName );
786 }
787 
789 //
791 
792 QFileInfo V2Content::GetVideo( int nId )
793 {
794  QString sFileName;
795 
796  // ----------------------------------------------------------------------
797  // Load Track's FileName
798  // ----------------------------------------------------------------------
799 
800  MSqlQuery query(MSqlQuery::InitCon());
801 
802  if (query.isConnected())
803  {
804  query.prepare("SELECT filename FROM videometadata WHERE intid = :KEY" );
805  query.bindValue(":KEY", nId );
806 
807  if (!query.exec())
808  {
809  MythDB::DBError("GetVideo()", query);
810  return {};
811  }
812 
813  if (query.next())
814  sFileName = query.value(0).toString();
815  }
816 
817  if (sFileName.isEmpty())
818  return {};
819 
820  if (!QFile::exists( sFileName ))
821  return GetFile( "Videos", sFileName );
822 
823  return QFileInfo( sFileName );
824 }
825 
827 //
829 
830 QString V2Content::GetHash( const QString &sStorageGroup,
831  const QString &sFileName )
832 {
833  if ((sFileName.isEmpty()) ||
834  (sFileName.contains("/../")) ||
835  (sFileName.startsWith("../")))
836  {
837  LOG(VB_GENERAL, LOG_ERR,
838  QString("ERROR checking for file, filename '%1' "
839  "fails sanity checks").arg(sFileName));
840  return {};
841  }
842 
843  QString storageGroup = "Default";
844 
845  if (!sStorageGroup.isEmpty())
846  storageGroup = sStorageGroup;
847 
848  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
849 
850  QString fullname = sgroup.FindFile(sFileName);
851  QString hash = FileHash(fullname);
852 
853  if (hash == "NULL")
854  return {};
855 
856  return hash;
857 }
858 
860 //
862 
863 bool V2Content::DownloadFile( const QString &sURL, const QString &sStorageGroup )
864 {
865  QFileInfo finfo(sURL);
866  QString filename = finfo.fileName();
867  StorageGroup sgroup(sStorageGroup, gCoreContext->GetHostName(), false);
868  QString outDir = sgroup.FindNextDirMostFree();
869  QString outFile;
870 
871  if (outDir.isEmpty())
872  {
873  LOG(VB_GENERAL, LOG_ERR,
874  QString("Unable to determine directory "
875  "to write to in %1 write command").arg(sURL));
876  return false;
877  }
878 
879  if ((filename.contains("/../")) ||
880  (filename.startsWith("../")))
881  {
882  LOG(VB_GENERAL, LOG_ERR,
883  QString("ERROR: %1 write filename '%2' does not "
884  "pass sanity checks.").arg(sURL, filename));
885  return false;
886  }
887 
888  outFile = outDir + "/" + filename;
889 
890  return GetMythDownloadManager()->download(sURL, outFile);
891 }
892 
893 
894 // V2LiveStreamInfo *V2Content::AddLiveStream( const QString &sStorageGroup,
895 // const QString &sFileName,
896 // const QString &sHostName,
897 // int nMaxSegments,
898 // int nWidth,
899 // int nHeight,
900 // int nBitrate,
901 // int nAudioBitrate,
902 // int nSampleRate )
903 // {
904 // QString sGroup = sStorageGroup;
905 
906 // if (sGroup.isEmpty())
907 // {
908 // LOG(VB_UPNP, LOG_WARNING,
909 // "AddLiveStream - StorageGroup missing... using 'Default'");
910 // sGroup = "Default";
911 // }
912 
913 // if (sFileName.isEmpty())
914 // {
915 // QString sMsg ( "AddLiveStream - FileName missing." );
916 
917 // LOG(VB_UPNP, LOG_ERR, sMsg);
918 
919 // throw QString(sMsg);
920 // }
921 
922 // // ------------------------------------------------------------------
923 // // Search for the filename
924 // // ------------------------------------------------------------------
925 
926 // QString sFullFileName;
927 // if (sHostName.isEmpty() || sHostName == gCoreContext->GetHostName())
928 // {
929 // StorageGroup storage( sGroup );
930 // sFullFileName = storage.FindFile( sFileName );
931 
932 // if (sFullFileName.isEmpty())
933 // {
934 // LOG(VB_UPNP, LOG_ERR,
935 // QString("AddLiveStream - Unable to find %1.").arg(sFileName));
936 
937 // return nullptr;
938 // }
939 // }
940 // else
941 // {
942 // sFullFileName =
943 // MythCoreContext::GenMythURL(sHostName, 0, sFileName, sStorageGroup);
944 // }
945 
946 // auto *hls = new HTTPLiveStream(sFullFileName, nWidth, nHeight, nBitrate,
947 // nAudioBitrate, nMaxSegments, 0, 0, nSampleRate);
948 
949 // if (!hls)
950 // {
951 // LOG(VB_UPNP, LOG_ERR,
952 // "AddLiveStream - Unable to create HTTPLiveStream.");
953 // return nullptr;
954 // }
955 
956 // V2LiveStreamInfo *lsInfo = hls->StartStream();
957 
958 // delete hls;
959 
960 // return lsInfo;
961 // }
962 
963 // /////////////////////////////////////////////////////////////////////////////
964 // //
965 // /////////////////////////////////////////////////////////////////////////////
966 
967 // bool V2Content::RemoveLiveStream( int nId )
968 // {
969 // return HTTPLiveStream::RemoveStream(nId);
970 // }
971 
972 // /////////////////////////////////////////////////////////////////////////////
973 // //
974 // /////////////////////////////////////////////////////////////////////////////
975 
976 // V2LiveStreamInfo *V2Content::StopLiveStream( int nId )
977 // {
978 // return HTTPLiveStream::StopStream(nId);
979 // }
980 
981 // /////////////////////////////////////////////////////////////////////////////
982 // //
983 // /////////////////////////////////////////////////////////////////////////////
984 
985 // V2LiveStreamInfo *V2Content::GetLiveStream( int nId )
986 // {
987 // auto *hls = new HTTPLiveStream(nId);
988 
989 // if (!hls)
990 // {
991 // LOG( VB_UPNP, LOG_ERR,
992 // QString("GetLiveStream - for stream id %1 failed").arg( nId ));
993 // return nullptr;
994 // }
995 
996 // V2LiveStreamInfo *hlsInfo = hls->GetLiveStreamInfo();
997 // if (!hlsInfo)
998 // {
999 // LOG( VB_UPNP, LOG_ERR,
1000 // QString("HLS::GetLiveStreamInfo - for stream id %1 failed")
1001 // .arg( nId ));
1002 // return nullptr;
1003 // }
1004 
1005 // delete hls;
1006 // return hlsInfo;
1007 // }
1008 
1009 // /////////////////////////////////////////////////////////////////////////////
1010 // //
1011 // /////////////////////////////////////////////////////////////////////////////
1012 
1013 // V2LiveStreamInfoList *V2Content::GetLiveStreamList( const QString &FileName )
1014 // {
1015 // return HTTPLiveStream::GetLiveStreamInfoList(FileName);
1016 // }
1017 
1018 // /////////////////////////////////////////////////////////////////////////////
1019 // //
1020 // /////////////////////////////////////////////////////////////////////////////
1021 
1022 // V2LiveStreamInfo *V2Content::AddRecordingLiveStream(
1023 // int nRecordedId,
1024 // int nChanId,
1025 // const QDateTime &StartTime,
1026 // int nMaxSegments,
1027 // int nWidth,
1028 // int nHeight,
1029 // int nBitrate,
1030 // int nAudioBitrate,
1031 // int nSampleRate )
1032 // {
1033 // if ((nRecordedId <= 0) &&
1034 // (nChanId <= 0 || !StartTime.isValid()))
1035 // throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
1036 
1037 // // ------------------------------------------------------------------
1038 // // Read Recording From Database
1039 // // ------------------------------------------------------------------
1040 
1041 // // TODO Should use RecordingInfo
1042 // ProgramInfo pginfo;
1043 // if (nRecordedId > 0)
1044 // pginfo = ProgramInfo(nRecordedId);
1045 // else
1046 // pginfo = ProgramInfo(nChanId, StartTime.toUTC());
1047 
1048 // if (!pginfo.GetChanID())
1049 // {
1050 // LOG(VB_UPNP, LOG_ERR,
1051 // QString("AddRecordingLiveStream - for %1, %2 failed")
1052 // .arg(QString::number(nRecordedId)));
1053 // return nullptr;
1054 // }
1055 
1056 // bool masterBackendOverride = gCoreContext->GetBoolSetting("MasterBackendOverride", false);
1057 
1058 // if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
1059 // && ! masterBackendOverride)
1060 // {
1061 // // We only handle requests for local resources
1062 
1063 // QString sMsg =
1064 // QString("GetRecording: Wrong Host '%1' request from '%2'.")
1065 // .arg( gCoreContext->GetHostName(),
1066 // pginfo.GetHostname() );
1067 
1068 // LOG(VB_UPNP, LOG_ERR, sMsg);
1069 
1070 // throw V2HttpRedirectException( pginfo.GetHostname() );
1071 // }
1072 
1073 // QString sFileName( GetPlaybackURL(&pginfo) );
1074 
1075 // // ----------------------------------------------------------------------
1076 // // check to see if the file exists
1077 // // ----------------------------------------------------------------------
1078 
1079 // if (!QFile::exists( sFileName ))
1080 // {
1081 // LOG( VB_UPNP, LOG_ERR, QString("AddRecordingLiveStream - for %1, %2 failed")
1082 // .arg( nChanId )
1083 // .arg( StartTime.toUTC().toString() ));
1084 // return nullptr;
1085 // }
1086 
1087 // QFileInfo fInfo( sFileName );
1088 
1089 // QString hostName;
1090 // if (masterBackendOverride)
1091 // hostName = gCoreContext->GetHostName();
1092 // else
1093 // hostName = pginfo.GetHostname();
1094 
1095 // return AddLiveStream( pginfo.GetStorageGroup(), fInfo.fileName(),
1096 // hostName, nMaxSegments, nWidth,
1097 // nHeight, nBitrate, nAudioBitrate, nSampleRate );
1098 // }
1099 
1100 // /////////////////////////////////////////////////////////////////////////////
1101 // //
1102 // /////////////////////////////////////////////////////////////////////////////
1103 
1104 // V2LiveStreamInfo *V2Content::AddVideoLiveStream( int nId,
1105 // int nMaxSegments,
1106 // int nWidth,
1107 // int nHeight,
1108 // int nBitrate,
1109 // int nAudioBitrate,
1110 // int nSampleRate )
1111 // {
1112 // if (nId < 0)
1113 // throw QString( "Id is invalid" );
1114 
1115 // VideoMetadataListManager::VideoMetadataPtr metadata =
1116 // VideoMetadataListManager::loadOneFromDatabase(nId);
1117 
1118 // if (!metadata)
1119 // {
1120 // LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - no metadata for %1")
1121 // .arg( nId ));
1122 // return nullptr;
1123 // }
1124 
1125 // if ( metadata->GetHost().toLower() != gCoreContext->GetHostName().toLower())
1126 // {
1127 // // We only handle requests for local resources
1128 
1129 // QString sMsg =
1130 // QString("AddVideoLiveStream: Wrong Host '%1' request from '%2'.")
1131 // .arg( gCoreContext->GetHostName(),
1132 // metadata->GetHost() );
1133 
1134 // LOG(VB_UPNP, LOG_ERR, sMsg);
1135 
1136 // throw V2HttpRedirectException( metadata->GetHost() );
1137 // }
1138 
1139 // StorageGroup sg("Videos", metadata->GetHost());
1140 // QString sFileName = sg.FindFile(metadata->GetFilename());
1141 
1142 // // ----------------------------------------------------------------------
1143 // // check to see if the file exists
1144 // // ----------------------------------------------------------------------
1145 
1146 // if (!QFile::exists( sFileName ))
1147 // {
1148 // LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - file does not exist."));
1149 // return nullptr;
1150 // }
1151 
1152 // return AddLiveStream( "Videos", metadata->GetFilename(),
1153 // metadata->GetHost(), nMaxSegments, nWidth,
1154 // nHeight, nBitrate, nAudioBitrate, nSampleRate );
1155 // }
1156 
1157 // NOLINTEND(modernize-return-braced-init-list)
MythHTTPService::HAS_PARAMv2
bool HAS_PARAMv2(const QString &p)
Definition: mythhttpservice.h:36
V2Content::GetHash
static QString GetHash(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:830
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
RemoteFile::SaveAs
bool SaveAs(QByteArray &data)
Definition: remotefile.cpp:1183
ProgramInfo::GetHostname
QString GetHostname(void) const
Definition: programinfo.h:422
V2HttpRedirectException
Definition: mythhttpservice.h:40
CONTENT_HANDLE
#define CONTENT_HANDLE
Definition: v2content.h:35
metadataimagehelper.h
V2ArtworkInfoList
Definition: v2artworkInfoList.h:22
Q_GLOBAL_STATIC_WITH_ARGS
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(CONTENT_HANDLE, V2Content::staticMetaObject, &V2Content::RegisterCustomTypes)) void V2Content
Definition: v2content.cpp:63
MusicMetadata::createFromID
static MusicMetadata * createFromID(int trackid)
Definition: musicmetadata.cpp:255
V2Content::GetProgramArtworkList
static V2ArtworkInfoList * GetProgramArtworkList(const QString &Inetref, int Season)
Definition: v2content.cpp:341
RemoteFile::Exists
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:460
StorageGroup::FindFile
QString FindFile(const QString &filename)
Definition: storagegroup.cpp:597
PreviewGenerator
This class creates a preview image of a recording.
Definition: previewgenerator.h:27
v2content.h
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
GetArtwork
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)
Definition: metadataimagehelper.cpp:23
V2Content::GetFile
static QFileInfo GetFile(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:80
mythhttpmetaservice.h
RemoteFile
Definition: remotefile.h:17
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MusicMetadata
Definition: musicmetadata.h:81
V2Content::GetAlbumArt
static QFileInfo GetAlbumArt(int Id, int Width, int Height)
Definition: v2content.cpp:414
VideoArtworkType
VideoArtworkType
Definition: metadataimagehelper.h:10
V2Content::GetImageFile
static QFileInfo GetImageFile(const QString &StorageGroup, const QString &FileName, int Width, int Height)
Definition: v2content.cpp:135
PreviewGenerator::kLocal
@ kLocal
Definition: previewgenerator.h:43
ProgramInfo::GetInetRef
QString GetInetRef(void) const
Definition: programinfo.h:441
fileserverutil.h
mythdate.h
kArtworkCoverart
@ kArtworkCoverart
Definition: metadataimagehelper.h:11
ProgramInfo::SetPathname
void SetPathname(const QString &pn)
Definition: programinfo.cpp:2449
V2Content::GetMusic
static QFileInfo GetMusic(int Id)
Definition: v2content.cpp:749
StorageGroup::GetFileList
QStringList GetFileList(const QString &Path, bool recursive=false)
Definition: storagegroup.cpp:271
programinfo.h
kArtworkFanart
@ kArtworkFanart
Definition: metadataimagehelper.h:12
V2Content::V2Content
V2Content()
Definition: v2content.cpp:74
v2serviceUtil.h
remotefile.h
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
compat.h
kArtworkBanner
@ kArtworkBanner
Definition: metadataimagehelper.h:13
MusicMetadata::getAlbumArtFile
QString getAlbumArtFile(void)
Definition: musicmetadata.cpp:1277
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
GetPlaybackURL
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
Definition: fileserverutil.cpp:46
V2Content::GetRecordingArtworkList
static V2ArtworkInfoList * GetRecordingArtworkList(int RecordedId, int ChanId, const QDateTime &StartTime)
Definition: v2content.cpp:323
V2Content::GetVideoArtwork
static QFileInfo GetVideoArtwork(const QString &Type, int Id, int Width, int Height)
Definition: v2content.cpp:355
MythDownloadManager::download
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
Definition: mythdownloadmanager.cpp:431
storagegroup.h
StorageGroup::GetDirList
QStringList GetDirList(void) const
Definition: storagegroup.h:23
MSqlQuery::isConnected
bool isConnected(void) const
Only updated once during object creation.
Definition: mythdbcon.h:137
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
V2Content::GetDirList
static QStringList GetDirList(const QString &StorageGroup)
Definition: v2content.cpp:237
MythHTTPService
Definition: mythhttpservice.h:19
videometadatalistmanager.h
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:910
V2Content::DownloadFile
static bool DownloadFile(const QString &URL, const QString &StorageGroup)
Definition: v2content.cpp:863
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:373
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
mythmiscutil.h
makeFileAccessible
bool makeFileAccessible(const QString &filename)
Definition: mythmiscutil.cpp:395
ProgramInfo::GetSeason
uint GetSeason(void) const
Definition: programinfo.h:367
mythcorecontext.h
StorageGroup::FindNextDirMostFree
QString FindNextDirMostFree(void)
Definition: storagegroup.cpp:666
ProgramInfo::IsLocal
bool IsLocal(void) const
Definition: programinfo.h:352
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
V2Content::GetVideo
static QFileInfo GetVideo(int Id)
Definition: v2content.cpp:792
StorageGroup
Definition: storagegroup.h:11
V2Content::GetRecording
QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &StartTime, const QString &Download)
Definition: v2content.cpp:687
ArtworkMap
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
Definition: metadataimagehelper.h:31
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:842
MythHTTPService::m_request
HTTPRequest2 m_request
Definition: mythhttpservice.h:35
previewgenerator.h
mythdownloadmanager.h
build_compdb.filename
filename
Definition: build_compdb.py:21
FileHash
QString FileHash(const QString &filename)
Definition: mythmiscutil.cpp:548
V2Content::GetRecordingArtwork
static QFileInfo GetRecordingArtwork(const QString &Type, const QString &Inetref, int Season, int Width, int Height)
Definition: v2content.cpp:277
V2FillArtworkInfoList
void V2FillArtworkInfoList(V2ArtworkInfoList *pArtworkInfoList, const QString &sInetref, uint nSeason)
Definition: v2serviceUtil.cpp:331
musicmetadata.h
MythHTTPMetaService
Definition: mythhttpmetaservice.h:10
V2Content::GetFileList
static QStringList GetFileList(const QString &StorageGroup)
Definition: v2content.cpp:257
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:146
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
V2Content::GetPreviewImage
static QFileInfo GetPreviewImage(int RecordedId, int ChanId, const QDateTime &StartTime, int Width, int Height, int SecsIn, const QString &Format)
Definition: v2content.cpp:513
V2Content::RegisterCustomTypes
static void RegisterCustomTypes()