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