MythTV master
v2content.cpp
Go to the documentation of this file.
1
2// 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"
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< QFileInfo >();
70 qRegisterMetaType<V2ArtworkInfoList*>("V2ArtworkInfoList");
71 qRegisterMetaType<V2ArtworkInfo*>("V2ArtworkInfo");
72 // qRegisterMetaType<V2LiveStreamInfo*>("V2LiveStreamInfo");
73 // qRegisterMetaType<V2LiveStreamInfoList*>("V2LiveStreamInfoList");
74}
75
77
79//
81
82QFileInfo V2Content::GetFile( const QString &sStorageGroup,
83 const QString &sFileName )
84{
85 QString sGroup = sStorageGroup;
86
87 if (sGroup.isEmpty())
88 {
89 LOG(VB_UPNP, LOG_WARNING,
90 "GetFile - StorageGroup missing... using 'Default'");
91 sGroup = "Default";
92 }
93
94 if (sFileName.isEmpty())
95 {
96 QString sMsg ( "GetFile - FileName missing." );
97
98 //LOG(VB_UPNP, LOG_ERR, sMsg);
99
100 throw QString(sMsg);
101 }
102
103 // ------------------------------------------------------------------
104 // Search for the filename
105 // ------------------------------------------------------------------
106
107 StorageGroup storage( sGroup );
108 QString sFullFileName = storage.FindFile( sFileName );
109
110 if (sFullFileName.isEmpty())
111 {
112 LOG(VB_UPNP, LOG_ERR,
113 QString("GetFile - Unable to find %1.").arg(sFileName));
114
115 return {};
116 }
117
118 // ----------------------------------------------------------------------
119 // check to see if the file (still) exists
120 // ----------------------------------------------------------------------
121
122 if (QFile::exists( sFullFileName ))
123 {
124 return QFileInfo( sFullFileName );
125 }
126
127 LOG(VB_UPNP, LOG_ERR,
128 QString("GetFile - File Does not exist %1.").arg(sFullFileName));
129
130 return {};
131}
132
134//
136
137QFileInfo V2Content::GetImageFile( const QString &sStorageGroup,
138 const QString &sFileName,
139 int nWidth,
140 int nHeight)
141{
142 QString sGroup = sStorageGroup;
143
144 if (sGroup.isEmpty())
145 {
146 LOG(VB_UPNP, LOG_WARNING,
147 "GetImageFile - StorageGroup missing... using 'Default'");
148 sGroup = "Default";
149 }
150
151 if (sFileName.isEmpty())
152 {
153 QString sMsg ( "GetImageFile - FileName missing." );
154
155 //LOG(VB_UPNP, LOG_WARNING, sMsg);
156
157 throw QString(sMsg);
158 }
159
160 // ------------------------------------------------------------------
161 // Search for the filename
162 // ------------------------------------------------------------------
163
164 StorageGroup storage( sGroup );
165 QString sFullFileName = storage.FindFile( sFileName );
166
167 if (sFullFileName.isEmpty())
168 {
169 LOG(VB_UPNP, LOG_WARNING,
170 QString("GetImageFile - Unable to find %1.").arg(sFileName));
171
172 return {};
173 }
174
175 // ----------------------------------------------------------------------
176 // check to see if the file (still) exists
177 // ----------------------------------------------------------------------
178
179 if (!QFile::exists( sFullFileName ))
180 {
181 LOG(VB_UPNP, LOG_WARNING,
182 QString("GetImageFile - File Does not exist %1.").arg(sFullFileName));
183 return {};
184 }
185
186 // ----------------------------------------------------------------------
187 // If no scaling is required return the file info
188 // ----------------------------------------------------------------------
189 if ((nWidth == 0) && (nHeight == 0))
190 return QFileInfo( sFullFileName );
191
192 // ----------------------------------------------------------------------
193 // Create a filename for the scaled copy
194 // ----------------------------------------------------------------------
195 QString sNewFileName = QString( "%1.%2x%3.jpg" )
196 .arg( sFullFileName )
197 .arg( nWidth )
198 .arg( nHeight );
199
200 // ----------------------------------------------------------------------
201 // check to see if image is already created.
202 // ----------------------------------------------------------------------
203
204 if (QFile::exists( sNewFileName ))
205 return QFileInfo( sNewFileName );
206
207 // ----------------------------------------------------------------------
208 // Must generate Generate Image and save.
209 // ----------------------------------------------------------------------
210
211 auto *pImage = new QImage( sFullFileName );
212
213 if (!pImage || pImage->isNull())
214 return {};
215
216 float fAspect = (float)(pImage->width()) / pImage->height();
217
218 if ( nWidth == 0 )
219 nWidth = (int)std::rint(nHeight * fAspect);
220
221 if ( nHeight == 0 )
222 nHeight = (int)std::rint(nWidth / fAspect);
223
224 QImage img = pImage->scaled( nWidth, nHeight, Qt::KeepAspectRatio,
225 Qt::SmoothTransformation);
226
227 QByteArray fname = sNewFileName.toLatin1();
228 img.save( fname.constData(), "JPG", 60 );
229
230 delete pImage;
231
232 return QFileInfo( sNewFileName );
233}
234
236//
238
239QStringList V2Content::GetDirList( const QString &sStorageGroup )
240{
241
242 if (sStorageGroup.isEmpty())
243 {
244 QString sMsg( "GetDirList - StorageGroup missing.");
245 LOG(VB_UPNP, LOG_ERR, sMsg);
246
247 throw QString(sMsg);
248 }
249
250 StorageGroup sgroup(sStorageGroup);
251
252 return sgroup.GetDirList("", true);
253}
254
256//
258
259QStringList V2Content::GetFileList( const QString &sStorageGroup )
260{
261
262 if (sStorageGroup.isEmpty())
263 {
264 QString sMsg( "GetFileList - StorageGroup missing.");
265 LOG(VB_UPNP, LOG_ERR, sMsg);
266
267 throw QString(sMsg);
268 }
269
270 StorageGroup sgroup(sStorageGroup);
271
272 return sgroup.GetFileList("", true);
273}
274
276//
278
279QFileInfo V2Content::GetRecordingArtwork ( const QString &sType,
280 const QString &sInetref,
281 int nSeason,
282 int nWidth,
283 int nHeight)
284{
285 ArtworkMap map = GetArtwork(sInetref, nSeason);
286
287 if (map.isEmpty())
288 return {};
289
291 QString sgroup;
292
293 if (sType.toLower() == "coverart")
294 {
295 sgroup = "Coverart";
297 }
298 else if (sType.toLower() == "fanart")
299 {
300 sgroup = "Fanart";
302 }
303 else if (sType.toLower() == "banner")
304 {
305 sgroup = "Banners";
307 }
308
309 if (!map.contains(type))
310 return {};
311
312 QUrl url(map.value(type).url);
313 QString sFileName = url.path();
314
315 if (sFileName.isEmpty())
316 return {};
317
318 return GetImageFile( sgroup, sFileName, nWidth, nHeight);
319}
320
322//
324
326 int chanid,
327 const QDateTime &StartTime)
328{
329 if ((RecordedId <= 0) &&
330 (chanid <= 0 || !StartTime.isValid()))
331 throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
332
333 // TODO Should use RecordingInfo
334 ProgramInfo pginfo;
335 if (RecordedId > 0)
336 pginfo = ProgramInfo(RecordedId);
337 else
338 pginfo = ProgramInfo(chanid, StartTime.toUTC());
339
340 return GetProgramArtworkList(pginfo.GetInetRef(), pginfo.GetSeason());
341}
342
344 int nSeason )
345{
346 auto *pArtwork = new V2ArtworkInfoList();
347
348 V2FillArtworkInfoList (pArtwork, sInetref, nSeason);
349
350 return pArtwork;
351}
353//
355
356// NOTE: If you rename this, you must also update upnpcdsvideo.cpp
357QFileInfo V2Content::GetVideoArtwork( const QString &sType,
358 int nId, int nWidth, int nHeight )
359{
360 LOG(VB_UPNP, LOG_INFO, QString("GetVideoArtwork ID = %1").arg(nId));
361
362 QString sgroup = "Coverart";
363 QString column = "coverfile";
364
365 if (sType.toLower() == "coverart")
366 {
367 sgroup = "Coverart";
368 column = "coverfile";
369 }
370 else if (sType.toLower() == "fanart")
371 {
372 sgroup = "Fanart";
373 column = "fanart";
374 }
375 else if (sType.toLower() == "banner")
376 {
377 sgroup = "Banners";
378 column = "banner";
379 }
380 else if (sType.toLower() == "screenshot")
381 {
382 sgroup = "Screenshots";
383 column = "screenshot";
384 }
385
386 // ----------------------------------------------------------------------
387 // Read Video artwork file path from database
388 // ----------------------------------------------------------------------
389
391
392 QString querystr = QString("SELECT %1 FROM videometadata WHERE "
393 "intid = :ITEMID").arg(column);
394
395 query.prepare(querystr);
396 query.bindValue(":ITEMID", nId);
397
398 if (!query.exec())
399 MythDB::DBError("GetVideoArtwork ", query);
400
401 if (!query.next())
402 return {};
403
404 QString sFileName = query.value(0).toString();
405
406 if (sFileName.isEmpty())
407 return {};
408
409 return GetImageFile( sgroup, sFileName, nWidth, nHeight );
410}
411
413//
415
416QFileInfo V2Content::GetAlbumArt( int nTrackId, int nWidth, int nHeight )
417{
418 // ----------------------------------------------------------------------
419 // Read AlbumArt file path from database
420 // ----------------------------------------------------------------------
421
422 MusicMetadata *metadata = MusicMetadata::createFromID(nTrackId);
423
424 if (!metadata)
425 return {};
426
427 QString sFullFileName = metadata->getAlbumArtFile();
428 LOG(VB_GENERAL, LOG_DEBUG, QString("GetAlbumArt: %1").arg(sFullFileName));
429
430 delete metadata;
431
432 if (!RemoteFile::Exists(sFullFileName))
433 return {};
434
435 QString sNewFileName = QString( "/tmp/%1.%2x%3.jpg" )
436 .arg( QFileInfo(sFullFileName).fileName() )
437 .arg( nWidth )
438 .arg( nHeight );
439
440 // ----------------------------------------------------------------------
441 // check to see if albumart image is already created.
442 // ----------------------------------------------------------------------
443
444 if (QFile::exists( sNewFileName ))
445 return QFileInfo( sNewFileName );
446
447 // ----------------------------------------------------------------------
448 // Must generate Albumart Image, Generate Image and save.
449 // ----------------------------------------------------------------------
450
451
452 QImage img;
453 if (sFullFileName.startsWith("myth://"))
454 {
455 RemoteFile rf(sFullFileName, false, false, 0s);
456 QByteArray data;
457 rf.SaveAs(data);
458
459 img.loadFromData(data);
460 }
461 else
462 {
463 img.load(sFullFileName);
464 }
465
466 if (img.isNull())
467 return {};
468
469 // We don't need to scale if no height and width were specified
470 // but still need to save as jpg if it's in another format
471 if ((nWidth == 0) && (nHeight == 0))
472 {
473 if (!sFullFileName.startsWith("myth://"))
474 {
475 QFileInfo fi(sFullFileName);
476 if (fi.suffix().toLower() == "jpg")
477 return fi;
478 }
479 }
480 else if (nWidth > img.width() && nHeight > img.height())
481 {
482 // Requested dimensions are larger than the source image, so instead of
483 // scaling up which will produce horrible results return the fullsize
484 // image and the user can scale further if they want instead
485 // NOTE: If this behaviour is changed, for example making it optional,
486 // then upnp code will need changing to compensate
487 }
488 else
489 {
490 float fAspect = (float)(img.width()) / img.height();
491
492 if ( nWidth == 0 || nWidth > img.width() )
493 nWidth = (int)std::rint(nHeight * fAspect);
494
495 if ( nHeight == 0 || nHeight > img.height() )
496 nHeight = (int)std::rint(nWidth / fAspect);
497
498 img = img.scaled( nWidth, nHeight, Qt::KeepAspectRatio,
499 Qt::SmoothTransformation);
500 }
501
502 QString fname = sNewFileName.toLatin1().constData();
503 // Use JPG not PNG for compatibility with the most uPnP devices and
504 // faster loading (smaller file to send over network)
505 if (!img.save( fname, "JPG" ))
506 return {};
507
508 return QFileInfo( sNewFileName );
509}
510
512//
514
515QFileInfo V2Content::GetPreviewImage( int nRecordedId,
516 int nChanId,
517 const QDateTime &StartTime,
518 int nWidth,
519 int nHeight,
520 int nSecsIn,
521 const QString &sFormat )
522{
523 if ((nRecordedId <= 0) &&
524 (nChanId <= 0 || !StartTime.isValid()))
525 throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
526
527 if (!sFormat.isEmpty()
528 && !QImageWriter::supportedImageFormats().contains(sFormat.toLower().toLocal8Bit()))
529 {
530 throw QString("GetPreviewImage: Specified 'Format' is not supported.");
531 }
532
533 // ----------------------------------------------------------------------
534 // Read Recording From Database
535 // ----------------------------------------------------------------------
536
537 // TODO Should use RecordingInfo
538 ProgramInfo pginfo;
539 if (nRecordedId > 0)
540 pginfo = ProgramInfo(nRecordedId);
541 else
542 pginfo = ProgramInfo(nChanId, StartTime.toUTC());
543
544 if (!pginfo.GetChanID())
545 {
546 LOG(VB_GENERAL, LOG_ERR,
547 QString("GetPreviewImage: No recording for '%1'")
548 .arg(nRecordedId));
549 return {};
550 }
551
552 if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
553 && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
554 {
555 QString sMsg =
556 QString("GetPreviewImage: Wrong Host '%1' request from '%2'")
557 .arg( gCoreContext->GetHostName(),
558 pginfo.GetHostname() );
559
560 LOG(VB_UPNP, LOG_ERR, sMsg);
561
562 throw V2HttpRedirectException( pginfo.GetHostname() );
563 }
564
565 QString sImageFormat = sFormat;
566 if (sImageFormat.isEmpty())
567 sImageFormat = "PNG";
568
569 QString sFileName = GetPlaybackURL(&pginfo);
570
571 // ----------------------------------------------------------------------
572 // check to see if default preview image is already created.
573 // ----------------------------------------------------------------------
574
575 QString sPreviewFileName;
576
577 auto nSecs = std::chrono::seconds(nSecsIn);
578 if (nSecs <= 0s)
579 {
580 nSecs = -1s;
581 sPreviewFileName = QString("%1.png").arg(sFileName);
582 }
583 else
584 {
585 sPreviewFileName = QString("%1.%2.png").arg(sFileName).arg(nSecsIn);
586 }
587
588 if (!QFile::exists( sPreviewFileName ))
589 {
590 // ------------------------------------------------------------------
591 // Must generate Preview Image, Generate Image and save.
592 // ------------------------------------------------------------------
593 if (!pginfo.IsLocal() && sFileName.startsWith("/"))
594 pginfo.SetPathname(sFileName);
595
596 if (!pginfo.IsLocal())
597 return {};
598
599 auto *previewgen = new PreviewGenerator( &pginfo, QString(),
601 previewgen->SetPreviewTimeAsSeconds( nSecs );
602 previewgen->SetOutputFilename ( sPreviewFileName );
603
604 bool ok = previewgen->Run();
605
606 previewgen->deleteLater();
607
608 if (!ok)
609 return {};
610 }
611
612 bool bDefaultPixmap = (nWidth == 0) && (nHeight == 0);
613
614 QString sNewFileName;
615
616 if (bDefaultPixmap)
617 {
618 sNewFileName = sPreviewFileName;
619 }
620 else
621 {
622 sNewFileName = QString( "%1.%2.%3x%4.%5" )
623 .arg( sFileName )
624 .arg( nSecsIn )
625 .arg( nWidth == 0 ? -1 : nWidth )
626 .arg( nHeight == 0 ? -1 : nHeight )
627 .arg( sImageFormat.toLower() );
628
629 // ----------------------------------------------------------------------
630 // check to see if scaled preview image is already created and isn't
631 // out of date
632 // ----------------------------------------------------------------------
633 if (QFile::exists( sNewFileName ))
634 {
635 if (QFileInfo(sPreviewFileName).lastModified() <=
636 QFileInfo(sNewFileName).lastModified())
637 return QFileInfo( sNewFileName );
638 }
639
640 QImage image = QImage(sPreviewFileName);
641
642 if (image.isNull())
643 return {};
644
645 // We can just re-scale the default (full-size version) to avoid
646 // a preview generator run
647 if ( nWidth <= 0 )
648 image = image.scaledToHeight(nHeight, Qt::SmoothTransformation);
649 else if ( nHeight <= 0 )
650 image = image.scaledToWidth(nWidth, Qt::SmoothTransformation);
651 else
652 image = image.scaled(nWidth, nHeight, Qt::IgnoreAspectRatio,
653 Qt::SmoothTransformation);
654
655 image.save(sNewFileName, sImageFormat.toUpper().toLocal8Bit().constData());
656
657 // Let anybody update it
658 bool ret = makeFileAccessible(sNewFileName.toLocal8Bit().constData());
659 if (!ret)
660 {
661 LOG(VB_GENERAL, LOG_ERR, "Unable to change permissions on "
662 "preview image. Backends and frontends "
663 "running under different users will be "
664 "unable to access it");
665 }
666 }
667
668 if (QFile::exists( sNewFileName ))
669 return QFileInfo( sNewFileName );
670
671 auto *previewgen = new PreviewGenerator( &pginfo, QString(),
673 previewgen->SetPreviewTimeAsSeconds( nSecs );
674 previewgen->SetOutputFilename ( sNewFileName );
675 previewgen->SetOutputSize (QSize(nWidth,nHeight));
676
677 bool ok = previewgen->Run();
678
679 previewgen->deleteLater();
680
681 if (!ok)
682 return {};
683
684 return QFileInfo( sNewFileName );
685}
686
688//
690
691QFileInfo V2Content::GetRecording( int nRecordedId,
692 int nChanId,
693 const QDateTime &StartTime,
694 const QString &Download )
695{
696 if ((nRecordedId <= 0) &&
697 (nChanId <= 0 || !StartTime.isValid()))
698 throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
699
700 // ------------------------------------------------------------------
701 // Read Recording From Database
702 // ------------------------------------------------------------------
703
704 // TODO Should use RecordingInfo
705 ProgramInfo pginfo;
706 if (nRecordedId > 0)
707 pginfo = ProgramInfo(nRecordedId);
708 else
709 pginfo = ProgramInfo(nChanId, StartTime.toUTC());
710
711 if (!pginfo.GetChanID())
712 {
713 LOG(VB_UPNP, LOG_ERR, QString("GetRecording - for '%1' failed")
714 .arg(nRecordedId));
715
716 return {};
717 }
718
719 if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
720 && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
721 {
722 // We only handle requests for local resources
723
724 QString sMsg =
725 QString("GetRecording: Wrong Host '%1' request from '%2'.")
726 .arg( gCoreContext->GetHostName(),
727 pginfo.GetHostname() );
728
729 LOG(VB_UPNP, LOG_ERR, sMsg);
730
731 throw V2HttpRedirectException( pginfo.GetHostname() );
732 }
733
734 QString sFileName( GetPlaybackURL(&pginfo) );
735
736 if (HAS_PARAMv2("Download"))
737 m_request->m_headers->insert("mythtv-download",Download);
738
739 // ----------------------------------------------------------------------
740 // check to see if the file exists
741 // ----------------------------------------------------------------------
742
743 if (QFile::exists( sFileName ))
744 return QFileInfo( sFileName );
745
746 return {};
747}
748
750//
752
753QFileInfo V2Content::GetMusic( int nId )
754{
755 QString sFileName;
756
757 // ----------------------------------------------------------------------
758 // Load Track's FileName
759 // ----------------------------------------------------------------------
760
762
763 if (query.isConnected())
764 {
765 query.prepare("SELECT CONCAT_WS('/', music_directories.path, "
766 "music_songs.filename) AS filename FROM music_songs "
767 "LEFT JOIN music_directories ON "
768 "music_songs.directory_id="
769 "music_directories.directory_id "
770 "WHERE music_songs.song_id = :KEY");
771
772 query.bindValue(":KEY", nId );
773
774 if (!query.exec())
775 {
776 MythDB::DBError("GetMusic()", query);
777 return {};
778 }
779
780 if (query.next())
781 {
782 sFileName = query.value(0).toString();
783 }
784 }
785
786 if (sFileName.isEmpty())
787 return {};
788
789 return GetFile( "Music", sFileName );
790}
791
793//
795
796QFileInfo V2Content::GetVideo( int nId )
797{
798 QString sFileName;
799
800 // ----------------------------------------------------------------------
801 // Load Track's FileName
802 // ----------------------------------------------------------------------
803
805
806 if (query.isConnected())
807 {
808 query.prepare("SELECT filename FROM videometadata WHERE intid = :KEY" );
809 query.bindValue(":KEY", nId );
810
811 if (!query.exec())
812 {
813 MythDB::DBError("GetVideo()", query);
814 return {};
815 }
816
817 if (query.next())
818 sFileName = query.value(0).toString();
819 }
820
821 if (sFileName.isEmpty())
822 return {};
823
824 if (!QFile::exists( sFileName ))
825 return GetFile( "Videos", sFileName );
826
827 return QFileInfo( sFileName );
828}
829
831//
833
834QString V2Content::GetHash( const QString &sStorageGroup,
835 const QString &sFileName )
836{
837 if ((sFileName.isEmpty()) ||
838 (sFileName.contains("/../")) ||
839 (sFileName.startsWith("../")))
840 {
841 LOG(VB_GENERAL, LOG_ERR,
842 QString("ERROR checking for file, filename '%1' "
843 "fails sanity checks").arg(sFileName));
844 return {};
845 }
846
847 QString storageGroup = "Default";
848
849 if (!sStorageGroup.isEmpty())
850 storageGroup = sStorageGroup;
851
852 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
853
854 QString fullname = sgroup.FindFile(sFileName);
855 QString hash = FileHash(fullname);
856
857 if (hash == "NULL")
858 return {};
859
860 return hash;
861}
862
864//
866
867bool V2Content::DownloadFile( const QString &sURL, const QString &sStorageGroup )
868{
869 QFileInfo finfo(sURL);
870 QString filename = finfo.fileName();
871 StorageGroup sgroup(sStorageGroup, gCoreContext->GetHostName(), false);
872 QString outDir = sgroup.FindNextDirMostFree();
873 QString outFile;
874
875 if (outDir.isEmpty())
876 {
877 LOG(VB_GENERAL, LOG_ERR,
878 QString("Unable to determine directory "
879 "to write to in %1 write command").arg(sURL));
880 return false;
881 }
882
883 if ((filename.contains("/../")) ||
884 (filename.startsWith("../")))
885 {
886 LOG(VB_GENERAL, LOG_ERR,
887 QString("ERROR: %1 write filename '%2' does not "
888 "pass sanity checks.").arg(sURL, filename));
889 return false;
890 }
891
892 outFile = outDir + "/" + filename;
893
894 return GetMythDownloadManager()->download(sURL, outFile);
895}
896
897// NOLINTEND(modernize-return-braced-init-list)
898
899#include "moc_v2content.cpp"
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:205
bool isConnected(void) const
Only updated once during object creation.
Definition: mythdbcon.h:138
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
QString getAlbumArtFile(void)
static MusicMetadata * createFromID(int trackid)
QString GetHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
bool HAS_PARAMv2(const QString &p)
HTTPRequest2 m_request
This class creates a preview image of a recording.
Holds information on recordings and videos.
Definition: programinfo.h:74
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:380
QString GetInetRef(void) const
Definition: programinfo.h:448
QString GetHostname(void) const
Definition: programinfo.h:429
bool IsLocal(void) const
Definition: programinfo.h:358
uint GetSeason(void) const
Definition: programinfo.h:373
void SetPathname(const QString &pn)
bool SaveAs(QByteArray &data)
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:463
QStringList GetDirList(void) const
Definition: storagegroup.h:23
QString FindFile(const QString &filename)
QString FindNextDirMostFree(void)
QStringList GetFileList(const QString &Path, bool recursive=false)
QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &StartTime, const QString &Download)
Definition: v2content.cpp:691
static V2ArtworkInfoList * GetProgramArtworkList(const QString &Inetref, int Season)
Definition: v2content.cpp:343
static V2ArtworkInfoList * GetRecordingArtworkList(int RecordedId, int ChanId, const QDateTime &StartTime)
Definition: v2content.cpp:325
static void RegisterCustomTypes()
static QString GetHash(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:834
static QFileInfo GetFile(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:82
static QFileInfo GetVideoArtwork(const QString &Type, int Id, int Width, int Height)
Definition: v2content.cpp:357
static bool DownloadFile(const QString &URL, const QString &StorageGroup)
Definition: v2content.cpp:867
static QFileInfo GetRecordingArtwork(const QString &Type, const QString &Inetref, int Season, int Width, int Height)
Definition: v2content.cpp:279
static QFileInfo GetAlbumArt(int Id, int Width, int Height)
Definition: v2content.cpp:416
static QFileInfo GetMusic(int Id)
Definition: v2content.cpp:753
static QFileInfo GetImageFile(const QString &StorageGroup, const QString &FileName, int Width, int Height)
Definition: v2content.cpp:137
static QStringList GetDirList(const QString &StorageGroup)
Definition: v2content.cpp:239
static QFileInfo GetVideo(int Id)
Definition: v2content.cpp:796
static QFileInfo GetPreviewImage(int RecordedId, int ChanId, const QDateTime &StartTime, int Width, int Height, int SecsIn, const QString &Format)
Definition: v2content.cpp:515
static QStringList GetFileList(const QString &StorageGroup)
Definition: v2content.cpp:259
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
VideoArtworkType
@ kArtworkFanart
@ kArtworkBanner
@ kArtworkCoverart
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString FileHash(const QString &filename)
bool makeFileAccessible(const QString &filename)
bool exists(str path)
Definition: xbmcvfs.py:51
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(CONTENT_HANDLE, V2Content::staticMetaObject, &V2Content::RegisterCustomTypes)) void V2Content
Definition: v2content.cpp:64
#define CONTENT_HANDLE
Definition: v2content.h:35
void V2FillArtworkInfoList(V2ArtworkInfoList *pArtworkInfoList, const QString &sInetref, uint nSeason)